@playpilot/tpi 6.6.4 → 6.7.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/dist/link-injections.js +9 -9
- package/package.json +1 -1
- package/src/lib/enums/TrackingEvent.ts +2 -0
- package/src/routes/components/Button.svelte +17 -7
- package/src/routes/components/ContextMenu.svelte +46 -22
- package/src/routes/components/Editorial/EditorItem.svelte +1 -1
- package/src/routes/components/Icons/IconNotify.svelte +3 -0
- package/src/routes/components/Icons/IconPlay.svelte +2 -2
- package/src/routes/components/Icons/IconSave.svelte +3 -0
- package/src/routes/components/Icons/IconShare.svelte +3 -2
- package/src/routes/components/Notify.svelte +63 -0
- package/src/routes/components/Playlinks/Playlinks.svelte +16 -33
- package/src/routes/components/Rails/TitlesRail.svelte +1 -0
- package/src/routes/components/Save.svelte +40 -0
- package/src/routes/components/Share.svelte +19 -57
- package/src/routes/components/Title.svelte +14 -3
- package/src/routes/components/Trailer.svelte +1 -2
- package/src/tests/routes/components/Button.test.js +6 -0
- package/src/tests/routes/components/ContextMenu.test.js +15 -3
- package/src/tests/routes/components/Playlinks/Playlinks.test.js +4 -3
- package/src/tests/routes/components/Share.test.js +17 -12
- package/src/tests/routes/components/Title.test.js +4 -4
package/package.json
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
variant?: 'filled' | 'border' | 'link'
|
|
6
|
-
size?: 'base' | 'large'
|
|
6
|
+
size?: 'base' | 'large' | 'wide'
|
|
7
|
+
label?: string | null
|
|
7
8
|
active?: boolean
|
|
8
9
|
onclick?: (event: MouseEvent) => void
|
|
9
10
|
children?: Snippet
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const { variant = 'filled', size = 'base', active = false, onclick, children }: Props = $props()
|
|
13
|
+
const { variant = 'filled', size = 'base', label = null, active = false, onclick, children }: Props = $props()
|
|
13
14
|
</script>
|
|
14
15
|
|
|
15
|
-
<button class="button {variant} {size}" class:active {onclick}>
|
|
16
|
+
<button class="button {variant} {size}" class:active {onclick} aria-label={label}>
|
|
16
17
|
{@render children?.()}
|
|
17
18
|
</button>
|
|
18
19
|
|
|
@@ -35,13 +36,13 @@
|
|
|
35
36
|
cursor: pointer;
|
|
36
37
|
|
|
37
38
|
:global(svg) {
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
--icon-size: 1.5em;
|
|
40
|
+
width: var(--icon-size);
|
|
41
|
+
height: var(--icon-size);
|
|
40
42
|
opacity: 0.75;
|
|
41
43
|
|
|
42
44
|
&:last-child {
|
|
43
|
-
|
|
44
|
-
height: 0.85em;
|
|
45
|
+
--icon-size: 0.85em;
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
}
|
|
@@ -96,5 +97,14 @@
|
|
|
96
97
|
.large {
|
|
97
98
|
padding: 0.5em 1em;
|
|
98
99
|
}
|
|
100
|
+
|
|
101
|
+
.wide {
|
|
102
|
+
font-size: margin(1);
|
|
103
|
+
padding: 0.5em 1.5em;
|
|
104
|
+
|
|
105
|
+
:global(svg:first-child) {
|
|
106
|
+
--icon-size: #{margin(0.95)};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
99
109
|
</style>
|
|
100
110
|
|
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { scale } from 'svelte/transition'
|
|
3
3
|
import IconDots from './Icons/IconDots.svelte'
|
|
4
4
|
import type { Snippet } from 'svelte'
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
|
-
|
|
7
|
+
label?: string
|
|
8
|
+
align?: 'right' | 'center'
|
|
9
|
+
button?: Snippet<[any]>
|
|
8
10
|
children: Snippet
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
const {
|
|
13
|
+
const { label = '', align = 'right', button, children }: Props = $props()
|
|
12
14
|
|
|
13
15
|
let active = $state(false)
|
|
14
|
-
let
|
|
16
|
+
let element: Element | null = $state(null)
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @param {MouseEvent} event
|
|
19
|
-
*/
|
|
20
|
-
function closeOnOutsideClick(event: MouseEvent): void {
|
|
21
|
-
const target = event.target as HTMLElement
|
|
18
|
+
function toggle(event: MouseEvent): void {
|
|
19
|
+
const target = event.currentTarget
|
|
22
20
|
|
|
23
|
-
if (target ===
|
|
21
|
+
if (target === element?.querySelector('button')) {
|
|
22
|
+
event.stopPropagation()
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
active =
|
|
25
|
+
active = !active
|
|
26
26
|
}
|
|
27
27
|
</script>
|
|
28
28
|
|
|
29
|
-
<svelte:window onclick={
|
|
29
|
+
<svelte:window onclick={() => active = false} />
|
|
30
30
|
|
|
31
|
-
<div class="context-menu">
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
<div class="context-menu {align}" bind:this={element}>
|
|
32
|
+
{#if button}
|
|
33
|
+
{@render button({ toggle })}
|
|
34
|
+
{:else}
|
|
35
|
+
<button aria-label={label} aria-expanded={active} onclick={toggle}>
|
|
36
|
+
<IconDots />
|
|
37
|
+
</button>
|
|
38
|
+
{/if}
|
|
35
39
|
|
|
36
40
|
{#if active}
|
|
37
|
-
<div class="content" in:
|
|
41
|
+
<div class="content" in:scale={{ duration: 50, start: 0.85 }}>
|
|
38
42
|
{@render children()}
|
|
39
43
|
</div>
|
|
40
44
|
{/if}
|
|
@@ -59,12 +63,32 @@
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
.content {
|
|
62
|
-
z-index:
|
|
66
|
+
z-index: 10;
|
|
63
67
|
position: absolute;
|
|
64
|
-
|
|
68
|
+
bottom: calc(100% + margin(0.5));
|
|
65
69
|
right: 0;
|
|
66
70
|
max-width: margin(15);
|
|
67
|
-
border-radius: margin(0.5);
|
|
68
|
-
background: theme(lighter);
|
|
71
|
+
border-radius: theme(context-menu-border-radius, margin(0.5));
|
|
72
|
+
background: theme(detail-background, lighter);
|
|
73
|
+
box-shadow: theme(shadow);
|
|
74
|
+
|
|
75
|
+
.center & {
|
|
76
|
+
right: auto;
|
|
77
|
+
left: 50%;
|
|
78
|
+
transform: translateX(-50%);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&::before {
|
|
82
|
+
content: "";
|
|
83
|
+
display: block;
|
|
84
|
+
position: absolute;
|
|
85
|
+
top: 0;
|
|
86
|
+
right: 0;
|
|
87
|
+
bottom: 0;
|
|
88
|
+
left: 0;
|
|
89
|
+
border-radius: theme(context-menu-border-radius, margin(0.5));
|
|
90
|
+
background: theme(context-menu-background-lightness, rgba(255, 255, 255, 0.05));
|
|
91
|
+
pointer-events: none;
|
|
92
|
+
}
|
|
69
93
|
}
|
|
70
94
|
</style>
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
</div>
|
|
132
132
|
|
|
133
133
|
<div class="context-menu">
|
|
134
|
-
<ContextMenu
|
|
134
|
+
<ContextMenu label="More options">
|
|
135
135
|
<button class="context-menu-action" onclick={onremove}>Remove</button>
|
|
136
136
|
<button class="context-menu-action" onclick={showReportIssueModal}>Report issue</button>
|
|
137
137
|
</ContextMenu>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="15" height="16" viewBox="0 0 15 16">
|
|
2
|
+
<path fill="currentColor" stroke-width="1" d="M5.06753 12.8367H2.01027C1.5832 12.8367 1.17362 12.667 0.871638 12.365C0.569653 12.0631 0.4 11.6535 0.4 11.2264C0.4 10.7993 0.569653 10.3898 0.871638 10.0878C1.17342 9.78599 1.58266 9.61636 2.00942 9.61613M5.06753 12.8367L2.01027 9.61613C2.00999 9.61613 2.00971 9.61613 2.00942 9.61613M5.06753 12.8367C5.09277 13.4454 5.34568 14.0241 5.77823 14.4566C6.23463 14.913 6.85364 15.1694 7.49908 15.1694C8.14452 15.1694 8.76352 14.913 9.21992 14.4566C9.65247 14.0241 9.90538 13.4454 9.93063 12.8367H12.9897C13.4168 12.8367 13.8264 12.667 14.1284 12.365C14.4303 12.0631 14.6 11.6535 14.6 11.2264C14.6 10.7993 14.4303 10.3898 14.1284 10.0878C13.8265 9.78595 13.4172 9.61631 12.9904 9.61613C12.981 9.61583 12.9721 9.61192 12.9655 9.60521C12.9588 9.59833 12.955 9.58906 12.9551 9.57944V9.57875V6.28436C12.9551 4.83782 12.3805 3.45053 11.3576 2.42767C10.3348 1.40481 8.94746 0.830176 7.50092 0.830176C6.05438 0.830176 4.66709 1.40481 3.64423 2.42767C2.62137 3.45053 2.04673 4.83782 2.04673 6.28436V9.57832M5.06753 12.8367L2.04673 9.57832M2.00942 9.61613C2.01927 9.61588 2.02866 9.61184 2.03561 9.60484C2.04262 9.59778 2.04661 9.58827 2.04673 9.57832M2.00942 9.61613L2.04673 9.57832M12.9868 10.6395L12.987 10.6404C13.1425 10.6404 13.2917 10.7022 13.4016 10.8122C13.5116 10.9222 13.5734 11.0713 13.5734 11.2269C13.5734 11.3824 13.5116 11.5316 13.4016 11.6415C13.2917 11.7515 13.1425 11.8133 12.987 11.8133H2.00751C1.85197 11.8133 1.70281 11.7515 1.59283 11.6415C1.48285 11.5316 1.42106 11.3824 1.42106 11.2269C1.42106 11.0713 1.48285 10.9222 1.59283 10.8122C1.70281 10.7022 1.85197 10.6404 2.00751 10.6404H2.0076C2.289 10.6402 2.55878 10.5282 2.75766 10.3291C2.95654 10.13 3.06826 9.86015 3.06826 9.57875V6.28436C3.06826 5.10936 3.53502 3.98248 4.36588 3.15163C5.19673 2.32077 6.32361 1.854 7.49862 1.854C8.67362 1.854 9.8005 2.32077 10.6314 3.15163C11.4622 3.98248 11.929 5.10936 11.929 6.28436L11.929 9.57764L11.929 9.57783M12.9868 10.6395L11.929 9.57783M12.9868 10.6395C12.8484 10.6392 12.7114 10.6119 12.5835 10.5589C12.4546 10.5056 12.3376 10.4274 12.239 10.3287C12.1405 10.2301 12.0624 10.113 12.0092 9.98408C11.956 9.85525 11.9287 9.7172 11.929 9.57783M12.9868 10.6395L11.929 9.57783M6.09242 12.8367H8.90574C8.88156 13.1738 8.73688 13.4924 8.49629 13.733C8.23181 13.9975 7.87311 14.146 7.49908 14.146C7.12505 14.146 6.76634 13.9975 6.50186 13.733C6.26127 13.4924 6.11659 13.1738 6.09242 12.8367Z" />
|
|
3
|
+
</svg>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<svg width="
|
|
2
|
-
<path
|
|
1
|
+
<svg width="12" height="12" viewBox="0 0 12 12">
|
|
2
|
+
<path stroke="currentColor" stroke-width="1.1" transform="translate(2 0)" d="M9.47461 5.37598C9.77461 5.54918 9.77461 5.98207 9.47461 6.15527L1.22461 10.9189C0.924693 11.0916 0.549804 10.8745 0.549804 10.5283L0.549805 1.00293C0.549805 0.656711 0.924691 0.439623 1.22461 0.612304L9.47461 5.37598Z" />
|
|
3
3
|
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16">
|
|
2
|
+
<path stroke="currentColor" stroke-width="1.33" d="M3.3335 5.2C3.3335 4.0799 3.3335 3.51984 3.55148 3.09202C3.74323 2.71569 4.04919 2.40973 4.42552 2.21799C4.85334 2 5.41339 2 6.5335 2H9.46683C10.5869 2 11.147 2 11.5748 2.21799C11.9511 2.40973 12.2571 2.71569 12.4488 3.09202C12.6668 3.51984 12.6668 4.0799 12.6668 5.2V14L8.00016 11.3333L3.3335 14V5.2Z" />
|
|
3
|
+
</svg>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
<svg
|
|
2
|
-
<path d="
|
|
1
|
+
<svg width="16" height="15" viewBox="0 0 16 15">
|
|
2
|
+
<path fill="currentColor" d="M6.70138 1.36838C6.70187 0.320494 7.75924 -0.233702 8.60177 0.0939687L8.76779 0.172094L8.82345 0.203344L8.87521 0.240453L14.4748 4.35666C15.273 4.89508 15.2742 6.06459 14.4807 6.60666L8.8879 10.8586L8.83029 10.9026L8.76779 10.9367C7.90544 11.4141 6.70148 10.8588 6.70138 9.74045V9.051C6.21939 9.09485 5.65912 9.18628 5.11056 9.37034C4.41863 9.60256 3.79163 9.96387 3.33907 10.5061C2.89934 11.0331 2.56088 11.8063 2.56075 12.9924C2.56075 13.1083 2.55351 13.2694 2.50802 13.4348C2.48558 13.5162 2.34386 14.0279 1.78439 14.1965C1.18154 14.3778 0.775385 13.9725 0.706262 13.9006C0.580913 13.7702 0.500537 13.626 0.452356 13.5305C0.257874 13.1448 0.107806 12.5395 0.0402463 11.841C-0.0994489 10.3951 0.0801391 8.23145 1.2922 5.90647C2.38232 3.81585 4.09969 2.85246 5.51095 2.42209C5.94157 2.29082 6.34701 2.20931 6.70138 2.15745V1.36838ZM8.28341 1.04709C8.02513 0.904267 7.70193 1.08266 7.70138 1.36838V3.07346C6.71677 3.09168 3.72663 3.40026 2.17892 6.36838L1.98165 6.76584C0.053228 10.8548 1.56075 14.193 1.56075 12.9924C1.56118 8.17098 6.39327 7.98172 7.70138 8.01877V9.74045C7.70147 9.9908 7.94881 10.1585 8.1838 10.1008L8.28341 10.0627L13.883 5.80686C14.1412 5.66368 14.1413 5.30543 13.883 5.16233L8.28341 1.04709Z" />
|
|
3
3
|
</svg>
|
|
4
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { track } from '$lib/tracking'
|
|
3
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
4
|
+
import { t } from '$lib/localization'
|
|
5
|
+
import { heading } from '$lib/actions/heading'
|
|
6
|
+
import type { TitleData } from '$lib/types/title'
|
|
7
|
+
import Button from './Button.svelte'
|
|
8
|
+
import ContextMenu from './ContextMenu.svelte'
|
|
9
|
+
import IconNotify from './Icons/IconNotify.svelte'
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
title: TitleData
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { title }: Props = $props()
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<div class="notify">
|
|
19
|
+
<div class="heading" use:heading={3}>{title.title} is currently not available to stream.</div>
|
|
20
|
+
|
|
21
|
+
<ContextMenu align="center">
|
|
22
|
+
{#snippet button({ toggle })}
|
|
23
|
+
<Button onclick={(event) => {
|
|
24
|
+
toggle(event)
|
|
25
|
+
track(TrackingEvent.NotifyTitle, title)
|
|
26
|
+
}} size="wide" label={t('Save')}>
|
|
27
|
+
<IconNotify /> <span class="label">Notify me when available</span>
|
|
28
|
+
</Button>
|
|
29
|
+
{/snippet}
|
|
30
|
+
|
|
31
|
+
<div class="content">
|
|
32
|
+
This feature is coming soon! Thanks for checking it out. We're still putting the finishing touches on it. Stay tuned!
|
|
33
|
+
</div>
|
|
34
|
+
</ContextMenu>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<style lang="scss">
|
|
38
|
+
.notify {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: margin(0.5);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.heading {
|
|
46
|
+
color: theme(text-color-alt);
|
|
47
|
+
text-align: center;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.content {
|
|
51
|
+
width: margin(14);
|
|
52
|
+
padding: margin(1);
|
|
53
|
+
margin: 0;
|
|
54
|
+
font-size: theme(font-size-small);
|
|
55
|
+
font-weight: theme(font-bold);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.label {
|
|
59
|
+
margin-left: margin(0.25);
|
|
60
|
+
font-size: theme(font-size-base);
|
|
61
|
+
font-weight: theme(font-bold);
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { campaignToPlaylink, getFirstAdOfType } from '$lib/api/ads'
|
|
10
10
|
import { getContext } from 'svelte'
|
|
11
11
|
import Playlink from './Playlink.svelte'
|
|
12
|
+
import Notify from '../Notify.svelte'
|
|
12
13
|
|
|
13
14
|
interface Props {
|
|
14
15
|
playlinks: PlaylinkData[]
|
|
@@ -34,31 +35,28 @@
|
|
|
34
35
|
}
|
|
35
36
|
</script>
|
|
36
37
|
|
|
37
|
-
<div class="heading" use:heading={3}>{t('Where To Stream Online')}</div>
|
|
38
38
|
|
|
39
|
-
{#if
|
|
39
|
+
{#if mergedPlaylinks.length}
|
|
40
|
+
<div class="heading" use:heading={3}>{t('Where To Stream Online')}</div>
|
|
41
|
+
|
|
40
42
|
<div class="disclaimer" data-testid="commission-disclaimer">
|
|
41
43
|
{window?.PlayPilotLinkInjections?.config?.playlinks_disclaimer_text || t('Commission Disclaimer')}
|
|
42
44
|
<a href="https://playpilot.com/" target="_blank" rel="sponsored">PlayPilot.com</a>
|
|
43
45
|
</div>
|
|
44
|
-
{/if}
|
|
45
46
|
|
|
46
|
-
<div class="playlinks" class:list bind:clientWidth={outerWidth}>
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
<div class="playlinks" class:list bind:clientWidth={outerWidth}>
|
|
48
|
+
{#each mergedPlaylinks as playlink, index}
|
|
49
|
+
<Playlink {playlink} onclick={() => onclick(playlink.name)} />
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
</div>
|
|
60
|
-
{/if}
|
|
61
|
-
</div>
|
|
51
|
+
<!-- A fake highlighted playlink as part of the display ad, to be shown after the first playlink -->
|
|
52
|
+
{#if displayAd && (index === 0)}
|
|
53
|
+
<Playlink playlink={campaignToPlaylink(displayAd)} onclick={() => track(TrackingEvent.DisplayedAdPlaylickClick, title, { campaign_name: displayAd.campaign_name })} hideCategory disclaimer={displayAd.disclaimer || ''} />
|
|
54
|
+
{/if}
|
|
55
|
+
{/each}
|
|
56
|
+
</div>
|
|
57
|
+
{:else}
|
|
58
|
+
<Notify {title} />
|
|
59
|
+
{/if}
|
|
62
60
|
|
|
63
61
|
<style lang="scss">
|
|
64
62
|
.heading {
|
|
@@ -102,19 +100,4 @@
|
|
|
102
100
|
}
|
|
103
101
|
}
|
|
104
102
|
}
|
|
105
|
-
|
|
106
|
-
.empty {
|
|
107
|
-
grid-column: span 2;
|
|
108
|
-
padding: margin(0.75);
|
|
109
|
-
margin-top: margin(0.5);
|
|
110
|
-
background: theme(playlink-background, lighter);
|
|
111
|
-
box-shadow: theme(playlink-shadow, shadow);
|
|
112
|
-
border-radius: theme(playlink-border-radius, border-radius);
|
|
113
|
-
white-space: initial;
|
|
114
|
-
line-height: 1.35;
|
|
115
|
-
|
|
116
|
-
.list & {
|
|
117
|
-
grid-column: 1;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
103
|
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { track } from '$lib/tracking'
|
|
3
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
4
|
+
import { t } from '$lib/localization'
|
|
5
|
+
import type { TitleData } from '$lib/types/title'
|
|
6
|
+
import Button from './Button.svelte'
|
|
7
|
+
import ContextMenu from './ContextMenu.svelte'
|
|
8
|
+
import IconSave from './Icons/IconSave.svelte'
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
title: TitleData
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { title }: Props = $props()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<ContextMenu>
|
|
18
|
+
{#snippet button({ toggle })}
|
|
19
|
+
<Button onclick={(event) => {
|
|
20
|
+
toggle(event)
|
|
21
|
+
track(TrackingEvent.SaveTitle, title)
|
|
22
|
+
}} size="wide" label={t('Save')}>
|
|
23
|
+
<IconSave />
|
|
24
|
+
</Button>
|
|
25
|
+
{/snippet}
|
|
26
|
+
|
|
27
|
+
<div class="content">
|
|
28
|
+
This feature is coming soon! Thanks for checking it out. We're still putting the finishing touches on it. Stay tuned!
|
|
29
|
+
</div>
|
|
30
|
+
</ContextMenu>
|
|
31
|
+
|
|
32
|
+
<style lang="scss">
|
|
33
|
+
.content {
|
|
34
|
+
width: margin(14);
|
|
35
|
+
padding: margin(1);
|
|
36
|
+
margin: 0;
|
|
37
|
+
font-size: theme(font-size-small);
|
|
38
|
+
font-weight: theme(font-bold);
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { scale } from 'svelte/transition'
|
|
3
2
|
import { mobileBreakpoint } from '$lib/constants'
|
|
4
3
|
import { copyToClipboard } from '$lib/clipboard'
|
|
5
4
|
import { track } from '$lib/tracking'
|
|
@@ -11,6 +10,7 @@
|
|
|
11
10
|
import IconEmail from './Icons/IconEmail.svelte'
|
|
12
11
|
import Button from './Button.svelte'
|
|
13
12
|
import { onMount } from 'svelte'
|
|
13
|
+
import ContextMenu from './ContextMenu.svelte'
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
16
|
title: string
|
|
@@ -22,24 +22,18 @@
|
|
|
22
22
|
const isMobile = window.innerWidth < mobileBreakpoint
|
|
23
23
|
const useShareApi = isMobile && typeof navigator.share !== 'undefined'
|
|
24
24
|
|
|
25
|
-
let showContextMenu = $state(false)
|
|
26
|
-
|
|
27
25
|
onMount(() => {
|
|
28
26
|
url = url + '?utm_source=tpi'
|
|
29
27
|
})
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (useShareApi) {
|
|
35
|
-
await navigator.share({ title, url })
|
|
36
|
-
|
|
37
|
-
track(TrackingEvent.ShareTitle, null, { title, url: getFullUrlPath(), method: 'native' })
|
|
38
|
-
|
|
29
|
+
function shareApiOrToggle(event: MouseEvent, toggle: Function): void {
|
|
30
|
+
if (!useShareApi) {
|
|
31
|
+
toggle(event)
|
|
39
32
|
return
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
|
|
35
|
+
track(TrackingEvent.ShareTitle, null, { title, url: getFullUrlPath(), method: 'native' })
|
|
36
|
+
navigator.share({ title, url })
|
|
43
37
|
}
|
|
44
38
|
|
|
45
39
|
function copy(): void {
|
|
@@ -53,57 +47,25 @@
|
|
|
53
47
|
}
|
|
54
48
|
</script>
|
|
55
49
|
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
<ContextMenu>
|
|
51
|
+
{#snippet button({ toggle })}
|
|
52
|
+
<Button onclick={(event) => shareApiOrToggle(event, toggle)} size="wide" label={t('Share')}>
|
|
53
|
+
<IconShare />
|
|
54
|
+
</Button>
|
|
55
|
+
{/snippet}
|
|
62
56
|
|
|
63
|
-
{
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
<IconLink /> {t('Copy URL')}
|
|
67
|
-
</button>
|
|
57
|
+
<button class="item" onclick={copy}>
|
|
58
|
+
<IconLink /> {t('Copy URL')}
|
|
59
|
+
</button>
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{/if}
|
|
74
|
-
</div>
|
|
61
|
+
<button class="item" onclick={email}>
|
|
62
|
+
<IconEmail /> {t('Email')}
|
|
63
|
+
</button>
|
|
64
|
+
</ContextMenu>
|
|
75
65
|
|
|
76
66
|
<style lang="scss">
|
|
77
67
|
$border-radius: theme(context-menu-border-radius, margin(0.5));
|
|
78
68
|
|
|
79
|
-
.share {
|
|
80
|
-
position: relative;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.context-menu {
|
|
84
|
-
z-index: 10;
|
|
85
|
-
position: absolute;
|
|
86
|
-
bottom: calc(100% + margin(0.5));
|
|
87
|
-
left: 0;
|
|
88
|
-
max-width: margin(15);
|
|
89
|
-
border-radius: $border-radius;
|
|
90
|
-
background: theme(detail-background, lighter);
|
|
91
|
-
box-shadow: theme(shadow);
|
|
92
|
-
|
|
93
|
-
&::before {
|
|
94
|
-
content: "";
|
|
95
|
-
display: block;
|
|
96
|
-
position: absolute;
|
|
97
|
-
top: 0;
|
|
98
|
-
right: 0;
|
|
99
|
-
bottom: 0;
|
|
100
|
-
left: 0;
|
|
101
|
-
border-radius: $border-radius;
|
|
102
|
-
background: theme(context-menu-background-lightness, rgba(255, 255, 255, 0.05));
|
|
103
|
-
pointer-events: none;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
69
|
.item {
|
|
108
70
|
cursor: pointer;
|
|
109
71
|
appearance: none;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import ParticipantsRail from './Rails/ParticipantsRail.svelte'
|
|
7
7
|
import SimilarRail from './Rails/SimilarRail.svelte'
|
|
8
8
|
import TitlePoster from './TitlePoster.svelte'
|
|
9
|
+
import Save from './Save.svelte'
|
|
9
10
|
import Share from './Share.svelte'
|
|
10
11
|
import Trailer from './Trailer.svelte'
|
|
11
12
|
import { t } from '$lib/localization'
|
|
@@ -55,9 +56,13 @@
|
|
|
55
56
|
|
|
56
57
|
<div class="actions">
|
|
57
58
|
{#if title.embeddable_url}
|
|
58
|
-
<Trailer
|
|
59
|
-
<Share title={title.title} url={titleUrl(title)} />
|
|
59
|
+
<Trailer {title} />
|
|
60
60
|
{/if}
|
|
61
|
+
|
|
62
|
+
<div class="right">
|
|
63
|
+
<Save {title} />
|
|
64
|
+
<Share title={title.title} url={titleUrl(title)} />
|
|
65
|
+
</div>
|
|
61
66
|
</div>
|
|
62
67
|
</div>
|
|
63
68
|
|
|
@@ -182,7 +187,13 @@
|
|
|
182
187
|
.actions {
|
|
183
188
|
display: flex;
|
|
184
189
|
gap: margin(0.5);
|
|
185
|
-
margin-top: margin(
|
|
190
|
+
margin-top: margin(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.right {
|
|
194
|
+
display: flex;
|
|
195
|
+
gap: inherit;
|
|
196
|
+
margin-left: auto;
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
.background {
|
|
@@ -25,4 +25,10 @@ describe('Button.svelte', () => {
|
|
|
25
25
|
|
|
26
26
|
expect(onclick).toHaveBeenCalled()
|
|
27
27
|
})
|
|
28
|
+
|
|
29
|
+
it('Should include given label as aria-label', () => {
|
|
30
|
+
const { getByLabelText } = render(Button, { label: 'Some label' })
|
|
31
|
+
|
|
32
|
+
expect(getByLabelText('Some label')).toBeTruthy()
|
|
33
|
+
})
|
|
28
34
|
})
|
|
@@ -8,7 +8,7 @@ describe('ContextMenu.svelte', () => {
|
|
|
8
8
|
const children = createRawSnippet(() => ({ render: () => '<p>Some snippet</p>' }))
|
|
9
9
|
|
|
10
10
|
it('Should open the context menu on click', async () => {
|
|
11
|
-
const { getByRole, queryByText } = render(ContextMenu, {
|
|
11
|
+
const { getByRole, queryByText } = render(ContextMenu, { label: '', children })
|
|
12
12
|
|
|
13
13
|
expect(queryByText('Some snippet')).not.toBeTruthy()
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ describe('ContextMenu.svelte', () => {
|
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
it('Should close the context menu on second click', async () => {
|
|
21
|
-
const { getByRole, queryByText } = render(ContextMenu, {
|
|
21
|
+
const { getByRole, queryByText } = render(ContextMenu, { label: '', children })
|
|
22
22
|
|
|
23
23
|
await fireEvent.click(getByRole('button'))
|
|
24
24
|
await fireEvent.click(getByRole('button'))
|
|
@@ -27,11 +27,23 @@ describe('ContextMenu.svelte', () => {
|
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
it('Should close the context menu when clicking outside of the button', async () => {
|
|
30
|
-
const { getByRole, queryByText } = render(ContextMenu, {
|
|
30
|
+
const { getByRole, queryByText } = render(ContextMenu, { label: '', children })
|
|
31
31
|
|
|
32
32
|
await fireEvent.click(getByRole('button'))
|
|
33
33
|
await fireEvent.click(document.body)
|
|
34
34
|
|
|
35
35
|
expect(queryByText('Some snippet')).not.toBeTruthy()
|
|
36
36
|
})
|
|
37
|
+
|
|
38
|
+
it('Should include center class when align is set to center', async () => {
|
|
39
|
+
const { container } = render(ContextMenu, { align: 'center', children })
|
|
40
|
+
|
|
41
|
+
expect(container.querySelector('.context-menu')?.classList).toContain('center')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('Should include right class when align is not set to center', async () => {
|
|
45
|
+
const { container } = render(ContextMenu, { children })
|
|
46
|
+
|
|
47
|
+
expect(container.querySelector('.context-menu')?.classList).toContain('right')
|
|
48
|
+
})
|
|
37
49
|
})
|
|
@@ -63,12 +63,13 @@ describe('Playlinks.svelte', () => {
|
|
|
63
63
|
expect(getByText('Some disclaimer')).toBeTruthy()
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
it('Should show empty state without commission disclaimer when no playlinks
|
|
66
|
+
it('Should show empty state without commission disclaimer when no playlinks are given', () => {
|
|
67
67
|
/** @type {import('$lib/types/playlink').PlaylinkData[]} */
|
|
68
68
|
const playlinks = []
|
|
69
|
-
const { queryByTestId } = render(Playlinks, { playlinks, title })
|
|
69
|
+
const { queryByTestId, getByText } = render(Playlinks, { playlinks, title })
|
|
70
70
|
|
|
71
|
-
expect(
|
|
71
|
+
expect(getByText(`${title.title} is currently not available to stream.`)).toBeTruthy()
|
|
72
|
+
expect(getByText('Notify me when available')).toBeTruthy()
|
|
72
73
|
expect(queryByTestId('commission-disclaimer')).not.toBeTruthy()
|
|
73
74
|
})
|
|
74
75
|
|