@playpilot/tpi 8.10.2 → 8.11.0-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.
- package/.env +1 -1
- package/dist/editorial.mount.js +9 -9
- package/dist/link-injections.js +1 -1
- package/dist/mount.js +6 -6
- package/package.json +1 -1
- package/src/lib/data/translations.ts +5 -0
- package/src/lib/enums/TrackingEvent.ts +4 -4
- package/src/lib/fakeData.ts +32 -44
- package/src/lib/injection.ts +9 -10
- package/src/lib/modal.ts +1 -3
- package/src/lib/popover.ts +12 -13
- package/src/lib/types/injection.d.ts +0 -5
- package/src/routes/components/Editorial/Editor.svelte +3 -2
- package/src/routes/components/Editorial/EditorItem.svelte +9 -23
- package/src/routes/components/Editorial/ManualInjection.svelte +0 -1
- package/src/routes/components/Explore/ExploreLayout.svelte +9 -3
- package/src/routes/components/Explore/Routes/ExploreHome.svelte +6 -4
- package/src/routes/components/ListTitle.svelte +6 -27
- package/src/routes/components/Modals/RailModal.svelte +1 -1
- package/src/routes/components/Participant.svelte +6 -18
- package/src/routes/components/Playlinks/Playlinks.svelte +1 -2
- package/src/routes/components/Rails/TitlesRail.svelte +19 -4
- package/src/routes/components/Title.svelte +2 -2
- package/src/routes/components/{InjectionPopover.svelte → TitlePopover.svelte} +9 -26
- package/src/routes/elements/+page.svelte +3 -3
- package/src/tests/helpers.js +0 -1
- package/src/tests/lib/injection.test.js +3 -44
- package/src/tests/lib/popover.test.js +7 -7
- package/src/tests/routes/components/Editorial/EditorItem.test.js +0 -10
- package/src/tests/routes/components/Editorial/ManualInjection.test.js +0 -4
- package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +0 -2
- package/src/tests/routes/components/ListTitle.test.js +0 -7
- package/src/tests/routes/components/Participant.test.js +0 -7
- package/src/tests/routes/components/Playlinks/AfterArticlePlaylinks.test.js +0 -4
- package/src/tests/routes/components/Playlinks/Playlinks.test.js +1 -1
- package/src/tests/routes/components/TitlePopover.test.js +78 -0
- package/src/tests/routes/components/InjectionPopover.test.js +0 -117
package/package.json
CHANGED
|
@@ -166,6 +166,11 @@ export const translations = {
|
|
|
166
166
|
[Language.Swedish]: 'Streaming Guide',
|
|
167
167
|
[Language.Danish]: 'Streaming Guide',
|
|
168
168
|
},
|
|
169
|
+
'Streaming Guide Heading': {
|
|
170
|
+
[Language.English]: 'Discover and search all movies and tv-shows',
|
|
171
|
+
[Language.Swedish]: 'Upptäck och sök bland alla filmer och tv-serier',
|
|
172
|
+
[Language.Danish]: 'Opdag og søg i alle film og tv-serier',
|
|
173
|
+
},
|
|
169
174
|
'Streaming Guide Description': {
|
|
170
175
|
[Language.English]: 'Find where to watch movies online - the ultimate guide that helps you find the best movies and shows across streaming services.',
|
|
171
176
|
[Language.Swedish]: 'Sök bland alla filmer och serier för att ta reda på var du kan streama dem',
|
|
@@ -16,10 +16,10 @@ export const TrackingEvent = {
|
|
|
16
16
|
ParticipantModalClose: 'ali_participant_modal_close',
|
|
17
17
|
|
|
18
18
|
// Popover
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
TitlePopoverView: 'ali_title_popover_view',
|
|
20
|
+
TitlePopoverClose: 'ali_title_popover_close',
|
|
21
|
+
TitlePopoverSaveClick: 'ali_title_popover_save_click',
|
|
22
|
+
TitlePopoverPlaylinkClick: 'ali_title_popover_playlink_click',
|
|
23
23
|
|
|
24
24
|
// Rails
|
|
25
25
|
SimilarTitleClick: 'ali_similar_title_click',
|
package/src/lib/fakeData.ts
CHANGED
|
@@ -43,6 +43,38 @@ 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,
|
|
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,
|
|
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,
|
|
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
|
+
|
|
46
78
|
export const participants: ParticipantData[] = [
|
|
47
79
|
{
|
|
48
80
|
sid: 'pr5C5W',
|
|
@@ -112,50 +144,6 @@ export const participants: ParticipantData[] = [
|
|
|
112
144
|
},
|
|
113
145
|
]
|
|
114
146
|
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
159
147
|
export const campaign: Campaign = {
|
|
160
148
|
campaign_format: 'card',
|
|
161
149
|
campaign_type: 'image',
|
package/src/lib/injection.ts
CHANGED
|
@@ -168,7 +168,7 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
|
|
|
168
168
|
// The function itself will decide whether or not it should actually insert the component based on the config.
|
|
169
169
|
if (document.querySelector(keySelector)) insertInTextDisclaimer(elements)
|
|
170
170
|
|
|
171
|
-
return mergedInjections.filter(
|
|
171
|
+
return mergedInjections.filter(i => i.title_details).map((injection, index) => {
|
|
172
172
|
const hasManualEquivalent = !injection.manual && isAvailableAsManualInjection(injection, index, mergedInjections)
|
|
173
173
|
const duplicate = injection.duplicate ?? hasManualEquivalent
|
|
174
174
|
|
|
@@ -376,11 +376,11 @@ export function separateLinkInjectionTypes(injections: LinkInjection[]): LinkInj
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
export function isValidInjection(injection: LinkInjection): boolean {
|
|
379
|
-
return !injection.inactive && !injection.removed && !injection.duplicate &&
|
|
379
|
+
return !injection.inactive && !injection.removed && !injection.duplicate && !!injection.title_details && isValidPlaylinkType(injection)
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
/**
|
|
383
|
-
* An injection can be of various playlink types, when all are false equivalent, the link is not valid.
|
|
383
|
+
* An injection can have be of various playlink types, when all are false equivalent, the link is not valid.
|
|
384
384
|
* It should be treated similar to an inactive playlink in this case.
|
|
385
385
|
*/
|
|
386
386
|
export function isValidPlaylinkType(injection: LinkInjection): boolean {
|
|
@@ -388,10 +388,16 @@ export function isValidPlaylinkType(injection: LinkInjection): boolean {
|
|
|
388
388
|
return !!injection.after_article
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Filter links for in-text injections, removing after article, inactive, removed, duplicate, and items without title_details
|
|
393
|
+
*/
|
|
391
394
|
export function filterInvalidInTextInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
392
395
|
return filterRemovedAndInactiveInjections(injections).filter(i => i.in_text !== false && isValidInjection(i))
|
|
393
396
|
}
|
|
394
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Filter links for after article injections, removing in-text only, inactive, removed, duplicate, and items without title_details
|
|
400
|
+
*/
|
|
395
401
|
export function filterInvalidAfterArticleInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
396
402
|
return filterRemovedAndInactiveInjections(injections).filter(i => i.after_article === true && isValidInjection(i))
|
|
397
403
|
}
|
|
@@ -436,13 +442,6 @@ export function isEquivalentInjection(injection1: LinkInjection, injection2: Lin
|
|
|
436
442
|
return injection1.title === injection2.title && cleanPhrase(injection1.sentence) === cleanPhrase(injection2.sentence)
|
|
437
443
|
}
|
|
438
444
|
|
|
439
|
-
export function hasValidTypeData(injection: LinkInjection): boolean {
|
|
440
|
-
if (injection.type === 'title' && !!injection.title_details) return true
|
|
441
|
-
if (injection.type === 'participant' && !!injection.participant_details) return true
|
|
442
|
-
|
|
443
|
-
return false
|
|
444
|
-
}
|
|
445
|
-
|
|
446
445
|
export function getPlayPilotWrapperElement(): Element {
|
|
447
446
|
return document.querySelector('[data-playpilot-link-injections]') || document.body
|
|
448
447
|
}
|
package/src/lib/modal.ts
CHANGED
|
@@ -133,9 +133,7 @@ export function openModalForInjectedLink(event: MouseEvent, injections: LinkInje
|
|
|
133
133
|
event.preventDefault()
|
|
134
134
|
|
|
135
135
|
playFallbackViewTransition(() => {
|
|
136
|
-
const data = injection.title_details || injection.participant_details
|
|
137
|
-
|
|
138
136
|
destroyLinkPopover(false)
|
|
139
|
-
openModal({ event, injection, data
|
|
137
|
+
openModal({ event, injection, data: injection.title_details })
|
|
140
138
|
}, !prefersReducedMotion.current && window.innerWidth >= mobileBreakpoint && !window.matchMedia('(pointer: coarse)').matches)
|
|
141
139
|
}
|
package/src/lib/popover.ts
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import { mount, unmount } from 'svelte'
|
|
2
|
+
import TitlePopover from '../routes/components/TitlePopover.svelte'
|
|
2
3
|
import { getPlayPilotWrapperElement, keyDataAttribute, keySelector } from './injection'
|
|
3
4
|
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
|
+
*/
|
|
9
13
|
export function openPopoverForInjectedLink(event: MouseEvent, injection: LinkInjection): void {
|
|
14
|
+
// Skip touch devices
|
|
10
15
|
if (window.matchMedia('(pointer: coarse)').matches) return
|
|
11
16
|
if (activePopoverInsertedComponent) destroyLinkPopover()
|
|
12
17
|
|
|
13
|
-
const
|
|
14
|
-
currentlyHoveredInjection =
|
|
18
|
+
const target = event.currentTarget as Element
|
|
19
|
+
currentlyHoveredInjection = target
|
|
15
20
|
|
|
16
21
|
// Only show if the link is hovered for more than 100ms. This is to prevent the popover from showing
|
|
17
22
|
// when a user just happens to mouseover as they are moving their mouse about.
|
|
18
23
|
setTimeout(() => {
|
|
19
|
-
if (currentlyHoveredInjection !==
|
|
24
|
+
if (currentlyHoveredInjection !== target) return // User is no longer hovering this link
|
|
20
25
|
|
|
21
|
-
|
|
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
|
-
}
|
|
26
|
+
activePopoverInsertedComponent = mount(TitlePopover, { target: getPlayPilotWrapperElement(), props: { event, title: injection.title_details! } })
|
|
28
27
|
}, 100)
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -45,7 +44,7 @@ export async function destroyLinkPopover(outro: boolean = true): Promise<void> {
|
|
|
45
44
|
// In that case we remove the element straight from the dom.
|
|
46
45
|
// Doing this will prevent the outro animation from playing, but this being a fallback, that's ok.
|
|
47
46
|
// TODO: Find the actual cause of this bug.
|
|
48
|
-
document.querySelectorAll<HTMLElement>('[data-playpilot-
|
|
47
|
+
document.querySelectorAll<HTMLElement>('[data-playpilot-title-popover]').forEach(element => element.remove())
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
|
|
@@ -54,7 +53,7 @@ export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
|
|
|
54
53
|
const target = event.target as Element
|
|
55
54
|
|
|
56
55
|
// Mousemove is inside of popover or link that popover
|
|
57
|
-
if (target.hasAttribute('data-playpilot-
|
|
56
|
+
if (target.hasAttribute('data-playpilot-title-popover') || target.closest('[data-playpilot-title-popover]') ||
|
|
58
57
|
target.hasAttribute(keyDataAttribute) || target.closest(keySelector)) return
|
|
59
58
|
|
|
60
59
|
destroyLinkPopover()
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { ParticipantData } from './participant'
|
|
2
1
|
import type { TitleData } from './title'
|
|
3
2
|
|
|
4
|
-
export type LinkInjectionDataType = 'title' | 'participant'
|
|
5
|
-
|
|
6
3
|
export type LinkInjection = {
|
|
7
4
|
sid: string
|
|
8
5
|
title: string
|
|
@@ -10,7 +7,6 @@ export type LinkInjection = {
|
|
|
10
7
|
playpilot_url: string
|
|
11
8
|
key: string
|
|
12
9
|
title_details?: TitleData
|
|
13
|
-
participant_details?: ParticipantData
|
|
14
10
|
phrase_before?: string | null
|
|
15
11
|
phrase_after?: string | null
|
|
16
12
|
inactive?: boolean
|
|
@@ -23,7 +19,6 @@ export type LinkInjection = {
|
|
|
23
19
|
removed?: boolean
|
|
24
20
|
duplicate?: boolean
|
|
25
21
|
matchingElement?: null | Element
|
|
26
|
-
type: LinkInjectionDataType
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
export type LinkInjectionRanges = Record<string, { elementIndex: number, from: number, to: number }>
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
const linkInjectionsString = $derived(JSON.stringify(linkInjections))
|
|
59
59
|
const showControls = $derived(!aiRunning && allowEditing)
|
|
60
60
|
const hasChanged = $derived(initialStateString && initialStateString !== linkInjectionsString)
|
|
61
|
-
|
|
61
|
+
// Filter out injections without title_details, injections that are removed, duplicate, or are AI injections that failed to inject
|
|
62
|
+
const filteredInjections = $derived(linkInjections.filter((i) => i.title_details && !i.removed && !i.duplicate && (i.manual || (!i.manual && !i.failed))))
|
|
62
63
|
const sortedInjections = $derived(sortInjections(filteredInjections))
|
|
63
64
|
const initialAiRunning = $derived(!loading && untrack(() => aiStatus.aiRunning))
|
|
64
65
|
|
|
@@ -74,7 +75,7 @@
|
|
|
74
75
|
function sortInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
75
76
|
return injections.sort((a, b) => {
|
|
76
77
|
if (a.failed !== b.failed) return a.failed ? 1 : -1
|
|
77
|
-
return
|
|
78
|
+
return a.title_details!.title.localeCompare(b.title_details!.title)
|
|
78
79
|
})
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -11,6 +11,7 @@
|
|
|
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'
|
|
14
15
|
import { cleanPhrase, truncateAroundPhrase } from '$lib/text'
|
|
15
16
|
import { isValidPlaylinkType } from '$lib/injection'
|
|
16
17
|
import { getLinkInjectionElements, getLinkInjectionsParentElement } from '$lib/injectionElements'
|
|
@@ -26,8 +27,9 @@
|
|
|
26
27
|
|
|
27
28
|
const { linkInjection = $bindable(), onremove = () => null, onhighlight = () => null }: Props = $props()
|
|
28
29
|
|
|
29
|
-
const { key,
|
|
30
|
+
const { key, sentence, title_details, failed, failed_message, inactive } = $derived(linkInjection || {})
|
|
30
31
|
|
|
32
|
+
const title: TitleData = $derived(title_details!)
|
|
31
33
|
const truncatedSentence = $derived(truncateAroundPhrase(linkInjection.sentence, linkInjection.title, 60))
|
|
32
34
|
|
|
33
35
|
let expanded = $state(false)
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
let element: HTMLElement | null = $state(null)
|
|
37
39
|
|
|
38
40
|
onMount(() => {
|
|
39
|
-
if (failed) track(TrackingEvent.InjectionFailed,
|
|
41
|
+
if (failed) track(TrackingEvent.InjectionFailed, title, { phrase: linkInjection.title, sentence})
|
|
40
42
|
})
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -111,14 +113,10 @@
|
|
|
111
113
|
bind:this={element}
|
|
112
114
|
out:slide|global={{ duration: 200 }}>
|
|
113
115
|
<div class="header">
|
|
114
|
-
{
|
|
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}
|
|
116
|
+
<img class="poster" src={removeImageUrlPrefix(title.standing_poster)} alt="" width="32" height="48" onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl} />
|
|
119
117
|
|
|
120
118
|
<div class="info">
|
|
121
|
-
<div class="title">{
|
|
119
|
+
<div class="title">{title.title}</div>
|
|
122
120
|
|
|
123
121
|
<div class="sentence">
|
|
124
122
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
@@ -145,13 +143,9 @@
|
|
|
145
143
|
<Alert>{failed_message}</Alert>
|
|
146
144
|
{:else}
|
|
147
145
|
<div class="actions">
|
|
148
|
-
{
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
</button>
|
|
152
|
-
{:else}
|
|
153
|
-
<div></div>
|
|
154
|
-
{/if}
|
|
146
|
+
<button class="expand" onclick={() => expanded = !expanded} aria-label="Expand" aria-expanded={expanded}>
|
|
147
|
+
<IconChevron {expanded} />
|
|
148
|
+
</button>
|
|
155
149
|
|
|
156
150
|
{#if !isValidPlaylinkType(linkInjection)}
|
|
157
151
|
<div class="warning" transition:fade={{ duration: 100 }} aria-label="Invalid playlink settings">
|
|
@@ -216,14 +210,6 @@
|
|
|
216
210
|
}
|
|
217
211
|
}
|
|
218
212
|
|
|
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
|
-
|
|
227
213
|
.title {
|
|
228
214
|
font-size: margin(0.875);
|
|
229
215
|
word-break: break-word;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
let { navigate = () => null, searchQuery = $bindable(''), filter = $bindable({}), children }: Props = $props()
|
|
23
23
|
|
|
24
|
+
// svelte-ignore non_reactive_update
|
|
24
25
|
let element: HTMLElement | null = null
|
|
25
26
|
let height: string | null = $state(null)
|
|
26
27
|
let clientWidth: number = $state(window.innerWidth)
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
<div class="divider"></div>
|
|
45
46
|
|
|
46
47
|
<div class="heading" use:heading>
|
|
47
|
-
{t('Streaming Guide')}
|
|
48
|
+
{t('Streaming Guide Heading')}
|
|
48
49
|
</div>
|
|
49
50
|
|
|
50
51
|
{#if !useExploreRouter()}
|
|
@@ -98,8 +99,13 @@
|
|
|
98
99
|
display: flex;
|
|
99
100
|
flex-direction: column;
|
|
100
101
|
gap: margin(0.5);
|
|
101
|
-
margin: theme(explore-header-margin, 0 0 margin(
|
|
102
|
+
margin: theme(explore-header-margin, 0 0 margin(1));
|
|
102
103
|
width: 100%;
|
|
104
|
+
max-width: margin(12);
|
|
105
|
+
|
|
106
|
+
@include desktop {
|
|
107
|
+
max-width: margin(20);
|
|
108
|
+
}
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
.divider {
|
|
@@ -112,7 +118,7 @@
|
|
|
112
118
|
.heading {
|
|
113
119
|
margin: theme(explore-heading-margin, margin(0.25) 0);
|
|
114
120
|
color: theme(text-color);
|
|
115
|
-
font-size: theme(explore-heading-size, clamp(margin(1
|
|
121
|
+
font-size: theme(explore-heading-size, clamp(margin(1), 2.5vw, margin(1.5)));
|
|
116
122
|
font-weight: theme(explore-heading-font-weight, font-bold);
|
|
117
123
|
text-transform: theme(explore-heading-text-transform, normal);
|
|
118
124
|
line-height: theme(explore-heading-line-height, 1.5);
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import { track } from '$lib/tracking'
|
|
7
7
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
8
8
|
import TitlesRail from '../../Rails/TitlesRail.svelte'
|
|
9
|
+
import { mobileBreakpoint } from '$lib/constants'
|
|
9
10
|
|
|
10
11
|
let expandedTitle: TitleData | null = $state(null)
|
|
11
12
|
let expandedRailKey: string | null = $state(null)
|
|
13
|
+
let windowWidth: number = $state(window.innerWidth)
|
|
12
14
|
|
|
13
15
|
const rails: { heading: string, params: Record<string, any>, properties: Record<string, any> }[] = [{
|
|
14
16
|
heading: 'List: Trending',
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
}, {
|
|
18
20
|
heading: 'List: New',
|
|
19
21
|
params: { from_playlist_sid: 'li42WR', include_playable_types: 'SVOD,FREE' },
|
|
20
|
-
properties: { aside: true },
|
|
22
|
+
properties: { aside: true, size: 'flexible' },
|
|
21
23
|
}, {
|
|
22
24
|
heading: 'List: Upcoming',
|
|
23
25
|
params: { from_playlist_sid: 'li42wf', region: null, no_region_filter: true },
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
}, {
|
|
30
32
|
heading: 'List: Demand',
|
|
31
33
|
params: { from_playlist_sid: 'licBS' },
|
|
32
|
-
properties: { aside: true },
|
|
34
|
+
properties: { aside: true, size: 'flexible' },
|
|
33
35
|
}]
|
|
34
36
|
|
|
35
37
|
async function getListTitles(params: Record<string, any> = {}): Promise<TitleData[]> {
|
|
@@ -43,7 +45,7 @@
|
|
|
43
45
|
}
|
|
44
46
|
</script>
|
|
45
47
|
|
|
46
|
-
<svelte:window {onscroll} />
|
|
48
|
+
<svelte:window {onscroll} bind:innerWidth={windowWidth} />
|
|
47
49
|
|
|
48
50
|
<div data-testid="explore-home"></div>
|
|
49
51
|
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
<TitlesRail
|
|
53
55
|
heading={t(heading)}
|
|
54
56
|
titles={getListTitles(params)}
|
|
55
|
-
size=
|
|
57
|
+
size={windowWidth >= mobileBreakpoint ? 'flexible' : 'huge'}
|
|
56
58
|
minimumLength={7}
|
|
57
59
|
{...properties}
|
|
58
60
|
bind:expandedTitle
|
|
@@ -8,16 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
10
|
title: TitleData
|
|
11
|
-
compact?: boolean
|
|
12
11
|
onclick?: (event: MouseEvent) => void
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
const { title,
|
|
14
|
+
const { title, onclick = () => null }: Props = $props()
|
|
16
15
|
|
|
17
16
|
const noAffiliate = !!window.PlayPilotLinkInjections?.no_affiliate
|
|
18
17
|
</script>
|
|
19
18
|
|
|
20
|
-
<button class="title"
|
|
19
|
+
<button class="title" {onclick} data-testid="title">
|
|
21
20
|
<div class="poster">
|
|
22
21
|
<TitlePoster {title} width={30} height={43} lazy />
|
|
23
22
|
</div>
|
|
@@ -39,14 +38,12 @@
|
|
|
39
38
|
{/if}
|
|
40
39
|
</div>
|
|
41
40
|
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</div>
|
|
46
|
-
{/if}
|
|
41
|
+
<div class="description" class:large={noAffiliate}>
|
|
42
|
+
{title.description}
|
|
43
|
+
</div>
|
|
47
44
|
|
|
48
45
|
{#if !noAffiliate}
|
|
49
|
-
<PlaylinksCompact playlinks={title.providers}
|
|
46
|
+
<PlaylinksCompact playlinks={title.providers} />
|
|
50
47
|
{/if}
|
|
51
48
|
</div>
|
|
52
49
|
|
|
@@ -87,10 +84,6 @@
|
|
|
87
84
|
border-radius: theme(detail-image-border-radius, border-radius);
|
|
88
85
|
background: theme(detail-image-background, content);
|
|
89
86
|
overflow: hidden;
|
|
90
|
-
|
|
91
|
-
.compact & {
|
|
92
|
-
width: margin(3.5);
|
|
93
|
-
}
|
|
94
87
|
}
|
|
95
88
|
|
|
96
89
|
.content {
|
|
@@ -104,17 +97,11 @@
|
|
|
104
97
|
@include desktop() {
|
|
105
98
|
padding-right: margin(1);
|
|
106
99
|
}
|
|
107
|
-
|
|
108
|
-
.compact & {
|
|
109
|
-
padding-left: margin(0.75);
|
|
110
|
-
padding-right: 0;
|
|
111
|
-
}
|
|
112
100
|
}
|
|
113
101
|
|
|
114
102
|
.heading {
|
|
115
103
|
color: theme(list-item-title-text-color, text-color);
|
|
116
104
|
font-weight: theme(list-item-title-font-weight, font-bold);
|
|
117
|
-
line-height: 1.2;
|
|
118
105
|
}
|
|
119
106
|
|
|
120
107
|
.meta {
|
|
@@ -129,10 +116,6 @@
|
|
|
129
116
|
> div {
|
|
130
117
|
text-transform: capitalize;
|
|
131
118
|
}
|
|
132
|
-
|
|
133
|
-
.compact & {
|
|
134
|
-
margin: margin(0.125) 0 margin(0.25) 0;
|
|
135
|
-
}
|
|
136
119
|
}
|
|
137
120
|
|
|
138
121
|
.imdb {
|
|
@@ -169,9 +152,5 @@
|
|
|
169
152
|
&:active {
|
|
170
153
|
color: theme(list-item-action-hover-color, text-color);
|
|
171
154
|
}
|
|
172
|
-
|
|
173
|
-
.compact & {
|
|
174
|
-
display: none;
|
|
175
|
-
}
|
|
176
155
|
}
|
|
177
156
|
</style>
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
border-radius: theme(rail-modal-item-border-radius, border-radius-huge) theme(rail-modal-item-border-radius, border-radius-huge) 0 0;
|
|
132
132
|
background: theme(rail-modal-item-background, light);
|
|
133
133
|
box-shadow: none;
|
|
134
|
-
height:
|
|
134
|
+
height: 90vh;
|
|
135
135
|
overflow: auto;
|
|
136
136
|
overscroll-behavior: contain;
|
|
137
137
|
transition: box-shadow var(--transition-duration);
|
|
@@ -13,10 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
participant: ParticipantData
|
|
16
|
-
small?: boolean
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
const { participant
|
|
18
|
+
const { participant }: Props = $props()
|
|
20
19
|
|
|
21
20
|
const { name, birth_date, death_date } = $derived(participant)
|
|
22
21
|
|
|
@@ -53,8 +52,8 @@
|
|
|
53
52
|
}
|
|
54
53
|
</script>
|
|
55
54
|
|
|
56
|
-
<div class="header"
|
|
57
|
-
<div class="heading" use:heading={2} id="
|
|
55
|
+
<div class="header">
|
|
56
|
+
<div class="heading" use:heading={2} id="name">{name}</div>
|
|
58
57
|
|
|
59
58
|
{#if birth_date}
|
|
60
59
|
<p class="dates">
|
|
@@ -64,13 +63,11 @@
|
|
|
64
63
|
</div>
|
|
65
64
|
|
|
66
65
|
<div class="content">
|
|
67
|
-
|
|
68
|
-
<div class="heading subheading" use:heading={3} id="credits">{t('Credits')}</div>
|
|
69
|
-
{/if}
|
|
66
|
+
<div class="heading small" use:heading={3} id="credits">{t('Credits')}</div>
|
|
70
67
|
|
|
71
68
|
<div class="list">
|
|
72
69
|
{#each titles as title}
|
|
73
|
-
<ListTitle {title}
|
|
70
|
+
<ListTitle {title} onclick={(event) => openModal({ event, data: title })} />
|
|
74
71
|
{/each}
|
|
75
72
|
|
|
76
73
|
{#if loading}
|
|
@@ -91,10 +88,6 @@
|
|
|
91
88
|
font-size: theme(detail-font-size, font-size-base);
|
|
92
89
|
line-height: theme(participant-description-line-height, normal);
|
|
93
90
|
color: theme(detail-text-color, text-color);
|
|
94
|
-
|
|
95
|
-
&.small {
|
|
96
|
-
padding: margin(1);
|
|
97
|
-
}
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
.heading {
|
|
@@ -105,7 +98,7 @@
|
|
|
105
98
|
line-height: normal;
|
|
106
99
|
font-style: theme(detail-title-font-style, normal);
|
|
107
100
|
|
|
108
|
-
&.
|
|
101
|
+
&.small {
|
|
109
102
|
margin: 0 0 margin(0.5);
|
|
110
103
|
font-size: theme(detail-title-small-font-size, margin(1.25));
|
|
111
104
|
}
|
|
@@ -123,11 +116,6 @@
|
|
|
123
116
|
font-size: theme(detail-font-size, font-size-base);
|
|
124
117
|
line-height: normal;
|
|
125
118
|
font-style: normal;
|
|
126
|
-
|
|
127
|
-
::view-transition-old(content),
|
|
128
|
-
::view-transition-new(content) {
|
|
129
|
-
height: 100%;
|
|
130
|
-
}
|
|
131
119
|
}
|
|
132
120
|
|
|
133
121
|
.list {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
const { playlinks, title }: Props = $props()
|
|
23
23
|
|
|
24
24
|
const isModal = getContext('scope') === 'modal'
|
|
25
|
-
const type = getContext('type')
|
|
26
25
|
const displayAd = getFirstAdOfType('card')
|
|
27
26
|
const categorize = !!window.PlayPilotLinkInjections?.config?.categorize_playlinks
|
|
28
27
|
|
|
@@ -38,7 +37,7 @@
|
|
|
38
37
|
const list = $derived(outerWidth < 500 || !!displayAd || mergedPlaylinks.length === 1)
|
|
39
38
|
|
|
40
39
|
function onclick(playlink: string): void {
|
|
41
|
-
track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.
|
|
40
|
+
track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink })
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
function categorizePlaylinks(playlinks: PlaylinkData[]): CategorizedPlaylinks {
|