Skip to content
Snippets Groups Projects
Table.vue 8.38 KiB
Newer Older
onny's avatar
onny committed
<!--
  - @copyright Copyright (c) 2021 Jonas Heinrich
onny's avatar
onny committed
  -
  - @author Jonas Heinrich <onny@project-insanity.org>
  -
  - @license GNU AGPL version 3 or any later version
  -
  - This program is free software: you can redistribute it and/or modify
  - it under the terms of the GNU Affero General Public License as
  - published by the Free Software Foundation, either version 3 of the
  - License, or (at your option) any later version.
  -
  - This program is distributed in the hope that it will be useful,
  - but WITHOUT ANY WARRANTY; without even the implied warranty of
  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  - GNU Affero General Public License for more details.
  -
  - You should have received a copy of the GNU Affero General Public License
  - along with this program. If not, see <http://www.gnu.org/licenses/>.
  -
  -->

<template>
onny's avatar
onny committed
		<thead>
			<tr>
				<th class="iconColumn" />
				<th class="nameColumn">
onny's avatar
onny committed
					{{ t('podcast', 'Name') }}
onny's avatar
onny committed
				</th>
				<th class="actionColumn" />
onny's avatar
onny committed
					{{ t('podcast', 'Duration') }}
				</th>
onny's avatar
onny committed
					{{ t('podcast', 'Date') }}
				</th>
onny's avatar
onny committed
			</tr>
		</thead>
		<tbody>
				v-for="(episode, idx) in episodes"
				:key="idx"
onny's avatar
onny committed
				:class="{ selected: isPlaying(episode.id)}">
				<td class="iconColumn">
onny's avatar
onny committed
					<div v-lazy:background-image="episode.imgURL"
						class="episodeImage" />
				</td>
					@click="changeRoute(`/browse/show/${episode.podcast_id}/${episode.id}`)">
onny's avatar
onny committed
					<div class="eq-animation">
onny's avatar
onny committed
						<div class="-amp-video-eq-col">
							<div class="-amp-video-eq-1-1" />
							<div class="-amp-video-eq-1-2" />
						</div>
						<div class="-amp-video-eq-col">
							<div class="-amp-video-eq-2-1" />
							<div class="-amp-video-eq-2-2" />
						</div>
						<div class="-amp-video-eq-col">
							<div class="-amp-video-eq-3-1" />
							<div class="-amp-video-eq-3-2" />
						</div>
						<div class="-amp-video-eq-col">
							<div class="-amp-video-eq-4-1" />
							<div class="-amp-video-eq-4-2" />
						</div>
onny's avatar
onny committed
					</div>
onny's avatar
onny committed
					<b>{{ episode.title }}</b>
					<vue-show-more-text
						:text="escapedEpisodeDescription(episode.description)"
						:lines="2"
						:has-more="false"
						additional-container-css="padding: 0px;" />
				</td>
				<td class="actionColumn">
					<Actions>
						<ActionButton
onny's avatar
onny committed
							:icon="isPlaying(episode.id) ? 'icon-pause' : 'icon-play'"
							:close-after-click="true"
onny's avatar
onny committed
							@click="doPlay(episode)">
							{{ playButtonText(episode.id) }}
						</ActionButton>
						<ActionButton
							icon="icon-info"
							:close-after-click="true"
							@click="changeRoute(`/browse/show/${episode.podcast_id}/${episode.id}`)">
							{{ t('podcast', 'Show') }}
						</ActionButton>
						<ActionButton
							icon="icon-download"
							:close-after-click="true"
							@click="downloadFile(episode.enclosure)">
							{{ t('podcast', 'Download') }}
						</ActionButton>
						<ActionButton
							icon="icon-shared"
							:close-after-click="true">
							{{ t('podcast', 'Share') }}
						</ActionButton>
					</Actions>
				</td>
					@click="changeRoute(`/browse/show/${episode.podcast_id}/${episode.id}`)">
					{{ episode.duration_string }}
				</td>
					@click="changeRoute(`/browse/show/${episode.podcast_id}/${episode.id}`)">
onny's avatar
onny committed
					<span :title="readableDate(episode.inserted)">
						{{ readableTimeAgo(episode.inserted) }}
					</span>
				</td>
			</tr>
onny's avatar
onny committed
		</tbody>
	</table>
</template>

<script>
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import TimeAgo from 'javascript-time-ago'
import en from 'javascript-time-ago/locale/en'
import vueShowMoreText from 'vue-show-more-text'

TimeAgo.addDefaultLocale(en)
const timeAgo = new TimeAgo('en-US')
onny's avatar
onny committed

