@playpilot/tpi 8.18.0 → 8.19.1-beta.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.
Files changed (24) hide show
  1. package/dist/editorial.mount.js +11 -11
  2. package/dist/link-injections.js +2 -2
  3. package/dist/mount.js +6 -6
  4. package/package.json +1 -1
  5. package/src/lib/constants.ts +1 -0
  6. package/src/lib/data/translations.ts +5 -0
  7. package/src/lib/scss/_mixins.scss +7 -5
  8. package/src/main.ts +2 -1
  9. package/src/routes/+page.svelte +3 -3
  10. package/src/routes/components/Explore/ExploreLayout.svelte +2 -2
  11. package/src/routes/components/Explore/Filter/Filter.svelte +5 -3
  12. package/src/routes/components/Explore/Routes/ExploreHome.svelte +4 -4
  13. package/src/routes/components/Modals/RailModal.svelte +7 -2
  14. package/src/routes/components/Playlinks/Playlink.svelte +1 -1
  15. package/src/routes/components/Playlinks/PlaylinkIcon.svelte +1 -1
  16. package/src/routes/components/Playlinks/Playlinks.svelte +19 -4
  17. package/src/tests/routes/components/Playlinks/Playlinks.test.js +50 -0
  18. package/src/tests/routes/components/{TrackAnyClick.test.js → Tracking/TrackAnyClick.test.js} +1 -1
  19. package/src/tests/routes/components/{TrackingPixels.test.js → Tracking/TrackingPixels.test.js} +1 -1
  20. /package/src/routes/components/{TrackAnyClick.svelte → Tracking/TrackAnyClick.svelte} +0 -0
  21. /package/src/routes/components/{TrackScrollDistance.svelte → Tracking/TrackScrollDistance.svelte} +0 -0
  22. /package/src/routes/components/{TrackingPixels.svelte → Tracking/TrackingPixels.svelte} +0 -0
  23. /package/src/routes/components/{TrackingPixelsForTitleDataPerLink.svelte → Tracking/TrackingPixelsForTitleDataPerLink.svelte} +0 -0
  24. /package/src/routes/components/{UserJourney.svelte → Tracking/UserJourney.svelte} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "8.18.0",
3
+ "version": "8.19.1-beta.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,6 +1,7 @@
1
1
  export const playPilotBaseUrl = 'https://www.playpilot.com'
2
2
  export const apiBaseUrl = 'https://partner-api.playpilot.tech/1.0'
3
3
  export const imageBaseUrl = 'https://img-external.playpilot.tech'
4
+ export const cdnBaseUrl = `https://cdn.jsdelivr.net/npm/@playpilot/tpi@${__SCRIPT_VERSION__}`
4
5
 
5
6
  export const imagePlaceholderDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAAB+CAMAAACNgsajAAAAb1BMVEU1Q2fI1N5YZoNVYoFndI9qd5JBT3FFU3S8yNS3xNC4xNFBTnDG0t2lscJJV3e9ytaptcWToLOOm6/BzdiZprh3hJ1aaIWJlatte5VPXHx7iJ9zgZlfbYk3RWmNmq5jcY1XZIO2wtCjsMB+i6I9S25IprTeAAABqUlEQVRo3u3W2ZKCMBCF4T5ExRmRVXBfZnn/Z5wKiU5pz1AJ7ZXV/x03HxVCB0jTNE3TNE3TNO0l2rbPNxcFdk8334AINTUD5eSaWbPoQs0qw0CVN98BzNNgE4NVv+ZHsJliuNVt7UgotATAafp/5mZiG4waAB0N5k0kUeg0wMykKDfLvRTl5pxyCFFupjQVo9ykiRTlphzl5nNQNu8C9Hv2lylDT0W2NMyUoeXdLC68KUNLuM7O9HskQ0uLLAEUR2aOQjfORE5LzHP2PMehxpl2k6otM8eh2aQGkBlieyRBYbs3y5Rk6IPZn8mT65XJR6LcROGErwaoxqLm4XvkiTVsy1FoYe5n06zcjazp1Wk0umHz3k9BT6+bXjXR6PnRtKpr755PfRjx4WPz7tXW/26gGYnOvOmrM7xtiK4qYtDOrpGZtnR7JFcSi+Z1XZt7k5d4NCZmcrWxqMzkbRgqN+nAULHpx1RiylFvftJwlxjUz2bWdpPB7NnSxZih5RFrD20Vai4izGOgeenPukMSUE6hte5denI7NMyU1xrSNE3TNE3TNE17hX4ADHsS0j0OCOoAAAAASUVORK5CYII='
