236 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Vue
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Vue
		
	
	
		
			Executable File
		
	
	
| <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>
 |