Forked from
onny / nextcloud-app-podcast
128 commits behind the upstream repository.
Table.vue 8.03 KiB
<!--
- @copyright Copyright (c) 2020 Jonas Heinrich
-
- @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>
<table class="episodeTable">
<thead>
<tr>
<th class="iconColumn" />
<th class="nameColumn">
{{ t('podcast', 'Name') }}
</th>
<th class="actionColumn" />
<th class="durationColumn">
{{ t('podcast', 'Duration') }}
</th>
<th class="dateColumn">
{{ t('podcast', 'Date') }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(episode, idx) in episodes"
:key="idx"
:class="{ selected: idx === activeItem}">
<td class="iconColumn">
<div v-lazy:background-image="episode.imgURL"
class="episodeImage" />
</td>
<td
class="nameColumn"
@click="changeRoute(`/browse/show/${episode.podcast_id}/${episode.id}`)">
<div class="eq-animation">
<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>
</div>
<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
icon="icon-play"
:close-after-click="true"
@click="doPlay(idx, episode)">
{{ t('podcast', 'Play') }}
</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>
<td
class="durationColumn"
@click="changeRoute(`/browse/show/${episode.podcast_id}/${episode.id}`)">
{{ episode.duration_string }}
</td>
<td
class="dateColumn"
@click="changeRoute(`/browse/show/${episode.podcast_id}/${episode.id}`)">
<span :title="readableDate(episode.inserted)">
{{ readableTimeAgo(episode.inserted) }}
</span>
</td>
</tr>
</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')
export default {
name: 'Table',
components: {
Actions,
ActionButton,
vueShowMoreText,
},
props: {
episodes: {
type: Array,
default() { return [] },
},
},
data: () => ({
activeItem: null,
}),
methods: {
escapedEpisodeDescription(episodeDescription) {
episodeDescription = episodeDescription.replace(/\n/g, '')
return episodeDescription
},
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')
},
doPlay(idx, episode) {
this.activeItem = idx
this.$emit('doPlay', episode)
},
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;
}
table.episodeTable {
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;
width: 115px;
}
th.nameColumn {
width: 100%;
}
th.actionColumn {
width: 72px;
}
th.durationColumn {
width: 90px;
}
th.dateColumn {
width: 130px;
}
}
tbody {
tr {
height: 91px;
background: var(--color-background-light);
transition: opacity 500ms ease 0s;
.selected {
background: var(--color-primary-light);
}
* {
cursor: pointer;
}
}
td {
padding: 0 15px;
font-style: normal;
border-bottom: 1px solid var(--color-border);
}
td.iconColumn {
padding-right: 0px;
padding-left: 35px;
.episodeImage {
width: 74px;
height: 74px;
background: #ccc;
background-size: cover;
background-position: center;
transition: opacity .4s ease;
}
}
td.nameColumn {
overflow: hidden;
text-overflow: ellipsis;
padding-right: 0px;
b {
color: var(--color-main-text);
user-select: none;
cursor: pointer;
font-size: 1.05em;
}
}
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;
}
}
}
}
.eq-animation {
align-items: flex-end;
display: none;
width: 20px;
height: 12px;
overflow: hidden;
opacity: 0.8;
position: relative;
float: left;
top: 5px;
margin-right: 5px;
}
.eq-animation .-amp-video-eq-col {
flex: 1;
position: relative;
height: 100%;
margin-right: 1px;
}
.eq-animation .-amp-video-eq-col div {
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);
}
}
table.episodeTable tr.selected .eq-animation {
display: flex;
}
[lazy=loading] {
opacity: 0;
}
[lazy=loaded] {
opacity: 1;
}
</style>