export default {
	name: 'Table',
	components: {
		Actions,
		ActionButton,
onny's avatar
onny committed
	},
	props: {
		episodes: {
onny's avatar
onny committed
			type: Array,
			default() { return [] },
		},
	},
	methods: {
		playButtonText(episodeId) {
			if (this.isPlaying(episodeId)) {
				return t('podcast', 'Pause')
			} else if (this.isPaused(episodeId)) {
				return t('podcast', 'Resume')
			} else {
				return t('podcast', 'Play')
			}
		},
onny's avatar
onny committed
		isPlaying(episodeId) {
			return this.$store.getters.isPlaying(episodeId)
		},
		isPaused(episodeId) {
			return this.$store.getters.isPaused(episodeId)
		},
		escapedEpisodeDescription(episodeDescription) {
			episodeDescription = episodeDescription.replace(/\n/g, '')
			return episodeDescription
		},
onny's avatar
onny committed
		readableDate(datetime) {
			const date = new Date(datetime)
			return date.toDateString()
		},
		readableTimeAgo(datetime) {
			return timeAgo.format(Date.parse(datetime), 'twitter-minute-now')
		downloadFile(episodeURL) {
			window.open(episodeURL, 'download')
onny's avatar
onny committed
		},
onny's avatar
onny committed
		doPlay(episode) {
			this.$emit('doPlay', episode)
onny's avatar
onny committed
		},
		changeRoute(path) {
			this.$router.push({ path })
		},
	},
}
</script>

<style lang="scss">

/* Workaround wrong positioning
   actions popover menu
	  https://github.com/nextcloud/nextcloud-vue/issues/1384 */
body {
	min-height: 100%;
	height: auto;
}

onny's avatar
onny committed
	width: 100%;
	min-width: 250px;
	table-layout:fixed;
	position: relative;

	thead {
		background-color: var(--color-main-background-translucent);
		z-index: 60;
		position: sticky;
		top: 50px;

		th {
			border-bottom: 1px solid var(--color-border);
			padding: 15px;
			height: 50px;
			color: var(--color-text-maxcontrast);
		}

		th.iconColumn {
			padding: 0px;
onny's avatar
onny committed
			width: 115px;
onny's avatar
onny committed
		}

		th.nameColumn {
			width: 100%;
		}

		th.actionColumn {
			width: 72px;
		}

		th.durationColumn {
			width: 90px;
		}

		th.dateColumn {
			width: 130px;
		}

onny's avatar
onny committed
	}

	tbody {

		tr {
onny's avatar
onny committed
			height: 91px;
onny's avatar
onny committed
			background: var(--color-background-light);
onny's avatar
onny committed
			transition: opacity 500ms ease 0s;

onny's avatar
onny committed
			.selected {
				background: var(--color-primary-light);
onny's avatar
onny committed
			}

onny's avatar
onny committed
			* {
				cursor: pointer;
			}
onny's avatar
onny committed

		}

onny's avatar
onny committed
		td {
			padding: 0 15px;
			font-style: normal;
			border-bottom: 1px solid var(--color-border);
onny's avatar
onny committed
		}

onny's avatar
onny committed
		td.iconColumn {
onny's avatar
onny committed
			padding-right: 0px;
			padding-left: 35px;

onny's avatar
onny committed
			.episodeImage {
				width: 74px;
				height: 74px;
onny's avatar
onny committed
				background: #ccc;
				background-size: cover;
				background-position: center;
onny's avatar
onny committed
				transition: opacity .4s ease;
onny's avatar
onny committed
		}

		td.nameColumn {
			overflow: hidden;
			text-overflow: ellipsis;
			padding-right: 0px;
onny's avatar
onny committed
			b {
				color: var(--color-main-text);
				user-select: none;
				cursor: pointer;
				font-size: 1.05em;
			}
onny's avatar
onny committed
		}

onny's avatar
onny committed
		tr:hover td {
			background: var(--color-background-hover);
@media only screen and (max-width: 500px) {
	table {
		thead {
			th.iconColumn {
				width: 85px;
			}
			th.nameColumn {
				padding-left: 0px;
			}
			th.actionColumn {
				padding-left: 5px;
				padding-right: 5px;
				width: 50px;
			}
			th.durationColumn, th.dateColumn {
				display: none;
			}
		}
		tbody {
			td.iconColumn {
				padding-left: 10px;
			}
			td.nameColumn {
				padding-left: 0px;
			}
			td.actionColumn {
				padding-left: 5px;
				padding-right: 5px;
				width: 50px;
			}
			td.durationColumn, td.dateColumn {
				display: none;
			}
		}
	}
}
onny's avatar
onny committed
.eq-animation {
onny's avatar
onny committed
	align-items: flex-end;
	display: none;
	width: 20px;
	height: 12px;
	overflow: hidden;
	opacity: 0.8;
	position: relative;
	float: left;
	top: 5px;
	margin-right: 5px;
}

onny's avatar
onny committed
.eq-animation .-amp-video-eq-col {
onny's avatar
onny committed
	flex: 1;
	position: relative;
	height: 100%;
	margin-right: 1px;
}

onny's avatar
onny committed
.eq-animation .-amp-video-eq-col div {
onny's avatar
onny committed
	animation-name: amp-video-eq-animation;
	animation-timing-function: linear;
	animation-iteration-count: infinite;
	animation-direction: alternate;
	background-color: rgb(0, 130, 201);
	position: absolute;
	width: 100%;
	height: 100%;
	transform: translateY(100%);
	will-change: transform;
}

.-amp-video-eq-1-1 {
	animation-duration: 0.3s;
}

.-amp-video-eq-1-2 {
	animation-duration: 0.45s;
}

.-amp-video-eq-2-1 {
	animation-duration: 0.5s;
}

.-amp-video-eq-2-2 {
	animation-duration: 0.4s;
}

.-amp-video-eq-3-1 {
	animation-duration: 0.3s;
}

.-amp-video-eq-3-2 {
	animation-duration: 0.35s;
}

.-amp-video-eq-4-1 {
	animation-duration: 0.4s;
}

.-amp-video-eq-4-2 {
	animation-duration: 0.25s;
}

@keyframes amp-video-eq-animation {
	0% {
		transform: translateY(100%);
	}
	100% {
		transform: translateY(0);
	}
}

onny's avatar
onny committed
table.episodeTable tr.selected .eq-animation {
onny's avatar
onny committed
	display: flex;
}
onny's avatar
onny committed
[lazy=loading] {
	opacity: 0;
}

[lazy=loaded] {
	opacity: 1;
onny's avatar
onny committed
</style>