@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.
Files changed (69) hide show
  1. package/dist/link-injections.js +25 -10
  2. package/package.json +1 -1
  3. package/src/lib/api/titles.ts +13 -1
  4. package/src/lib/data/countries.json +216 -0
  5. package/src/lib/data/translations.ts +5 -0
  6. package/src/lib/enums/SplitTest.ts +1 -6
  7. package/src/lib/explore.ts +59 -0
  8. package/src/lib/fakeData.ts +1 -0
  9. package/src/lib/images/titles-list.webp +0 -0
  10. package/src/lib/modal.ts +7 -6
  11. package/src/lib/scss/global.scss +0 -2
  12. package/src/lib/trailer.ts +22 -0
  13. package/src/lib/types/api.d.ts +6 -0
  14. package/src/lib/types/config.d.ts +12 -0
  15. package/src/lib/types/filter.d.ts +2 -0
  16. package/src/lib/types/title.d.ts +4 -1
  17. package/src/routes/+page.svelte +6 -1
  18. package/src/routes/components/Ads/TopScroll.svelte +4 -18
  19. package/src/routes/components/Button.svelte +101 -0
  20. package/src/routes/components/Debugger.svelte +25 -0
  21. package/src/routes/components/Explore/Explore.svelte +240 -0
  22. package/src/routes/components/Explore/ExploreCallToAction.svelte +58 -0
  23. package/src/routes/components/Explore/ExploreModal.svelte +15 -0
  24. package/src/routes/components/Explore/Filter/Dropdown.svelte +72 -0
  25. package/src/routes/components/Explore/Filter/Filter.svelte +99 -0
  26. package/src/routes/components/Explore/Filter/FilterItem.svelte +57 -0
  27. package/src/routes/components/Explore/Filter/FilterSorting.svelte +70 -0
  28. package/src/routes/components/Explore/Filter/Search.svelte +57 -0
  29. package/src/routes/components/Explore/Filter/TogglesWithSearch.svelte +142 -0
  30. package/src/routes/components/GridTitle.svelte +122 -0
  31. package/src/routes/components/GridTitleSkeleton.svelte +36 -0
  32. package/src/routes/components/Icons/IconArrow.svelte +10 -2
  33. package/src/routes/components/Icons/IconClose.svelte +9 -1
  34. package/src/routes/components/Icons/IconFilter.svelte +5 -0
  35. package/src/routes/components/Icons/IconPlay.svelte +3 -0
  36. package/src/routes/components/Icons/IconSearch.svelte +3 -0
  37. package/src/routes/components/ListTitle.svelte +10 -68
  38. package/src/routes/components/ListTitleSkeleton.svelte +42 -0
  39. package/src/routes/components/Modal.svelte +5 -23
  40. package/src/routes/components/Participant.svelte +0 -4
  41. package/src/routes/components/Playlinks/PlaylinkIcon.svelte +1 -1
  42. package/src/routes/components/Playlinks/PlaylinksCompact.svelte +62 -0
  43. package/src/routes/components/Share.svelte +5 -23
  44. package/src/routes/components/Title.svelte +22 -22
  45. package/src/routes/components/TitleModal.svelte +4 -1
  46. package/src/routes/components/Trailer.svelte +18 -0
  47. package/src/routes/components/YouTubeEmbedOverlay.svelte +96 -0
  48. package/src/routes/elements/+page.svelte +39 -2
  49. package/src/routes/explore/+page.svelte +60 -0
  50. package/src/tests/lib/api/ads.test.js +0 -1
  51. package/src/tests/lib/api/titles.test.js +55 -0
  52. package/src/tests/lib/explore.test.js +139 -0
  53. package/src/tests/lib/trailer.test.js +56 -0
  54. package/src/tests/routes/components/Button.test.js +28 -0
  55. package/src/tests/routes/components/Explore/Explore.test.js +94 -0
  56. package/src/tests/routes/components/Explore/Filter/Dropdown.test.js +16 -0
  57. package/src/tests/routes/components/Explore/Filter/Filter.test.js +28 -0
  58. package/src/tests/routes/components/Explore/Filter/FilterItem.test.js +50 -0
  59. package/src/tests/routes/components/Explore/Filter/FilterSorting.test.js +34 -0
  60. package/src/tests/routes/components/Explore/Filter/Search.test.js +26 -0
  61. package/src/tests/routes/components/Explore/Filter/TogglesWithSearch.test.js +53 -0
  62. package/src/tests/routes/components/GridTitle.test.js +42 -0
  63. package/src/tests/routes/components/ListTitle.test.js +1 -1
  64. package/src/tests/routes/components/Playlinks/PlaylinksCompact.test.js +42 -0
  65. package/src/tests/routes/components/Share.test.js +12 -12
  66. package/src/tests/routes/components/Title.test.js +13 -0
  67. package/src/tests/routes/components/Trailer.test.js +20 -0
  68. package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +31 -0
  69. package/src/tests/setup.js +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "5.34.1",
