@playpilot/tpi 8.34.5 → 8.35.0-beta.2
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/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 +613 -573
- package/src/lib/fakeData.ts +6 -0
- package/src/lib/types/filter.d.ts +5 -2
- package/src/lib/types/participant.d.ts +1 -1
- package/src/routes/components/Explore/Filter/Filter.svelte +154 -148
- package/src/routes/components/Explore/Filter/FilterSorting.svelte +5 -4
- package/src/routes/components/Explore/Routes/ExploreResults.svelte +17 -10
- package/src/routes/components/Explore/Routes/ExploreTitle.svelte +1 -1
- package/src/routes/components/GridTitle.svelte +11 -4
- package/src/routes/components/Modals/Modal.svelte +5 -1
- package/src/routes/components/Modals/ParticipantModal.svelte +1 -5
- package/src/routes/components/Participants/Participant.svelte +75 -135
- package/src/routes/components/Rails/ParticipantsRail.svelte +1 -1
- package/src/tests/routes/components/Participants/Participant.test.js +52 -14
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount } from 'svelte'
|
|
3
2
|
import { heading } from '$lib/actions/heading'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import type { ParticipantData } from '$lib/types/participant'
|
|
3
|
+
import type { Job, ParticipantData } from '$lib/types/participant'
|
|
4
|
+
import type { ExploreFilter, ExploreTitleSorting } from '$lib/types/filter'
|
|
7
5
|
import type { TitleData } from '$lib/types/title'
|
|
8
6
|
import { Sorting } from '$lib/enums/Sorting'
|
|
9
7
|
import { t } from '$lib/localization'
|
|
@@ -11,66 +9,40 @@
|
|
|
11
9
|
import { trackViaPixel } from '@playpilot/retargeting-tracking'
|
|
12
10
|
import { MetaEvent } from '$lib/enums/TrackingEvent'
|
|
13
11
|
import ParticipantImage from './ParticipantImage.svelte'
|
|
14
|
-
import
|
|
15
|
-
import
|
|
12
|
+
import ExploreResults from '../Explore/Routes/ExploreResults.svelte'
|
|
13
|
+
import Filter from '../Explore/Filter/Filter.svelte'
|
|
16
14
|
import Button from '../Button.svelte'
|
|
15
|
+
import { openModal } from '$lib/modal'
|
|
17
16
|
|
|
18
17
|
interface Props {
|
|
19
18
|
participant: ParticipantData
|
|
20
19
|
small?: boolean
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
type TitleSorting = { label: string; value: (typeof Sorting)[keyof typeof Sorting] }
|
|
24
|
-
|
|
25
22
|
const { participant, small = false }: Props = $props()
|
|
26
23
|
|
|
27
|
-
const { name,
|
|
24
|
+
const { name, description } = $derived(participant)
|
|
28
25
|
|
|
29
|
-
const
|
|
26
|
+
const filter: ExploreFilter = $state({})
|
|
27
|
+
const sortings: ExploreTitleSorting[] = [
|
|
30
28
|
{ label: 'Popularity', value: Sorting.Popular },
|
|
31
29
|
{ label: 'Top Rated', value: Sorting.Best },
|
|
32
30
|
{ label: 'Year', value: Sorting.Chronological },
|
|
33
31
|
]
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let titles: TitleData[] = $state([])
|
|
38
|
-
let page = $state(1)
|
|
39
|
-
let hasMorePages = $state(true)
|
|
40
|
-
let loading = $state(true)
|
|
41
|
-
let currentSorting = $state(sortings[0])
|
|
33
|
+
let currentJob: 'all' | Job = $state('all')
|
|
34
|
+
let descriptionExpanded: boolean = $state(false)
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
const filterWithParticipant: ExploreFilter = $derived({
|
|
37
|
+
...filter,
|
|
38
|
+
participant_sid: { type: 'string', value: participant.sid },
|
|
39
|
+
participant_job: { type: 'string', value: currentJob === 'all' ? null : currentJob }
|
|
40
|
+
})
|
|
44
41
|
|
|
45
42
|
if (isRetargetingAllowed()) trackViaPixel(MetaEvent.ParticipantView, { participant: participant.name })
|
|
46
43
|
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function loadMore(): Promise<void> {
|
|
53
|
-
loading = true
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const results = await fetchTitlesForParticipant(participant, { page, sorting: currentSorting.value })
|
|
57
|
-
|
|
58
|
-
titles = [...titles, ...results]
|
|
59
|
-
hasMorePages = results?.length >= pageSize
|
|
60
|
-
} catch {
|
|
61
|
-
hasMorePages = false
|
|
62
|
-
} finally {
|
|
63
|
-
loading = false
|
|
64
|
-
page += 1
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function setSorting(sorting: TitleSorting): void {
|
|
69
|
-
currentSorting = sorting
|
|
70
|
-
titles = []
|
|
71
|
-
page = 1
|
|
72
|
-
|
|
73
|
-
loadMore()
|
|
44
|
+
function onTitleClick(event: MouseEvent, titles: TitleData[], index: number): void {
|
|
45
|
+
openModal({ event, type: 'titles-rail', data: titles, props: { initialIndex: index } })
|
|
74
46
|
}
|
|
75
47
|
</script>
|
|
76
48
|
|
|
@@ -80,60 +52,46 @@
|
|
|
80
52
|
<div>
|
|
81
53
|
<div class="heading" use:heading={2} id="heading">{name}</div>
|
|
82
54
|
|
|
83
|
-
{#if
|
|
84
|
-
<p class="
|
|
85
|
-
{
|
|
55
|
+
{#if description}
|
|
56
|
+
<p class="description" class:expanded={descriptionExpanded}>
|
|
57
|
+
{description}
|
|
86
58
|
</p>
|
|
59
|
+
|
|
60
|
+
<div class="read-more">
|
|
61
|
+
<Button variant="link" onclick={() => descriptionExpanded = !descriptionExpanded}>
|
|
62
|
+
{descriptionExpanded ? t('Read Less') : t('Read More')}
|
|
63
|
+
</Button>
|
|
64
|
+
</div>
|
|
87
65
|
{/if}
|
|
88
66
|
</div>
|
|
89
67
|
</div>
|
|
90
68
|
|
|
91
|
-
<div class="content">
|
|
92
|
-
{#if !small
|
|
69
|
+
<div class="content" class:small>
|
|
70
|
+
{#if !small}
|
|
93
71
|
<div class="list-header">
|
|
94
72
|
<div class="heading subheading" use:heading={3} id="credits">{t('Credits')}</div>
|
|
73
|
+
</div>
|
|
95
74
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{/snippet}
|
|
103
|
-
|
|
104
|
-
{#snippet content()}
|
|
105
|
-
<div class="sortings">
|
|
106
|
-
{#each sortings as sorting}
|
|
107
|
-
<Button variant="link" onclick={() => setSorting(sorting)}>{t(sorting.label)}</Button>
|
|
108
|
-
{/each}
|
|
109
|
-
</div>
|
|
110
|
-
{/snippet}
|
|
111
|
-
</Dropdown>
|
|
112
|
-
</div>
|
|
75
|
+
<div class="jobs">
|
|
76
|
+
{#each ['all', ...participant.jobs] as job}
|
|
77
|
+
<Button variant="border" size="large" active={currentJob === job} onclick={() => currentJob = job as 'all' | Job}>
|
|
78
|
+
{t('Job: ' + job)}
|
|
79
|
+
</Button>
|
|
80
|
+
{/each}
|
|
113
81
|
</div>
|
|
114
|
-
{/if}
|
|
115
82
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<ListTitle {title} compact={small} onclick={(event) => openModal({ event, data: title })} />
|
|
119
|
-
{/each}
|
|
83
|
+
<Filter {filter} {sortings} hideActive />
|
|
84
|
+
{/if}
|
|
120
85
|
|
|
121
|
-
|
|
122
|
-
{#each { length: 6 }}
|
|
123
|
-
<div class="skeleton" data-testid="skeleton"></div>
|
|
124
|
-
{/each}
|
|
125
|
-
{:else if hasMorePages}
|
|
126
|
-
<button class="more" onclick={loadMore}>{t('Show More')}</button>
|
|
127
|
-
{/if}
|
|
128
|
-
</div>
|
|
86
|
+
<ExploreResults forceGrid hidePlaylinks filter={filterWithParticipant} {onTitleClick} />
|
|
129
87
|
</div>
|
|
130
88
|
|
|
131
89
|
<style lang="scss">
|
|
132
90
|
.header {
|
|
133
|
-
--participant-image-size: #{margin(
|
|
91
|
+
--participant-image-size: #{margin(5)};
|
|
134
92
|
display: grid;
|
|
135
|
-
grid-template-columns: calc(var(--participant-image-size))
|
|
136
|
-
align-items:
|
|
93
|
+
grid-template-columns: var(--participant-image-size) calc(100% - var(--participant-image-size) - margin(1));
|
|
94
|
+
align-items: start;
|
|
137
95
|
gap: margin(1);
|
|
138
96
|
padding: margin(3) margin(1) margin(2);
|
|
139
97
|
font-family: theme(detail-font-family, font-family);
|
|
@@ -144,11 +102,13 @@
|
|
|
144
102
|
|
|
145
103
|
@include desktop {
|
|
146
104
|
--participant-image-size: #{margin(8)};
|
|
105
|
+
padding: margin(3) margin(2) margin(2);
|
|
147
106
|
}
|
|
148
107
|
|
|
149
108
|
&.small {
|
|
150
109
|
--participant-image-size: #{margin(6)};
|
|
151
|
-
padding: margin(1);
|
|
110
|
+
padding: margin(1) margin(1) 0;
|
|
111
|
+
align-items: center;
|
|
152
112
|
}
|
|
153
113
|
}
|
|
154
114
|
|
|
@@ -161,19 +121,14 @@
|
|
|
161
121
|
font-style: theme(detail-title-font-style, normal);
|
|
162
122
|
|
|
163
123
|
&.subheading {
|
|
164
|
-
margin: 0;
|
|
165
124
|
font-size: theme(detail-title-small-font-size, margin(1.25));
|
|
166
125
|
}
|
|
167
126
|
|
|
168
127
|
.small & {
|
|
169
|
-
font-size:
|
|
128
|
+
font-size: 1.25em;
|
|
170
129
|
}
|
|
171
130
|
}
|
|
172
131
|
|
|
173
|
-
.dates {
|
|
174
|
-
margin: margin(0.5) 0 0;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
132
|
.content {
|
|
178
133
|
padding: 0 margin(1) margin(1);
|
|
179
134
|
color: theme(detail-text-color, text-color);
|
|
@@ -183,16 +138,38 @@
|
|
|
183
138
|
line-height: normal;
|
|
184
139
|
font-style: normal;
|
|
185
140
|
|
|
141
|
+
@include desktop {
|
|
142
|
+
padding: 0 margin(2) margin(2);
|
|
143
|
+
|
|
144
|
+
&.small {
|
|
145
|
+
padding: 0 margin(1) margin(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
186
149
|
::view-transition-old(content),
|
|
187
150
|
::view-transition-new(content) {
|
|
188
151
|
height: 100%;
|
|
189
152
|
}
|
|
190
153
|
}
|
|
191
154
|
|
|
192
|
-
.
|
|
193
|
-
display:
|
|
194
|
-
|
|
195
|
-
|
|
155
|
+
.description {
|
|
156
|
+
display: -webkit-box;
|
|
157
|
+
-webkit-box-orient: vertical;
|
|
158
|
+
margin: margin(0.5) 0 0;
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
-webkit-line-clamp: 3;
|
|
161
|
+
line-clamp: 3;
|
|
162
|
+
color: theme(text-color-alt);
|
|
163
|
+
|
|
164
|
+
&.expanded {
|
|
165
|
+
display: block;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.read-more {
|
|
170
|
+
:global(.button) {
|
|
171
|
+
font-weight: bold;
|
|
172
|
+
}
|
|
196
173
|
}
|
|
197
174
|
|
|
198
175
|
.list-header {
|
|
@@ -202,47 +179,10 @@
|
|
|
202
179
|
margin-bottom: margin(0.5);
|
|
203
180
|
}
|
|
204
181
|
|
|
205
|
-
.
|
|
206
|
-
min-height: margin(7.25);
|
|
207
|
-
border-radius: theme(playlink-border-radius, border-radius);
|
|
208
|
-
background: theme(detail-background-light, lighter);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.more {
|
|
212
|
-
cursor: pointer;
|
|
213
|
-
appearance: none;
|
|
214
|
-
padding: margin(0.5) margin(1);
|
|
215
|
-
border: 0;
|
|
216
|
-
border-radius: theme(participants-load-more-border-radius, border-radius);
|
|
217
|
-
background: theme(participants-load-more-background, content);
|
|
218
|
-
color: inherit;
|
|
219
|
-
font-size: inherit;
|
|
220
|
-
font-family: inherit;
|
|
221
|
-
|
|
222
|
-
&:hover {
|
|
223
|
-
filter: brightness(1.2);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.sort {
|
|
182
|
+
.jobs {
|
|
228
183
|
display: flex;
|
|
229
|
-
|
|
230
|
-
gap: margin(0.
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.sortings {
|
|
235
|
-
:global(.button) {
|
|
236
|
-
display: block;
|
|
237
|
-
width: 100%;
|
|
238
|
-
padding: margin(0.5) margin(1);
|
|
239
|
-
text-align: left;
|
|
240
|
-
white-space: nowrap;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
:global(.button:hover) {
|
|
244
|
-
background: theme(content-light);
|
|
245
|
-
border-radius: 0;
|
|
246
|
-
}
|
|
184
|
+
flex-wrap: wrap;
|
|
185
|
+
gap: margin(0.5);
|
|
186
|
+
margin-bottom: margin(0.5);
|
|
247
187
|
}
|
|
248
188
|
</style>
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
async function getParticipants(): Promise<ParticipantData[]> {
|
|
57
57
|
const participants = await fetchParticipantsForTitle(title)
|
|
58
58
|
|
|
59
|
-
if (participants
|
|
59
|
+
if (participants?.length) track(TrackingEvent.TitleParticipantsView, title, { participants: participants.map(p => p.name).slice(0, 20) })
|
|
60
60
|
|
|
61
61
|
return participants
|
|
62
62
|
}
|
|
@@ -3,15 +3,15 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
|
3
3
|
|
|
4
4
|
import Participant from '../../../../routes/components/Participants/Participant.svelte'
|
|
5
5
|
import { participants, title } from '$lib/fakeData'
|
|
6
|
-
import {
|
|
6
|
+
import { fetchTitles } from '$lib/api/titles'
|
|
7
7
|
import { Sorting } from '$lib/enums/Sorting'
|
|
8
8
|
|
|
9
9
|
vi.mock('$lib/tracking', () => ({
|
|
10
10
|
track: vi.fn(),
|
|
11
11
|
}))
|
|
12
12
|
|
|
13
|
-
vi.mock('$lib/api/
|
|
14
|
-
|
|
13
|
+
vi.mock('$lib/api/titles', () => ({
|
|
14
|
+
fetchTitles: vi.fn(),
|
|
15
15
|
}))
|
|
16
16
|
|
|
17
17
|
describe('Participant.svelte', () => {
|
|
@@ -19,18 +19,20 @@ describe('Participant.svelte', () => {
|
|
|
19
19
|
vi.resetAllMocks()
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
it('Should call
|
|
22
|
+
it('Should call fetchTitles on mount', async () => {
|
|
23
23
|
render(Participant, { participant: participants[0] })
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
await waitFor(() => {
|
|
26
|
+
expect(fetchTitles).toHaveBeenCalled()
|
|
27
|
+
})
|
|
26
28
|
})
|
|
27
29
|
|
|
28
30
|
it('Should render fetched titles after showing skeletons', async () => {
|
|
29
|
-
vi.mocked(
|
|
31
|
+
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title, title], next: 'next', previous: 'previous' })
|
|
30
32
|
|
|
31
33
|
const { getAllByTestId } = render(Participant, { participant: participants[0] })
|
|
32
34
|
|
|
33
|
-
expect(getAllByTestId('skeleton')).toHaveLength(
|
|
35
|
+
expect(getAllByTestId('skeleton')).toHaveLength(24)
|
|
34
36
|
|
|
35
37
|
await waitFor(() => {
|
|
36
38
|
expect(getAllByTestId('title')).toHaveLength(2)
|
|
@@ -38,7 +40,7 @@ describe('Participant.svelte', () => {
|
|
|
38
40
|
})
|
|
39
41
|
|
|
40
42
|
it('Should not show load more button while loading and when fewer than page size of titles are fetched', async () => {
|
|
41
|
-
vi.mocked(
|
|
43
|
+
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title, title], next: 'next', previous: 'previous' })
|
|
42
44
|
|
|
43
45
|
const { queryByText, getAllByTestId } = render(Participant, { participant: participants[0] })
|
|
44
46
|
|
|
@@ -52,8 +54,8 @@ describe('Participant.svelte', () => {
|
|
|
52
54
|
})
|
|
53
55
|
|
|
54
56
|
it('Should show load more button after loading and if more than page size of titles are fetched', async () => {
|
|
55
|
-
const resolved = Array(30).fill(title)
|
|
56
|
-
vi.mocked(
|
|
57
|
+
const resolved = ({ results: Array(30).fill(title), next: 'next', previous: 'previous' })
|
|
58
|
+
vi.mocked(fetchTitles).mockResolvedValueOnce(resolved)
|
|
57
59
|
|
|
58
60
|
const { getByText } = render(Participant, { participant: participants[0] })
|
|
59
61
|
|
|
@@ -63,8 +65,8 @@ describe('Participant.svelte', () => {
|
|
|
63
65
|
})
|
|
64
66
|
|
|
65
67
|
it('Should fetch more titles when load more button is clicked', async () => {
|
|
66
|
-
const resolved = Array(30).fill(title)
|
|
67
|
-
vi.mocked(
|
|
68
|
+
const resolved = ({ results: Array(30).fill(title), next: 'next', previous: 'previous' })
|
|
69
|
+
vi.mocked(fetchTitles).mockResolvedValueOnce(resolved)
|
|
68
70
|
|
|
69
71
|
const { getByText } = render(Participant, { participant: participants[0] })
|
|
70
72
|
|
|
@@ -72,7 +74,34 @@ describe('Participant.svelte', () => {
|
|
|
72
74
|
|
|
73
75
|
await fireEvent.click(getByText('Show more'))
|
|
74
76
|
|
|
75
|
-
expect(
|
|
77
|
+
expect(fetchTitles).toHaveBeenCalledWith({ page: 2, page_size: expect.any(Number), participant_job: null, participant_sid: participants[0].sid })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('Should include participant role in fetchTitles when selected and refetch', async () => {
|
|
81
|
+
const resolved = ({ results: Array(30).fill(title), next: 'next', previous: 'previous' })
|
|
82
|
+
vi.mocked(fetchTitles).mockResolvedValueOnce(resolved)
|
|
83
|
+
|
|
84
|
+
const { getByText } = render(Participant, { participant: participants[0] })
|
|
85
|
+
|
|
86
|
+
await fireEvent.click(getByText('Actor'))
|
|
87
|
+
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
expect(fetchTitles).toHaveBeenCalledWith(expect.objectContaining({ participant_job: 'actor' }))
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('Should include selected sorting specific to participants', async () => {
|
|
94
|
+
const resolved = ({ results: Array(30).fill(title), next: 'next', previous: 'previous' })
|
|
95
|
+
vi.mocked(fetchTitles).mockResolvedValueOnce(resolved)
|
|
96
|
+
|
|
97
|
+
const { getByText } = render(Participant, { participant: participants[0] })
|
|
98
|
+
|
|
99
|
+
await fireEvent.click(getByText('Sort by'))
|
|
100
|
+
await fireEvent.click(getByText('Year'))
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
expect(fetchTitles).toHaveBeenCalledWith(expect.objectContaining({ ordering: Sorting.Chronological }))
|
|
104
|
+
})
|
|
76
105
|
})
|
|
77
106
|
|
|
78
107
|
it('Should render as small variant when given', () => {
|
|
@@ -93,11 +122,20 @@ describe('Participant.svelte', () => {
|
|
|
93
122
|
})
|
|
94
123
|
|
|
95
124
|
it('Should not render image if not given', async () => {
|
|
96
|
-
|
|
97
125
|
const { queryByRole } = render(Participant, { participant: participants[0] })
|
|
98
126
|
|
|
99
127
|
await waitFor(() => {
|
|
100
128
|
expect(queryByRole('presentation')).not.toBeTruthy()
|
|
101
129
|
})
|
|
102
130
|
})
|
|
131
|
+
|
|
132
|
+
it('Should render description and toggle more on click of Read more button', async () => {
|
|
133
|
+
const { getByText } = render(Participant, { participant: participants[0] })
|
|
134
|
+
|
|
135
|
+
expect(getByText(participants[0].description)).toBeTruthy()
|
|
136
|
+
|
|
137
|
+
await fireEvent.click(getByText('Read more'))
|
|
138
|
+
|
|
139
|
+
expect(getByText('Read less')).toBeTruthy()
|
|
140
|
+
})
|
|
103
141
|
})
|