mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-04 03:17:00 -05:00 
			
		
		
		
	Add:Delete library files, condense item options in more menu #1439
This commit is contained in:
		
							parent
							
								
									24ef105732
								
							
						
					
					
						commit
						5a21e63d0b
					
				@ -18,25 +18,10 @@
 | 
				
			|||||||
            <th class="text-left px-4">{{ $strings.LabelPath }}</th>
 | 
					            <th class="text-left px-4">{{ $strings.LabelPath }}</th>
 | 
				
			||||||
            <th class="text-left w-24 min-w-24">{{ $strings.LabelSize }}</th>
 | 
					            <th class="text-left w-24 min-w-24">{{ $strings.LabelSize }}</th>
 | 
				
			||||||
            <th class="text-left px-4 w-24">{{ $strings.LabelType }}</th>
 | 
					            <th class="text-left px-4 w-24">{{ $strings.LabelType }}</th>
 | 
				
			||||||
            <th v-if="userCanDownload && !isMissing" class="text-center w-20">{{ $strings.LabelDownload }}</th>
 | 
					            <th v-if="userCanDelete || userCanDownload" class="text-center w-16"></th>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          <template v-for="file in files">
 | 
					          <template v-for="file in files">
 | 
				
			||||||
            <tr :key="file.path">
 | 
					            <tables-library-files-table-row :key="file.path" :libraryItemId="libraryItemId" :showFullPath="showFullPath" :file="file" />
 | 
				
			||||||
              <td class="px-4">
 | 
					 | 
				
			||||||
                {{ showFullPath ? file.metadata.path : file.metadata.relPath }}
 | 
					 | 
				
			||||||
              </td>
 | 
					 | 
				
			||||||
              <td class="font-mono">
 | 
					 | 
				
			||||||
                {{ $bytesPretty(file.metadata.size) }}
 | 
					 | 
				
			||||||
              </td>
 | 
					 | 
				
			||||||
              <td class="text-xs">
 | 
					 | 
				
			||||||
                <div class="flex items-center">
 | 
					 | 
				
			||||||
                  <p>{{ file.fileType }}</p>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </td>
 | 
					 | 
				
			||||||
              <td v-if="userCanDownload && !isMissing" class="text-center">
 | 
					 | 
				
			||||||
                <a :href="`${$config.routerBasePath}/s/item/${libraryItemId}/${$encodeUriPath(file.metadata.relPath).replace(/^\//, '')}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
 | 
					 | 
				
			||||||
              </td>
 | 
					 | 
				
			||||||
            </tr>
 | 
					 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
        </table>
 | 
					        </table>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@ -68,6 +53,9 @@ export default {
 | 
				
			|||||||
    userCanDownload() {
 | 
					    userCanDownload() {
 | 
				
			||||||
      return this.$store.getters['user/getUserCanDownload']
 | 
					      return this.$store.getters['user/getUserCanDownload']
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    userIsAdmin() {
 | 
					    userIsAdmin() {
 | 
				
			||||||
      return this.$store.getters['user/getIsAdminOrUp']
 | 
					      return this.$store.getters['user/getIsAdminOrUp']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										105
									
								
								client/components/tables/LibraryFilesTableRow.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								client/components/tables/LibraryFilesTableRow.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <tr>
 | 
				
			||||||
 | 
					    <td class="px-4">
 | 
				
			||||||
 | 
					      {{ showFullPath ? file.metadata.path : file.metadata.relPath }}
 | 
				
			||||||
 | 
					    </td>
 | 
				
			||||||
 | 
					    <td class="font-mono">
 | 
				
			||||||
 | 
					      {{ $bytesPretty(file.metadata.size) }}
 | 
				
			||||||
 | 
					    </td>
 | 
				
			||||||
 | 
					    <td class="text-xs">
 | 
				
			||||||
 | 
					      <div class="flex items-center">
 | 
				
			||||||
 | 
					        <p>{{ file.fileType }}</p>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </td>
 | 
				
			||||||
 | 
					    <td v-if="contextMenuItems.length" class="text-center">
 | 
				
			||||||
 | 
					      <ui-context-menu-dropdown :items="contextMenuItems" menu-width="110px" @action="contextMenuAction" />
 | 
				
			||||||
 | 
					    </td>
 | 
				
			||||||
 | 
					  </tr>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    libraryItemId: String,
 | 
				
			||||||
 | 
					    showFullPath: Boolean,
 | 
				
			||||||
 | 
					    file: {
 | 
				
			||||||
 | 
					      type: Object,
 | 
				
			||||||
 | 
					      default: () => {}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    userToken() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getToken']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDownload() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDownload']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    downloadUrl() {
 | 
				
			||||||
 | 
					      return `${process.env.serverUrl}/s/item/${this.libraryItemId}/${this.$encodeUriPath(this.file.metadata.relPath).replace(/^\//, '')}?token=${this.userToken}`
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    contextMenuItems() {
 | 
				
			||||||
 | 
					      const items = []
 | 
				
			||||||
 | 
					      if (this.userCanDownload) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: this.$strings.LabelDownload,
 | 
				
			||||||
 | 
					          action: 'download'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.userCanDelete) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: this.$strings.ButtonDelete,
 | 
				
			||||||
 | 
					          action: 'delete'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return items
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    contextMenuAction(action) {
 | 
				
			||||||
 | 
					      if (action === 'delete') {
 | 
				
			||||||
 | 
					        this.deleteLibraryFile()
 | 
				
			||||||
 | 
					      } else if (action === 'download') {
 | 
				
			||||||
 | 
					        this.downloadLibraryFile()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    deleteLibraryFile() {
 | 
				
			||||||
 | 
					      const payload = {
 | 
				
			||||||
 | 
					        message: 'This will delete the file from your file system. Are you sure?',
 | 
				
			||||||
 | 
					        callback: (confirmed) => {
 | 
				
			||||||
 | 
					          if (confirmed) {
 | 
				
			||||||
 | 
					            this.$axios
 | 
				
			||||||
 | 
					              .$delete(`/api/items/${this.libraryItemId}/file/${this.file.ino}`)
 | 
				
			||||||
 | 
					              .then(() => {
 | 
				
			||||||
 | 
					                this.$toast.success('File deleted')
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					              .catch((error) => {
 | 
				
			||||||
 | 
					                console.error('Failed to delete file', error)
 | 
				
			||||||
 | 
					                this.$toast.error('Failed to delete file')
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        type: 'yesNo'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    downloadLibraryFile() {
 | 
				
			||||||
 | 
					      const a = document.createElement('a')
 | 
				
			||||||
 | 
					      a.style.display = 'none'
 | 
				
			||||||
 | 
					      a.href = this.downloadUrl
 | 
				
			||||||
 | 
					      a.download = this.file.metadata.filename
 | 
				
			||||||
 | 
					      document.body.appendChild(a)
 | 
				
			||||||
 | 
					      a.click()
 | 
				
			||||||
 | 
					      setTimeout(() => {
 | 
				
			||||||
 | 
					        a.remove()
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@ -1,11 +1,13 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="relative h-9 w-9" v-click-outside="clickOutsideObj">
 | 
					  <div class="relative h-9 w-9" v-click-outside="clickOutsideObj">
 | 
				
			||||||
 | 
					    <slot :disabled="disabled" :showMenu="showMenu" :clickShowMenu="clickShowMenu">
 | 
				
			||||||
      <button type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
 | 
					      <button type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
 | 
				
			||||||
        <span class="material-icons" :class="iconClass">more_vert</span>
 | 
					        <span class="material-icons" :class="iconClass">more_vert</span>
 | 
				
			||||||
      </button>
 | 
					      </button>
 | 
				
			||||||
 | 
					    </slot>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <transition name="menu">
 | 
					    <transition name="menu">
 | 
				
			||||||
      <div v-show="showMenu" class="absolute right-0 mt-1 z-10 bg-bg border border-black-200 shadow-lg max-h-56 w-48 rounded-md py-1 overflow-auto focus:outline-none sm:text-sm">
 | 
					      <div v-show="showMenu" class="absolute right-0 mt-1 z-10 bg-bg border border-black-200 shadow-lg max-h-56 rounded-md py-1 overflow-auto focus:outline-none sm:text-sm" :style="{ width: menuWidth }">
 | 
				
			||||||
        <template v-for="(item, index) in items">
 | 
					        <template v-for="(item, index) in items">
 | 
				
			||||||
          <div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(item.action)">
 | 
					          <div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(item.action)">
 | 
				
			||||||
            <p>{{ item.text }}</p>
 | 
					            <p>{{ item.text }}</p>
 | 
				
			||||||
@ -27,6 +29,10 @@ export default {
 | 
				
			|||||||
    iconClass: {
 | 
					    iconClass: {
 | 
				
			||||||
      type: String,
 | 
					      type: String,
 | 
				
			||||||
      default: ''
 | 
					      default: ''
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    menuWidth: {
 | 
				
			||||||
 | 
					      type: String,
 | 
				
			||||||
 | 
					      default: '192px'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  data() {
 | 
					  data() {
 | 
				
			||||||
 | 
				
			|||||||
@ -201,34 +201,18 @@
 | 
				
			|||||||
              <ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
 | 
					              <ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
 | 
				
			||||||
            </ui-tooltip>
 | 
					            </ui-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ui-tooltip v-if="showCollectionsButton" :text="$strings.LabelCollections" direction="top">
 | 
					 | 
				
			||||||
              <ui-icon-btn icon="collections_bookmark" class="mx-0.5" outlined @click="collectionsClick" />
 | 
					 | 
				
			||||||
            </ui-tooltip>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ui-tooltip v-if="!isPodcast && tracks.length" :text="$strings.LabelYourPlaylists" direction="top">
 | 
					 | 
				
			||||||
              <ui-icon-btn icon="playlist_add" class="mx-0.5" outlined @click="playlistsClick" />
 | 
					 | 
				
			||||||
            </ui-tooltip>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- Only admin or root user can download new episodes -->
 | 
					            <!-- Only admin or root user can download new episodes -->
 | 
				
			||||||
            <ui-tooltip v-if="isPodcast && userIsAdminOrUp" :text="$strings.LabelFindEpisodes" direction="top">
 | 
					            <ui-tooltip v-if="isPodcast && userIsAdminOrUp" :text="$strings.LabelFindEpisodes" direction="top">
 | 
				
			||||||
              <ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" />
 | 
					              <ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" />
 | 
				
			||||||
            </ui-tooltip>
 | 
					            </ui-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ui-tooltip v-if="bookmarks.length" :text="$strings.LabelYourBookmarks" direction="top">
 | 
					            <ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" menu-width="148px" @action="contextMenuAction">
 | 
				
			||||||
              <ui-icon-btn :icon="bookmarks.length ? 'bookmarks' : 'bookmark_border'" class="mx-0.5" @click="clickBookmarksBtn" />
 | 
					              <template #default="{ showMenu, clickShowMenu, disabled }">
 | 
				
			||||||
            </ui-tooltip>
 | 
					                <button type="button" :disabled="disabled" class="mx-0.5 icon-btn bg-primary border border-gray-600 w-9 h-9 rounded-md flex items-center justify-center relative" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
 | 
				
			||||||
 | 
					                  <span class="material-icons">more_vert</span>
 | 
				
			||||||
            <!-- RSS feed -->
 | 
					                </button>
 | 
				
			||||||
            <ui-tooltip v-if="showRssFeedBtn" :text="$strings.LabelOpenRSSFeed" direction="top">
 | 
					              </template>
 | 
				
			||||||
              <ui-icon-btn icon="rss_feed" class="mx-0.5" :bg-color="rssFeed ? 'success' : 'primary'" outlined @click="clickRSSFeed" />
 | 
					            </ui-context-menu-dropdown>
 | 
				
			||||||
            </ui-tooltip>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- Download -->
 | 
					 | 
				
			||||||
            <ui-tooltip v-if="userCanDownload" :text="$strings.LabelDownload" direction="top">
 | 
					 | 
				
			||||||
              <a :href="downloadPath" class="h-9 w-9 mx-0.5 rounded-md flex items-center justify-center relative bg-primary border border-gray-600">
 | 
					 | 
				
			||||||
                <span class="material-icons-outlined" :style="{ fontSize: '1.4rem' }">download</span>
 | 
					 | 
				
			||||||
              </a>
 | 
					 | 
				
			||||||
            </ui-tooltip>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div class="my-4 max-w-2xl">
 | 
					          <div class="my-4 max-w-2xl">
 | 
				
			||||||
@ -294,7 +278,7 @@ export default {
 | 
				
			|||||||
    userToken() {
 | 
					    userToken() {
 | 
				
			||||||
      return this.$store.getters['user/getToken']
 | 
					      return this.$store.getters['user/getToken']
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    downloadPath() {
 | 
					    downloadUrl() {
 | 
				
			||||||
      return `${process.env.serverUrl}/api/items/${this.libraryItemId}/download?token=${this.userToken}`
 | 
					      return `${process.env.serverUrl}/api/items/${this.libraryItemId}/download?token=${this.userToken}`
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    dateFormat() {
 | 
					    dateFormat() {
 | 
				
			||||||
@ -309,9 +293,6 @@ export default {
 | 
				
			|||||||
    userIsAdminOrUp() {
 | 
					    userIsAdminOrUp() {
 | 
				
			||||||
      return this.$store.getters['user/getIsAdminOrUp']
 | 
					      return this.$store.getters['user/getIsAdminOrUp']
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    isFile() {
 | 
					 | 
				
			||||||
      return this.libraryItem.isFile
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    bookCoverAspectRatio() {
 | 
					    bookCoverAspectRatio() {
 | 
				
			||||||
      return this.$store.getters['libraries/getBookCoverAspectRatio']
 | 
					      return this.$store.getters['libraries/getBookCoverAspectRatio']
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -321,6 +302,9 @@ export default {
 | 
				
			|||||||
    isDeveloperMode() {
 | 
					    isDeveloperMode() {
 | 
				
			||||||
      return this.$store.state.developerMode
 | 
					      return this.$store.state.developerMode
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    isFile() {
 | 
				
			||||||
 | 
					      return this.libraryItem.isFile
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    isBook() {
 | 
					    isBook() {
 | 
				
			||||||
      return this.libraryItem.mediaType === 'book'
 | 
					      return this.libraryItem.mediaType === 'book'
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -539,12 +523,56 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    showCollectionsButton() {
 | 
					    showCollectionsButton() {
 | 
				
			||||||
      return this.isBook && this.userCanUpdate
 | 
					      return this.isBook && this.userCanUpdate
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    contextMenuItems() {
 | 
				
			||||||
 | 
					      const items = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.showCollectionsButton) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: this.$strings.LabelCollections,
 | 
				
			||||||
 | 
					          action: 'collections'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!this.isPodcast && this.tracks.length) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: this.$strings.LabelYourPlaylists,
 | 
				
			||||||
 | 
					          action: 'playlists'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.bookmarks.length) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: this.$strings.LabelYourBookmarks,
 | 
				
			||||||
 | 
					          action: 'bookmarks'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.showRssFeedBtn) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: this.$strings.LabelOpenRSSFeed,
 | 
				
			||||||
 | 
					          action: 'rss-feeds'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.userCanDownload) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: this.$strings.LabelDownload,
 | 
				
			||||||
 | 
					          action: 'download'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // if (this.userCanDelete) {
 | 
				
			||||||
 | 
					      //   items.push({
 | 
				
			||||||
 | 
					      //     text: this.$strings.ButtonDelete,
 | 
				
			||||||
 | 
					      //     action: 'delete'
 | 
				
			||||||
 | 
					      //   })
 | 
				
			||||||
 | 
					      // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return items
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    clickBookmarksBtn() {
 | 
					 | 
				
			||||||
      this.showBookmarksModal = true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    selectBookmark(bookmark) {
 | 
					    selectBookmark(bookmark) {
 | 
				
			||||||
      if (!bookmark) return
 | 
					      if (!bookmark) return
 | 
				
			||||||
      if (this.isStreaming) {
 | 
					      if (this.isStreaming) {
 | 
				
			||||||
@ -720,14 +748,6 @@ export default {
 | 
				
			|||||||
          })
 | 
					          })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    collectionsClick() {
 | 
					 | 
				
			||||||
      this.$store.commit('setSelectedLibraryItem', this.libraryItem)
 | 
					 | 
				
			||||||
      this.$store.commit('globals/setShowCollectionsModal', true)
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    playlistsClick() {
 | 
					 | 
				
			||||||
      this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem }])
 | 
					 | 
				
			||||||
      this.$store.commit('globals/setShowPlaylistsModal', true)
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    clickRSSFeed() {
 | 
					    clickRSSFeed() {
 | 
				
			||||||
      this.$store.commit('globals/setRSSFeedOpenCloseModal', {
 | 
					      this.$store.commit('globals/setRSSFeedOpenCloseModal', {
 | 
				
			||||||
        id: this.libraryItemId,
 | 
					        id: this.libraryItemId,
 | 
				
			||||||
@ -785,6 +805,54 @@ export default {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        this.$store.commit('addItemToQueue', queueItem)
 | 
					        this.$store.commit('addItemToQueue', queueItem)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    downloadLibraryItem() {
 | 
				
			||||||
 | 
					      const a = document.createElement('a')
 | 
				
			||||||
 | 
					      a.style.display = 'none'
 | 
				
			||||||
 | 
					      a.href = this.downloadUrl
 | 
				
			||||||
 | 
					      document.body.appendChild(a)
 | 
				
			||||||
 | 
					      a.click()
 | 
				
			||||||
 | 
					      setTimeout(() => {
 | 
				
			||||||
 | 
					        a.remove()
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    deleteLibraryItem() {
 | 
				
			||||||
 | 
					      const payload = {
 | 
				
			||||||
 | 
					        message: 'This will delete the library item files from your file system. Are you sure?',
 | 
				
			||||||
 | 
					        callback: (confirmed) => {
 | 
				
			||||||
 | 
					          if (confirmed) {
 | 
				
			||||||
 | 
					            this.$axios
 | 
				
			||||||
 | 
					              .$delete(`/api/items/${this.libraryItemId}?hard=1`)
 | 
				
			||||||
 | 
					              .then(() => {
 | 
				
			||||||
 | 
					                this.$toast.success('Item deleted')
 | 
				
			||||||
 | 
					                this.$router.replace(`/library/${this.libraryId}/bookshelf`)
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					              .catch((error) => {
 | 
				
			||||||
 | 
					                console.error('Failed to delete item', error)
 | 
				
			||||||
 | 
					                this.$toast.error('Failed to delete item')
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        type: 'yesNo'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    contextMenuAction(action) {
 | 
				
			||||||
 | 
					      if (action === 'collections') {
 | 
				
			||||||
 | 
					        this.$store.commit('setSelectedLibraryItem', this.libraryItem)
 | 
				
			||||||
 | 
					        this.$store.commit('globals/setShowCollectionsModal', true)
 | 
				
			||||||
 | 
					      } else if (action === 'playlists') {
 | 
				
			||||||
 | 
					        this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem }])
 | 
				
			||||||
 | 
					        this.$store.commit('globals/setShowPlaylistsModal', true)
 | 
				
			||||||
 | 
					      } else if (action === 'bookmarks') {
 | 
				
			||||||
 | 
					        this.showBookmarksModal = true
 | 
				
			||||||
 | 
					      } else if (action === 'rss-feeds') {
 | 
				
			||||||
 | 
					        this.clickRSSFeed()
 | 
				
			||||||
 | 
					      } else if (action === 'download') {
 | 
				
			||||||
 | 
					        this.downloadLibraryItem()
 | 
				
			||||||
 | 
					      } else if (action === 'delete') {
 | 
				
			||||||
 | 
					        this.deleteLibraryItem()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mounted() {
 | 
					  mounted() {
 | 
				
			||||||
 | 
				
			|||||||
@ -482,6 +482,28 @@ class LibraryItemController {
 | 
				
			|||||||
    res.json(toneData)
 | 
					    res.json(toneData)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async deleteLibraryFile(req, res) {
 | 
				
			||||||
 | 
					    const libraryFile = req.libraryItem.libraryFiles.find(lf => lf.ino === req.params.ino)
 | 
				
			||||||
 | 
					    if (!libraryFile) {
 | 
				
			||||||
 | 
					      Logger.error(`[LibraryItemController] Unable to delete library file. Not found. "${req.params.ino}"`)
 | 
				
			||||||
 | 
					      return res.sendStatus(404)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await fs.remove(libraryFile.metadata.path)
 | 
				
			||||||
 | 
					    req.libraryItem.removeLibraryFile(req.params.ino)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (req.libraryItem.media.removeFileWithInode(req.params.ino)) {
 | 
				
			||||||
 | 
					      // If book has no more media files then mark it as missing
 | 
				
			||||||
 | 
					      if (req.libraryItem.mediaType === 'book' && !req.libraryItem.media.hasMediaEntities) {
 | 
				
			||||||
 | 
					        req.libraryItem.setMissing()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    req.libraryItem.updatedAt = Date.now()
 | 
				
			||||||
 | 
					    await this.db.updateLibraryItem(req.libraryItem)
 | 
				
			||||||
 | 
					    SocketAuthority.emitter('item_updated', req.libraryItem.toJSONExpanded())
 | 
				
			||||||
 | 
					    res.sendStatus(200)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  middleware(req, res, next) {
 | 
					  middleware(req, res, next) {
 | 
				
			||||||
    const item = this.db.libraryItems.find(li => li.id === req.params.id)
 | 
					    const item = this.db.libraryItems.find(li => li.id === req.params.id)
 | 
				
			||||||
    if (!item || !item.media) return res.sendStatus(404)
 | 
					    if (!item || !item.media) return res.sendStatus(404)
 | 
				
			||||||
 | 
				
			|||||||
@ -166,8 +166,12 @@ class Podcast {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  removeFileWithInode(inode) {
 | 
					  removeFileWithInode(inode) {
 | 
				
			||||||
 | 
					    const hasEpisode = this.episodes.some(ep => ep.audioFile.ino === inode)
 | 
				
			||||||
 | 
					    if (hasEpisode) {
 | 
				
			||||||
      this.episodes = this.episodes.filter(ep => ep.audioFile.ino !== inode)
 | 
					      this.episodes = this.episodes.filter(ep => ep.audioFile.ino !== inode)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    return hasEpisode
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  findFileWithInode(inode) {
 | 
					  findFileWithInode(inode) {
 | 
				
			||||||
    var episode = this.episodes.find(ep => ep.audioFile.ino === inode)
 | 
					    var episode = this.episodes.find(ep => ep.audioFile.ino === inode)
 | 
				
			||||||
 | 
				
			|||||||
@ -112,6 +112,7 @@ class ApiRouter {
 | 
				
			|||||||
    this.router.get('/items/:id/tone-object', LibraryItemController.middleware.bind(this), LibraryItemController.getToneMetadataObject.bind(this))
 | 
					    this.router.get('/items/:id/tone-object', LibraryItemController.middleware.bind(this), LibraryItemController.getToneMetadataObject.bind(this))
 | 
				
			||||||
    this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
 | 
					    this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
 | 
				
			||||||
    this.router.post('/items/:id/tone-scan/:index?', LibraryItemController.middleware.bind(this), LibraryItemController.toneScan.bind(this))
 | 
					    this.router.post('/items/:id/tone-scan/:index?', LibraryItemController.middleware.bind(this), LibraryItemController.toneScan.bind(this))
 | 
				
			||||||
 | 
					    this.router.delete('/items/:id/file/:ino', LibraryItemController.middleware.bind(this), LibraryItemController.deleteLibraryFile.bind(this))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
 | 
					    this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
 | 
				
			||||||
    this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this))
 | 
					    this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this))
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user