diff --git a/komga-webui/src/components/Dialogs.vue b/komga-webui/src/components/Dialogs.vue
index a2465b8cb..6c37e90f9 100644
--- a/komga-webui/src/components/Dialogs.vue
+++ b/komga-webui/src/components/Dialogs.vue
@@ -12,7 +12,7 @@
-
+
mdi-bookmark-check
@@ -34,7 +34,7 @@
-
+
mdi-bookmark-remove
@@ -61,7 +61,7 @@
-
+
mdi-pencil
@@ -69,6 +69,15 @@
{{ $t('menu.edit_metadata') }}
+
+
+
+
+ mdi-delete
+
+ {{ $t('menu.delete') }}
+
+
@@ -88,7 +97,10 @@ export default Vue.extend({
type: Array,
required: true,
},
- // books or series
+ /**
+ * The kind of items this toolbar acts on.
+ * @values books, series, collections, readlists
+ */
kind: {
type: String,
required: true,
@@ -125,6 +137,9 @@ export default Vue.extend({
edit () {
this.$emit('edit')
},
+ doDelete () {
+ this.$emit('delete')
+ },
},
})
diff --git a/komga-webui/src/components/dialogs/CollectionDeleteDialog.vue b/komga-webui/src/components/dialogs/CollectionDeleteDialog.vue
index 011873be1..a88d337ed 100644
--- a/komga-webui/src/components/dialogs/CollectionDeleteDialog.vue
+++ b/komga-webui/src/components/dialogs/CollectionDeleteDialog.vue
@@ -4,19 +4,30 @@
max-width="450"
>
- {{ $t('dialog.delete_collection.dialog_title') }}
+ {{ $t('dialog.delete_collection.dialog_title') }}
+ {{ $t('dialog.delete_collection.dialog_title_multiple') }}
-
+
+
-
- {{ $t('dialog.delete_collection.confirm_delete', {name: collection.name}) }}
+
+ {{ $t('dialog.delete_collection.confirm_delete', {name: collections.name}) }}
+
+
+ {{ $t('dialog.delete_collection.confirm_delete_multiple', {count: collections.length}) }}
@@ -66,8 +77,8 @@ export default Vue.extend({
},
props: {
value: Boolean,
- collection: {
- type: Object as () => CollectionDto,
+ collections: {
+ type: [Object as () => CollectionDto, Array as () => CollectionDto[]],
required: true,
},
},
@@ -79,24 +90,32 @@ export default Vue.extend({
!val && this.dialogCancel()
},
},
+ computed: {
+ single(): boolean {
+ return !Array.isArray(this.collections)
+ },
+ },
methods: {
dialogCancel() {
this.$emit('input', false)
this.confirmDelete = false
},
dialogConfirm() {
- this.deleteCollection()
+ this.deleteCollections()
this.$emit('input', false)
},
showSnack(message: string) {
this.snackText = message
this.snackbar = true
},
- async deleteCollection() {
- try {
- await this.$komgaCollections.deleteCollection(this.collection.id)
- } catch (e) {
- this.showSnack(e.message)
+ async deleteCollections() {
+ const toUpdate = (this.single ? [this.collections] : this.collections) as CollectionDto[]
+ for (const b of toUpdate) {
+ try {
+ await this.$komgaCollections.deleteCollection(b.id)
+ } catch (e) {
+ this.showSnack(e.message)
+ }
}
},
},
diff --git a/komga-webui/src/components/dialogs/ReadListDeleteDialog.vue b/komga-webui/src/components/dialogs/ReadListDeleteDialog.vue
index fb0a9f074..e19e01980 100644
--- a/komga-webui/src/components/dialogs/ReadListDeleteDialog.vue
+++ b/komga-webui/src/components/dialogs/ReadListDeleteDialog.vue
@@ -4,19 +4,30 @@
max-width="450"
>
- {{ $t('dialog.delete_readlist.dialog_title') }}
+ {{ $t('dialog.delete_readlist.dialog_title') }}
+ {{ $t('dialog.delete_readlist.dialog_title_multiple') }}
-
+
+
-
- {{ $t('dialog.delete_readlist.confirm_delete', { name: readList.name}) }}
+
+ {{ $t('dialog.delete_readlist.confirm_delete', { name: readLists.name}) }}
+
+
+ {{ $t('dialog.delete_readlist.confirm_delete_multiple', { count: readLists.length}) }}
@@ -64,8 +75,8 @@ export default Vue.extend({
},
props: {
value: Boolean,
- readList: {
- type: Object as () => ReadListDto,
+ readLists: {
+ type: [Object as () => ReadListDto, Array as () => ReadListDto[]],
required: true,
},
},
@@ -77,6 +88,11 @@ export default Vue.extend({
!val && this.dialogCancel()
},
},
+ computed: {
+ single(): boolean {
+ return !Array.isArray(this.readLists)
+ },
+ },
methods: {
dialogCancel() {
this.$emit('input', false)
@@ -91,10 +107,13 @@ export default Vue.extend({
this.snackbar = true
},
async delete() {
- try {
- await this.$komgaReadLists.deleteReadList(this.readList.id)
- } catch (e) {
- this.showSnack(e.message)
+ const toUpdate = (this.single ? [this.readLists] : this.readLists) as ReadListDto[]
+ for (const b of toUpdate) {
+ try {
+ await this.$komgaReadLists.deleteReadList(b.id)
+ } catch (e) {
+ this.showSnack(e.message)
+ }
}
},
},
diff --git a/komga-webui/src/locales/en.json b/komga-webui/src/locales/en.json
index a8105b336..8211f6c85 100644
--- a/komga-webui/src/locales/en.json
+++ b/komga-webui/src/locales/en.json
@@ -255,8 +255,11 @@
"button_cancel": "Cancel",
"button_confirm": "Delete",
"confirm_delete": "Yes, delete the collection \"{name}\"",
+ "confirm_delete_multiple": "Yes, delete {count} collections",
"dialog_title": "Delete Collection",
- "warning_html": "The collection {name} will be removed from this server. Your media files will not be affected. This cannot be undone. Continue?"
+ "dialog_title_multiple": "Delete Collections",
+ "warning_html": "The collection {name} will be removed from this server. Your media files will not be affected. This cannot be undone. Continue?",
+ "warning_multiple_html": "{count} collections will be removed from this server. Your media files will not be affected. This cannot be undone. Continue?"
},
"delete_library": {
"button_cancel": "Cancel",
@@ -269,8 +272,11 @@
"button_cancel": "Cancel",
"button_confirm": "Delete",
"confirm_delete": "Yes, delete the read list \"{name}\"",
+ "confirm_delete_multiple": "Yes, delete {count} read lists",
"dialog_title": "Delete Read List",
- "warning_html": "The read list {name} will be removed from this server. Your media files will not be affected. This cannot be undone. Continue?"
+ "dialog_title_multiple": "Delete Read Lists",
+ "warning_html": "The read list {name} will be removed from this server. Your media files will not be affected. This cannot be undone. Continue?",
+ "warning_multiple_html": "{count} read lists will be removed from this server. Your media files will not be affected. This cannot be undone. Continue?"
},
"delete_user": {
"button_cancel": "Cancel",
@@ -540,6 +546,7 @@
"add_to_readlist": "Add to read list",
"analyze": "Analyze",
"delete": "Delete",
+ "deselect_all": "Deselect all",
"download_series": "Download series",
"edit": "Edit",
"edit_metadata": "Edit metadata",
@@ -547,7 +554,6 @@
"mark_unread": "Mark as unread",
"refresh_metadata": "Refresh metadata",
"scan_library_files": "Scan library files",
- "deselect_all": "Deselect all",
"select_all": "Select all"
},
"navigation": {
diff --git a/komga-webui/src/store.ts b/komga-webui/src/store.ts
index 057086a8c..72df94ff7 100644
--- a/komga-webui/src/store.ts
+++ b/komga-webui/src/store.ts
@@ -18,14 +18,14 @@ export default new Vuex.Store({
addToCollectionDialog: false,
editCollection: {} as CollectionDto,
editCollectionDialog: false,
- deleteCollection: {} as CollectionDto,
+ deleteCollections: {} as CollectionDto | CollectionDto[],
deleteCollectionDialog: false,
// read lists
addToReadListBooks: {} as BookDto | BookDto[],
addToReadListDialog: false,
editReadList: {} as ReadListDto,
editReadListDialog: false,
- deleteReadList: {} as ReadListDto,
+ deleteReadLists: {} as ReadListDto | ReadListDto[],
deleteReadListDialog: false,
// libraries
editLibrary: {} as LibraryDto | undefined,
@@ -53,27 +53,27 @@ export default new Vuex.Store({
setEditCollectionDialog (state, dialog) {
state.editCollectionDialog = dialog
},
- setDeleteCollection (state, collection) {
- state.deleteCollection = collection
+ setDeleteCollections (state, collections) {
+ state.deleteCollections = collections
},
setDeleteCollectionDialog (state, dialog) {
state.deleteCollectionDialog = dialog
},
// Read Lists
- setAddToReadListBooks (state, Book) {
- state.addToReadListBooks = Book
+ setAddToReadListBooks (state, book) {
+ state.addToReadListBooks = book
},
setAddToReadListDialog (state, dialog) {
state.addToReadListDialog = dialog
},
- setEditReadList (state, ReadList) {
- state.editReadList = ReadList
+ setEditReadList (state, readList) {
+ state.editReadList = readList
},
setEditReadListDialog (state, dialog) {
state.editReadListDialog = dialog
},
- setDeleteReadList (state, ReadList) {
- state.deleteReadList = ReadList
+ setDeleteReadLists (state, readLists) {
+ state.deleteReadLists = readLists
},
setDeleteReadListDialog (state, dialog) {
state.deleteReadListDialog = dialog
@@ -122,8 +122,8 @@ export default new Vuex.Store({
dialogEditCollectionDisplay ({ commit }, value) {
commit('setEditCollectionDialog', value)
},
- dialogDeleteCollection ({ commit }, collection) {
- commit('setDeleteCollection', collection)
+ dialogDeleteCollection ({ commit }, collections) {
+ commit('setDeleteCollections', collections)
commit('setDeleteCollectionDialog', true)
},
dialogDeleteCollectionDisplay ({ commit }, value) {
@@ -144,8 +144,8 @@ export default new Vuex.Store({
dialogEditReadListDisplay ({ commit }, value) {
commit('setEditReadListDialog', value)
},
- dialogDeleteReadList ({ commit }, readList) {
- commit('setDeleteReadList', readList)
+ dialogDeleteReadList ({ commit }, readLists) {
+ commit('setDeleteReadLists', readLists)
commit('setDeleteReadListDialog', true)
},
dialogDeleteReadListDisplay ({ commit }, value) {
diff --git a/komga-webui/src/views/BrowseCollections.vue b/komga-webui/src/views/BrowseCollections.vue
index 4a4c38624..c17a06a67 100644
--- a/komga-webui/src/views/BrowseCollections.vue
+++ b/komga-webui/src/views/BrowseCollections.vue
@@ -1,6 +1,6 @@
-
+
@@ -21,6 +21,15 @@
+
+
@@ -33,7 +42,8 @@
@@ -59,6 +69,7 @@ import Vue from 'vue'
import {Location} from 'vue-router'
import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library'
import {LibrarySseDto} from "@/types/komga-sse";
+import MultiSelectBar from "@/components/bars/MultiSelectBar.vue";
export default Vue.extend({
name: 'BrowseCollections',
@@ -68,11 +79,13 @@ export default Vue.extend({
LibraryNavigation,
ItemBrowser,
PageSizeSelect,
+ MultiSelectBar,
},
data: () => {
return {
library: undefined as LibraryDto | undefined,
collections: [] as CollectionDto[],
+ selectedCollections: [] as CollectionDto[],
page: 1,
pageSize: 20,
totalPages: 1,
@@ -196,6 +209,8 @@ export default Vue.extend({
}
},
async loadPage(libraryId: string, page: number) {
+ this.selectedCollections = []
+
const pageRequest = {
page: page - 1,
size: this.pageSize,
@@ -218,6 +233,9 @@ export default Vue.extend({
editSingleCollection(collection: CollectionDto) {
this.$store.dispatch('dialogEditCollection', collection)
},
+ deleteCollections() {
+ this.$store.dispatch('dialogDeleteCollection', this.selectedCollections)
+ },
},
})
diff --git a/komga-webui/src/views/BrowseReadLists.vue b/komga-webui/src/views/BrowseReadLists.vue
index 67f8f3315..b31cfb57a 100644
--- a/komga-webui/src/views/BrowseReadLists.vue
+++ b/komga-webui/src/views/BrowseReadLists.vue
@@ -1,6 +1,6 @@
-
+
@@ -21,6 +21,15 @@
+
+
@@ -33,7 +42,8 @@
@@ -59,6 +69,7 @@ import Vue from 'vue'
import {Location} from 'vue-router'
import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library'
import {LibrarySseDto} from "@/types/komga-sse";
+import MultiSelectBar from "@/components/bars/MultiSelectBar.vue";
export default Vue.extend({
name: 'BrowseReadLists',
@@ -68,11 +79,13 @@ export default Vue.extend({
LibraryNavigation,
ItemBrowser,
PageSizeSelect,
+ MultiSelectBar,
},
data: () => {
return {
library: undefined as LibraryDto | undefined,
readLists: [] as ReadListDto[],
+ selectedReadLists: [] as ReadListDto[],
page: 1,
pageSize: 20,
totalPages: 1,
@@ -87,19 +100,19 @@ export default Vue.extend({
default: LIBRARIES_ALL,
},
},
- created () {
+ created() {
this.$eventHub.$on(READLIST_ADDED, this.reloadElements)
this.$eventHub.$on(READLIST_CHANGED, this.reloadElements)
this.$eventHub.$on(READLIST_DELETED, this.reloadElements)
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary)
},
- beforeDestroy () {
+ beforeDestroy() {
this.$eventHub.$off(READLIST_ADDED, this.reloadElements)
this.$eventHub.$off(READLIST_CHANGED, this.reloadElements)
this.$eventHub.$off(READLIST_DELETED, this.reloadElements)
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary)
},
- mounted () {
+ mounted() {
this.$store.commit('setLibraryRoute', {id: this.libraryId, route: LIBRARY_ROUTE.READLISTS})
this.pageSize = this.$store.state.persistedState.browsingPageSize || this.pageSize
@@ -111,7 +124,7 @@ export default Vue.extend({
this.setWatches()
},
- beforeRouteUpdate (to, from, next) {
+ beforeRouteUpdate(to, from, next) {
if (to.params.libraryId !== from.params.libraryId) {
// reset
this.page = 1
@@ -125,10 +138,10 @@ export default Vue.extend({
next()
},
computed: {
- isAdmin (): boolean {
+ isAdmin(): boolean {
return this.$store.getters.meAdmin
},
- paginationVisible (): number {
+ paginationVisible(): number {
switch (this.$vuetify.breakpoint.name) {
case 'xs':
return 5
@@ -143,7 +156,7 @@ export default Vue.extend({
},
},
methods: {
- setWatches () {
+ setWatches() {
this.pageSizeUnwatch = this.$watch('pageSize', (val) => {
this.$store.commit('setBrowsingPageSize', val)
this.updateRouteAndReload()
@@ -154,11 +167,11 @@ export default Vue.extend({
this.loadPage(this.libraryId, val)
})
},
- unsetWatches () {
+ unsetWatches() {
this.pageUnwatch()
this.pageSizeUnwatch()
},
- updateRouteAndReload () {
+ updateRouteAndReload() {
this.unsetWatches()
this.page = 1
@@ -168,10 +181,10 @@ export default Vue.extend({
this.setWatches()
},
- updateRoute () {
+ updateRoute() {
this.$router.replace({
name: this.$route.name,
- params: { libraryId: this.$route.params.libraryId },
+ params: {libraryId: this.$route.params.libraryId},
query: {
page: `${this.page}`,
pageSize: `${this.pageSize}`,
@@ -179,23 +192,25 @@ export default Vue.extend({
} as Location).catch((_: any) => {
})
},
- reloadElements () {
+ reloadElements() {
this.loadLibrary(this.libraryId)
},
- reloadLibrary (event: LibrarySseDto) {
+ reloadLibrary(event: LibrarySseDto) {
if (event.libraryId === this.libraryId) {
this.loadLibrary(this.libraryId)
}
},
- async loadLibrary (libraryId: string) {
+ async loadLibrary(libraryId: string) {
this.library = this.getLibraryLazy(libraryId)
await this.loadPage(libraryId, this.page)
if (this.totalElements === 0) {
- await this.$router.push({ name: 'browse-libraries', params: { libraryId: libraryId.toString() } })
+ await this.$router.push({name: 'browse-libraries', params: {libraryId: libraryId.toString()}})
}
},
- async loadPage (libraryId: string, page: number) {
+ async loadPage(libraryId: string, page: number) {
+ this.selectedReadLists = []
+
const pageRequest = {
page: page - 1,
size: this.pageSize,
@@ -208,16 +223,19 @@ export default Vue.extend({
this.totalElements = elementsPage.totalElements
this.readLists = elementsPage.content
},
- getLibraryLazy (libraryId: string): LibraryDto | undefined {
+ getLibraryLazy(libraryId: string): LibraryDto | undefined {
if (libraryId !== LIBRARIES_ALL) {
return this.$store.getters.getLibraryById(libraryId)
} else {
return undefined
}
},
- editSingle (element: ReadListDto) {
+ editSingle(element: ReadListDto) {
this.$store.dispatch('dialogEditReadList', element)
},
+ deleteReadLists () {
+ this.$store.dispatch('dialogDeleteReadList', this.selectedReadLists)
+ },
},
})
diff --git a/komga-webui/src/views/Search.vue b/komga-webui/src/views/Search.vue
index 0f56b085a..720f65199 100644
--- a/komga-webui/src/views/Search.vue
+++ b/komga-webui/src/views/Search.vue
@@ -26,6 +26,20 @@
@edit="editMultipleBooks"
/>
+
+
+
+
@@ -62,7 +76,7 @@
nowrap
:edit-function="singleEditBook"
:selected.sync="selectedBooks"
- :selectable="selectedSeries.length === 0"
+ :selectable="selectedSeries.length === 0 && selectedCollections.length === 0 && selectedReadLists.length === 0"
:fixed-item-width="fixedCardWidth"
/>
@@ -76,7 +90,8 @@
@@ -90,7 +105,8 @@
@@ -145,6 +161,8 @@ export default Vue.extend({
loading: false,
selectedSeries: [] as SeriesDto[],
selectedBooks: [] as BookDto[],
+ selectedCollections: [] as CollectionDto[],
+ selectedReadLists: [] as ReadListDto[],
}
},
created () {
@@ -179,6 +197,8 @@ export default Vue.extend({
this.loadResults(val)
this.selectedBooks = []
this.selectedSeries = []
+ this.selectedCollections = []
+ this.selectedReadLists = []
},
deep: true,
immediate: true,
@@ -189,7 +209,7 @@ export default Vue.extend({
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
},
showToolbar (): boolean {
- return this.selectedSeries.length === 0 && this.selectedBooks.length === 0
+ return this.selectedSeries.length === 0 && this.selectedBooks.length === 0 && this.selectedCollections.length === 0 && this.selectedReadLists.length === 0
},
emptyResults (): boolean {
return !this.loading && this.series.length === 0 && this.books.length === 0 && this.collections.length === 0 && this.readLists.length === 0
@@ -261,6 +281,12 @@ export default Vue.extend({
editMultipleBooks () {
this.$store.dispatch('dialogUpdateBooks', this.selectedBooks)
},
+ deleteCollections () {
+ this.$store.dispatch('dialogDeleteCollection', this.selectedCollections)
+ },
+ deleteReadLists () {
+ this.$store.dispatch('dialogDeleteReadList', this.selectedReadLists)
+ },
async markSelectedBooksRead () {
await Promise.all(this.selectedBooks.map(b =>
this.$komgaBooks.updateReadProgress(b.id, { completed: true }),
@@ -277,6 +303,8 @@ export default Vue.extend({
async loadResults (search: string) {
this.selectedBooks = []
this.selectedSeries = []
+ this.selectedCollections = []
+ this.selectedReadLists = []
if (search) {
this.loading = true