Skip to content

Commit 191946c

Browse files
committed
feat(panel): adds trailer support
1 parent a8c43d0 commit 191946c

10 files changed

+367
-10
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"dependencies": {
5454
"@dvcol/base-http-client": "^1.13.1",
5555
"@dvcol/common-utils": "^1.11.1",
56-
"@dvcol/simkl-http-client": "^1.1.1",
56+
"@dvcol/simkl-http-client": "^1.1.2",
5757
"@dvcol/tmdb-http-client": "^1.3.4",
5858
"@dvcol/trakt-http-client": "^1.4.9",
5959
"@dvcol/web-extension-utils": "^3.3.2",
@@ -62,6 +62,7 @@
6262
"naive-ui": "^2.38.1",
6363
"pinia": "^2.1.7",
6464
"vue": "^3.4.14",
65+
"vue-lite-youtube-embed": "^1.2.4",
6566
"vue-router": "^4.2.5"
6667
},
6768
"devDependencies": {

pnpm-lock.yaml

+37-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<script setup lang="ts">
2+
import { NFlex } from 'naive-ui';
3+
import LiteYouTubeEmbed from 'vue-lite-youtube-embed';
4+
5+
import type { PropType } from 'vue';
6+
import type { ImageResolution, YoutubePlayerProps } from '~/models/youtube-player.model';
7+
8+
const props = defineProps({
9+
id: {
10+
type: String,
11+
required: true,
12+
},
13+
title: {
14+
type: String,
15+
required: true,
16+
},
17+
announce: {
18+
type: String,
19+
required: false,
20+
default: 'Watch',
21+
},
22+
activatedClass: {
23+
type: String,
24+
required: false,
25+
default: 'lyt-activated',
26+
},
27+
adNetwork: {
28+
type: Boolean,
29+
required: false,
30+
default: true,
31+
},
32+
iframeClass: {
33+
type: String,
34+
required: false,
35+
default: '',
36+
},
37+
cookie: {
38+
type: Boolean,
39+
required: false,
40+
default: false,
41+
},
42+
params: {
43+
type: String,
44+
required: false,
45+
default: '',
46+
},
47+
playerClass: {
48+
type: String,
49+
required: false,
50+
default: 'lty-playbtn',
51+
},
52+
playlist: {
53+
type: Boolean,
54+
required: false,
55+
default: false,
56+
},
57+
playlistCoverId: {
58+
type: String,
59+
required: false,
60+
default: '',
61+
},
62+
poster: {
63+
type: String as PropType<ImageResolution>,
64+
required: false,
65+
default: 'hqdefault',
66+
},
67+
wrapperClass: {
68+
type: String,
69+
required: false,
70+
default: 'yt-lite',
71+
},
72+
muted: {
73+
type: Boolean,
74+
required: false,
75+
default: false,
76+
},
77+
thumbnail: {
78+
type: String,
79+
required: false,
80+
},
81+
webp: {
82+
type: Boolean,
83+
required: false,
84+
default: false,
85+
},
86+
rel: {
87+
type: String as PropType<YoutubePlayerProps['rel']>,
88+
required: false,
89+
default: 'preload',
90+
},
91+
aspectHeight: {
92+
type: Number,
93+
required: false,
94+
default: 9,
95+
},
96+
aspectWidth: {
97+
type: Number,
98+
required: false,
99+
default: 16,
100+
},
101+
});
102+
</script>
103+
104+
<template>
105+
<NFlex class="youtube-player-container" justify="center" align="center" :title="title">
106+
<LiteYouTubeEmbed v-bind="props" />
107+
</NFlex>
108+
</template>
109+
110+
<style lang="scss" scoped>
111+
.youtube-player-container {
112+
width: 100%;
113+
height: 100%;
114+
}
115+
</style>
116+
117+
<style lang="scss">
118+
.youtube-player-container {
119+
.yt-lite {
120+
position: relative;
121+
display: block;
122+
width: 100%;
123+
height: 100%;
124+
background-color: #000;
125+
background-position: 50%;
126+
background-size: cover;
127+
cursor: pointer;
128+
contain: content;
129+
}
130+
131+
.yt-lite::before {
132+
position: absolute;
133+
top: 0;
134+
display: block;
135+
box-sizing: content-box;
136+
width: 100%;
137+
height: 60px;
138+
padding-bottom: 50px;
139+
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==');
140+
background-repeat: repeat-x;
141+
background-position: top;
142+
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
143+
content: '';
144+
}
145+
146+
.yt-lite::after {
147+
display: block;
148+
padding-bottom: var(--aspect-ratio);
149+
content: '';
150+
}
151+
152+
.yt-lite > iframe {
153+
position: absolute;
154+
top: 0;
155+
left: 0;
156+
width: 100%;
157+
height: 100%;
158+
}
159+
160+
.yt-lite > .lty-playbtn {
161+
position: absolute;
162+
top: 50%;
163+
left: 50%;
164+
z-index: 1;
165+
display: block;
166+
width: 68px;
167+
height: 48px;
168+
background-color: transparent;
169+
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 48"><path d="M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z" fill="red"/><path d="M45 24 27 14v20" fill="white"/></svg>');
170+
border: none;
171+
transform: translate3d(-50%, -50%, 0);
172+
cursor: pointer;
173+
filter: grayscale(100%);
174+
transition: filter 0.2s cubic-bezier(0, 0, 0.2, 1);
175+
}
176+
177+
.yt-lite:hover > .lty-playbtn,
178+
.yt-lite:focus-within > .lty-playbtn {
179+
filter: none;
180+
}
181+
182+
.yt-lite.lyt-activated {
183+
cursor: unset;
184+
}
185+
186+
.yt-lite.lyt-activated::before,
187+
.yt-lite.lyt-activated > .lty-playbtn {
188+
opacity: 0;
189+
pointer-events: none;
190+
}
191+
}
192+
</style>

src/components/views/panel/PanelMovieStatistics.vue

+16-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import { computed, onMounted, type PropType, toRefs, watch } from 'vue';
1010
import type { RatingItem } from '~/models/rating.model';
1111
1212
import PanelStatistics from '~/components/views/panel/PanelStatistics.vue';
13+
import PanelTrailers from '~/components/views/panel/PanelTrailers.vue';
1314
import { DataSource, getUrlFromSource } from '~/models/source.model';
1415
import { ResolveExternalLinks } from '~/settings/external.links';
1516
import { useRatingsStore } from '~/stores/data/ratings.store';
1617
import { useSimklStore } from '~/stores/data/simkl.store';
1718
import { useExtensionSettingsStoreRefs } from '~/stores/settings/extension.store';
1819
import { useI18n } from '~/utils/i18n.utils';
20+
import { isTrailer } from '~/utils/string.utils';
1921
2022
const i18n = useI18n('panel', 'statistics');
2123
@@ -59,11 +61,22 @@ const simklMovie = computed(() => {
5961
if (!movie?.value?.ids?.imdb) return;
6062
return getMovie(movie.value.ids.imdb).value;
6163
});
64+
6265
const simklMovieLoading = computed(() => {
6366
if (!movie?.value?.ids?.imdb) return;
6467
return getMovieLoading(movie.value.ids.imdb).value;
6568
});
6669
70+
const trailers = computed(() => {
71+
if (!simklMovie.value?.trailers?.length) return;
72+
return simklMovie.value.trailers
73+
.filter((t, index) => !!t.youtube && (index < 2 || isTrailer(t.name)))
74+
.map(trailer => ({
75+
id: trailer.youtube,
76+
title: trailer.name ?? simklMovie.value?.title,
77+
}));
78+
});
79+
6780
const ratings = computed<RatingItem[]>(() => {
6881
const _ratings: RatingItem[] = [];
6982
_ratings.push({
@@ -121,6 +134,8 @@ onMounted(() => {
121134
:loading-score="scoreLoading"
122135
@on-score-edit="onScoreEdit"
123136
>
124-
<slot />
137+
<PanelTrailers :trailers="trailers">
138+
<slot />
139+
</PanelTrailers>
125140
</PanelStatistics>
126141
</template>

src/components/views/panel/PanelPoster.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ const { openTab } = useLinksStore();
123123
}
124124
125125
position: relative;
126-
margin: 1.75rem 0;
126+
width: var(--poster-width);
127+
height: var(--poster-height);
128+
margin: 1.25rem auto 1.5rem;
127129
border: 1px solid var(--border-white);
128130
border-radius: var(--poster-radius, 8px);
129131
box-shadow: var(--image-box-shadow);

0 commit comments

Comments
 (0)