Add:Multi-select for podcast episodes and batch delete, Update:Episode row ui for mobile screens

This commit is contained in:
advplyr 2022-08-14 12:34:21 -05:00
parent 9f200ece99
commit 2c0c53bbf1
3 changed files with 100 additions and 37 deletions

View File

@ -7,16 +7,17 @@
</template>
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
<div class="mb-4">
<p class="text-lg text-gray-200 mb-4">
<p v-if="episode" class="text-lg text-gray-200 mb-4">
Are you sure you want to remove episode<br /><span class="text-base">{{ episodeTitle }}</span
>?
</p>
<p v-else class="text-lg text-gray-200 mb-4">Are you sure you want to remove {{ episodes.length }} episodes?</p>
<p class="text-xs font-semibold text-warning text-opacity-90">Note: This does not delete the audio file unless toggling "Hard delete file"</p>
</div>
<div class="flex justify-between items-center pt-4">
<ui-checkbox v-model="hardDeleteFile" label="Hard delete file" check-color="error" checkbox-bg="bg" small label-class="text-base text-gray-200 pl-3" />
<ui-btn @click="submit">{{ hardDeleteFile ? 'Delete episode' : 'Remove episode' }}</ui-btn>
<ui-btn @click="submit">{{ btnText }}</ui-btn>
</div>
</div>
</modals-modal>
@ -30,9 +31,9 @@ export default {
type: Object,
default: () => {}
},
episode: {
type: Object,
default: () => {}
episodes: {
type: Array,
default: () => []
}
},
data() {
@ -55,34 +56,49 @@ export default {
this.$emit('input', val)
}
},
episode() {
if (this.episodes.length === 1) return this.episodes[0]
return null
},
title() {
if (this.episodes.length > 1) return `Remove ${this.episodes.length} episodes`
return 'Remove Episode'
},
episodeId() {
return this.episode ? this.episode.id : null
btnText() {
if (this.episodes.length > 1) return this.hardDeleteFile ? `Delete ${this.episodes.length} episodes` : `Remove ${this.episodes.length} episodes`
return this.hardDeleteFile ? 'Delete episode' : 'Remove episode'
},
episodeTitle() {
return this.episode ? this.episode.title : null
}
},
methods: {
submit() {
async submit() {
this.processing = true
var queryString = this.hardDeleteFile ? '?hard=1' : ''
this.$axios
.$delete(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}${queryString}`)
.then(() => {
this.processing = false
this.$toast.success('Podcast episode removed')
this.show = false
})
for (const episode of this.episodes) {
const success = await this.$axios
.$delete(`/api/podcasts/${this.libraryItem.id}/episode/${episode.id}${queryString}`)
.then(() => true)
.catch((error) => {
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed remove episode'
console.error('Failed update episode', error)
this.processing = false
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to remove episode'
console.error('Failed to remove episode', error)
this.$toast.error(errorMsg)
return false
})
if (!success) {
this.processing = false
this.show = false
this.$emit('clearSelected')
return
}
}
this.processing = false
this.$toast.success(`${this.episodes.length} episode${this.episodes.length > 1 ? 's' : ''} removed`)
this.show = false
this.$emit('clearSelected')
}
},
mounted() {}

View File

@ -1,6 +1,6 @@
<template>
<div class="w-full px-2 py-3 overflow-hidden relative border-b border-white border-opacity-10" @mouseover="mouseover" @mouseleave="mouseleave">
<div v-if="episode" class="flex items-center h-24 cursor-pointer" @click="$emit('view', episode)">
<div v-if="episode" class="flex items-center cursor-pointer" :class="{ 'opacity-70': isSelected || selectionMode }" @click="clickedEpisode">
<div class="flex-grow px-2">
<p class="text-sm font-semibold">
{{ title }}
@ -8,6 +8,12 @@
<p class="text-sm text-gray-200 episode-subtitle mt-1.5 mb-0.5">{{ subtitle }}</p>
<div class="flex justify-between pt-2 max-w-xl">
<p v-if="episode.season" class="text-sm text-gray-300">Season #{{ episode.season }}</p>
<p v-if="episode.episode" class="text-sm text-gray-300">Episode #{{ episode.episode }}</p>
<p v-if="publishedAt" class="text-sm text-gray-300">Published {{ $formatDate(publishedAt, 'MMM do, yyyy') }}</p>
</div>
<div class="flex items-center pt-2">
<button class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click.stop="playClick">
<span class="material-icons" :class="streamIsPlaying ? '' : 'text-success'">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span>
@ -17,20 +23,18 @@
<ui-tooltip :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
</ui-tooltip>
<p v-if="episode.season" class="px-4 text-sm text-gray-300">Season #{{ episode.season }}</p>
<p v-if="episode.episode" class="px-4 text-sm text-gray-300">Episode #{{ episode.episode }}</p>
<p v-if="publishedAt" class="px-4 text-sm text-gray-300">Published {{ $formatDate(publishedAt, 'MMM do, yyyy') }}</p>
<ui-icon-btn v-if="userCanUpdate" icon="edit" borderless @click="clickEdit" />
<ui-icon-btn v-if="userCanDelete" icon="close" borderless @click="removeClick" />
</div>
</div>
<div class="w-24 min-w-24" />
<div v-if="isHovering || isSelected || selectionMode" class="hidden md:block w-12 min-w-12" />
</div>
<div class="w-24 min-w-24 -right-0 absolute top-0 h-full transform transition-transform" :class="!isHovering ? 'translate-x-32' : 'translate-x-0'">
<div v-if="isSelected || selectionMode" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-10 z-10 cursor-pointer" @click.stop="clickedSelectionBg" />
<div class="hidden md:block md:w-12 md:min-w-12 md:-right-0 md:absolute md:top-0 h-full transform transition-transform z-20" :class="!isHovering && !isSelected && !selectionMode ? 'translate-x-24' : 'translate-x-0'">
<div class="flex h-full items-center">
<div class="mx-1">
<ui-icon-btn v-if="userCanUpdate" icon="edit" borderless @click="clickEdit" />
</div>
<div class="mx-1">
<ui-icon-btn v-if="userCanDelete" icon="close" borderless @click="removeClick" />
<ui-checkbox v-model="isSelected" @input="selectedUpdated" checkbox-bg="bg" />
</div>
</div>
</div>
@ -46,13 +50,15 @@ export default {
episode: {
type: Object,
default: () => {}
}
},
selectionMode: Boolean
},
data() {
return {
isProcessingReadUpdate: false,
processingRemove: false,
isHovering: false
isHovering: false,
isSelected: false
}
},
computed: {
@ -104,8 +110,17 @@ export default {
}
},
methods: {
clickedEpisode() {
this.$emit('view', this.episode)
},
clickedSelectionBg() {
this.isSelected = !this.isSelected
this.selectedUpdated(this.isSelected)
},
selectedUpdated(value) {
this.$emit('selected', { isSelected: value, episode: this.episode })
},
mouseover() {
// if (this.isDragging) return
this.isHovering = true
},
mouseleave() {

View File

@ -3,14 +3,18 @@
<div class="flex items-center mb-4">
<p class="text-lg mb-0 font-semibold">Episodes</p>
<div class="flex-grow" />
<controls-episode-sort-select v-model="sortKey" :descending.sync="sortDesc" class="w-36 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" />
<template v-if="isSelectionMode">
<ui-btn color="error" small @click="removeSelectedEpisodes">Remove {{ selectedEpisodes.length }} episode{{ selectedEpisodes.length > 1 ? 's' : '' }}</ui-btn>
<ui-btn small class="ml-2" @click="clearSelected">Cancel</ui-btn>
</template>
<controls-episode-sort-select v-else v-model="sortKey" :descending.sync="sortDesc" class="w-36 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" />
</div>
<p v-if="!episodes.length" class="py-4 text-center text-lg">No Episodes</p>
<template v-for="episode in episodesSorted">
<tables-podcast-episode-table-row :key="episode.id" :episode="episode" :library-item-id="libraryItem.id" class="item" @remove="removeEpisode" @edit="editEpisode" @view="viewEpisode" />
<tables-podcast-episode-table-row ref="episodeRow" :key="episode.id" :episode="episode" :library-item-id="libraryItem.id" :selection-mode="isSelectionMode" class="item" @remove="removeEpisode" @edit="editEpisode" @view="viewEpisode" @selected="episodeSelected" />
</template>
<modals-podcast-remove-episode v-model="showPodcastRemoveModal" :library-item="libraryItem" :episode="selectedEpisode" />
<modals-podcast-remove-episode v-model="showPodcastRemoveModal" @input="removeEpisodeModalToggled" :library-item="libraryItem" :episodes="episodesToRemove" @clearSelected="clearSelected" />
</div>
</template>
@ -28,7 +32,9 @@ export default {
sortKey: 'publishedAt',
sortDesc: true,
selectedEpisode: null,
showPodcastRemoveModal: false
showPodcastRemoveModal: false,
selectedEpisodes: [],
episodesToRemove: []
}
},
watch: {
@ -37,6 +43,9 @@ export default {
}
},
computed: {
isSelectionMode() {
return this.selectedEpisodes.length > 0
},
userCanUpdate() {
return this.$store.getters['user/getUserCanUpdate']
},
@ -59,8 +68,31 @@ export default {
}
},
methods: {
removeEpisodeModalToggled(val) {
if (!val) this.episodesToRemove = []
},
clearSelected() {
const episodeRows = this.$refs.episodeRow
if (episodeRows && episodeRows.length) {
for (const epRow of episodeRows) {
if (epRow) epRow.isSelected = false
}
}
this.selectedEpisodes = []
},
removeSelectedEpisodes() {
this.episodesToRemove = this.selectedEpisodes
this.showPodcastRemoveModal = true
},
episodeSelected({ isSelected, episode }) {
if (isSelected) {
this.selectedEpisodes.push(episode)
} else {
this.selectedEpisodes = this.selectedEpisodes.filter((ep) => ep.id !== episode.id)
}
},
removeEpisode(episode) {
this.selectedEpisode = episode
this.episodesToRemove = [episode]
this.showPodcastRemoveModal = true
},
editEpisode(episode) {