3
+ "version": "6.0.0-beta.explore.15",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,9 +1,21 @@
1
1
  import { getApiToken } from '$lib/token'
2
+ import type { APIPaginatedResult } from '$lib/types/api'
2
3
  import type { TitleData } from '../types/title'
3
4
  import { api } from './api'
4
5
 
6
+ export async function fetchTitles(params: Record<string, string | number> = {}): Promise<APIPaginatedResult<TitleData>> {
7
+ const apiToken = getApiToken()
8
+
9
+ if (!apiToken) throw new Error('No token was provided')
10
+
11
+ const paramsAsString = Object.entries(params).map(([key, value]) => `${key}=${value}`).join('&')
12
+ const response = await api<APIPaginatedResult<TitleData>>(`/titles/browse?api-token=${apiToken}&` + paramsAsString)
13
+
14
+ return response
15
+ }
16
+
5
17
  export async function fetchSimilarTitles(title: TitleData): Promise<TitleData[]> {
6
- const response = await api<{ results: TitleData[] }>(`/titles/browse?api-token=${getApiToken()}&related_to_sid=${title.sid}`)
18
+ const response = await fetchTitles({ related_to_sid: title.sid })
7
19
 
8
20
  return response.results
9
21
  }
@@ -0,0 +1,216 @@
1
+ [
2
+ { "label": "Aruba", "value": "AW" },
3
+ { "label": "Afghanistan", "value": "AF" },
4
+ { "label": "Angola", "value": "AO" },
5
+ { "label": "Anguilla", "value": "AI" },
6
+ { "label": "Albania", "value": "AL" },
7
+ { "label": "Andorra", "value": "AD" },
8
+ { "label": "United Arab Emirates", "value": "AE" },
9
+ { "label": "Argentina", "value": "AR" },
10
+ { "label": "Armenia", "value": "AM" },
11
+ { "label": "American Samoa", "value": "AS" },
12
+ { "label": "Antigua and Barbuda", "value": "AG" },
13
+ { "label": "Australia", "value": "AU" },
14
+ { "label": "Austria", "value": "AT" },
15
+ { "label": "Azerbaijan", "value": "AZ" },
16
+ { "label": "Burundi", "value": "BI" },
17
+ { "label": "Belgium", "value": "BE" },
18
+ { "label": "Benin", "value": "BJ" },
19
+ { "label": "Burkina Faso", "value": "BF" },
20
+ { "label": "Bangladesh", "value": "BD" },
21
+ { "label": "Bulgaria", "value": "BG" },
22
+ { "label": "Bahrain", "value": "BH" },
23
+ { "label": "Bahamas", "value": "BS" },
24
+ { "label": "Bosnia and Herzegovina", "value": "BA" },
25
+ { "label": "Belarus", "value": "BY" },
26
+ { "label": "Belize", "value": "BZ" },
27
+ { "label": "Bermuda", "value": "BM" },
28
+ { "label": "Bolivia", "value": "BO" },
29
+ { "label": "Brazil", "value": "BR" },
30
+ { "label": "Barbados", "value": "BB" },
31
+ { "label": "Brunei", "value": "BN" },
32
+ { "label": "Bhutan", "value": "BT" },
33
+ { "label": "Botswana", "value": "BW" },
34
+ { "label": "Central African Republic", "value": "CF" },
35
+ { "label": "Canada", "value": "CA" },
36
+ { "label": "Switzerland", "value": "CH" },
37
+ { "label": "Chile", "value": "CL" },
38
+ { "label": "China", "value": "CN" },
39
+ { "label": "Côte d'Ivoire", "value": "CI" },
40
+ { "label": "Cameroon", "value": "CM" },
41
+ { "label": "DR Congo", "value": "CD" },
42
+ { "label": "Congo", "value": "CG" },
43
+ { "label": "Cook Islands", "value": "CK" },
44
+ { "label": "Colombia", "value": "CO" },
45
+ { "label": "Comoros", "value": "KM" },
46
+ { "label": "Cabo Verde", "value": "CV" },
47
+ { "label": "Costa Rica", "value": "CR" },
48
+ { "label": "Cuba", "value": "CU" },
49
+ { "label": "Curaçao", "value": "CW" },
50
+ { "label": "Cayman Islands", "value": "KY" },
51
+ { "label": "Cyprus", "value": "CY" },
52
+ { "label": "Czechia", "value": "CZ" },
53
+ { "label": "Germany", "value": "DE" },
54
+ { "label": "Djibouti", "value": "DJ" },
55
+ { "label": "Dominica", "value": "DM" },
56
+ { "label": "Denmark", "value": "DK" },
57
+ { "label": "Dominican Republic", "value": "DO" },
58
+ { "label": "Algeria", "value": "DZ" },
59
+ { "label": "Ecuador", "value": "EC" },
60
+ { "label": "Egypt", "value": "EG" },
61
+ { "label": "Eritrea", "value": "ER" },
62
+ { "label": "Spain", "value": "ES" },
63
+ { "label": "Estonia", "value": "EE" },
64
+ { "label": "Ethiopia", "value": "ET" },
65
+ { "label": "Finland", "value": "FI" },
66
+ { "label": "Fiji", "value": "FJ" },
67
+ { "label": "Falkland Islands", "value": "FK" },
68
+ { "label": "France", "value": "FR" },
69
+ { "label": "Faroe Islands", "value": "FO" },
70
+ { "label": "Micronesia", "value": "FM" },
71
+ { "label": "Gabon", "value": "GA" },
72
+ { "label": "United Kingdom", "value": "GB" },
73
+ { "label": "Georgia", "value": "GE" },
74
+ { "label": "Ghana", "value": "GH" },
75
+ { "label": "Gibraltar", "value": "GI" },
76
+ { "label": "Guinea", "value": "GN" },
77
+ { "label": "Guadeloupe", "value": "GP" },
78
+ { "label": "Gambia", "value": "GM" },
79
+ { "label": "Guinea-Bissau", "value": "GW" },
80
+ { "label": "Equatorial Guinea", "value": "GQ" },
81
+ { "label": "Greece", "value": "GR" },
82
+ { "label": "Grenada", "value": "GD" },
83
+ { "label": "Greenland", "value": "GL" },
84
+ { "label": "Guatemala", "value": "GT" },
85
+ { "label": "Guyana", "value": "GY" },
86
+ { "label": "Hong Kong", "value": "HK" },
87
+ { "label": "Honduras", "value": "HN" },
88
+ { "label": "Croatia", "value": "HR" },
89
+ { "label": "Haiti", "value": "HT" },
90
+ { "label": "Hungary", "value": "HU" },
91
+ { "label": "Indonesia", "value": "ID" },
92
+ { "label": "India", "value": "IN" },
93
+ { "label": "Ireland", "value": "IE" },
94
+ { "label": "Iran", "value": "IR" },
95
+ { "label": "Iraq", "value": "IQ" },
96
+ { "label": "Iceland", "value": "IS" },
97
+ { "label": "Israel", "value": "IL" },
98
+ { "label": "Italy", "value": "IT" },
99
+ { "label": "Jamaica", "value": "JM" },
100
+ { "label": "Jordan", "value": "JO" },
101
+ { "label": "Japan", "value": "JP" },
102
+ { "label": "Kazakhstan", "value": "KZ" },
103
+ { "label": "Kenya", "value": "KE" },
104
+ { "label": "Kyrgyzstan", "value": "KG" },
105
+ { "label": "Cambodia", "value": "KH" },
106
+ { "label": "Kiribati", "value": "KI" },
107
+ { "label": "Saint Kitts and Nevis", "value": "KN" },
108
+ { "label": "South Korea", "value": "KR" },
109
+ { "label": "Kuwait", "value": "KW" },
110
+ { "label": "Laos", "value": "LA" },
111
+ { "label": "Lebanon", "value": "LB" },
112
+ { "label": "Liberia", "value": "LR" },
113
+ { "label": "Libya", "value": "LY" },
114
+ { "label": "Saint Lucia", "value": "LC" },
115
+ { "label": "Liechtenstein", "value": "LI" },
116
+ { "label": "Sri Lanka", "value": "LK" },
117
+ { "label": "Lesotho", "value": "LS" },
118
+ { "label": "Lithuania", "value": "LT" },
119
+ { "label": "Luxembourg", "value": "LU" },
120
+ { "label": "Latvia", "value": "LV" },
121
+ { "label": "Macao", "value": "MO" },
122
+ { "label": "Morocco", "value": "MA" },
123
+ { "label": "Monaco", "value": "MC" },
124
+ { "label": "Moldova", "value": "MD" },
125
+ { "label": "Madagascar", "value": "MG" },
126
+ { "label": "Maldives", "value": "MV" },
127
+ { "label": "Mexico", "value": "MX" },
128
+ { "label": "Marshall Islands", "value": "MH" },
129
+ { "label": "North Macedonia", "value": "MK" },
130
+ { "label": "Mali", "value": "ML" },
131
+ { "label": "Malta", "value": "MT" },
132
+ { "label": "Myanmar", "value": "MM" },
133
+ { "label": "Montenegro", "value": "ME" },
134
+ { "label": "Mongolia", "value": "MN" },
135
+ { "label": "Mozambique", "value": "MZ" },
136
+ { "label": "Mauritania", "value": "MR" },
137
+ { "label": "Martinique", "value": "MQ" },
138
+ { "label": "Mauritius", "value": "MU" },
139
+ { "label": "Malawi", "value": "MW" },
140
+ { "label": "Malaysia", "value": "MY" },
141
+ { "label": "Namibia", "value": "NA" },
142
+ { "label": "New Caledonia", "value": "NC" },
143
+ { "label": "Niger", "value": "NE" },
144
+ { "label": "Nigeria", "value": "NG" },
145
+ { "label": "Nicaragua", "value": "NI" },
146
+ { "label": "Niue", "value": "NU" },
147
+ { "label": "Netherlands", "value": "NL" },
148
+ { "label": "Norway", "value": "NO" },
149
+ { "label": "Nepal", "value": "NP" },
150
+ { "label": "Nauru", "value": "NR" },
151
+ { "label": "New Zealand", "value": "NZ" },
152
+ { "label": "Oman", "value": "OM" },
153
+ { "label": "Pakistan", "value": "PK" },
154
+ { "label": "Panama", "value": "PA" },
155
+ { "label": "Peru", "value": "PE" },
156
+ { "label": "Philippines", "value": "PH" },
157
+ { "label": "Palau", "value": "PW" },
158
+ { "label": "Papua New Guinea", "value": "PG" },
159
+ { "label": "Poland", "value": "PL" },
160
+ { "label": "Puerto Rico", "value": "PR" },
161
+ { "label": "North Korea", "value": "KP" },
162
+ { "label": "Portugal", "value": "PT" },
163
+ { "label": "Paraguay", "value": "PY" },
164
+ { "label": "Palestine", "value": "PS" },
165
+ { "label": "French Polynesia", "value": "PF" },
166
+ { "label": "Qatar", "value": "QA" },
167
+ { "label": "Romania", "value": "RO" },
168
+ { "label": "Russian Federation", "value": "RU" },
169
+ { "label": "Rwanda", "value": "RW" },
170
+ { "label": "Saudi Arabia", "value": "SA" },
171
+ { "label": "Sudan", "value": "SD" },
172
+ { "label": "Senegal", "value": "SN" },
173
+ { "label": "Singapore", "value": "SG" },
174
+ { "label": "Solomon Islands", "value": "SB" },
175
+ { "label": "Sierra Leone", "value": "SL" },
176
+ { "label": "El Salvador", "value": "SV" },
177
+ { "label": "San Marino", "value": "SM" },
178
+ { "label": "Somalia", "value": "SO" },
179
+ { "label": "Serbia", "value": "RS" },
180
+ { "label": "South Sudan", "value": "SS" },
181
+ { "label": "São Tomé and Príncipe", "value": "ST" },
182
+ { "label": "Suriname", "value": "SR" },
183
+ { "label": "Slovakia", "value": "SK" },
184
+ { "label": "Slovenia", "value": "SI" },
185
+ { "label": "Sweden", "value": "SE" },
186
+ { "label": "Eswatini", "value": "SZ" },
187
+ { "label": "Seychelles", "value": "SC" },
188
+ { "label": "Syria", "value": "SY" },
189
+ { "label": "Chad", "value": "TD" },
190
+ { "label": "Togo", "value": "TG" },
191
+ { "label": "Thailand", "value": "TH" },
192
+ { "label": "Tajikistan", "value": "TJ" },
193
+ { "label": "Turkmenistan", "value": "TM" },
194
+ { "label": "Timor-Leste", "value": "TL" },
195
+ { "label": "Tonga", "value": "TO" },
196
+ { "label": "Trinidad and Tobago", "value": "TT" },
197
+ { "label": "Tunisia", "value": "TN" },
198
+ { "label": "Turkey", "value": "TR" },
199
+ { "label": "Tuvalu", "value": "TV" },
200
+ { "label": "Taiwan", "value": "TW" },
201
+ { "label": "Tanzania", "value": "TZ" },
202
+ { "label": "Uganda", "value": "UG" },
203
+ { "label": "Ukraine", "value": "UA" },
204
+ { "label": "Uruguay", "value": "UY" },
205
+ { "label": "United States", "value": "US" },
206
+ { "label": "Uzbekistan", "value": "UZ" },
207
+ { "label": "Saint Vincent and the Grenadines", "value": "VC" },
208
+ { "label": "Venezuela", "value": "VE" },
209
+ { "label": "Vietnam", "value": "VN" },
210
+ { "label": "Vanuatu", "value": "VU" },
211
+ { "label": "Samoa", "value": "WS" },
212
+ { "label": "Yemen", "value": "YE" },
213
+ { "label": "South Africa", "value": "ZA" },
214
+ { "label": "Zambia", "value": "ZM" },
215
+ { "label": "Zimbabwe", "value": "ZW" }
216
+ ]
@@ -126,6 +126,11 @@ export const translations = {
126
126
  [Language.Swedish]: 'Liknande filmer och serier',
127
127
  [Language.Danish]: 'Lignende film og serier',
128
128
  },
