@playpilot/tpi 8.28.0-beta.1 → 8.29.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/.prettierrc +1 -1
- package/dist/editorial.mount.js +11 -11
- package/dist/link-injections.js +1 -1
- package/dist/mount.js +9 -9
- package/eslint.config.js +0 -1
- package/package.json +1 -1
- package/src/lib/explore.ts +2 -1
- package/src/lib/types/config.d.ts +2 -0
- package/src/lib/types/participant.d.ts +8 -0
- package/src/lib/types/position.d.ts +2 -0
- package/src/lib/types/script.d.ts +4 -2
- package/src/routes/components/Debugger.svelte +1 -1
- package/src/routes/components/Explore/Routes/ExploreModal.svelte +70 -19
- package/src/routes/components/Icons/IconAttribution.svelte +3 -0
- package/src/routes/components/InjectionPopover.svelte +1 -1
- package/src/routes/components/Modals/Modal.svelte +2 -1
- package/src/routes/components/Modals/ParticipantModal.svelte +1 -1
- package/src/routes/components/{Participant.svelte → Participants/Participant.svelte} +27 -9
- package/src/routes/components/Participants/ParticipantImage.svelte +130 -0
- package/src/routes/components/Rails/ParticipantsRail.svelte +21 -21
- package/src/routes/explore/+page.svelte +1 -2
- package/src/tests/lib/explore.test.js +4 -2
- package/src/tests/lib/injection.test.js +1 -1
- package/src/tests/lib/retargeting.test.js +2 -0
- package/src/tests/routes/components/{Participant.test.js → Participants/Participant.test.js} +20 -1
- package/src/tests/routes/components/Playlinks/Playlink.test.js +2 -0
- package/src/tests/routes/components/Rails/ParticipantsRail.test.js +13 -0
package/eslint.config.js
CHANGED
package/package.json
CHANGED
package/src/lib/explore.ts
CHANGED
|
@@ -44,7 +44,8 @@ export function insertExploreIntoNavigation(): boolean {
|
|
|
44
44
|
if (!config) return false
|
|
45
45
|
|
|
46
46
|
const { explore_navigation_selector: selector, explore_navigation_label: label, explore_navigation_path: path, explore_navigation_insert_position: insertPosition } = config
|
|
47
|
-
|
|
47
|
+
|
|
48
|
+
if (!selector || !path) return false
|
|
48
49
|
|
|
49
50
|
// Make sure to remove an element we created if it already exists. Should not be possible under normal circumstances.
|
|
50
51
|
document.querySelector('[data-playpilot-explore-navigation-element]')?.remove()
|
|
@@ -6,6 +6,14 @@ export type ParticipantData = {
|
|
|
6
6
|
jobs: Job[]
|
|
7
7
|
image: string | null
|
|
8
8
|
image_uuid: string | null
|
|
9
|
+
image_metadata?: {
|
|
10
|
+
license_name: string
|
|
11
|
+
license_url: string
|
|
12
|
+
source_text: string
|
|
13
|
+
source_url: string
|
|
14
|
+
author_text: string
|
|
15
|
+
author_url: string
|
|
16
|
+
}
|
|
9
17
|
gender: string
|
|
10
18
|
character?: string | null
|
|
11
19
|
bio?: string | null
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Campaign } from './campaign'
|
|
2
|
+
import type { ConfigResponse } from './config'
|
|
2
3
|
import type { ConsentOptions } from './consent'
|
|
3
4
|
import type { LinkInjection, LinkInjectionResponse } from './injection'
|
|
5
|
+
import type { InsertPosition } from './position'
|
|
4
6
|
import type { TitleData } from './title'
|
|
5
7
|
|
|
6
8
|
export type ScriptConfig = {
|
|
@@ -17,7 +19,7 @@ export type ScriptConfig = {
|
|
|
17
19
|
// Selector for the after article playlinks. Will be placed _after_ the given selector.
|
|
18
20
|
after_article_selector?: string
|
|
19
21
|
// Selector to change the insert position of the after article playlinks. Should only be used if no viable selector can be given with `after_article_selector`.
|
|
20
|
-
after_article_insert_position?: InsertPosition
|
|
22
|
+
after_article_insert_position?: InsertPosition
|
|
21
23
|
// The language of the page will be inferred from the html `lang` attribute. Can be set manually using this option in the config object.
|
|
22
24
|
language?: string | null
|
|
23
25
|
// Set to the last success external-pages fetch.
|
|
@@ -25,7 +27,7 @@ export type ScriptConfig = {
|
|
|
25
27
|
// Lists all tracked events through the `track()` function.
|
|
26
28
|
tracked_events?: { event: string, payload: Record<string, any> }[]
|
|
27
29
|
// Queued tracking events that were fired before consent was given. These might be fired later if the user consents.
|
|
28
|
-
queued_tracking_events?: { event: string, title: TitleData | null, payload: Record<
|
|
30
|
+
queued_tracking_events?: { event: string, title: TitleData | null, payload: Record<string, any> }[]
|
|
29
31
|
// Lists all split test identifiers as created by each running split test. Identifiers are only created when a split test is fired.
|
|
30
32
|
split_test_identifiers?: Record<string, number>
|
|
31
33
|
// All link injections as returned from external-pages, with AI and manual injections merged.
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
{#if !useExploreRouter()}
|
|
182
182
|
<button onclick={(() => {
|
|
183
183
|
destroyExplore()
|
|
184
|
-
window.PlayPilotLinkInjections.config
|
|
184
|
+
window.PlayPilotLinkInjections.config!.explore_use_router = !useExploreRouter()
|
|
185
185
|
insertExplore()
|
|
186
186
|
})}>Toggle new explore</button>
|
|
187
187
|
{/if}
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import { fetchSimilarTitles, fetchTitleBySid } from '$lib/api/titles'
|
|
3
3
|
import { mobileBreakpoint } from '$lib/constants'
|
|
4
4
|
import { SplitTest } from '$lib/enums/SplitTest'
|
|
5
|
+
import { t } from '$lib/localization'
|
|
5
6
|
import { openModal } from '$lib/modal'
|
|
6
7
|
import { getSplitTestVariantName, trackSplitTestView } from '$lib/splitTest'
|
|
7
8
|
import type { TitleData } from '$lib/types/title'
|
|
9
|
+
import Modal from '../../Modals/Modal.svelte'
|
|
8
10
|
|
|
9
11
|
interface Props {
|
|
10
12
|
navigate?: (key: string, pushState?: boolean) => void
|
|
@@ -12,8 +14,6 @@
|
|
|
12
14
|
|
|
13
15
|
const { navigate = () => null }: Props = $props()
|
|
14
16
|
|
|
15
|
-
openModalViaRoute()
|
|
16
|
-
|
|
17
17
|
async function openModalViaRoute(): Promise<void> {
|
|
18
18
|
const currentUrl = new URL(document.location.toString())
|
|
19
19
|
const sid = currentUrl.searchParams.get('sid')
|
|
@@ -26,22 +26,73 @@
|
|
|
26
26
|
|
|
27
27
|
if (isMobile) trackSplitTestView(SplitTest.Reels)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
43
|
-
} catch {
|
|
44
|
-
navigate('home')
|
|
45
|
-
}
|
|
29
|
+
const [title, railTitles] = (await Promise.allSettled([
|
|
30
|
+
fetchTitleBySid(sid),
|
|
31
|
+
fetchSimilarTitles({ sid } as unknown as TitleData)])
|
|
32
|
+
).map(promise => (promise.status === 'fulfilled' ? promise.value : null))
|
|
33
|
+
|
|
34
|
+
openModal({
|
|
35
|
+
type,
|
|
36
|
+
data: [(title as TitleData), ...(railTitles as TitleData[])],
|
|
37
|
+
props: {
|
|
38
|
+
onclose: () => navigate('home'),
|
|
39
|
+
pushState: false,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
46
42
|
}
|
|
47
43
|
</script>
|
|
44
|
+
|
|
45
|
+
{#await openModalViaRoute()}
|
|
46
|
+
<Modal blur pushState={false}>
|
|
47
|
+
{#snippet dialog()}
|
|
48
|
+
<div class="loading">
|
|
49
|
+
<div class="bar"></div>
|
|
50
|
+
<div class="bar"></div>
|
|
51
|
+
<div class="bar"></div>
|
|
52
|
+
</div>
|
|
53
|
+
{/snippet}
|
|
54
|
+
</Modal>
|
|
55
|
+
{:catch}
|
|
56
|
+
<Modal blur onclose={() => navigate('home')} pushState={false}>
|
|
57
|
+
<div class="error">
|
|
58
|
+
{t('An Error Occurred')}
|
|
59
|
+
</div>
|
|
60
|
+
</Modal>
|
|
61
|
+
{/await}
|
|
62
|
+
|
|
63
|
+
<style lang="scss">
|
|
64
|
+
.loading {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
height: 100%;
|
|
69
|
+
gap: margin(0.5);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@keyframes animate-bar {
|
|
73
|
+
to {
|
|
74
|
+
height: margin(3);
|
|
75
|
+
opacity: 0.5;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.bar {
|
|
80
|
+
height: margin(4);
|
|
81
|
+
width: margin(1);
|
|
82
|
+
border-radius: margin(0.25);
|
|
83
|
+
background: white;
|
|
84
|
+
animation: animate-bar 750ms infinite;
|
|
85
|
+
|
|
86
|
+
@for $i from 0 through 2 {
|
|
87
|
+
&:nth-child(#{$i}) {
|
|
88
|
+
animation-delay: $i * 250ms;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.error {
|
|
94
|
+
padding: margin(2);
|
|
95
|
+
font-size: 1.25em;
|
|
96
|
+
color: theme(text-color);
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg height="24px" viewBox="0 -960 960 960" width="24px">
|
|
2
|
+
<path fill="currentColor" d="M430-200h100v-180h60v-184q0-27-28.5-41.5T480-620q-53 0-81.5 14.5T370-564v184h60v180Zm-105 88.5q-73-31.5-127.5-86t-86-127.5Q80-398 80-480.5t31.5-155q31.5-72.5 86-127t127.5-86Q398-880 480.5-880t155 31.5q72.5 31.5 127 86t86 127Q880-563 880-480.5T848.5-325q-31.5 73-86 127.5t-127 86Q563-80 480.5-80T325-111.5Zm381.5-142Q800-347 800-480t-93.5-226.5Q613-800 480-800t-226.5 93.5Q160-613 160-480t93.5 226.5Q347-160 480-160t226.5-93.5ZM523-657q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43 177Z"/>
|
|
3
|
+
</svg>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { trackViaPixel } from '@playpilot/retargeting-tracking'
|
|
11
11
|
import Popover from './Popover.svelte'
|
|
12
12
|
import Title from './Title.svelte'
|
|
13
|
-
import Participant from './Participant.svelte'
|
|
13
|
+
import Participant from './Participants/Participant.svelte'
|
|
14
14
|
import TopScroll from './Ads/TopScroll.svelte'
|
|
15
15
|
import Display from './Ads/Display.svelte'
|
|
16
16
|
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
|
|
60
60
|
onMount(() => {
|
|
61
61
|
const scrollElement = getScrollRoot()
|
|
62
|
+
|
|
62
63
|
scrollElement.classList.add('playpilot-modal-open')
|
|
63
64
|
|
|
64
65
|
hasPreviousModal = !!getPreviousModal()
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
requestAnimationFrame(setInitialScrollPosition)
|
|
71
72
|
|
|
72
73
|
return () => {
|
|
73
|
-
scrollElement.classList.remove('playpilot-modal-open')
|
|
74
|
+
if (!document.querySelector('.modal')) scrollElement.classList.remove('playpilot-modal-open')
|
|
74
75
|
|
|
75
76
|
// Remove the modal entry from above from the browser history. This is done only once for the entire stack.
|
|
76
77
|
// We check for getPreviousModal not because we really care about the previous modal, but to check if all
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import Modal from './Modal.svelte'
|
|
4
|
-
import Participant from '../Participant.svelte'
|
|
4
|
+
import Participant from '../Participants/Participant.svelte'
|
|
5
5
|
import type { ParticipantData } from '$lib/types/participant'
|
|
6
6
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
7
7
|
import { track } from '$lib/tracking'
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
import { openModal } from '$lib/modal'
|
|
6
6
|
import type { ParticipantData } from '$lib/types/participant'
|
|
7
7
|
import type { TitleData } from '$lib/types/title'
|
|
8
|
-
import ListTitle from './ListTitle.svelte'
|
|
9
8
|
import { t } from '$lib/localization'
|
|
10
9
|
import { isRetargetingAllowed } from '$lib/retargeting'
|
|
11
10
|
import { trackViaPixel } from '@playpilot/retargeting-tracking'
|
|
12
11
|
import { MetaEvent } from '$lib/enums/TrackingEvent'
|
|
12
|
+
import ParticipantImage from './ParticipantImage.svelte'
|
|
13
|
+
import ListTitle from '../ListTitle.svelte'
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
15
16
|
participant: ParticipantData
|
|
@@ -54,13 +55,17 @@
|
|
|
54
55
|
</script>
|
|
55
56
|
|
|
56
57
|
<div class="header" class:small>
|
|
57
|
-
<
|
|
58
|
+
<ParticipantImage {participant} />
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
<div>
|
|
61
|
+
<div class="heading" use:heading={2} id="heading">{name}</div>
|
|
62
|
+
|
|
63
|
+
{#if birth_date}
|
|
64
|
+
<p class="dates">
|
|
65
|
+
{t('Born On')} <strong>{formatDate(birth_date)}</strong>{#if death_date}, {t('Died On')} <strong>{formatDate(death_date)}</strong>{/if}
|
|
66
|
+
</p>
|
|
67
|
+
{/if}
|
|
68
|
+
</div>
|
|
64
69
|
</div>
|
|
65
70
|
|
|
66
71
|
<div class="content">
|
|
@@ -85,13 +90,22 @@
|
|
|
85
90
|
|
|
86
91
|
<style lang="scss">
|
|
87
92
|
.header {
|
|
88
|
-
|
|
93
|
+
--participant-image-size: #{margin(6)};
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-columns: calc(var(--participant-image-size)) auto;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: margin(1);
|
|
98
|
+
padding: margin(3) margin(1) margin(2);
|
|
89
99
|
font-family: theme(detail-font-family, font-family);
|
|
90
100
|
font-weight: theme(detail-font-weight, normal);
|
|
91
101
|
font-size: theme(detail-font-size, font-size-base);
|
|
92
102
|
line-height: theme(participant-description-line-height, normal);
|
|
93
103
|
color: theme(detail-text-color, text-color);
|
|
94
104
|
|
|
105
|
+
@include desktop {
|
|
106
|
+
--participant-image-size: #{margin(8)};
|
|
107
|
+
}
|
|
108
|
+
|
|
95
109
|
&.small {
|
|
96
110
|
padding: margin(1);
|
|
97
111
|
}
|
|
@@ -101,7 +115,7 @@
|
|
|
101
115
|
margin: 0;
|
|
102
116
|
font-family: theme(detail-title-font-family, font-family);
|
|
103
117
|
font-weight: theme(detail-title-font-weight, lighter);
|
|
104
|
-
font-size:
|
|
118
|
+
font-size: clamp(1.25em, 5vw, 1.5em);
|
|
105
119
|
line-height: normal;
|
|
106
120
|
font-style: theme(detail-title-font-style, normal);
|
|
107
121
|
|
|
@@ -109,6 +123,10 @@
|
|
|
109
123
|
margin: 0 0 margin(0.5);
|
|
110
124
|
font-size: theme(detail-title-small-font-size, margin(1.25));
|
|
111
125
|
}
|
|
126
|
+
|
|
127
|
+
.small & {
|
|
128
|
+
font-size: 1em;
|
|
129
|
+
}
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
.dates {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '$lib/localization'
|
|
3
|
+
import type { ParticipantData } from '$lib/types/participant'
|
|
4
|
+
import IconAttribution from '../Icons/IconAttribution.svelte'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
participant?: ParticipantData | null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { participant = null }: Props = $props()
|
|
11
|
+
|
|
12
|
+
const { image_metadata } = $derived(participant || { image_metadata: null })
|
|
13
|
+
|
|
14
|
+
let showAttribution = $state(false)
|
|
15
|
+
|
|
16
|
+
function toggleAttribution(event: MouseEvent): void {
|
|
17
|
+
event.stopImmediatePropagation()
|
|
18
|
+
|
|
19
|
+
if ((event.target as HTMLElement).nodeName === 'A') return
|
|
20
|
+
|
|
21
|
+
event.preventDefault()
|
|
22
|
+
|
|
23
|
+
showAttribution = !showAttribution
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div class="image">
|
|
28
|
+
{#if participant?.image}
|
|
29
|
+
<img loading="lazy" src={participant.image} alt="" />
|
|
30
|
+
|
|
31
|
+
{#if image_metadata}
|
|
32
|
+
<button class="attribution-icon" onclick={toggleAttribution}>
|
|
33
|
+
<IconAttribution />
|
|
34
|
+
</button>
|
|
35
|
+
{/if}
|
|
36
|
+
{/if}
|
|
37
|
+
|
|
38
|
+
{#if participant && image_metadata && showAttribution}
|
|
39
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
40
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
41
|
+
<div class="attribution-overlay" onclick={toggleAttribution}>
|
|
42
|
+
<a href={image_metadata.source_url} target="_blank" rel="external nofollow noreferrer">{image_metadata.source_text}</a>
|
|
43
|
+
{t('By')}
|
|
44
|
+
<a href={image_metadata.author_url} target="_blank" rel="external nofollow noreferrer">{image_metadata.author_text}</a>
|
|
45
|
+
/
|
|
46
|
+
<a href={image_metadata.license_url} target="_blank" rel="external nofollow noreferrer">{image_metadata.license_name}</a>
|
|
47
|
+
</div>
|
|
48
|
+
{/if}
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<style lang="scss">
|
|
52
|
+
$item-width: #{var(--participant-image-size, margin(6))};
|
|
53
|
+
|
|
54
|
+
.image {
|
|
55
|
+
position: relative;
|
|
56
|
+
display: block;
|
|
57
|
+
width: $item-width;
|
|
58
|
+
height: $item-width;
|
|
59
|
+
border-radius: theme(border-radius);
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
background: theme(cast-background, lighter);
|
|
62
|
+
|
|
63
|
+
&::before,
|
|
64
|
+
&::after {
|
|
65
|
+
content: '';
|
|
66
|
+
display: block;
|
|
67
|
+
position: absolute;
|
|
68
|
+
width: 50%;
|
|
69
|
+
height: 50%;
|
|
70
|
+
top: 50%;
|
|
71
|
+
left: 50%;
|
|
72
|
+
border-radius: 50%;
|
|
73
|
+
transform: translateX(-50%) translateY(-50%);
|
|
74
|
+
background: currentColor;
|
|
75
|
+
opacity: 0.1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&::after {
|
|
79
|
+
top: 100%;
|
|
80
|
+
width: 75%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
img {
|
|
84
|
+
z-index: 1;
|
|
85
|
+
position: relative;
|
|
86
|
+
width: 100%;
|
|
87
|
+
height: auto;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.attribution-icon {
|
|
92
|
+
z-index: 2;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
position: absolute;
|
|
95
|
+
right: 0;
|
|
96
|
+
bottom: 0;
|
|
97
|
+
padding: margin(0.25);
|
|
98
|
+
margin: 0;
|
|
99
|
+
border: 0;
|
|
100
|
+
background: transparent;
|
|
101
|
+
color: rgba(255, 255, 255, 0.75);
|
|
102
|
+
|
|
103
|
+
&:hover {
|
|
104
|
+
color: white;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:global(svg) {
|
|
108
|
+
display: block;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.attribution-overlay {
|
|
113
|
+
z-index: 1;
|
|
114
|
+
position: absolute;
|
|
115
|
+
top: 0;
|
|
116
|
+
right: 0;
|
|
117
|
+
bottom: 0;
|
|
118
|
+
left: 0;
|
|
119
|
+
padding: margin(0.25);
|
|
120
|
+
background: rgba(0, 0, 0, 0.85);
|
|
121
|
+
font-size: 11px;
|
|
122
|
+
line-height: 1.25;
|
|
123
|
+
white-space: wrap;
|
|
124
|
+
text-align: left;
|
|
125
|
+
|
|
126
|
+
a {
|
|
127
|
+
color: currentColor;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { track } from '$lib/tracking'
|
|
9
9
|
import type { ParticipantData } from '$lib/types/participant'
|
|
10
10
|
import type { TitleData } from '$lib/types/title'
|
|
11
|
+
import ParticipantImage from '../Participants/ParticipantImage.svelte'
|
|
11
12
|
import Rail from './Rail.svelte'
|
|
12
13
|
|
|
13
14
|
interface Props {
|
|
@@ -35,8 +36,6 @@
|
|
|
35
36
|
participantsByOccurrence[participant.sid] = { participant, count }
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
// Sort all participants by their occurrences. If a participant did not occur their positioned will be retained
|
|
39
|
-
// relative to where it was before
|
|
40
39
|
const participantsSortedByCount = Object.values(participantsByOccurrence).sort((a, b) => b.count - a.count)
|
|
41
40
|
|
|
42
41
|
return participantsSortedByCount.map(({ participant }) => participant)
|
|
@@ -51,7 +50,10 @@
|
|
|
51
50
|
{#await fetchParticipantsForTitle(title)}
|
|
52
51
|
<Rail {heading}>
|
|
53
52
|
{#each { length: 5 }}
|
|
54
|
-
<div class="participant"
|
|
53
|
+
<div class="participant">
|
|
54
|
+
<ParticipantImage />
|
|
55
|
+
<div class="name"> </div>
|
|
56
|
+
</div>
|
|
55
57
|
{/each}
|
|
56
58
|
</Rail>
|
|
57
59
|
{:then participants}
|
|
@@ -59,11 +61,9 @@
|
|
|
59
61
|
<Rail {heading}>
|
|
60
62
|
{#each orderParticipantsByPageTextOccurrence(participants).slice(0, 15) as participant}
|
|
61
63
|
<button class="participant" data-testid="participant" onclick={event => onclick(event, participant)}>
|
|
62
|
-
<
|
|
64
|
+
<ParticipantImage {participant} />
|
|
63
65
|
|
|
64
|
-
{
|
|
65
|
-
<div class="character truncate">{participant.character}</div>
|
|
66
|
-
{/if}
|
|
66
|
+
<div class="name">{participant.name}</div>
|
|
67
67
|
</button>
|
|
68
68
|
{/each}
|
|
69
69
|
</Rail>
|
|
@@ -71,22 +71,25 @@
|
|
|
71
71
|
{/await}
|
|
72
72
|
|
|
73
73
|
<style lang="scss">
|
|
74
|
+
$item-width: #{theme(rail-size-default, margin(6))};
|
|
75
|
+
|
|
74
76
|
.participant {
|
|
77
|
+
--participant-image-size: #{$item-width};
|
|
75
78
|
display: flex;
|
|
76
79
|
flex-direction: column;
|
|
77
|
-
flex: 0 0
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
padding:
|
|
81
|
-
border: theme(cast-border, 0);
|
|
80
|
+
flex: 0 0 $item-width;
|
|
81
|
+
gap: margin(0.5);
|
|
82
|
+
width: $item-width;
|
|
83
|
+
padding: 0;
|
|
82
84
|
border-radius: theme(cast-border-radius, border-radius);
|
|
83
|
-
|
|
85
|
+
border: 0;
|
|
86
|
+
background: transparent;
|
|
84
87
|
cursor: pointer;
|
|
85
|
-
font-family: inherit;
|
|
86
|
-
text-align: left;
|
|
87
88
|
color: inherit;
|
|
89
|
+
font-family: inherit;
|
|
88
90
|
font-size: theme(cast-font-size, font-size-small);
|
|
89
91
|
line-height: normal;
|
|
92
|
+
text-align: center;
|
|
90
93
|
white-space: nowrap;
|
|
91
94
|
|
|
92
95
|
&:is(button):hover,
|
|
@@ -96,13 +99,10 @@
|
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
font-style: italic;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.truncate {
|
|
102
|
+
.name {
|
|
103
|
+
min-height: 1lh;
|
|
105
104
|
overflow: hidden;
|
|
105
|
+
color: theme(participant-text-color, text-color-alt) !important;
|
|
106
106
|
text-overflow: ellipsis;
|
|
107
107
|
white-space: nowrap;
|
|
108
108
|
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
<button onclick={(() => {
|
|
37
37
|
destroyExplore()
|
|
38
|
-
window.PlayPilotLinkInjections.config
|
|
38
|
+
window.PlayPilotLinkInjections.config!.explore_use_router = !useExploreRouter()
|
|
39
39
|
insertExplore()
|
|
40
40
|
})}>Toggle new explore</button>
|
|
41
41
|
|
|
@@ -73,4 +73,3 @@
|
|
|
73
73
|
background: theme(light);
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
|
-
|
|
@@ -120,7 +120,7 @@ describe('explore.js', () => {
|
|
|
120
120
|
|
|
121
121
|
it('Should not insert if config object is present but explore_navigation_selector is not given', () => {
|
|
122
122
|
window.PlayPilotLinkInjections.config = {
|
|
123
|
-
explore_navigation_selector:
|
|
123
|
+
explore_navigation_selector: '',
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
expect(insertExploreIntoNavigation()).toBe(false)
|
|
@@ -136,11 +136,12 @@ describe('explore.js', () => {
|
|
|
136
136
|
expect(insertExploreIntoNavigation()).toBe(false)
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
-
it('Should insert if explore_navigation_selector is given and selector
|
|
139
|
+
it('Should insert if explore_navigation_selector is given and selector and path are valid', () => {
|
|
140
140
|
document.body.innerHTML = '<nav><a></a></nav>'
|
|
141
141
|
|
|
142
142
|
window.PlayPilotLinkInjections.config = {
|
|
143
143
|
explore_navigation_selector: 'a',
|
|
144
|
+
explore_navigation_path: '/explore',
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
expect(insertExploreIntoNavigation()).toBe(true)
|
|
@@ -165,6 +166,7 @@ describe('explore.js', () => {
|
|
|
165
166
|
window.PlayPilotLinkInjections.config = {
|
|
166
167
|
explore_navigation_selector: 'a',
|
|
167
168
|
explore_navigation_label: 'Some label',
|
|
169
|
+
explore_navigation_path: '/explore',
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
insertExploreIntoNavigation()
|
|
@@ -984,7 +984,7 @@ describe('injection.ts', () => {
|
|
|
984
984
|
injectLinksInDocument(elements, { aiInjections: [injection], manualInjections: [] })
|
|
985
985
|
|
|
986
986
|
const link = /** @type {HTMLAnchorElement} */ (document.querySelector('a'))
|
|
987
|
-
expect(link.href).toBe(window.PlayPilotLinkInjections.config
|
|
987
|
+
expect(link.href).toBe(window.PlayPilotLinkInjections.config?.explore_navigation_path + `?route=modal&sid=${injection.title_details?.sid}`)
|
|
988
988
|
expect(link.target).not.toBeTruthy()
|
|
989
989
|
})
|
|
990
990
|
|
|
@@ -26,6 +26,7 @@ describe('retargeting.ts', () => {
|
|
|
26
26
|
describe('isPixelAllow', () => {
|
|
27
27
|
it('Should return false if no config object is set', () => {
|
|
28
28
|
vi.mocked(hasConsentedTo).mockImplementation(() => true)
|
|
29
|
+
// @ts-ignore
|
|
29
30
|
window.PlayPilotLinkInjections.config = null
|
|
30
31
|
|
|
31
32
|
expect(isRetargetingAllowed()).toBe(false)
|
|
@@ -33,6 +34,7 @@ describe('retargeting.ts', () => {
|
|
|
33
34
|
|
|
34
35
|
it('Should return false if config object is without allow_retargeting_pixels', () => {
|
|
35
36
|
vi.mocked(hasConsentedTo).mockImplementation(() => true)
|
|
37
|
+
// @ts-ignore
|
|
36
38
|
window.PlayPilotLinkInjections.config = { something: true }
|
|
37
39
|
|
|
38
40
|
expect(isRetargetingAllowed()).toBe(false)
|
package/src/tests/routes/components/{Participant.test.js → Participants/Participant.test.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fireEvent, render, waitFor } from '@testing-library/svelte'
|
|
2
2
|
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import Participant from '
|
|
4
|
+
import Participant from '../../../../routes/components/Participants/Participant.svelte'
|
|
5
5
|
import { participants, title } from '$lib/fakeData'
|
|
6
6
|
import { fetchTitlesForParticipant } from '$lib/api/participants'
|
|
7
7
|
|
|
@@ -80,4 +80,23 @@ describe('Participant.svelte', () => {
|
|
|
80
80
|
expect(queryByText('Credits')).not.toBeTruthy()
|
|
81
81
|
expect(container.querySelector('.small')).toBeTruthy()
|
|
82
82
|
})
|
|
83
|
+
|
|
84
|
+
it('Should render image if given', async () => {
|
|
85
|
+
const participantWithImage = { ...participants[0], image: 'some-image.jpg' }
|
|
86
|
+
|
|
87
|
+
const { getByRole } = render(Participant, { participant: participantWithImage })
|
|
88
|
+
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(getByRole('presentation')).toBeTruthy()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('Should not render image if not given', async () => {
|
|
95
|
+
|
|
96
|
+
const { queryByRole } = render(Participant, { participant: participants[0] })
|
|
97
|
+
|
|
98
|
+
await waitFor(() => {
|
|
99
|
+
expect(queryByRole('presentation')).not.toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
})
|
|
83
102
|
})
|