refactor track list components

This commit is contained in:
Thomas Amland 2021-03-20 13:11:06 +01:00
parent cddb6fe85e
commit 9ab8c444ef
20 changed files with 236 additions and 222 deletions

View File

@ -1,117 +0,0 @@
<template>
<table class="table table-hover table-borderless table-numbered">
<thead>
<tr>
<th>
#
</th>
<th class="text-left">
Title
</th>
<th v-if="!noArtist" class="text-left d-none d-lg-table-cell">
Artist
</th>
<th v-if="!noAlbum" class="text-left d-none d-md-table-cell">
Album
</th>
<th v-if="!noDuration" class="text-right d-none d-md-table-cell">
Duration
</th>
<th class="text-right">
Actions
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in tracks"
:key="index"
:class="{'active': item.id === playingTrackId}"
:draggable="true"
@dragstart="dragstart(item.id, $event)"
@click="play(index)"
>
<td>
<button>
<Icon class="icon" :icon="isPlaying && item.id === playingTrackId ? 'pause-fill' :'play-fill'" />
<span class="number">{{ item.track || 1 }}</span>
</button>
</td>
<td>
{{ item.title }}
<div class="d-lg-none text-muted">
<small>{{ item.artist }}</small>
</div>
</td>
<td v-if="!noArtist" class="d-none d-lg-table-cell">
<template v-if="item.artistId">
<router-link :to="{name: 'artist', params: {id: item.artistId}}" @click.native.stop>
{{ item.artist }}
</router-link>
</template>
<template v-else>
{{ item.artist }}
</template>
</td>
<td v-if="!noAlbum" class="d-none d-md-table-cell">
<template v-if="item.albumId">
<router-link :to="{name: 'album', params: {id: item.albumId}}" disabled @click.native.stop>
{{ item.album }}
</router-link>
</template>
<template v-else>
{{ item.album }}
</template>
</td>
<td v-if="!noDuration" class="text-right d-none d-md-table-cell">
{{ $formatDuration(item.duration) }}
</td>
<td class="text-right" @click.stop="">
<TrackContextMenu :track="item">
<slot name="context-menu" :index="index" :item="item" />
</TrackContextMenu>
</td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import Vue from 'vue'
import { mapActions, mapGetters } from 'vuex'
import TrackContextMenu from '@/library/TrackContextMenu.vue'
export default Vue.extend({
components: {
TrackContextMenu,
},
props: {
tracks: { type: Array, required: true },
noAlbum: { type: Boolean, default: false },
noArtist: { type: Boolean, default: false },
noDuration: { type: Boolean, default: false },
},
computed: {
...mapGetters({
playingTrackId: 'player/trackId',
isPlaying: 'player/isPlaying',
}),
},
methods: {
...mapActions({
playPause: 'player/playPause',
}),
play(index: number) {
if ((this.tracks as any)[index].id === this.playingTrackId) {
return this.$store.dispatch('player/playPause')
}
return this.$store.dispatch('player/playTrackList', {
index,
tracks: this.tracks,
})
},
dragstart(id: string, event: any) {
event.dataTransfer.setData('id', id)
},
}
})
</script>

View File