129
+ 'Watch Trailer': {
130
+ [Language.English]: 'Watch trailer',
131
+ [Language.Swedish]: 'Se trailer',
132
+ [Language.Danish]: 'Se trailer',
133
+ },
129
134
 
130
135
  // Genres
131
136
  'All': {
@@ -4,11 +4,6 @@ export const SplitTest = {
4
4
  numberOfVariants: 2,
5
5
  variantNames: ['Separated', 'Inline'] as string[],
6
6
  },
7
- ParticipantPlaylinkFormat: {
8
- key: 'participant_playlink_format',
9
- numberOfVariants: 2,
10
- variantNames: ['Image', 'Label'] as string[],
11
- },
12
7
  InTextEngagement: {
13
8
  key: 'in-text-engagement',
14
9
  numberOfVariants: 4,
@@ -18,5 +13,5 @@ export const SplitTest = {
18
13
  key: 'user_time_spent',
19
14
  numberOfVariants: 10,
20
15
  variantNames: ['No Links', 'Default', 'Default', 'Default', 'Default', 'Default', 'Default', 'Default', 'Default', 'Default'] as string[],
21
- }
16
+ },
22
17
  } as const
@@ -0,0 +1,59 @@
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
+ }
@@ -38,6 +38,7 @@ export const title: TitleData = {
38
38
  standing_poster: 'https://img.playpilot.tech/6239ee86a58f11efb0b50a58a9feac02/src/img?optimizer=image&class=2by3x18',
39
39
  title: 'Dune: Prophecy',
40
40
  original_title: 'Dune: Prophecy',
41
+ embeddable_url: null,
41
42
  }