6
7
  export const mobileBreakpoint = 600
@@ -301,6 +301,11 @@ export const translations = {
301
301
  [Language.Swedish]: 'Tyvärr stöds inte din region',
302
302
  [Language.Danish]: 'Beklager, din region understøttes ikke',
303
303
  },
304
+ 'Show Count More': {
305
+ [Language.English]: 'Show [[count]] more',
306
+ [Language.Swedish]: 'Visa [[count]] till',
307
+ [Language.Danish]: 'Vis [[count]] mere',
308
+ },
304
309
 
305
310
  // List titles
306
311
  'List: Trending': {
@@ -1,3 +1,5 @@
1
+ @use "./_functions.scss" as *;
2
+
1
3
  // Annoyingly, some pages include global styling to add a fill to every svg.
2
4
  // We don't want that, but we can't just apply global styling either, as that would affect their page.
3
5
  // So this mixins is to be used inside of other containers.
@@ -39,7 +41,7 @@
39
41
  }
40
42
 
41
43
  @mixin styled-scrollbar() {
42
- scrollbar-color: var(--playpilot-content-light) transparent;
44
+ scrollbar-color: theme(content-light) transparent;
43
45
  scrollbar-width: thin;
44
46
 
45
47
  &::-webkit-scrollbar {
@@ -47,20 +49,20 @@
47
49
  }
48
50
 
49
51
  &::-webkit-scrollbar-track {
50
- background: var(--playpilot-light);
52
+ background: theme(light);
51
53
  }
52
54
 
53
55
  &::-webkit-scrollbar-thumb {
54
- border: 2px solid var(--playpilot-light);
56
+ border: 2px solid theme(light);
55
57
  border-radius: margin(1);
56
58
  background: transparent;
57
59
 
58
60
  &:hover {
59
- background: var(--playpilot-content-light);
61
+ background: theme(content-light);
60
62
  }
61
63
 
62
64
  &:active {
63
- background: var(--playpilot-text-color-alt);
65
+ background: theme(text-color-alt);
64
66
  }
65
67
  }
66
68
  }
package/src/main.ts CHANGED
@@ -2,6 +2,7 @@ import { getAuthToken, isEditorialModeEnabled } from '$lib/api/auth'
2
2
  import { fetchConfig } from '$lib/api/config'
3
3
  import { pollLinkInjections } from '$lib/api/externalPages'
4
4
  import { setConsent } from '$lib/consent'
5
+ import { cdnBaseUrl } from '$lib/constants'
5
6
  import { isCrawler } from '$lib/crawler'
6
7
  import { isUrlExcludedViaConfig } from '$lib/url'
7
8
 
