Showing chapter information.
This commit is contained in:
parent
63b1198fca
commit
3aedfbf2d1
|
@ -49,12 +49,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="release.photos.length > 0 || release.caps.length > 0 || coversInAlbum"
|
v-if="release.photos.length > 0 || release.caps.length > 0 || release.chapters.some((chapter) => chapter.poster) || coversInAlbum"
|
||||||
class="album"
|
class="album"
|
||||||
:class="{ single: (release.photos.length + release.caps.length) === 1 }"
|
:class="{ single: (release.photos.length + release.caps.length + release.chapters.filter((chapter) => chapter.poster).length) === 1 }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="photo in [...(coversInAlbum ? release.covers : []), ...release.photos, ...release.caps]"
|
v-for="photo in [...(coversInAlbum ? release.covers : []), ...release.photos, ...release.caps, ...release.chapters.map((chapter) => chapter.poster).filter(Boolean)]"
|
||||||
:key="`photo-${photo.id}`"
|
:key="`photo-${photo.id}`"
|
||||||
class="photo-container"
|
class="photo-container"
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="timeline"
|
||||||
|
class="timeline"
|
||||||
|
>
|
||||||
|
<ul class="timeline-items nolist">
|
||||||
|
<li
|
||||||
|
v-for="chapter in timeline"
|
||||||
|
:key="`chapter-${chapter.id}`"
|
||||||
|
:style="{ left: `${(chapter.time / duration) * 100}%` }"
|
||||||
|
:title="formatDuration(chapter.time)"
|
||||||
|
class="timeline-item"
|
||||||
|
><a
|
||||||
|
:href="`/tag/${chapter.tags[0].slug}`"
|
||||||
|
class="link"
|
||||||
|
>{{ chapter.tags[0]?.name || ' ' }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-else
|
||||||
|
class="chapters nolist"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="chapter in chapters"
|
||||||
|
:key="`chapter-${chapter.id}`"
|
||||||
|
class="chapter"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="getPath(chapter.poster, 'thumbnail')"
|
||||||
|
:style="{ 'background-image': `url('${getPath(chapter.poster, 'lazy')}'` }"
|
||||||
|
loading="lazy"
|
||||||
|
class="chapter-poster"
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="chapter-details">
|
||||||
|
<span
|
||||||
|
v-if="typeof chapter.time === 'number'"
|
||||||
|
v-tooltip="'Time in video'"
|
||||||
|
class="chapter-time"
|
||||||
|
><Icon icon="film3" /> {{ formatDuration(chapter.time) }}</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="chapter.duration"
|
||||||
|
v-tooltip="'Duration'"
|
||||||
|
class="chapter-duration"
|
||||||
|
><Icon icon="stopwatch" />{{ formatDuration(chapter.duration) }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="chapter-info">
|
||||||
|
<h3
|
||||||
|
v-if="chapter.title"
|
||||||
|
class="chapter-row chapter-title"
|
||||||
|
:title="chapter.title"
|
||||||
|
>{{ chapter.title }}</h3>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="chapter.description"
|
||||||
|
class="chapter-row chapter-description"
|
||||||
|
>{{ chapter.description }}</p>
|
||||||
|
|
||||||
|
<ul class="chapter-tags chapter-row nolist">
|
||||||
|
<li
|
||||||
|
v-for="tag in chapter.tags"
|
||||||
|
:key="`chapter-tag-${tag.slug}`"
|
||||||
|
class="chapter-tag"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="`/tag/${tag.slug}`"
|
||||||
|
class="link"
|
||||||
|
>{{ tag.name }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import getPath from '#/src/get-path.js';
|
||||||
|
import { formatDuration } from '#/utils/format.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
chapters: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastChapter = props.chapters.at(-1);
|
||||||
|
const duration = lastChapter.time + lastChapter.duration;
|
||||||
|
|
||||||
|
const timeline = computed(() => {
|
||||||
|
if (props.chapters.every((chapter) => chapter.time)) {
|
||||||
|
return props.chapters.filter((chapter) => chapter.tags?.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chapters {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--background);
|
||||||
|
box-shadow: 0 0 3px var(--shadow-weak-30);
|
||||||
|
border-radius: .25rem;
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
font-size: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-poster {
|
||||||
|
width: 100%;
|
||||||
|
height: 10rem;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-details {
|
||||||
|
height: 1.75rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
border-radius: 0 0 .25rem .25rem;
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
color: var(--text-light);
|
||||||
|
background: var(--grey-dark-40);
|
||||||
|
font-size: .8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: var(--text-light);
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-duration,
|
||||||
|
.chapter-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-duration .icon {
|
||||||
|
/* narrower icon */
|
||||||
|
margin: -.1rem .3rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-info {
|
||||||
|
padding: 0 .5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-row {
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-title {
|
||||||
|
padding: 0;
|
||||||
|
font-size: .9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-description {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-tags {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-tag {
|
||||||
|
margin: 0 .5rem .25rem 0;
|
||||||
|
font-size: .75rem;
|
||||||
|
color: var(--glass-strong-10);
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-items {
|
||||||
|
position: relative;
|
||||||
|
height: 5rem;
|
||||||
|
border-bottom: solid 1px var(--shadow-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -.25rem;
|
||||||
|
padding: .1rem .5rem;
|
||||||
|
border-radius: .6rem;
|
||||||
|
color: var(--primary);
|
||||||
|
background: var(--background);
|
||||||
|
transform: rotate(-60deg);
|
||||||
|
transform-origin: 0 50%;
|
||||||
|
box-shadow: 0 0 3px var(--shadow-weak);
|
||||||
|
font-size: .8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 1rem;
|
||||||
|
height: 2px;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(-1rem + 1px);
|
||||||
|
margin: .3rem .5rem 0 0;
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(--small-30) {
|
||||||
|
.chapters {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -168,6 +168,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Chapters
|
||||||
|
v-if="scene.chapters.length > 0"
|
||||||
|
:chapters="scene.chapters"
|
||||||
|
class="section"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="scene.duration || scene.directors.length > 0 || scene.shootId || scene.qualities.length > 0"
|
v-if="scene.duration || scene.directors.length > 0 || scene.shootId || scene.qualities.length > 0"
|
||||||
class="section details"
|
class="section details"
|
||||||
|
@ -361,6 +367,7 @@ import Banner from '#/components/media/banner.vue';
|
||||||
import ActorTile from '#/components/actors/tile.vue';
|
import ActorTile from '#/components/actors/tile.vue';
|
||||||
import MovieTile from '#/components/movies/tile.vue';
|
import MovieTile from '#/components/movies/tile.vue';
|
||||||
import SerieTile from '#/components/series/tile.vue';
|
import SerieTile from '#/components/series/tile.vue';
|
||||||
|
import Chapters from '#/components/scenes/chapters.vue';
|
||||||
import Heart from '#/components/stashes/heart.vue';
|
import Heart from '#/components/stashes/heart.vue';
|
||||||
import Campaign from '#/components/campaigns/campaign.vue';
|
import Campaign from '#/components/campaigns/campaign.vue';
|
||||||
|
|
||||||
|
@ -380,6 +387,8 @@ const {
|
||||||
|
|
||||||
const { scene } = pageProps;
|
const { scene } = pageProps;
|
||||||
|
|
||||||
|
console.log(scene);
|
||||||
|
|
||||||
const showSummaryDialog = ref(false);
|
const showSummaryDialog = ref(false);
|
||||||
|
|
||||||
const qualities = {
|
const qualities = {
|
||||||
|
|
|
@ -58,6 +58,8 @@ function curateScene(rawScene, assets) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(assets.chapters);
|
||||||
|
|
||||||
const curatedScene = {
|
const curatedScene = {
|
||||||
id: rawScene.id,
|
id: rawScene.id,
|
||||||
title: rawScene.title,
|
title: rawScene.title,
|
||||||
|
@ -113,6 +115,18 @@ function curateScene(rawScene, assets) {
|
||||||
name: tag.name,
|
name: tag.name,
|
||||||
priority: tag.priority,
|
priority: tag.priority,
|
||||||
})),
|
})),
|
||||||
|
chapters: assets.chapters.map((chapter) => ({
|
||||||
|
id: chapter.id,
|
||||||
|
title: chapter.title,
|
||||||
|
time: chapter.time,
|
||||||
|
duration: chapter.duration,
|
||||||
|
poster: curateMedia(chapter.chapter_poster),
|
||||||
|
tags: chapter.chapter_tags.map((tag) => ({
|
||||||
|
id: tag.id,
|
||||||
|
name: tag.name,
|
||||||
|
slug: tag.slug,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
qualities: rawScene.qualities?.sort((qualityA, qualityB) => qualityB - qualityA) || [],
|
qualities: rawScene.qualities?.sort((qualityA, qualityB) => qualityB - qualityA) || [],
|
||||||
photoCount: rawScene.photo_count,
|
photoCount: rawScene.photo_count,
|
||||||
movies: assets.movies.map((movie) => ({
|
movies: assets.movies.map((movie) => ({
|
||||||
|
@ -151,6 +165,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||||
actors,
|
actors,
|
||||||
directors,
|
directors,
|
||||||
tags,
|
tags,
|
||||||
|
chapters,
|
||||||
movies,
|
movies,
|
||||||
series,
|
series,
|
||||||
posters,
|
posters,
|
||||||
|
@ -202,6 +217,19 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||||
.whereNotNull('tags.id')
|
.whereNotNull('tags.id')
|
||||||
.whereIn('release_id', sceneIds)
|
.whereIn('release_id', sceneIds)
|
||||||
.orderBy('priority', 'desc'),
|
.orderBy('priority', 'desc'),
|
||||||
|
chapters: knex('chapters')
|
||||||
|
.select(
|
||||||
|
'chapters.*',
|
||||||
|
knex.raw('coalesce(json_agg(tags) filter (where tags.id is not null), \'[]\') as chapter_tags'),
|
||||||
|
knex.raw('row_to_json(posters) as chapter_poster'),
|
||||||
|
)
|
||||||
|
.leftJoin('chapters_tags', 'chapters_tags.chapter_id', 'chapters.id')
|
||||||
|
.leftJoin('tags', 'tags.id', 'chapters_tags.tag_id')
|
||||||
|
.leftJoin('chapters_posters', 'chapters_posters.chapter_id', 'chapters.id')
|
||||||
|
.leftJoin('media as posters', 'posters.id', 'chapters_posters.media_id')
|
||||||
|
.whereIn('chapters.release_id', sceneIds)
|
||||||
|
.groupBy('chapters.id', 'posters.id')
|
||||||
|
.orderBy('time', 'asc'),
|
||||||
movies: context.includePartOf ? knex('movies_scenes')
|
movies: context.includePartOf ? knex('movies_scenes')
|
||||||
.select('movies_scenes.scene_id', 'movies.*', knex.raw('json_agg(media) as movie_covers'))
|
.select('movies_scenes.scene_id', 'movies.*', knex.raw('json_agg(media) as movie_covers'))
|
||||||
.leftJoin('movies', 'movies.id', 'movies_scenes.movie_id')
|
.leftJoin('movies', 'movies.id', 'movies_scenes.movie_id')
|
||||||
|
@ -289,6 +317,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||||
const sceneActors = actors.filter((actor) => actor.release_id === sceneId);
|
const sceneActors = actors.filter((actor) => actor.release_id === sceneId);
|
||||||
const sceneDirectors = directors.filter((director) => director.release_id === sceneId);
|
const sceneDirectors = directors.filter((director) => director.release_id === sceneId);
|
||||||
const sceneTags = tags.filter((tag) => tag.release_id === sceneId);
|
const sceneTags = tags.filter((tag) => tag.release_id === sceneId);
|
||||||
|
const sceneChapters = chapters.filter((chapter) => chapter.release_id === sceneId);
|
||||||
const sceneMovies = movies.filter((movie) => movie.scene_id === sceneId);
|
const sceneMovies = movies.filter((movie) => movie.scene_id === sceneId);
|
||||||
const sceneSeries = series.filter((serie) => serie.scene_id === sceneId);
|
const sceneSeries = series.filter((serie) => serie.scene_id === sceneId);
|
||||||
const scenePoster = posters.find((poster) => poster.release_id === sceneId);
|
const scenePoster = posters.find((poster) => poster.release_id === sceneId);
|
||||||
|
@ -305,6 +334,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||||
actors: sceneActors,
|
actors: sceneActors,
|
||||||
directors: sceneDirectors,
|
directors: sceneDirectors,
|
||||||
tags: sceneTags,
|
tags: sceneTags,
|
||||||
|
chapters: sceneChapters,
|
||||||
movies: sceneMovies,
|
movies: sceneMovies,
|
||||||
series: sceneSeries,
|
series: sceneSeries,
|
||||||
poster: scenePoster,
|
poster: scenePoster,
|
||||||
|
|
Loading…
Reference in New Issue