42
43
 
43
44
  export const linkInjections: LinkInjection[] = [{
Binary file
package/src/lib/modal.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { mount, unmount } from "svelte"
2
2
  import { isHoldingSpecialKey } from "./event"
3
- import TitleModal from "../routes/components/TitleModal.svelte"
4
3
  import type { LinkInjection } from "./types/injection"
5
- import ParticipantModal from "../routes/components/ParticipantModal.svelte"
6
4
  import type { TitleData } from "./types/title"
7
5
  import type { ParticipantData } from "./types/participant"
8
6
  import { mobileBreakpoint } from "./constants"
7
+ import TitleModal from "../routes/components/TitleModal.svelte"
8
+ import ParticipantModal from "../routes/components/ParticipantModal.svelte"
9
+ import ExploreModal from "../routes/components/Explore/ExploreModal.svelte"
9
10
  import { getPlayPilotWrapperElement, keyDataAttribute, keySelector } from "./injection"
10
11
  import { playFallbackViewTransition } from "./viewTransition"
11
12
  import { destroyLinkPopover } from "./popover"
@@ -13,7 +14,7 @@ import { prefersReducedMotion } from "svelte/motion"
13
14
  import { trackSplitTestAction } from "./splitTest"
14
15
  import { SplitTest } from "./enums/SplitTest"
15
16
 
16
- type ModalType = 'title' | 'participant'
17
+ type ModalType = 'title' | 'participant' | 'explore'
17
18
 
18
19
  type Modal = {
19
20
  injection?: LinkInjection | null
@@ -51,9 +52,9 @@ export function openModal(
51
52
  }
52
53
 
53
54
  function getModalComponentByType({ type = 'title', target, data, props = {} }: { type: ModalType, target: Element, data: TitleData | ParticipantData | null, props?: Record<string, any> }) {
54
- return type === 'title' ?
55
- mount(TitleModal, { target, props: { title: data as TitleData, ...props } }) :
56
- mount(ParticipantModal, { target, props: { participant: data as ParticipantData, ...props } })
55
+ if (type === 'participant') return mount(ParticipantModal, { target, props: { participant: data as ParticipantData, ...props } })
56
+ if (type === 'explore') return mount(ExploreModal, { target, props: { ...props } })
57
+ return mount(TitleModal, { target, props: { title: data as TitleData, ...props } })
57
58
  }
58
59
 
59
60
  function addModalToList({ type = 'title', injection = null, data, scrollPosition = 0, component }: Modal) {
@@ -60,9 +60,7 @@
60
60
  .playpilot-styled-scrollbar {
61
61
  scrollbar-color: var(--playpilot-content-light) var(--playpilot-lighter);
62
62
  scrollbar-width: thin;
63
- }
64
63
 
65
- .playpilot-styled-scrollbed {
66
64
  &::-webkit-scrollbar {
67
65
  width: margin(0.75);
68
66
  }
@@ -0,0 +1,22 @@
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
+ }
@@ -0,0 +1,6 @@
1
+ export type APIPaginatedResult<T> = {
2
+ next: string | null
3
+ previous: string | null
4
+ results: T[]
5
+ count?: number
6
+ }
@@ -39,4 +39,16 @@ export type ConfigResponse = {
39
39
  in_text_disclaimer_text?: string
40
40
  in_text_disclaimer_selector?: string
41
41
  in_text_disclaimer_insert_position?: InsertPosition
42
+
43
+ /**
44
+ * These options are all relevant for the Explore component, which can be inserted as a widget on any page or as a modal.
45
+ * `explore_navigation_selector` is used to select the navigation element that should be copied and inserted _after_.
46
+ * `explore_navigation_label` will end up being the label of the navigation item, defaults to `Streaming Guide`.
47
+ * `explore_navigation_path` is the path that the navigation item will lead to, this will be set up by the third party.
48
+ * `explore_navigation_insert_position` is used to determine if the navigation item should be inserted before or after the given selector.
49
+ */
50
+ explore_navigation_selector?: string
51
+ explore_navigation_label?: string
52
+ explore_navigation_path?: string
53
+ explore_navigation_insert_position?: InsertPosition
42
54
  }
@@ -0,0 +1,2 @@
1
+ // TODO: Add proper filter types
2
+ export type ExploreFilter = Record<string, string>
@@ -1,6 +1,8 @@
1
1
  import type { ParticipantData } from './participant'
2
2
  import type { PlaylinkData } from './playlink'
3
3
 
4
+ export type ContentType = 'movie' | 'series'
5
+
4
6
  export type TitleData = {
5
7
  sid: string
6
8
  slug: string
@@ -9,7 +11,7 @@ export type TitleData = {
9
11
  genres: string[]
10
12
  year: number
11
13
  imdb_score: number
12
- type: 'movie' | 'series'
14
+ type: ContentType
13
15
  providers: PlaylinkData[]
14
16
  description: string | null
15
17
  small_poster: string
@@ -17,6 +19,7 @@ export type TitleData = {
17
19
  standing_poster: string
18
20
  title: string
19
21
  original_title: string
22
+ embeddable_url: string | null
20
23
  length?: number
21
24
  blurb?: string
22
25
  participants?: ParticipantData[]
@@ -8,6 +8,7 @@
8
8
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
9
9
  import { fetchAds } from '$lib/api/ads'
10
10
  import { fetchConfig } from '$lib/api/config'
11
+ import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
11
12
  import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/api/auth'
12
13
  import { trackSplitTestView, getSplitTestVariantName } from '$lib/splitTest'
13
14
  import { SplitTest } from '$lib/enums/SplitTest'
@@ -44,6 +45,8 @@
44
45
  if (isEditorialMode && !loading) rerender()
45
46
  })
46
47
 
48
+ insertExplore()
49
+
47
50
  onDestroy(clearLinkInjections)
48
51
 
49
52
  // This function is called when a user has properly consented via tcfapi or if no consent is required.
@@ -80,6 +83,7 @@
80
83
  if (isUrlExcluded) return
81
84
 
82
85
  if (config?.custom_style) insertCustomStyle(config.custom_style || '')
86
+ if (config?.explore_navigation_selector) insertExploreIntoNavigation()
83
87
 
84
88
  setElements(config?.html_selector || '', config?.exclude_elements_selector || '')
85
89
  } catch(error) {
@@ -254,7 +258,8 @@
254
258
  <style lang="scss">
255
259
  @import url('$lib/scss/global.scss');
256
260
 
257
- .playpilot-link-injections {
261
+ .playpilot-link-injections,
262
+ :global([data-playpilot-explore]) {
258
263
  :global(*) {
259
264
  box-sizing: border-box;
260
265
  }
@@ -4,7 +4,7 @@
4
4
  import { SplitTest } from '$lib/enums/SplitTest'
5
5
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
6
6
  import { imageFromUUID } from '$lib/image'
7
- import { isInSplitTestVariant, trackSplitTestView } from '$lib/splitTest'
7
+ import { trackSplitTestView } from '$lib/splitTest'
8
8
  import { track } from '$lib/tracking'
9
9
  import type { Campaign } from '$lib/types/campaign'
10
10
  import Disclaimer from './Disclaimer.svelte'
@@ -22,7 +22,6 @@
22
22
  const { format, header, header_logo: logo, image_uuid: backgroundImageUUID } = $derived(content)
23
23
  const { header: buttonLabel, url: href } = $derived(cta)
24
24
 
25
- const inline = isInSplitTestVariant(SplitTest.TopScrollFormat, 1)
26
25
  const simple = $derived(format === 'large')
27
26
 
28
27
  const backgroundImage = $derived(imageFromUUID(backgroundImageUUID, ImageDimensions.TopScrollBackground))
@@ -43,7 +42,6 @@
43
42
  target="_blank"
44
43
  class="top-scroll"
45
44
  class:simple
46
- class:inline
47
45
  tabindex="-1"
48
46
  rel="sponsored"
49
47
  style:--width="{clientWidth}px">
@@ -81,17 +79,13 @@
81
79
  position: relative;
82
80
  display: block;
83
81
  width: 100%;
84
- border-radius: $border-radius-size;
82
+ border-radius: $border-radius-size $border-radius-size 0 0;
85
83
  background: black;
86
84
  color: theme(top-scroll-text-color, white);
87
85
  font-family: theme(top-scroll-font-family, font-family);
88
86
  font-size: theme(top-scroll-font-size, font-size-base);
89
87
  text-decoration: none;
90
88
  line-height: 1.35;
91
-
92
- &.inline {
93
- border-radius: $border-radius-size $border-radius-size 0 0;
94
- }
95
89
  }
96
90
 
97
91
  .content {
@@ -152,7 +146,7 @@
152
146
  right: 0;
153
147
  bottom: 0;
154
148
  left: 0;
155
- border-radius: $border-radius-size;
149
+ border-radius: $border-radius-size $border-radius-size 0 0;
156
150
  background-image: var(--background);
157
151
  background-position: center;
158
152
  background-size: cover;
@@ -161,10 +155,6 @@
161
155
  .top-scroll:hover & {
162
156
  filter: brightness(1.15);
163
157
  }
164
-
165
- .inline & {
166
- border-radius: $border-radius-size $border-radius-size 0 0;
167
- }
168
158
  }
169
159
 
170
160
  .content-image {
@@ -172,14 +162,10 @@
172
162
  max-width: 100%;
173
163
  height: auto;
174
164
  background: black;
175
- border-radius: $border-radius-size;
165
+ border-radius: $border-radius-size $border-radius-size 0 0;
176
166
 
177
167
  .top-scroll:hover & {
178
168
  filter: brightness(1.15);
179
169
  }
180
-
181
- .inline & {
182
- border-radius: $border-radius-size $border-radius-size 0 0;
183
- }
184
170
  }
185
171
  </style>