@@ -87,7 +88,7 @@ window.PlayPilotLinkInjections ||= {
87
88
  const script = document.createElement('script')
88
89
 
89
90
  script.id = 'playpilot-mount'
90
- script.src = `https://cdn.jsdelivr.net/npm/@playpilot/tpi@${__SCRIPT_VERSION__}/dist/${shouldLoadEditorial ? 'editorial.' : ''}mount.js`
91
+ script.src = `${cdnBaseUrl}/dist/${shouldLoadEditorial ? 'editorial.' : ''}mount.js`
91
92
  // script.src = './dist/mount.js' // Use me during development of this script
92
93
 
93
94
  script.onload = () => window.PlayPilotMount?.mount()
@@ -15,12 +15,12 @@
15
15
  import Editor from './components/Editorial/Editor.svelte'
16
16
  import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
17
17
  import Alert from './components/Editorial/Alert.svelte'
18
- import TrackingPixels from './components/TrackingPixels.svelte'
18
+ import TrackingPixels from './components/Tracking/TrackingPixels.svelte'
19
+ import TrackingPixelsForTitleDataPerLink from './components/Tracking/TrackingPixelsForTitleDataPerLink.svelte'
20
+ import UserJourney from './components/Tracking/UserJourney.svelte'
19
21
  import Consent from './components/Consent.svelte'
20
22
  import Debugger from './components/Debugger.svelte'
21
- import UserJourney from './components/UserJourney.svelte'
22
23
  import PostersScrollReveal from './components/PostersScrollReveal.svelte'
23
- import TrackingPixelsForTitleDataPerLink from './components/TrackingPixelsForTitleDataPerLink.svelte'
24
24
 
25
25
  let response: LinkInjectionResponse | null = $state(null)
26
26
  let isEditorialMode = $state(isEditorialModeEnabled())
@@ -10,8 +10,8 @@
10
10
  import { onMount, type Snippet } from 'svelte'
11
11
  import Search from './Filter/Search.svelte'
12
12
  import Filter from './Filter/Filter.svelte'
13
- import TrackAnyClick from '../TrackAnyClick.svelte'
14
- import TrackScrollDistance from '../TrackScrollDistance.svelte'
13
+ import TrackAnyClick from '../Tracking/TrackAnyClick.svelte'
14
+ import TrackScrollDistance from '../Tracking/TrackScrollDistance.svelte'
15
15
 
16
16
  interface Props {
17
17
  navigate?: (key: string) => void
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { ExploreFilter } from '$lib/types/filter'
3
3
  import genres from '$lib/data/genres.json'
4
- import countries from '$lib/data/countries.json'
5
4
  import { fetchProviders } from '$lib/api/providers'
6
5
  import { t } from '$lib/localization'
7
6
  import FilterItem from './FilterItem.svelte'
@@ -9,9 +8,10 @@
9
8
  import Button from '../../Button.svelte'
10
9
  import IconArrow from '../../Icons/IconArrow.svelte'
11
10
  import IconFilter from '../../Icons/IconFilter.svelte'
12
- import { fly, scale } from 'svelte/transition'
11
+ import { fly, scale } from 'svelte/transition'
13
12
  import ActiveFilterItems from './ActiveFilterItems.svelte'
14
13
  import { isFilterItemActive } from '$lib/filter'
14
+ import { cdnBaseUrl } from '$lib/constants'
15
15
 
16
16
  interface Props {
17
17
  filter: ExploreFilter
@@ -56,7 +56,9 @@
56
56
  }, {
57
57
  label: t('Countries'),
58
58
  param: 'country_codes',
59
- data: countries,
59
+ fetchData: async () => import.meta.env.DEV ?
60
+ (await import('$lib/data/countries.json')).default :
61
+ (await fetch(`${cdnBaseUrl}/src/lib/data/countries.json`)).json(),
60
62
  }]
61
63
 
62
64
  let limited = $state(limit)
@@ -13,6 +13,10 @@
13
13
  let windowWidth: number = $state(window.innerWidth)
14
14
 
15
15
  const rails: { heading: string, params: Record<string, any>, properties: Record<string, any> }[] = [{
16
+ heading: 'List: Upcoming',
17
+ params: { from_playlist_sid: 'li42wf', region: null, no_region_filter: true },
18
+ properties: {},
19
+ }, {
16
20
  heading: 'List: Trending',
17
21
  params: { ordering: Sorting.Popular },
18
22
  properties: { expandable: true },
@@ -20,10 +24,6 @@
20
24
  heading: 'List: New',
21
25
  params: { from_playlist_sid: 'li42WR', include_playable_types: 'SVOD,FREE' },
22
26
  properties: { aside: true, size: 'flexible' },
23
- }, {
24
- heading: 'List: Upcoming',
25
- params: { from_playlist_sid: 'li42wf', region: null, no_region_filter: true },
26
- properties: {},
27
27
  }, {
28
28
  heading: 'List: Cinema',
29
29
  params: { from_playlist_sid: 'li42WS', region: null, no_region_filter: true },
@@ -45,6 +45,7 @@
45
45
  <!-- svelte-ignore a11y_click_events_have_key_events -->
46
46
  <!-- svelte-ignore a11y_no_static_element_interactions -->
47
47
  <div class="item" class:active={index === currentIndex} onclick={() => setSliderIndex(index)}>
48
+ <span style="color:white">{currentIndex}</span>
48
49
  {#if Math.abs(index - currentIndex) === 1 || currentIndex === index}
49
50
  <div class="content" transition:fade={{ duration: initialized ? transitionDuration : 0 }}>
50
51
  {@render each(item, currentIndex)}
@@ -79,7 +80,7 @@
79
80
  </Modal>
80
81
 
81
82
  <style lang="scss">
82
- $size: min(600px, 85vw);
83
+ $size: min(800px, 85vw);
83
84
 
84
85
  .rail-modal {
85
86
  --gap: #{margin(0.25)};
@@ -132,13 +133,17 @@
132
133
  border-radius: theme(rail-modal-item-border-radius, border-radius-huge) theme(rail-modal-item-border-radius, border-radius-huge) 0 0;
133
134
  background: theme(rail-modal-item-background, light);
134
135
  box-shadow: none;
135
- height: 90vh;
136
+ height: calc(90vh - env(safe-area-inset-top));
136
137
  overflow: auto;
137
138
  overscroll-behavior: contain;
138
139
  transition: box-shadow var(--transition-duration);
139
140
 
140
141
  @include styled-scrollbar;
141
142
 
143
+ @supports (height: 1dvh) {
144
+ height: calc(90dvh - env(safe-area-inset-top));
145
+ }
146
+
142
147
  @include desktop {
143
148
  height: auto;
144
149
  max-height: 90vh;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Disclaimer from '../Ads/Disclaimer.svelte'
3
- import TrackingPixels from '../TrackingPixels.svelte'
3
+ import TrackingPixels from '../Tracking/TrackingPixels.svelte'
4
4
  import { hasConsentedTo } from '$lib/consent'
5
5
  import { removeImageUrlPrefix } from '$lib/image'
6
6
  import { t } from '$lib/localization'
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import TrackingPixels from '../TrackingPixels.svelte'
2
+ import TrackingPixels from '../Tracking/TrackingPixels.svelte'
3
3
  import { removeImageUrlPrefix } from '$lib/image'
4
4
  import type { PlaylinkData } from '$lib/types/playlink'
5
5
 
@@ -9,6 +9,7 @@
9
9
  import { campaignToPlaylink, getFirstAdOfType } from '$lib/api/ads'
10
10
  import { getContext } from 'svelte'
11
11
  import Playlink from './Playlink.svelte'
12
+ import Button from '../Button.svelte'
12
13
 
13
14
  type Category = PlaylinkCategory | '' | 'Other'
14
15
  type CategorizedPlaylinks = Partial<Record<Category, PlaylinkData[]>>
@@ -17,9 +18,10 @@
17
18
  interface Props {
18
19
  playlinks: PlaylinkData[]
19
20
  title: TitleData
21
+ limit?: number
20
22
  }
21
23
 
22
- const { playlinks, title }: Props = $props()
24
+ const { playlinks, title, limit = 3 }: Props = $props()
23
25
 
24
26
  const isModal = getContext('scope') === 'modal'
25
27
  const type = getContext('type')
@@ -27,10 +29,11 @@
27
29
  const categorize = !!window.PlayPilotLinkInjections?.config?.categorize_playlinks
28
30
 
29
31
  let outerWidth = $state(0)
32
+ let limited = $state(!categorize)
30
33
 
31
34
  const mergedPlaylinks = $derived(mergePlaylinks(playlinks))
32
35
  const categorizedPlaylinks = $derived(categorizePlaylinks(playlinks))
33
- const shownPlaylinks: SortedPlaylinks = $derived(categorize ? sortCategories(categorizedPlaylinks) : [['' as Category, mergedPlaylinks]])
36
+ const sortedPlaylinks: SortedPlaylinks = $derived(categorize ? sortCategories(categorizedPlaylinks) : [['' as Category, mergedPlaylinks]])
34
37
 
35
38
  // Grid turns into a list when the playlinks container is small enough
36
39
  // It is also a list by default if a display ad is present, as that would
@@ -87,13 +90,13 @@
87
90
  </div>
88
91
  {/if}
89
92
 
90
- {#each shownPlaylinks as [category, playlinks]}
93
+ {#each sortedPlaylinks as [category, playlinks]}
91
94
  {#if category && playlinks.length}
92
95
  <div class="heading category" use:heading={4}>{t(`Category: ${category}`)}</div>
93
96
  {/if}
94
97
 
95
98
  <div class="playlinks" data-testid="category-{category}" class:list bind:clientWidth={outerWidth}>
96
- {#each playlinks as playlink, index}
99
+ {#each playlinks.slice(0, limited ? limit : playlinks.length) as playlink, index}
97
100
  <Playlink {playlink} onclick={() => onclick(playlink.name)} />
98
101
 
99
102
  <!-- A fake highlighted playlink as part of the display ad, to be shown after the first playlink -->
@@ -102,6 +105,14 @@
102
105
  {/if}
103
106
  {/each}
104
107
  </div>
108
+
109
+ {#if playlinks.length > limit && limited}
110
+ <div class="show-more" data-testid="show-more">
111
+ <Button onclick={() => limited = !limited} size="large">
112
+ {t('Show Count More').replace('[[count]]', (playlinks.length - limit).toString())}
113
+ </Button>
114
+ </div>
115
+ {/if}
105
116
  {/each}
106
117
 
107
118
  <style lang="scss">
@@ -153,6 +164,10 @@
153
164
  }
154
165
  }
155
166
 
167
+ .show-more {
168
+ margin-top: margin(0.5);
169
+ }
170
+
156
171
  .empty {
157
172
  padding: margin(0.75);
158
173
  margin-top: margin(0.5);
@@ -176,4 +176,54 @@ describe('Playlinks.svelte', () => {
176
176
  expect(getByText('Streaming services'))
177
177
  expect(getByTestId('category-SVOD').querySelectorAll('[data-playlink]')).toHaveLength(1)
178
178
  })
179
+
180
+ it('Should limit number of shown playlinks and shown all playlinks after click of show more button', async () => {
181
+ const playlinks = [
182
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
183
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
184
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
185
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
186
+ ]
187
+
188
+ // @ts-ignore
189
+ const { getAllByText, getByText, getByTestId } = render(Playlinks, { playlinks, title, limit: 3 })
190
+
191
+ expect(getAllByText('Some playlink')).toHaveLength(3)
192
+ expect(getByTestId('show-more')).toBeTruthy()
193
+
194
+ await fireEvent.click(getByText('Show 1 more'))
195
+
196
+ expect(getAllByText('Some playlink')).toHaveLength(4)
197
+ })
198
+
199
+ it('Should not render show more button if fewer playlinks than limit are given', () => {
200
+ const playlinks = [
201
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
202
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
203
+ ]
204
+
205
+ // @ts-ignore
206
+ const { queryByTestId } = render(Playlinks, { playlinks, title, limit: 3 })
207
+
208
+ expect(queryByTestId('show-more')).not.toBeTruthy()
209
+ })
210
+
211
+ it('Should not render show more button if playlinks are categorized', () => {
212
+ // @ts-ignore
213
+ window.PlayPilotLinkInjections = {
214
+ config: { categorize_playlinks: true },
215
+ }
216
+
217
+ const playlinks = [
218
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
219
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
220
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
221
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
222
+ ]
223
+
224
+ // @ts-ignore
225
+ const { queryByTestId } = render(Playlinks, { playlinks, title, limit: 3 })
226
+
227
+ expect(queryByTestId('show-more')).not.toBeTruthy()
228
+ })
179
229
  })
@@ -2,7 +2,7 @@ import { render, fireEvent } from '@testing-library/svelte'
2
2
  import { describe, expect, it, vi, beforeEach } from 'vitest'
3
3
  import { track } from '$lib/tracking'
4
4
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
5
- import ClickTracking from '../../../routes/components/TrackAnyClick.svelte'
5
+ import ClickTracking from '../../../../routes/components/Tracking/TrackAnyClick.svelte'
6
6
 
7
7
  vi.mock('$lib/tracking', () => ({
8
8
  track: vi.fn(),
@@ -1,7 +1,7 @@
1
1
  import { render, waitFor } from '@testing-library/svelte'
2
2
  import { describe, expect, it, vi } from 'vitest'
3
3
 
4
- import TrackingPixels from '../../../routes/components/TrackingPixels.svelte'
4
+ import TrackingPixels from '../../../../routes/components/Tracking/TrackingPixels.svelte'
5
5
  import { hasConsentedTo } from '$lib/consent'
6
6
 
7
7
  vi.mock('$lib/consent', () => ({