<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" ><router-link :to="`/tag/${chapter.tags[0].slug}`" class="link" >{{ chapter.tags[0]?.name || ' ' }}</router-link></li> </ul> </div> <ul v-else class="chapters nolist" > <li v-for="chapter in chapters" :key="`chapter-${chapter.id}`" class="chapter" > <a v-if="chapter.poster" :href="getPath(chapter.poster)" target="_blank" rel="noopener noreferrer" > <img :src="getPath(chapter.poster, 'thumbnail')" class="chapter-poster" > </a> <span class="chapter-details"> <span v-if="chapter.time" 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> <Tags :tags="chapter.tags" class="chapter-row chapter-tags" /> </div> </li> </ul> </template> <script> import Tags from './tags.vue'; function timeline() { if (this.chapters.every(chapter => chapter.time)) { return this.chapters.filter(chapter => chapter.tags?.length > 0); } return null; } export default { components: { Tags, }, props: { chapters: { type: Array, default: () => [], }, }, data() { const lastChapter = this.chapters[this.chapters.length - 1]; return { duration: lastChapter.time + lastChapter.duration, }; }, computed: { timeline, }, }; </script> <style lang="scss"> .chapter-tags.tags-container { margin: 0 0 .5rem 0; .tags { padding: 2px 0 0 2px; } } </style> <style lang="scss" scoped> @import 'breakpoints'; .chapters { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); grid-gap: 1rem; } .chapter { display: flex; flex-direction: column; background: var(--background); box-shadow: 0 0 3px var(--shadow-weak); margin: 0 0 .5rem 0; font-size: 0; } .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; margin: 0 0 .75rem 0; color: var(--text-light); background: var(--profile); font-size: .9rem; font-weight: bold; .icon { fill: var(--text-light); margin: -.1rem .5rem 0 0; } } .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: 1rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .chapter-description { line-height: 1.5; } .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(max-width: $breakpoint-micro) { .chapters { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); } } </style>