@ -54,7 +54,7 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import TrackList from '@/library/TrackList.vue' import TrackList from '@/library/track/TrackList.vue'
import { Album } from '@/shared/api' import { Album } from '@/shared/api'
export default Vue.extend({ export default Vue.extend({

View File

@ -28,7 +28,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import AlbumList from '@/library/album/AlbumList.vue' import AlbumList from '@/library/album/AlbumList.vue'
import TrackList from '@/library/TrackList.vue' import TrackList from '@/library/track/TrackList.vue'
import InfiniteList from '@/shared/components/InfiniteList.vue' import InfiniteList from '@/shared/components/InfiniteList.vue'
export default Vue.extend({ export default Vue.extend({

View File

@ -2,58 +2,44 @@
<ContentLoader v-slot :loading="podcast ==null"> <ContentLoader v-slot :loading="podcast ==null">
<h1>{{ podcast.name }}</h1> <h1>{{ podcast.name }}</h1>
<p>{{ podcast.description }}</p> <p>{{ podcast.description }}</p>
<table class="table table-hover table-borderless table-numbered"> <BaseTable>
<thead> <BaseTableHead>
<tr>
<th>
#
</th>
<th class="text-left">
Title
</th>
<th class="text-right d-none d-md-table-cell"> <th class="text-right d-none d-md-table-cell">
Duration Duration
</th> </th>
<th class="text-right"> </BaseTableHead>
Actions
</th>
</tr>
</thead>
<tbody> <tbody>
<tr v-for="(item, index) in podcast.tracks" <tr v-for="(item, index) in podcast.tracks" :key="index"
:key="index"
:class="{'active': item.id === playingTrackId, 'disabled': !item.playable}" :class="{'active': item.id === playingTrackId, 'disabled': !item.playable}"
@click="click(item)"> @click="play(item)">
<td> <CellTrackNumber :active="item.id === playingTrackId && isPlaying" :track="item" />
<button> <CellTitle :track="item" />
<Icon class="icon" :icon="isPlaying && item.id === playingTrackId ? 'pause-fill' :'play-fill'" /> <CellDuration :track="item" />
<span class="number">{{ item.track }}</span> <CellActions :track="item" />
</button>
</td>
<td>
{{ item.title }}
<div class="text-muted">
<small>{{ item.description }}</small>
</div>
</td>
<td class="text-right d-none d-md-table-cell">
<template v-if="item.duration">
{{ $formatDuration(item.duration) }}
</template>
</td>
<td class="text-right" @click.stop="">
<OverflowMenu :disabled="!item.playable" />
</td>
</tr> </tr>
</tbody> </tbody>
</table> </BaseTable>
</ContentLoader> </ContentLoader>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import CellTrackNumber from '@/library/track/CellTrackNumber.vue'
import CellActions from '@/library/track/CellActions.vue'
import CellDuration from '@/library/track/CellDuration.vue'
import CellTitle from '@/library/track/CellTitle.vue'
import BaseTable from '@/library/track/BaseTable.vue'
import BaseTableHead from '@/library/track/BaseTableHead.vue'
export default Vue.extend({ export default Vue.extend({
components: {
BaseTableHead,
BaseTable,
CellTitle,
CellDuration,
CellActions,
CellTrackNumber
},
props: { props: {
id: { type: String, required: true }, id: { type: String, required: true },
}, },
@ -72,7 +58,7 @@
this.podcast = await this.$api.getPodcast(this.id) this.podcast = await this.$api.getPodcast(this.id)
}, },
methods: { methods: {
async click(track: any) { async play(track: any) {
if (!track.playable) { if (!track.playable) {
return return
} }

View File

@ -1,56 +1,37 @@
<template> <template>
<div v-if="items"> <div v-if="items">
<h1>Radio</h1> <h1>Radio</h1>
<table class="table table-hover table-borderless table-numbered"> <BaseTable>
<thead> <BaseTableHead />
<tr>
<th>
#
</th>
<th class="text-left">
Title
</th>
<th class="text-right">
Actions
</th>
</tr>
</thead>
<tbody> <tbody>
<tr v-for="(item, index) in items" <tr v-for="(item, index) in items" :key="index"
:key="index"
:class="{'active': item.id === playingTrackId}" :class="{'active': item.id === playingTrackId}"
@click="play(index)"> @click="play(index)">
<td> <CellTrackNumber :active="item.id === playingTrackId && isPlaying" :track="item" />
<button> <CellTitle :track="item" />
<Icon class="icon" :icon="isPlaying && item.id === playingTrackId ? 'pause-fill' :'play-fill'" /> <CellActions :track="item" />
<span class="number">{{ index + 1 }}</span>
</button>
</td>
<td>
{{ item.title }}
<div>
<small class="text-muted">
{{ item.description }}
</small>
</div>
</td>
<td class="text-right" @click.stop="">
<TrackContextMenu :track="item" />
</td>
</tr> </tr>
</tbody> </tbody>
</table> </BaseTable>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import TrackContextMenu from '@/library/TrackContextMenu.vue'
import { RadioStation } from '@/shared/api' import { RadioStation } from '@/shared/api'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import CellTrackNumber from '@/library/track/CellTrackNumber.vue'
import CellActions from '@/library/track/CellActions.vue'
import CellTitle from '@/library/track/CellTitle.vue'
import BaseTable from '@/library/track/BaseTable.vue'
import BaseTableHead from '@/library/track/BaseTableHead.vue'
export default Vue.extend({ export default Vue.extend({
components: { components: {
TrackContextMenu, BaseTableHead,
BaseTable,
CellTitle,
CellActions,
CellTrackNumber,
}, },
data() { data() {
return { return {

View File

@ -18,7 +18,7 @@
import Vue from 'vue' import Vue from 'vue'
import AlbumList from '@/library/album/AlbumList.vue' import AlbumList from '@/library/album/AlbumList.vue'
import ArtistList from '@/library/artist/ArtistList.vue' import ArtistList from '@/library/artist/ArtistList.vue'
import TrackList from '@/library/TrackList.vue' import TrackList from '@/library/track/TrackList.vue'
export default Vue.extend({ export default Vue.extend({
components: { components: {

View File

@ -0,0 +1,5 @@
<template functional>
<table class="table table-hover table-borderless table-numbered">
<slot />
</table>
</template>

View File

@ -0,0 +1,14 @@
<template functional>
<thead>
<tr>
<th>#</th>
<th class="text-left">
Title
</th>
<slot />
<th class="text-right">
Actions
</th>
</tr>
</thead>
</template>

View File

@ -1,8 +1,6 @@
<template> <template>
<b-dropdown variant="link" boundary="window" no-caret toggle-class="p-0"> <td class="text-right" @click.stop="">
<template #button-content> <OverflowMenu :disabled="track.playable === false">
<Icon icon="three-dots-vertical" />
</template>
<b-dropdown-item-button @click="setNextInQueue()"> <b-dropdown-item-button @click="setNextInQueue()">
Play next Play next
</b-dropdown-item-button> </b-dropdown-item-button>
@ -16,7 +14,8 @@
Download Download
</b-dropdown-item-button> </b-dropdown-item-button>
<slot :item="track" /> <slot :item="track" />
</b-dropdown> </OverflowMenu>
</td>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'

View File

@ -0,0 +1,12 @@
<template functional>
<td class="d-none d-md-table-cell">
<template v-if="props.track.albumId">
<router-link :to="{name: 'album', params: {id: props.track.albumId}}" @click.native.stop>
{{ props.track.album }}
</router-link>
</template>
<template v-else>
{{ props.track.album }}
</template>
</td>
</template>

View File

@ -0,0 +1,12 @@
<template functional>
<td class="d-none d-lg-table-cell">
<template v-if="props.track.artistId">
<router-link :to="{name: 'artist', params: {id: props.track.artistId}}" @click.native.stop>
{{ props.track.artist }}
</router-link>
</template>
<template v-else>
{{ props.track.artist }}
</template>
</td>
</template>

View File

@ -0,0 +1,7 @@
<template functional>
<td class="text-right d-none d-md-table-cell">
<template v-if="props.track.duration">
{{ parent.$formatDuration(props.track.duration) }}
</template>
</td>
</template>

View File

@ -0,0 +1,11 @@
<template functional>
<td>
{{ props.track.title }}
<div v-if="props.track.description" class="text-muted">
<small>{{ props.track.description }}</small>
</div>
<div v-else-if="props.track.artist" class="d-lg-none text-muted">
<small>{{ props.track.artist }}</small>
</div>
</td>
</template>

View File

@ -0,0 +1,8 @@
<template functional>
<td>
<button>
<Icon class="icon" :icon="props.active ? 'pause-fill' :'play-fill'" />
<span class="number">{{ props.track.track || 1 }}</span>
</button>
</td>
</template>

View File

@ -0,0 +1,84 @@
<template>
<BaseTable>
<BaseTableHead>
<th v-if="!noArtist" class="text-left d-none d-lg-table-cell">
Artist
</th>
<th v-if="!noAlbum" class="text-left d-none d-md-table-cell">
Album
</th>
<th v-if="!noDuration" class="text-right d-none d-md-table-cell">
Duration
</th>
</BaseTableHead>
<tbody>
<tr v-for="(item, index) in tracks" :key="index"
:class="{'active': item.id === playingTrackId}"
:draggable="true" @dragstart="dragstart(item.id, $event)"
@click="play(index)">
<CellTrackNumber :active="item.id === playingTrackId && isPlaying" :track="item" />
<CellTitle :track="item" />
<CellArtist v-if="!noArtist" :track="item" />
<CellAlbum v-if="!noAlbum" :track="item" />
<CellDuration v-if="!noDuration" :track="item" />
<CellActions :track="item">
<slot name="context-menu" :index="index" :item="item" />
</CellActions>
</tr>
</tbody>
</BaseTable>
</template>
<script lang="ts">
import Vue from 'vue'
import { mapActions, mapGetters } from 'vuex'
import CellDuration from '@/library/track/CellDuration.vue'
import CellArtist from '@/library/track/CellArtist.vue'
import CellAlbum from '@/library/track/CellAlbum.vue'
import CellTrackNumber from '@/library/track/CellTrackNumber.vue'
import CellActions from '@/library/track/CellActions.vue'
import CellTitle from '@/library/track/CellTitle.vue'
import BaseTable from '@/library/track/BaseTable.vue'
import BaseTableHead from '@/library/track/BaseTableHead.vue'
export default Vue.extend({
components: {
BaseTableHead,
BaseTable,
CellTitle,
CellActions,
CellTrackNumber,
CellAlbum,
CellArtist,
CellDuration,
},
props: {
tracks: { type: Array, required: true },
noAlbum: { type: Boolean, default: false },
noArtist: { type: Boolean, default: false },
noDuration: { type: Boolean, default: false },
},
computed: {
...mapGetters({
playingTrackId: 'player/trackId',
isPlaying: 'player/isPlaying',
}),
},
methods: {
...mapActions({
playPause: 'player/playPause',
}),
play(index: number) {
if ((this.tracks as any)[index].id === this.playingTrackId) {
return this.$store.dispatch('player/playPause')
}
return this.$store.dispatch('player/playTrackList', {
index,
tracks: this.tracks,
})
},
dragstart(id: string, event: any) {
event.dataTransfer.setData('id', id)
},
}
})
</script>

View File

@ -20,7 +20,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import { mapState, mapMutations } from 'vuex' import { mapState, mapMutations } from 'vuex'
import TrackList from '@/library/TrackList.vue' import TrackList from '@/library/track/TrackList.vue'
export default Vue.extend({ export default Vue.extend({
components: { components: {

View File

@ -14,7 +14,7 @@
<p v-if="playlist.comment" class="text-muted"> <p v-if="playlist.comment" class="text-muted">
{{ playlist.comment }} {{ playlist.comment }}
</p> </p>
<TrackList :tracks="playlist.tracks" @remove="remove(index)"> <TrackList :tracks="playlist.tracks">
<template #context-menu="{index}"> <template #context-menu="{index}">
<b-dropdown-item-button @click="remove(index)"> <b-dropdown-item-button @click="remove(index)">
Remove Remove
@ -38,7 +38,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import TrackList from '@/library/TrackList.vue' import TrackList from '@/library/track/TrackList.vue'
import EditModal from '@/shared/components/EditModal.vue' import EditModal from '@/shared/components/EditModal.vue'
export default Vue.extend({ export default Vue.extend({

View File

@ -18,7 +18,7 @@
import Vue from 'vue' import Vue from 'vue'
import AlbumList from '@/library/album/AlbumList.vue' import AlbumList from '@/library/album/AlbumList.vue'
import ArtistList from '@/library/artist/ArtistList.vue' import ArtistList from '@/library/artist/ArtistList.vue'
import TrackList from '@/library/TrackList.vue' import TrackList from '@/library/track/TrackList.vue'
export default Vue.extend({ export default Vue.extend({
components: { components: {

View File

@ -290,6 +290,7 @@ export class API {
async getRadioStations(): Promise<RadioStation[]> { async getRadioStations(): Promise<RadioStation[]> {
const response = await this.get('rest/getInternetRadioStations') const response = await this.get('rest/getInternetRadioStations')
return (response?.internetRadioStations?.internetRadioStation || []) return (response?.internetRadioStations?.internetRadioStation || [])
.map((item: any, idx: number) => ({ ...item, track: idx + 1 }))
.map(this.normalizeRadioStation, this) .map(this.normalizeRadioStation, this)
} }
@ -345,6 +346,7 @@ export class API {
id: `radio-${item.id}`, id: `radio-${item.id}`,
title: item.name, title: item.name,
description: item.homePageUrl, description: item.homePageUrl,
track: item.track,
url: item.streamUrl, url: item.streamUrl,
duration: 0, duration: 0,
starred: false, starred: false,

View File

@ -1,5 +1,11 @@
<template> <template>
<b-dropdown variant="link" boundary="window" no-caret toggle-class="p-0"> <b-dropdown
variant="link"
boundary="window"
no-caret
toggle-class="p-0"
:disabled="disabled"
>
<template #button-content> <template #button-content>
<Icon icon="three-dots-vertical" /> <Icon icon="three-dots-vertical" />
</template> </template>
@ -9,5 +15,9 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
export default Vue.extend({}) export default Vue.extend({
props: {
disabled: { type: Boolean, default: false }
}
})
</script> </script>