<template> <div v-if="release" ref="content" class="content" @scroll="events.emit('scroll', $event)" > <Scroll v-slot="slotProps" class="scroll-light banner" :style="{ 'background-image': bannerBackground }" :expandable="false" > <Banner :release="release" class="media" @load="slotProps.loaded" /> </Scroll> <Details :release="release" /> <button v-if="release.photos?.length > 0 || release.scenesPhotos?.length > 0" class="album-toggle" @click="$router.push({ hash: '#album' })" ><Icon icon="grid3" />View album</button> <Album v-if="showAlbum" :items="[release.poster, ...(release.photos || []), ...(release.scenesPhotos || [])]" :title="release.title" :path="config.media.mediaPath" @close="$router.replace({ hash: undefined })" /> <div class="info column"> <div class="row row-title"> <h2 v-if="release.title" class="title" > {{ release.title }} <template v-if="release.movies?.[0]?.title && /^scene \d+$/i.test(release.title)"><span class="title-composed"> from </span>{{ release.movies[0].title }}</template> </h2> <h2 v-else-if="release.actors.length > 0" class="title title-composed" > {{ release.actors.map(actor => actor.name).join(', ') }} for {{ release.entity.name }} <Icon v-tooltip="`This scene has no known official title`" icon="question2" /> </h2> <StashButton :stashed-by="stashedBy" @stash="(stash) => stashScene(stash)" @unstash="(stash) => unstashScene(stash)" /> </div> <div class="row associations"> <ul class="actors nolist"> <li v-for="actor in release.actors" :key="actor.id" > <Actor :actor="actor" /> </li> </ul> </div> <Tags v-if="release.tags.length > 0" :tags="release.tags" /> <div v-if="release.movies?.length > 0 || release.series?.length > 0" class="row" > <span class="row-label">Part of</span> <div class="movies"> <router-link v-for="movie in [...release.movies, ...release.series]" :key="`movie-${movie.id}`" :to="{ name: movie.type || 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }" class="movie" > <span class="movie-title">{{ movie.title }}</span> <img v-if="movie.covers.length > 0 || movie.poster" :src="getPath(movie.covers[0] || movie.poster, 'thumbnail')" class="movie-cover" > </router-link> </div> </div> <Releases v-if="release.scenes && release.scenes.length > 0" :releases="release.scenes" /> <div v-if="release.directors && release.directors.length > 0" class="row" > <span class="row-label">Director</span> <router-link v-for="director in release.directors" :key="`director-${director.id}`" class="link director" :to="`/director/${director.id}/${director.slug}`" >{{ director.name }}</router-link> </div> <div v-if="release.description" class="row" > <span class="row-label">Description</span> <p class="description">{{ release.description }}</p> </div> <div v-if="release.chapters?.length > 0" class="row nolist" > <span class="row-label">Chapters</span> <Chapters :chapters="release.chapters" /> </div> <div class="row row-tidbits"> <div v-if="release.duration" class="row-tidbit" > <span class="row-label">Duration</span> <div class="duration">{{ formatDuration(release.duration) }}</div> </div> <div v-if="release.shootId" class="row-tidbit" > <span class="row-label">Shoot #</span> {{ release.shootId }} </div> <div v-if="release.studio" class="row-tidbit" > <span class="row-label">Studio</span> <router-link :to="`/studio/${release.studio.slug}`" class="link studio" >{{ release.studio.name }}</router-link> </div> <div v-if="release.productionDate" class="row-tidbit" > <span class="row-label">Shoot date</span> {{ formatDate(release.productionDate, 'MMMM D, YYYY') }} </div> <div v-if="release.productionLocation" class="row-tidbit" > <span class="row-label">Location</span> <span class="location"> <span v-if="release.productionLocation.city" class="location-segment" >{{ release.productionLocation.city }}, </span> <span v-if="release.productionLocation.state" class="location-segment" >{{ release.productionLocation.state }}, </span> <span v-if="release.productionLocation.country" class="location-segment" >{{ release.productionLocation.country.alias || release.productionLocation.country.name }} <img class="flag" :src="`/img/flags/${release.productionLocation.country.alpha2.toLowerCase()}.svg`" > </span> </span> </div> </div> <div v-if="release.comment" class="row" > <span class="row-label">Comment</span> <span>{{ release.comment }}</span> </div> <div class="row"> <span class="row-label">Added</span> <router-link :to="`/added/${formatDate(release.createdAt, 'YYYY/MM/DD')}`" :title="`Added on ${formatDate(release.createdAt, 'MMMM D, YYYY HH:mm')}`" class="link added" >{{ release.createdBatchId }}: {{ formatDate(release.createdAt, 'MMMM D, YYYY HH:mm') }}</router-link> </div> </div> </div> </template> <script> import Details from './details.vue'; import Banner from './banner.vue'; import StashButton from '../stashes/button.vue'; import Album from '../album/album.vue'; import Tags from './tags.vue'; import Chapters from './chapters.vue'; import Actor from '../actors/tile.vue'; import Releases from './releases.vue'; import Scroll from '../scroll/scroll.vue'; async function fetchRelease(scroll = true) { if (this.$route.name === 'scene') { this.release = await this.$store.dispatch('fetchReleaseById', this.$route.params.releaseId); } if (this.$route.name === 'movie') { this.release = await this.$store.dispatch('fetchMovieById', this.$route.params.releaseId); } if (this.$route.name === 'serie') { this.release = await this.$store.dispatch('fetchSerieById', this.$route.params.releaseId); } if (scroll && this.$refs.content) { this.$refs.content.scrollTop = 0; } this.stashedBy = this.release.stashes; } async function stashScene(stashId) { this.stashedBy = await this.$store.dispatch(this.$route.name === 'movie' ? 'stashMovie' : 'stashScene', { sceneId: this.release.id, movieId: this.release.id, stashId, }); } async function unstashScene(stashId) { this.stashedBy = await this.$store.dispatch(this.$route.name === 'movie' ? 'unstashMovie' : 'unstashScene', { sceneId: this.release.id, movieId: this.release.id, stashId, }); } function me() { return this.$store.state.auth.user; } function bannerBackground() { return (this.release.poster && this.getBgPath(this.release.poster, 'thumbnail')) || (this.release.covers.length > 0 && this.getBgPath(this.release.covers[0], 'thumbnail')); } function pageTitle() { return this.release && (this.release.title || (this.release.actors.length > 0 ? `${this.release.actors.map((actor) => actor.name).join(', ')} for ${this.release.entity.name}` : null)); } function showAlbum() { return (this.release.photos?.length > 0 || this.release.scenesPhotos?.length > 0) && this.$route.hash === '#album'; } export default { components: { Actor, Album, Banner, Chapters, Details, Releases, Scroll, StashButton, Tags, }, data() { return { release: null, stashedBy: [], }; }, computed: { pageTitle, bannerBackground, me, showAlbum, }, watch: { $route: fetchRelease, }, mounted: fetchRelease, methods: { fetchRelease, stashScene, unstashScene, }, }; </script> <style lang="scss" scoped> @import 'breakpoints'; .expand-bottom { border-bottom: solid 1px var(--shadow-hint); } .banner { background-position: center; background-size: cover; :deep(.scrollable) { backdrop-filter: blur(1rem); } } .info { padding: 1rem 0; border-left: solid 1px var(--shadow-hint); border-right: solid 1px var(--shadow-hint); flex-grow: 1; } .row { padding: 0 1rem; margin: 0 0 1rem 0; &.associations { align-items: start; } } .row-label { display: block; margin: 0 0 .5rem 0; color: var(--shadow); font-weight: bold; .icon { margin: 0 .5rem 0 0; fill: var(--shadow); } } .row-tidbit { display: inline-block; margin: 0 2rem 0 0; } .row-title { display: flex; justify-content: space-between; } .title { display: inline-flex; margin: 0; font-size: 1.5rem; line-height: 1.25; .icon { fill: var(--shadow); padding: .25rem; &:hover { fill: var(--primary); cursor: pointer; } } } .title-composed { color: var(--shadow); } .album-toggle { height: fit-content; display: inline-flex; align-items: center; justify-content: center; padding: .5rem 1rem; border: none; border-bottom: solid 1px var(--shadow-hint); color: var(--shadow); background: none; font-size: 1rem; font-weight: bold; .icon { fill: var(--shadow); margin: -.1rem .5rem 0 0; } &:hover { background: var(--shadow-hint); cursor: pointer; } } .description { line-height: 1.5; margin: -.25rem 0 0 0; } .actors { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); grid-gap: .5rem; flex-grow: 1; flex-wrap: wrap; } .movies { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); grid-gap: .5rem; flex-grow: 1; flex-wrap: wrap; } .movie { display: flex; flex-direction: column; background: var(--background); box-shadow: 0 0 3px var(--shadow-weak); color: var(--text); text-decoration: none; &:hover .movie-title { color: var(--primary); } } .movie-cover { width: 100%; } .movie-title { padding: .5rem; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .releases { margin: 0 0 .5rem 0; } .flag { height: 1rem; margin: 0 0 -.15rem .1rem; } .link { display: inline-flex; color: var(--link); text-decoration: none; &.director:not(:last-child)::after { content: ', '; } &:hover { color: var(--primary); .icon { fill: var(--primary); } } } .showable { display: none; } @media(max-width: $breakpoint-kilo) { .releases { padding: .5rem; } } @media(max-width: $breakpoint) { .hideable { display: none; } .row .showable { display: block; } .tidbit .showable { display: inline-block; } .title { font-size: 1.25rem; } .actors { grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); } } </style>