@playpilot/tpi 8.17.1 → 8.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +1 -1
- package/dist/editorial.mount.js +11 -11
- package/dist/link-injections.js +2 -2
- package/dist/mount.js +9 -9
- package/package.json +1 -1
- package/src/lib/api/titles.ts +2 -2
- package/src/lib/constants.ts +1 -0
- package/src/lib/data/translations.ts +5 -0
- package/src/lib/enums/TrackingEvent.ts +4 -4
- package/src/lib/fakeData.ts +44 -32
- package/src/lib/injection.ts +10 -9
- package/src/lib/modal.ts +3 -1
- package/src/lib/popover.ts +13 -12
- package/src/lib/scss/_mixins.scss +29 -0
- package/src/lib/scss/global.scss +0 -27
- package/src/lib/tracking.ts +2 -0
- package/src/lib/types/injection.d.ts +5 -0
- package/src/main.ts +2 -1
- package/src/routes/+page.svelte +3 -3
- package/src/routes/components/Editorial/Editor.svelte +8 -5
- package/src/routes/components/Editorial/EditorItem.svelte +23 -9
- package/src/routes/components/Editorial/ManualInjection.svelte +1 -0
- package/src/routes/components/Explore/ExploreLayout.svelte +5 -3
- package/src/routes/components/Explore/Filter/Dropdown.svelte +3 -1
- package/src/routes/components/Explore/Filter/Filter.svelte +5 -3
- package/src/routes/components/Explore/Routes/ExploreHome.svelte +4 -4
- package/src/routes/components/{TitlePopover.svelte → InjectionPopover.svelte} +26 -9
- package/src/routes/components/ListTitle.svelte +27 -6
- package/src/routes/components/Modals/Modal.svelte +2 -0
- package/src/routes/components/Modals/RailModal.svelte +9 -3
- package/src/routes/components/Participant.svelte +18 -6
- package/src/routes/components/Playlinks/Playlink.svelte +1 -1
- package/src/routes/components/Playlinks/PlaylinkIcon.svelte +1 -1
- package/src/routes/components/Playlinks/Playlinks.svelte +21 -5
- package/src/routes/components/Popover.svelte +2 -1
- package/src/routes/components/Title.svelte +1 -1
- package/src/routes/elements/+page.svelte +3 -3
- package/src/tests/helpers.js +1 -0
- package/src/tests/lib/api/titles.test.js +4 -4
- package/src/tests/lib/injection.test.js +44 -3
- package/src/tests/lib/popover.test.js +7 -7
- package/src/tests/lib/tracking.test.js +8 -0
- package/src/tests/routes/components/Editorial/EditorItem.test.js +10 -0
- package/src/tests/routes/components/Editorial/ManualInjection.test.js +4 -0
- package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +2 -0
- package/src/tests/routes/components/InjectionPopover.test.js +117 -0
- package/src/tests/routes/components/ListTitle.test.js +7 -0
- package/src/tests/routes/components/Participant.test.js +7 -0
- package/src/tests/routes/components/Playlinks/AfterArticlePlaylinks.test.js +4 -0
- package/src/tests/routes/components/Playlinks/Playlinks.test.js +51 -1
- package/src/tests/routes/components/{TrackAnyClick.test.js → Tracking/TrackAnyClick.test.js} +1 -1
- package/src/tests/routes/components/{TrackingPixels.test.js → Tracking/TrackingPixels.test.js} +1 -1
- package/src/tests/routes/components/TitlePopover.test.js +0 -78
- /package/src/routes/components/{TrackAnyClick.svelte → Tracking/TrackAnyClick.svelte} +0 -0
- /package/src/routes/components/{TrackScrollDistance.svelte → Tracking/TrackScrollDistance.svelte} +0 -0
- /package/src/routes/components/{TrackingPixels.svelte → Tracking/TrackingPixels.svelte} +0 -0
- /package/src/routes/components/{TrackingPixelsForTitleDataPerLink.svelte → Tracking/TrackingPixelsForTitleDataPerLink.svelte} +0 -0
- /package/src/routes/components/{UserJourney.svelte → Tracking/UserJourney.svelte} +0 -0
package/package.json
CHANGED
package/src/lib/api/titles.ts
CHANGED
|
@@ -18,9 +18,9 @@ export async function fetchTitles(params: Record<string, any> = {}): Promise<API
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (params.region !== null) {
|
|
21
|
-
|
|
21
|
+
let region = params.region || await getRegionBasedOnIp()
|
|
22
22
|
|
|
23
|
-
if (!await isUserRegionSupported(region))
|
|
23
|
+
if (!await isUserRegionSupported(region)) region = 'gl'
|
|
24
24
|
|
|
25
25
|
params.region = region
|
|
26
26
|
}
|
package/src/lib/constants.ts
CHANGED
|
@@ -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': {
|
|
@@ -16,10 +16,10 @@ export const TrackingEvent = {
|
|
|
16
16
|
ParticipantModalClose: 'ali_participant_modal_close',
|
|
17
17
|
|
|
18
18
|
// Popover
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
InjectionPopoverView: 'ali_injection_popover_view',
|
|
20
|
+
InjectionPopoverClose: 'ali_injection_popover_close',
|
|
21
|
+
InjectionPopoverSaveClick: 'ali_injection_popover_save_click',
|
|
22
|
+
InjectionPopoverPlaylinkClick: 'ali_injection_popover_playlink_click',
|
|
23
23
|
|
|
24
24
|
// Rails
|
|
25
25
|
SimilarTitleClick: 'ali_similar_title_click',
|
package/src/lib/fakeData.ts
CHANGED
|
@@ -43,38 +43,6 @@ export const title: TitleData = {
|
|
|
43
43
|
embeddable_url: null,
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export const linkInjections: LinkInjection[] = [{
|
|
47
|
-
sid: '1',
|
|
48
|
-
title: 'Quan',
|
|
49
|
-
sentence: 'In an interview with Epire Magazine, Quan reveals he quested starring in Love Hurts',
|
|
50
|
-
playpilot_url: 'https://playpilot.com/movie/example/',
|
|
51
|
-
key: 'some-key-1',
|
|
52
|
-
title_details: { ...title, sid: '1' },
|
|
53
|
-
}, {
|
|
54
|
-
sid: '2',
|
|
55
|
-
title: 'The Long Kiss Goodnight',
|
|
56
|
-
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
57
|
-
playpilot_url: 'https://playpilot.com/movie/example-2/',
|
|
58
|
-
key: 'some-key-2',
|
|
59
|
-
after_article: false,
|
|
60
|
-
title_details: { ...title, sid: '2' },
|
|
61
|
-
}, {
|
|
62
|
-
sid: '3',
|
|
63
|
-
title: 'Nobody',
|
|
64
|
-
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
65
|
-
playpilot_url: 'https://playpilot.com/movie/example-3/',
|
|
66
|
-
key: 'some-key-3',
|
|
67
|
-
after_article: true,
|
|
68
|
-
title_details: { ...title, sid: '3' },
|
|
69
|
-
manual: false,
|
|
70
|
-
}, {
|
|
71
|
-
sid: '4',
|
|
72
|
-
title: 'The Wheel of Time',
|
|
73
|
-
sentence: '(The more I think about the Bene Gesserit the more I see how much they influenced not just the Jedi in Star Wars but also the Aes Sedai in The Wheel of Time and even the sorceresses in The Witcher books, though Herbert\'s order is the most ominous and terrifying of them all)',
|
|
74
|
-
playpilot_url: 'https://playpilot.com/movie/example-4/',
|
|
75
|
-
key: 'some-key-4',
|
|
76
|
-
}]
|
|
77
|
-
|
|
78
46
|
export const participants: ParticipantData[] = [
|
|
79
47
|
{
|
|
80
48
|
sid: 'pr5C5W',
|
|
@@ -144,6 +112,50 @@ export const participants: ParticipantData[] = [
|
|
|
144
112
|
},
|
|
145
113
|
]
|
|
146
114
|
|
|
115
|
+
export const linkInjections: LinkInjection[] = [{
|
|
116
|
+
sid: '1',
|
|
117
|
+
title: 'Quan',
|
|
118
|
+
sentence: 'In an interview with Epire Magazine, Quan reveals he quested starring in Love Hurts',
|
|
119
|
+
playpilot_url: 'https://playpilot.com/movie/example/',
|
|
120
|
+
key: 'some-key-1',
|
|
121
|
+
title_details: { ...title, sid: '1' },
|
|
122
|
+
type: 'title',
|
|
123
|
+
}, {
|
|
124
|
+
sid: '2',
|
|
125
|
+
title: 'The Long Kiss Goodnight',
|
|
126
|
+
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
127
|
+
playpilot_url: 'https://playpilot.com/movie/example-2/',
|
|
128
|
+
key: 'some-key-2',
|
|
129
|
+
after_article: false,
|
|
130
|
+
title_details: { ...title, sid: '2' },
|
|
131
|
+
type: 'title',
|
|
132
|
+
}, {
|
|
133
|
+
sid: '3',
|
|
134
|
+
title: 'Nobody',
|
|
135
|
+
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
136
|
+
playpilot_url: 'https://playpilot.com/movie/example-3/',
|
|
137
|
+
key: 'some-key-3',
|
|
138
|
+
after_article: true,
|
|
139
|
+
title_details: { ...title, sid: '3' },
|
|
140
|
+
manual: false,
|
|
141
|
+
type: 'title',
|
|
142
|
+
}, {
|
|
143
|
+
sid: '4',
|
|
144
|
+
title: 'The Wheel of Time',
|
|
145
|
+
sentence: '(The more I think about the Bene Gesserit the more I see how much they influenced not just the Jedi in Star Wars but also the Aes Sedai in The Wheel of Time and even the sorceresses in The Witcher books, though Herbert\'s order is the most ominous and terrifying of them all)',
|
|
146
|
+
playpilot_url: 'https://playpilot.com/movie/example-4/',
|
|
147
|
+
key: 'some-key-4',
|
|
148
|
+
type: 'title',
|
|
149
|
+
}, {
|
|
150
|
+
sid: '5',
|
|
151
|
+
title: 'Jack Black',
|
|
152
|
+
sentence: 'Jason Momoa (”Aquaman”), Jack Black (”Nacho Libre”) och Jennifer Coolidge (”The White Lotus”) medverkar i den Jared Hess-regisserade (”Napolen Dynamite”) filmen.',
|
|
153
|
+
playpilot_url: 'https://playpilot.com/name/pr875J-jack-black/',
|
|
154
|
+
key: 'some-key-5',
|
|
155
|
+
participant_details: participants[2],
|
|
156
|
+
type: 'participant',
|
|
157
|
+
}]
|
|
158
|
+
|
|
147
159
|
export const campaign: Campaign = {
|
|
148
160
|
campaign_format: 'card',
|
|
149
161
|
campaign_type: 'image',
|
package/src/lib/injection.ts
CHANGED
|
@@ -172,7 +172,7 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
|
|
|
172
172
|
|
|
173
173
|
insertInTextWidgets(foundInjections)
|
|
174
174
|
|
|
175
|
-
return mergedInjections.filter(
|
|
175
|
+
return mergedInjections.filter(injection => hasValidTypeData(injection)).map((injection, index) => {
|
|
176
176
|
const hasManualEquivalent = !injection.manual && isAvailableAsManualInjection(injection, index, mergedInjections)
|
|
177
177
|
const duplicate = injection.duplicate ?? hasManualEquivalent
|
|
178
178
|
|
|
@@ -390,11 +390,11 @@ export function separateLinkInjectionTypes(injections: LinkInjection[]): LinkInj
|
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
export function isValidInjection(injection: LinkInjection): boolean {
|
|
393
|
-
return !injection.inactive && !injection.removed && !injection.duplicate &&
|
|
393
|
+
return !injection.inactive && !injection.removed && !injection.duplicate && hasValidTypeData(injection) && isValidPlaylinkType(injection)
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
/**
|
|
397
|
-
* An injection can
|
|
397
|
+
* An injection can be of various playlink types, when all are false equivalent, the link is not valid.
|
|
398
398
|
* It should be treated similar to an inactive playlink in this case.
|
|
399
399
|
*/
|
|
400
400
|
export function isValidPlaylinkType(injection: LinkInjection): boolean {
|
|
@@ -402,16 +402,10 @@ export function isValidPlaylinkType(injection: LinkInjection): boolean {
|
|
|
402
402
|
return !!injection.after_article
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
/**
|
|
406
|
-
* Filter links for in-text injections, removing after article, inactive, removed, duplicate, and items without title_details
|
|
407
|
-
*/
|
|
408
405
|
export function filterInvalidInTextInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
409
406
|
return filterRemovedAndInactiveInjections(injections).filter(i => i.in_text !== false && isValidInjection(i))
|
|
410
407
|
}
|
|
411
408
|
|
|
412
|
-
/**
|
|
413
|
-
* Filter links for after article injections, removing in-text only, inactive, removed, duplicate, and items without title_details
|
|
414
|
-
*/
|
|
415
409
|
export function filterInvalidAfterArticleInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
416
410
|
return filterRemovedAndInactiveInjections(injections).filter(i => i.after_article === true && isValidInjection(i))
|
|
417
411
|
}
|
|
@@ -456,6 +450,13 @@ export function isEquivalentInjection(injection1: LinkInjection, injection2: Lin
|
|
|
456
450
|
return injection1.title === injection2.title && cleanPhrase(injection1.sentence) === cleanPhrase(injection2.sentence)
|
|
457
451
|
}
|
|
458
452
|
|
|
453
|
+
export function hasValidTypeData(injection: LinkInjection): boolean {
|
|
454
|
+
if (injection.type === 'title' && !!injection.title_details) return true
|
|
455
|
+
if (injection.type === 'participant' && !!injection.participant_details) return true
|
|
456
|
+
|
|
457
|
+
return false
|
|
458
|
+
}
|
|
459
|
+
|
|
459
460
|
export function getPlayPilotWrapperElement(): Element {
|
|
460
461
|
return document.querySelector('[data-playpilot-link-injections]') || document.body
|
|
461
462
|
}
|
package/src/lib/modal.ts
CHANGED
|
@@ -131,7 +131,9 @@ export function openModalForInjectedLink(event: MouseEvent, injections: LinkInje
|
|
|
131
131
|
event.preventDefault()
|
|
132
132
|
|
|
133
133
|
playFallbackViewTransition(() => {
|
|
134
|
+
const data = injection.title_details || injection.participant_details
|
|
135
|
+
|
|
134
136
|
destroyLinkPopover(false)
|
|
135
|
-
openModal({ event, injection, data: injection.
|
|
137
|
+
openModal({ event, injection, data, type: injection.type })
|
|
136
138
|
}, !prefersReducedMotion.current && window.innerWidth >= mobileBreakpoint && !window.matchMedia('(pointer: coarse)').matches)
|
|
137
139
|
}
|
package/src/lib/popover.ts
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import { mount, unmount } from 'svelte'
|
|
2
|
-
import TitlePopover from '../routes/components/TitlePopover.svelte'
|
|
3
2
|
import { getPlayPilotWrapperElement, keyDataAttribute, keySelector } from './injection'
|
|
4
3
|
import type { LinkInjection } from './types/injection'
|
|
4
|
+
import InjectionPopover from '../routes/components/InjectionPopover.svelte'
|
|
5
5
|
|
|
6
6
|
export let currentlyHoveredInjection: EventTarget | null = null
|
|
7
7
|
export let activePopoverInsertedComponent: object | null = null
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* When a link is hovered it is shown as a popover. The component is mounted when a mouse enters the link,
|
|
11
|
-
* and removed when clicked or on mouseleave.
|
|
12
|
-
*/
|
|
13
9
|
export function openPopoverForInjectedLink(event: MouseEvent, injection: LinkInjection): void {
|
|
14
|
-
// Skip touch devices
|
|
15
10
|
if (window.matchMedia('(pointer: coarse)').matches) return
|
|
16
11
|
if (activePopoverInsertedComponent) destroyLinkPopover()
|
|
17
12
|
|
|
18
|
-
const
|
|
19
|
-
currentlyHoveredInjection =
|
|
13
|
+
const currentTarget = event.currentTarget as Element
|
|
14
|
+
currentlyHoveredInjection = currentTarget
|
|
20
15
|
|
|
21
16
|
// Only show if the link is hovered for more than 100ms. This is to prevent the popover from showing
|
|
22
17
|
// when a user just happens to mouseover as they are moving their mouse about.
|
|
23
18
|
setTimeout(() => {
|
|
24
|
-
if (currentlyHoveredInjection !==
|
|
19
|
+
if (currentlyHoveredInjection !== currentTarget) return
|
|
25
20
|
|
|
26
|
-
|
|
21
|
+
const target = getPlayPilotWrapperElement()
|
|
22
|
+
|
|
23
|
+
if (injection.type === 'title') {
|
|
24
|
+
activePopoverInsertedComponent = mount(InjectionPopover, { target, props: { event, type: 'title', data: injection.title_details! } })
|
|
25
|
+
} else if (injection.type === 'participant') {
|
|
26
|
+
activePopoverInsertedComponent = mount(InjectionPopover, { target, props: { event, type: 'participant', data: injection.participant_details! } })
|
|
27
|
+
}
|
|
27
28
|
}, 100)
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -44,7 +45,7 @@ export async function destroyLinkPopover(outro: boolean = true): Promise<void> {
|
|
|
44
45
|
// In that case we remove the element straight from the dom.
|
|
45
46
|
// Doing this will prevent the outro animation from playing, but this being a fallback, that's ok.
|
|
46
47
|
// TODO: Find the actual cause of this bug.
|
|
47
|
-
document.querySelectorAll<HTMLElement>('[data-playpilot-
|
|
48
|
+
document.querySelectorAll<HTMLElement>('[data-playpilot-injection-popover]').forEach(element => element.remove())
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
|
|
@@ -53,7 +54,7 @@ export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
|
|
|
53
54
|
const target = event.target as Element
|
|
54
55
|
|
|
55
56
|
// Mousemove is inside of popover or link that popover
|
|
56
|
-
if (target.hasAttribute('data-playpilot-
|
|
57
|
+
if (target.hasAttribute('data-playpilot-injection-popover') || target.closest('[data-playpilot-injection-popover]') ||
|
|
57
58
|
target.hasAttribute(keyDataAttribute) || target.closest(keySelector)) return
|
|
58
59
|
|
|
59
60
|
destroyLinkPopover()
|
|
@@ -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.
|
|
@@ -37,3 +39,30 @@
|
|
|
37
39
|
@content;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
|
|
43
|
+
@mixin styled-scrollbar() {
|
|
44
|
+
scrollbar-color: theme(content-light) transparent;
|
|
45
|
+
scrollbar-width: thin;
|
|
46
|
+
|
|
47
|
+
&::-webkit-scrollbar {
|
|
48
|
+
width: margin(0.75);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&::-webkit-scrollbar-track {
|
|
52
|
+
background: theme(light);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&::-webkit-scrollbar-thumb {
|
|
56
|
+
border: 2px solid theme(light);
|
|
57
|
+
border-radius: margin(1);
|
|
58
|
+
background: transparent;
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
background: theme(content-light);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&:active {
|
|
65
|
+
background: theme(text-color-alt);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/lib/scss/global.scss
CHANGED
|
@@ -18,33 +18,6 @@
|
|
|
18
18
|
scroll-margin: margin(5);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
.playpilot-styled-scrollbar {
|
|
22
|
-
scrollbar-color: var(--playpilot-content-light) var(--playpilot-lighter);
|
|
23
|
-
scrollbar-width: thin;
|
|
24
|
-
|
|
25
|
-
&::-webkit-scrollbar {
|
|
26
|
-
width: margin(0.75);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
&::-webkit-scrollbar-track {
|
|
30
|
-
background: var(--playpilot-light);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
&::-webkit-scrollbar-thumb {
|
|
34
|
-
border: 2px solid var(--playpilot-light);
|
|
35
|
-
border-radius: margin(1);
|
|
36
|
-
background: var(--playpilot-lighter);
|
|
37
|
-
|
|
38
|
-
&:hover {
|
|
39
|
-
background: var(--playpilot-content-light);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
&:active {
|
|
43
|
-
background: var(--playpilot-text-color-alt);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
21
|
.playpilot-modal-open {
|
|
49
22
|
overflow-y: hidden !important;
|
|
50
23
|
}
|
package/src/lib/tracking.ts
CHANGED
|
@@ -23,6 +23,8 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
23
23
|
return
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
if ((window.PlayPilotLinkInjections.tracked_events?.length || 0) > 500) return
|
|
27
|
+
|
|
26
28
|
const headers = new Headers({ 'Content-Type': 'application/json' })
|
|
27
29
|
|
|
28
30
|
if (title) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { ParticipantData } from './participant'
|
|
1
2
|
import type { TitleData } from './title'
|
|
2
3
|
|
|
4
|
+
export type LinkInjectionDataType = 'title' | 'participant'
|
|
5
|
+
|
|
3
6
|
export type LinkInjection = {
|
|
4
7
|
sid: string
|
|
5
8
|
title: string
|
|
@@ -7,6 +10,7 @@ export type LinkInjection = {
|
|
|
7
10
|
playpilot_url: string
|
|
8
11
|
key: string
|
|
9
12
|
title_details?: TitleData
|
|
13
|
+
participant_details?: ParticipantData
|
|
10
14
|
phrase_before?: string | null
|
|
11
15
|
phrase_after?: string | null
|
|
12
16
|
inactive?: boolean
|
|
@@ -19,6 +23,7 @@ export type LinkInjection = {
|
|
|
19
23
|
removed?: boolean
|
|
20
24
|
duplicate?: boolean
|
|
21
25
|
matchingElement?: null | Element
|
|
26
|
+
type: LinkInjectionDataType
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export type LinkInjectionRanges = Record<string, { elementIndex: number, from: number, to: number }>
|
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 =
|
|
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()
|
package/src/routes/+page.svelte
CHANGED
|
@@ -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())
|
|
@@ -59,8 +59,7 @@
|
|
|
59
59
|
const linkInjectionsString = $derived(JSON.stringify(linkInjections))
|
|
60
60
|
const showControls = $derived(!aiRunning && allowEditing)
|
|
61
61
|
const hasChanged = $derived(initialStateString && initialStateString !== linkInjectionsString)
|
|
62
|
-
|
|
63
|
-
const filteredInjections = $derived(linkInjections.filter((i) => i.title_details && !i.removed && !i.duplicate && (i.manual || (!i.manual && !i.failed))))
|
|
62
|
+
const filteredInjections = $derived(linkInjections.filter((i) => (i.title_details || i.participant_details) && !i.removed && !i.duplicate && (i.manual || (!i.manual && !i.failed))))
|
|
64
63
|
const sortedInjections = $derived(sortInjections(filteredInjections))
|
|
65
64
|
const initialAiRunning = $derived(!loading && untrack(() => aiStatus.aiRunning))
|
|
66
65
|
|
|
@@ -76,7 +75,7 @@
|
|
|
76
75
|
function sortInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
77
76
|
return injections.sort((a, b) => {
|
|
78
77
|
if (a.failed !== b.failed) return a.failed ? 1 : -1
|
|
79
|
-
return a.title_details
|
|
78
|
+
return (a.title_details?.title || a.participant_details?.name)!.localeCompare((b.title_details?.title || b.participant_details?.name)!)
|
|
80
79
|
})
|
|
81
80
|
}
|
|
82
81
|
|
|
@@ -147,7 +146,7 @@
|
|
|
147
146
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins:400,600,700">
|
|
148
147
|
</svelte:head>
|
|
149
148
|
|
|
150
|
-
<section class="editor
|
|
149
|
+
<section class="editor" class:panel-open={manualInjectionActive} class:loading bind:this={editorElement} {onscroll}>
|
|
151
150
|
{#if editorElement && !loading}
|
|
152
151
|
<div class="handles">
|
|
153
152
|
<div class="handle">
|
|
@@ -227,7 +226,7 @@
|
|
|
227
226
|
|
|
228
227
|
{#if manualInjectionActive}
|
|
229
228
|
<div
|
|
230
|
-
class="panel
|
|
229
|
+
class="panel"
|
|
231
230
|
style:top="{scrollDistance}px"
|
|
232
231
|
transition:fly={{ x: Math.min(window.innerWidth, 320), duration: 200, opacity: 1 }}>
|
|
233
232
|
<ManualInjection
|
|
@@ -260,6 +259,8 @@
|
|
|
260
259
|
overflow-y: auto;
|
|
261
260
|
overflow-x: hidden;
|
|
262
261
|
line-height: normal;
|
|
262
|
+
|
|
263
|
+
@include styled-scrollbar;
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
.panel-open {
|
|
@@ -389,5 +390,7 @@
|
|
|
389
390
|
padding: margin(1.5);
|
|
390
391
|
background: theme(dark);
|
|
391
392
|
overflow-y: auto;
|
|
393
|
+
|
|
394
|
+
@include styled-scrollbar;
|
|
392
395
|
}
|
|
393
396
|
</style>
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import { track } from '$lib/tracking'
|
|
12
12
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
13
13
|
import type { LinkInjection } from '$lib/types/injection'
|
|
14
|
-
import type { TitleData } from '$lib/types/title'
|
|
15
14
|
import { cleanPhrase, truncateAroundPhrase } from '$lib/text'
|
|
16
15
|
import { isValidPlaylinkType } from '$lib/injection'
|
|
17
16
|
import { getLinkInjectionElements, getLinkInjectionsParentElement } from '$lib/injectionElements'
|
|
@@ -27,9 +26,8 @@
|
|
|
27
26
|
|
|
28
27
|
const { linkInjection = $bindable(), onremove = () => null, onhighlight = () => null }: Props = $props()
|
|
29
28
|
|
|
30
|
-
const { key, sentence, title_details, failed, failed_message, inactive } = $derived(linkInjection || {})
|
|
29
|
+
const { key, type, sentence, title_details, participant_details, failed, failed_message, inactive } = $derived(linkInjection || {})
|
|
31
30
|
|
|
32
|
-
const title: TitleData = $derived(title_details!)
|
|
33
31
|
const truncatedSentence = $derived(truncateAroundPhrase(linkInjection.sentence, linkInjection.title, 60))
|
|
34
32
|
|
|
35
33
|
let expanded = $state(false)
|
|
@@ -38,7 +36,7 @@
|
|
|
38
36
|
let element: HTMLElement | null = $state(null)
|
|
39
37
|
|
|
40
38
|
onMount(() => {
|
|
41
|
-
if (failed) track(TrackingEvent.InjectionFailed, title, { phrase: linkInjection.title, sentence})
|
|
39
|
+
if (failed) track(TrackingEvent.InjectionFailed, type === 'title' ? title_details : null, { phrase: linkInjection.title, sentence})
|
|
42
40
|
})
|
|
43
41
|
|
|
44
42
|
/**
|
|
@@ -113,10 +111,14 @@
|
|
|
113
111
|
bind:this={element}
|
|
114
112
|
out:slide|global={{ duration: 200 }}>
|
|
115
113
|
<div class="header">
|
|
116
|
-
|
|
114
|
+
{#if type === 'title'}
|
|
115
|
+
<img class="poster" src={removeImageUrlPrefix(title_details!.standing_poster)} alt="" width="32" height="48" onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl} />
|
|
116
|
+
{:else}
|
|
117
|
+
<div class="placeholder-image"></div>
|
|
118
|
+
{/if}
|
|
117
119
|
|
|
118
120
|
<div class="info">
|
|
119
|
-
<div class="title">{title
|
|
121
|
+
<div class="title">{title_details?.title || participant_details?.name}</div>
|
|
120
122
|
|
|
121
123
|
<div class="sentence">
|
|
122
124
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
@@ -143,9 +145,13 @@
|
|
|
143
145
|
<Alert>{failed_message}</Alert>
|
|
144
146
|
{:else}
|
|
145
147
|
<div class="actions">
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
|
|
148
|
+
{#if type === 'title'}
|
|
149
|
+
<button class="expand" onclick={() => expanded = !expanded} aria-label="Expand" aria-expanded={expanded}>
|
|
150
|
+
<IconChevron {expanded} />
|
|
151
|
+
</button>
|
|
152
|
+
{:else}
|
|
153
|
+
<div></div>
|
|
154
|
+
{/if}
|
|
149
155
|
|
|
150
156
|
{#if !isValidPlaylinkType(linkInjection)}
|
|
151
157
|
<div class="warning" transition:fade={{ duration: 100 }} aria-label="Invalid playlink settings">
|
|
@@ -210,6 +216,14 @@
|
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
218
|
|
|
219
|
+
.placeholder-image {
|
|
220
|
+
flex: 0 0 margin(2);
|
|
221
|
+
width: margin(2);
|
|
222
|
+
height: margin(2);
|
|
223
|
+
border-radius: 50%;
|
|
224
|
+
background: theme(content);
|
|
225
|
+
}
|
|
226
|
+
|
|
213
227
|
.title {
|
|
214
228
|
font-size: margin(0.875);
|
|
215
229
|
word-break: break-word;
|
|
@@ -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
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
})
|
|
41
41
|
</script>
|
|
42
42
|
|
|
43
|
-
<div class="explore
|
|
43
|
+
<div class="explore" bind:this={element} bind:clientWidth style:height style:--playpilot-explore-width="{clientWidth}px">
|
|
44
44
|
<div class="header" role="banner">
|
|
45
45
|
<div>
|
|
46
46
|
<div class="divider"></div>
|
|
@@ -108,6 +108,8 @@
|
|
|
108
108
|
font-weight: theme(detail-font-weight, normal);
|
|
109
109
|
font-size: theme(detail-font-size, font-size-base);
|
|
110
110
|
|
|
111
|
+
@include styled-scrollbar;
|
|
112
|
+
|
|
111
113
|
:global(*) {
|
|
112
114
|
box-sizing: border-box;
|
|
113
115
|
}
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
|
|
69
69
|
{#if active}
|
|
70
70
|
<div
|
|
71
|
-
class="content
|
|
71
|
+
class="content"
|
|
72
72
|
style:--max-height={maxHeight ? `${maxHeight}px` : null}
|
|
73
73
|
style:--offset={offset ? `${offset}px` : null}
|
|
74
74
|
bind:this={contentElement}
|
|
@@ -96,6 +96,8 @@
|
|
|
96
96
|
overflow-y: auto;
|
|
97
97
|
overflow-x: hidden;
|
|
98
98
|
|
|
99
|
+
@include styled-scrollbar;
|
|
100
|
+
|
|
99
101
|
.left & {
|
|
100
102
|
right: auto;
|
|
101
103
|
left: var(--offset, 0px);
|
|
@@ -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 {
|
|
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
|
-
|
|
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 },
|