mirror of
https://github.com/gotson/komga.git
synced 2025-12-21 16:03:03 +01:00
parent
f0c864f4eb
commit
27edf17424
38 changed files with 1539 additions and 52 deletions
|
|
@ -19,6 +19,25 @@
|
|||
@deleted="collectionDeleted"
|
||||
/>
|
||||
|
||||
<read-list-add-to-dialog
|
||||
v-model="addToReadListDialog"
|
||||
:books="addToReadListBooks"
|
||||
@added="readListAdded"
|
||||
@created="readListAdded"
|
||||
/>
|
||||
|
||||
<read-list-edit-dialog
|
||||
v-model="editReadListDialog"
|
||||
:read-list="editReadList"
|
||||
@updated="readListUpdated"
|
||||
/>
|
||||
|
||||
<read-list-delete-dialog
|
||||
v-model="deleteReadListDialog"
|
||||
:read-list="deleteReadList"
|
||||
@deleted="readListDeleted"
|
||||
/>
|
||||
|
||||
<library-edit-dialog
|
||||
v-model="editLibraryDialog"
|
||||
:library="editLibrary"
|
||||
|
|
@ -62,10 +81,17 @@ import {
|
|||
collectionToEventCollectionDeleted,
|
||||
LIBRARY_DELETED,
|
||||
libraryToEventLibraryDeleted,
|
||||
READLIST_CHANGED,
|
||||
READLIST_DELETED,
|
||||
readListToEventReadListChanged,
|
||||
readListToEventReadListDeleted,
|
||||
SERIES_CHANGED,
|
||||
seriesToEventSeriesChanged,
|
||||
} from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import ReadListAddToDialog from '@/components/dialogs/ReadListAddToDialog.vue'
|
||||
import ReadListDeleteDialog from '@/components/dialogs/ReadListDeleteDialog.vue'
|
||||
import ReadListEditDialog from '@/components/dialogs/ReadListEditDialog.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Dialogs',
|
||||
|
|
@ -73,12 +99,16 @@ export default Vue.extend({
|
|||
CollectionAddToDialog,
|
||||
CollectionEditDialog,
|
||||
CollectionDeleteDialog,
|
||||
ReadListAddToDialog,
|
||||
ReadListEditDialog,
|
||||
ReadListDeleteDialog,
|
||||
LibraryEditDialog,
|
||||
LibraryDeleteDialog,
|
||||
EditBooksDialog,
|
||||
EditSeriesDialog,
|
||||
},
|
||||
computed: {
|
||||
// collections
|
||||
addToCollectionDialog: {
|
||||
get (): boolean {
|
||||
return this.$store.state.addToCollectionDialog
|
||||
|
|
@ -112,6 +142,41 @@ export default Vue.extend({
|
|||
deleteCollection (): CollectionDto {
|
||||
return this.$store.state.deleteCollection
|
||||
},
|
||||
// read lists
|
||||
addToReadListDialog: {
|
||||
get (): boolean {
|
||||
return this.$store.state.addToReadListDialog
|
||||
},
|
||||
set (val) {
|
||||
this.$store.dispatch('dialogAddBooksToReadListDisplay', val)
|
||||
},
|
||||
},
|
||||
addToReadListBooks (): BookDto | BookDto[] {
|
||||
return this.$store.state.addToReadListBooks
|
||||
},
|
||||
editReadListDialog: {
|
||||
get (): boolean {
|
||||
return this.$store.state.editReadListDialog
|
||||
},
|
||||
set (val) {
|
||||
this.$store.dispatch('dialogEditReadListDisplay', val)
|
||||
},
|
||||
},
|
||||
editReadList (): ReadListDto {
|
||||
return this.$store.state.editReadList
|
||||
},
|
||||
deleteReadListDialog: {
|
||||
get (): boolean {
|
||||
return this.$store.state.deleteReadListDialog
|
||||
},
|
||||
set (val) {
|
||||
this.$store.dispatch('dialogDeleteReadListDisplay', val)
|
||||
},
|
||||
},
|
||||
deleteReadList (): ReadListDto {
|
||||
return this.$store.state.deleteReadList
|
||||
},
|
||||
// libraries
|
||||
editLibraryDialog: {
|
||||
get (): boolean {
|
||||
return this.$store.state.editLibraryDialog
|
||||
|
|
@ -134,6 +199,7 @@ export default Vue.extend({
|
|||
deleteLibrary (): LibraryDto {
|
||||
return this.$store.state.deleteLibrary
|
||||
},
|
||||
// books
|
||||
updateBooksDialog: {
|
||||
get (): boolean {
|
||||
return this.$store.state.updateBooksDialog
|
||||
|
|
@ -145,6 +211,7 @@ export default Vue.extend({
|
|||
updateBooks (): BookDto | BookDto[] {
|
||||
return this.$store.state.updateBooks
|
||||
},
|
||||
// series
|
||||
updateSeriesDialog: {
|
||||
get (): boolean {
|
||||
return this.$store.state.updateSeriesDialog
|
||||
|
|
@ -174,6 +241,22 @@ export default Vue.extend({
|
|||
collectionDeleted () {
|
||||
this.$eventHub.$emit(COLLECTION_DELETED, collectionToEventCollectionDeleted(this.deleteCollection))
|
||||
},
|
||||
readListAdded (readList: ReadListDto) {
|
||||
if (Array.isArray(this.addToReadListBooks)) {
|
||||
this.addToReadListBooks.forEach(b => {
|
||||
this.$eventHub.$emit(BOOK_CHANGED, bookToEventBookChanged(b))
|
||||
})
|
||||
} else {
|
||||
this.$eventHub.$emit(BOOK_CHANGED, bookToEventBookChanged(this.addToReadListBooks))
|
||||
}
|
||||
this.$eventHub.$emit(READLIST_CHANGED, readListToEventReadListChanged(readList))
|
||||
},
|
||||
readListUpdated () {
|
||||
this.$eventHub.$emit(READLIST_CHANGED, readListToEventReadListChanged(this.editReadList))
|
||||
},
|
||||
readListDeleted () {
|
||||
this.$eventHub.$emit(READLIST_DELETED, readListToEventReadListDeleted(this.deleteReadList))
|
||||
},
|
||||
libraryDeleted () {
|
||||
this.$eventHub.$emit(LIBRARY_DELETED, libraryToEventLibraryDeleted(this.deleteLibrary))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@
|
|||
:collection="item"
|
||||
:menu.sync="actionMenuState"
|
||||
/>
|
||||
<read-list-actions-menu v-if="computedItem.type() === ItemTypes.READLIST"
|
||||
:read-list="item"
|
||||
:menu.sync="actionMenuState"
|
||||
/>
|
||||
</div>
|
||||
</v-overlay>
|
||||
</v-fade-transition>
|
||||
|
|
@ -119,13 +123,14 @@ import { ReadStatus } from '@/types/enum-books'
|
|||
import { createItem, Item, ItemTypes } from '@/types/items'
|
||||
import Vue from 'vue'
|
||||
import { RawLocation } from 'vue-router'
|
||||
import ReadListActionsMenu from '@/components/menus/ReadListActionsMenu.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ItemCard',
|
||||
components: { BookActionsMenu, SeriesActionsMenu, CollectionActionsMenu },
|
||||
components: { BookActionsMenu, SeriesActionsMenu, CollectionActionsMenu, ReadListActionsMenu },
|
||||
props: {
|
||||
item: {
|
||||
type: Object as () => BookDto | SeriesDto | CollectionDto,
|
||||
type: Object as () => BookDto | SeriesDto | CollectionDto | ReadListDto,
|
||||
required: true,
|
||||
},
|
||||
// hide the bottom part of the card
|
||||
|
|
@ -184,7 +189,7 @@ export default Vue.extend({
|
|||
overlay (): boolean {
|
||||
return this.onEdit !== undefined || this.onSelected !== undefined || this.bookReady || this.canReadPages || this.actionMenu
|
||||
},
|
||||
computedItem (): Item<BookDto | SeriesDto | CollectionDto> {
|
||||
computedItem (): Item<BookDto | SeriesDto | CollectionDto | ReadListDto> {
|
||||
return createItem(this.item)
|
||||
},
|
||||
disableHover (): boolean {
|
||||
|
|
|
|||
|
|
@ -1,31 +1,78 @@
|
|||
<template>
|
||||
<v-bottom-navigation grow color="primary"
|
||||
:fixed="$vuetify.breakpoint.name === 'xs'"
|
||||
<v-bottom-navigation
|
||||
v-if="collectionsCount > 0 || readListsCount > 0"
|
||||
grow color="primary"
|
||||
:fixed="$vuetify.breakpoint.name === 'xs'"
|
||||
>
|
||||
<v-btn :to="{name: 'browse-libraries', params: {libraryId: libraryId}}">
|
||||
<span>Browse</span>
|
||||
<v-icon>mdi-bookshelf</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :to="{name: 'browse-collections', params: {libraryId: libraryId}}">
|
||||
<v-btn
|
||||
v-if="collectionsCount > 0"
|
||||
:to="{name: 'browse-collections', params: {libraryId: libraryId}}"
|
||||
>
|
||||
<span>Collections</span>
|
||||
<v-icon>mdi-layers-triple</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="readListsCount > 0"
|
||||
:to="{name: 'browse-readlists', params: {libraryId: libraryId}}"
|
||||
>
|
||||
<span>Read Lists</span>
|
||||
<v-icon>mdi-book-multiple</v-icon>
|
||||
</v-btn>
|
||||
|
||||
</v-bottom-navigation>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { COLLECTION_CHANGED, READLIST_CHANGED } from '@/types/events'
|
||||
import { LIBRARIES_ALL } from '@/types/library'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'LibraryNavigation',
|
||||
data: () => {
|
||||
return {
|
||||
collectionsCount: 0,
|
||||
readListsCount: 0,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
libraryId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
libraryId: {
|
||||
handler (val) {
|
||||
this.loadCounts(val)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadCounts)
|
||||
this.$eventHub.$on(READLIST_CHANGED, this.reloadCounts)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$eventHub.$off(COLLECTION_CHANGED, this.reloadCounts)
|
||||
this.$eventHub.$off(READLIST_CHANGED, this.reloadCounts)
|
||||
},
|
||||
methods: {
|
||||
reloadCounts () {
|
||||
this.loadCounts(this.libraryId)
|
||||
},
|
||||
async loadCounts (libraryId: string) {
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
this.collectionsCount = (await this.$komgaCollections.getCollections(lib, { size: 1 })).totalElements
|
||||
this.readListsCount = (await this.$komgaReadLists.getReadLists(lib, { size: 1 })).totalElements
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
71
komga-webui/src/components/ReadListsExpansionPanels.vue
Normal file
71
komga-webui/src/components/ReadListsExpansionPanels.vue
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<v-expansion-panels v-model="readListPanel">
|
||||
<v-expansion-panel v-for="(r, index) in readLists"
|
||||
:key="index"
|
||||
>
|
||||
<v-expansion-panel-header>{{ r.name }} read list</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<horizontal-scroller>
|
||||
<template v-slot:prepend>
|
||||
<router-link class="text-overline"
|
||||
:to="{name: 'browse-readlist', params: {readListId: r.id}}"
|
||||
>Manage read list
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="readListsContent[index]"
|
||||
nowrap
|
||||
:selectable="false"
|
||||
:action-menu="false"
|
||||
:fixed-item-width="100"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReadListsExpansionPanels',
|
||||
components: {
|
||||
HorizontalScroller,
|
||||
ItemBrowser,
|
||||
},
|
||||
props: {
|
||||
readLists: {
|
||||
type: Array as () => ReadListDto[],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
readListPanel: undefined as number | undefined,
|
||||
readListsContent: [[]] as any[],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
readLists: {
|
||||
handler (val) {
|
||||
this.readListPanel = undefined
|
||||
this.readListsContent = [...Array(val.length)].map(elem => new Array(0))
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
async readListPanel (val) {
|
||||
if (val !== undefined) {
|
||||
const rlId = this.readLists[val].id
|
||||
if (this.$_.isEmpty(this.readListsContent[val])) {
|
||||
const content = (await this.$komgaReadLists.getBooks(rlId, { unpaged: true } as PageRequest)).content
|
||||
this.readListsContent.splice(val, 1, content)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -19,7 +19,9 @@
|
|||
:min-width="$vuetify.breakpoint.mdAndUp ? $vuetify.breakpoint.width * .4 : $vuetify.breakpoint.width * .8"
|
||||
>
|
||||
<v-list>
|
||||
<v-list-item v-if="series.length === 0 && books.length === 0 && collections.length === 0">No results
|
||||
<v-list-item
|
||||
v-if="series.length === 0 && books.length === 0 && collections.length === 0 && readLists.length === 0">
|
||||
No results
|
||||
</v-list-item>
|
||||
|
||||
<template v-if="series.length !== 0">
|
||||
|
|
@ -76,13 +78,31 @@
|
|||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template v-if="readLists.length !== 0">
|
||||
<v-subheader>READ LISTS</v-subheader>
|
||||
<v-list-item v-for="item in readLists"
|
||||
:key="item.id"
|
||||
link
|
||||
:to="{name: 'browse-readlist', params: {readListId: item.id}}"
|
||||
>
|
||||
<v-img :src="readListThumbnailUrl(item.id)"
|
||||
height="50"
|
||||
max-width="35"
|
||||
class="ma-1 mr-3"
|
||||
/>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.name"/>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { bookThumbnailUrl, collectionThumbnailUrl, seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { bookThumbnailUrl, collectionThumbnailUrl, readListThumbnailUrl, seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { debounce } from 'lodash'
|
||||
import Vue from 'vue'
|
||||
|
||||
|
|
@ -96,6 +116,7 @@ export default Vue.extend({
|
|||
series: [] as SeriesDto[],
|
||||
books: [] as BookDto[],
|
||||
collections: [] as CollectionDto[],
|
||||
readLists: [] as ReadListDto[],
|
||||
pageSize: 10,
|
||||
}
|
||||
},
|
||||
|
|
@ -114,6 +135,7 @@ export default Vue.extend({
|
|||
this.series = (await this.$komgaSeries.getSeries(undefined, { size: this.pageSize }, query)).content
|
||||
this.books = (await this.$komgaBooks.getBooks(undefined, { size: this.pageSize }, query)).content
|
||||
this.collections = (await this.$komgaCollections.getCollections(undefined, { size: this.pageSize }, query)).content
|
||||
this.readLists = (await this.$komgaReadLists.getReadLists(undefined, { size: this.pageSize }, query)).content
|
||||
this.showResults = true
|
||||
this.loading = false
|
||||
} else {
|
||||
|
|
@ -126,6 +148,7 @@ export default Vue.extend({
|
|||
this.series = []
|
||||
this.books = []
|
||||
this.collections = []
|
||||
this.readLists = []
|
||||
},
|
||||
searchDetails () {
|
||||
const s = this.search
|
||||
|
|
@ -142,6 +165,9 @@ export default Vue.extend({
|
|||
collectionThumbnailUrl (collectionId: string): string {
|
||||
return collectionThumbnailUrl(collectionId)
|
||||
},
|
||||
readListThumbnailUrl (readListId: string): string {
|
||||
return readListThumbnailUrl(readListId)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@
|
|||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="addToReadList" v-if="isAdmin">
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-icon v-on="on">mdi-book-plus-multiple</v-icon>
|
||||
</template>
|
||||
<span>Add to read list</span>
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="edit" v-if="isAdmin">
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
|
|
@ -71,6 +80,9 @@ export default Vue.extend({
|
|||
markUnread () {
|
||||
this.$emit('mark-unread')
|
||||
},
|
||||
addToReadList () {
|
||||
this.$emit('add-to-readlist')
|
||||
},
|
||||
edit () {
|
||||
this.$emit('edit')
|
||||
},
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ export default Vue.extend({
|
|||
snackText: '',
|
||||
modal: false,
|
||||
collections: [] as CollectionDto[],
|
||||
selectedCollection: null,
|
||||
newCollection: '',
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@
|
|||
label="Collections"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="form.importComicInfoReadList"
|
||||
label="Read lists"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
|
|
@ -186,6 +191,7 @@ export default Vue.extend({
|
|||
importComicInfoBook: true,
|
||||
importComicInfoSeries: true,
|
||||
importComicInfoCollection: true,
|
||||
importComicInfoReadList: true,
|
||||
importEpubBook: true,
|
||||
importEpubSeries: true,
|
||||
importLocalArtwork: true,
|
||||
|
|
@ -254,6 +260,7 @@ export default Vue.extend({
|
|||
this.form.importComicInfoBook = library ? library.importComicInfoBook : true
|
||||
this.form.importComicInfoSeries = library ? library.importComicInfoSeries : true
|
||||
this.form.importComicInfoCollection = library ? library.importComicInfoCollection : true
|
||||
this.form.importComicInfoReadList = library ? library.importComicInfoReadList : true
|
||||
this.form.importEpubBook = library ? library.importEpubBook : true
|
||||
this.form.importEpubSeries = library ? library.importEpubSeries : true
|
||||
this.form.importLocalArtwork = library ? library.importLocalArtwork : true
|
||||
|
|
@ -271,6 +278,7 @@ export default Vue.extend({
|
|||
importComicInfoBook: this.form.importComicInfoBook,
|
||||
importComicInfoSeries: this.form.importComicInfoSeries,
|
||||
importComicInfoCollection: this.form.importComicInfoCollection,
|
||||
importComicInfoReadList: this.form.importComicInfoReadList,
|
||||
importEpubBook: this.form.importEpubBook,
|
||||
importEpubSeries: this.form.importEpubSeries,
|
||||
importLocalArtwork: this.form.importLocalArtwork,
|
||||
|
|
|
|||
172
komga-webui/src/components/dialogs/ReadListAddToDialog.vue
Normal file
172
komga-webui/src/components/dialogs/ReadListAddToDialog.vue
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="modal"
|
||||
max-width="450"
|
||||
scrollable
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>Add to read list</v-card-title>
|
||||
<v-btn icon absolute top right @click="dialogClose">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-divider/>
|
||||
|
||||
<v-card-text style="height: 50%">
|
||||
<v-container fluid>
|
||||
|
||||
<v-row align="center">
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="newReadList"
|
||||
label="Create new read list"
|
||||
@keydown.enter="create"
|
||||
:error-messages="duplicate"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="create"
|
||||
:disabled="newReadList.length === 0 || duplicate !== ''"
|
||||
>Create
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider/>
|
||||
|
||||
<v-row v-if="readLists.length !== 0">
|
||||
<v-col>
|
||||
<v-list elevation="5">
|
||||
<div v-for="(c, index) in readLists"
|
||||
:key="index"
|
||||
>
|
||||
<v-list-item @click="addTo(c)"
|
||||
two-line
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ c.name }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ c.bookIds.length }} books</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-divider v-if="index !== readLists.length-1"/>
|
||||
</div>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
bottom
|
||||
color="error"
|
||||
>
|
||||
{{ snackText }}
|
||||
<v-btn
|
||||
text
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReadListAddToDialog',
|
||||
data: () => {
|
||||
return {
|
||||
confirmDelete: false,
|
||||
snackbar: false,
|
||||
snackText: '',
|
||||
modal: false,
|
||||
readLists: [] as ReadListDto[],
|
||||
newReadList: '',
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: Boolean,
|
||||
books: {
|
||||
type: [Object as () => BookDto, Array as () => BookDto[]],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
async value (val) {
|
||||
this.modal = val
|
||||
if (val) {
|
||||
this.newReadList = ''
|
||||
this.readLists = (await this.$komgaReadLists.getReadLists(undefined, { unpaged: true } as PageRequest)).content
|
||||
}
|
||||
},
|
||||
modal (val) {
|
||||
!val && this.dialogClose()
|
||||
},
|
||||
},
|
||||
async mounted () {
|
||||
|
||||
},
|
||||
computed: {
|
||||
bookIds (): string[] {
|
||||
if (Array.isArray(this.books)) return this.books.map(s => s.id)
|
||||
else return [this.books.id]
|
||||
},
|
||||
duplicate (): string {
|
||||
if (this.newReadList !== '' && this.readLists.some(e => e.name === this.newReadList)) {
|
||||
return 'A read list with this name already exists'
|
||||
} else return ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dialogClose () {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
showSnack (message: string) {
|
||||
this.snackText = message
|
||||
this.snackbar = true
|
||||
},
|
||||
async addTo (readList: ReadListDto) {
|
||||
const bookIds = this.$_.uniq(readList.bookIds.concat(this.bookIds))
|
||||
|
||||
const toUpdate = {
|
||||
bookIds: bookIds,
|
||||
} as ReadListUpdateDto
|
||||
|
||||
try {
|
||||
await this.$komgaReadLists.patchReadList(readList.id, toUpdate)
|
||||
this.$emit('added', readList)
|
||||
this.dialogClose()
|
||||
} catch (e) {
|
||||
this.showSnack(e.message)
|
||||
}
|
||||
},
|
||||
async create () {
|
||||
const toCreate = {
|
||||
name: this.newReadList,
|
||||
bookIds: this.bookIds,
|
||||
} as ReadListCreationDto
|
||||
|
||||
try {
|
||||
const created = await this.$komgaReadLists.postReadList(toCreate)
|
||||
this.$emit('created', created)
|
||||
this.dialogClose()
|
||||
} catch (e) {
|
||||
this.showSnack(e.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
112
komga-webui/src/components/dialogs/ReadListDeleteDialog.vue
Normal file
112
komga-webui/src/components/dialogs/ReadListDeleteDialog.vue
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="modal"
|
||||
max-width="450"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>Delete Read List</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col>The read list <b>{{ readList.name }}</b> will be removed from this server. Your media files will
|
||||
not be affected. This <b>cannot</b> be undone. Continue ?
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-checkbox v-model="confirmDelete" color="red">
|
||||
<template v-slot:label>
|
||||
Yes, delete the read list "{{ readList.name }}"
|
||||
</template>
|
||||
</v-checkbox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn text @click="dialogCancel">Cancel</v-btn>
|
||||
<v-btn text class="red--text"
|
||||
@click="dialogConfirm"
|
||||
:disabled="!confirmDelete"
|
||||
>Delete
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
bottom
|
||||
color="error"
|
||||
>
|
||||
{{ snackText }}
|
||||
<v-btn
|
||||
text
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReadListDeleteDialog',
|
||||
data: () => {
|
||||
return {
|
||||
confirmDelete: false,
|
||||
snackbar: false,
|
||||
snackText: '',
|
||||
modal: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: Boolean,
|
||||
readList: {
|
||||
type: Object as () => ReadListDto,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.modal = val
|
||||
},
|
||||
modal (val) {
|
||||
!val && this.dialogCancel()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dialogCancel () {
|
||||
this.$emit('input', false)
|
||||
this.confirmDelete = false
|
||||
},
|
||||
dialogConfirm () {
|
||||
this.delete()
|
||||
this.$emit('input', false)
|
||||
},
|
||||
showSnack (message: string) {
|
||||
this.snackText = message
|
||||
this.snackbar = true
|
||||
},
|
||||
async delete () {
|
||||
try {
|
||||
await this.$komgaReadLists.deleteReadList(this.readList.id)
|
||||
this.$emit('deleted', true)
|
||||
} catch (e) {
|
||||
this.showSnack(e.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
139
komga-webui/src/components/dialogs/ReadListEditDialog.vue
Normal file
139
komga-webui/src/components/dialogs/ReadListEditDialog.vue
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="modal"
|
||||
max-width="450"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>Edit read list</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field v-model="form.name"
|
||||
label="Name"
|
||||
:error-messages="getErrorsName"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn text @click="dialogCancel">Cancel</v-btn>
|
||||
<v-btn text class="primary--text"
|
||||
@click="dialogConfirm"
|
||||
:disabled="getErrorsName !== ''"
|
||||
>Save changes
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
bottom
|
||||
color="error"
|
||||
>
|
||||
{{ snackText }}
|
||||
<v-btn
|
||||
text
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { UserRoles } from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReadListEditDialog',
|
||||
data: () => {
|
||||
return {
|
||||
UserRoles,
|
||||
snackbar: false,
|
||||
snackText: '',
|
||||
modal: false,
|
||||
readLists: [] as ReadListDto[],
|
||||
form: {
|
||||
name: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: Boolean,
|
||||
readList: {
|
||||
type: Object as () => ReadListDto,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
async value (val) {
|
||||
this.modal = val
|
||||
if (val) {
|
||||
this.readLists = (await this.$komgaReadLists.getReadLists(undefined, { unpaged: true } as PageRequest)).content
|
||||
this.dialogReset(this.readList)
|
||||
}
|
||||
},
|
||||
modal (val) {
|
||||
!val && this.dialogCancel()
|
||||
},
|
||||
readList: {
|
||||
handler (val) {
|
||||
this.dialogReset(val)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
libraries (): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
getErrorsName (): string {
|
||||
if (this.form.name === '') return 'Name is required'
|
||||
if (this.form.name !== this.readList.name && this.readLists.some(e => e.name === this.form.name)) {
|
||||
return 'A read list with this name already exists'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async dialogReset (readList: ReadListDto) {
|
||||
this.form.name = readList.name
|
||||
},
|
||||
dialogCancel () {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
dialogConfirm () {
|
||||
this.edit()
|
||||
this.$emit('input', false)
|
||||
},
|
||||
showSnack (message: string) {
|
||||
this.snackText = message
|
||||
this.snackbar = true
|
||||
},
|
||||
async edit () {
|
||||
try {
|
||||
const update = {
|
||||
name: this.form.name,
|
||||
} as ReadListUpdateDto
|
||||
|
||||
await this.$komgaReadLists.patchReadList(this.readList.id, update)
|
||||
this.$emit('updated', true)
|
||||
} catch (e) {
|
||||
this.showSnack(e.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -6,13 +6,16 @@
|
|||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list dense>
|
||||
<v-list-item @click="analyze" v-if="isAdmin">
|
||||
<v-list-item-title>Analyze</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="refreshMetadata" v-if="isAdmin">
|
||||
<v-list-item-title>Refresh metadata</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="addToReadList" v-if="isAdmin">
|
||||
<v-list-item-title>Add to read list</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="markRead" v-if="!isRead">
|
||||
<v-list-item-title>Mark as read</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
|
@ -69,6 +72,9 @@ export default Vue.extend({
|
|||
refreshMetadata () {
|
||||
this.$komgaBooks.refreshMetadata(this.book)
|
||||
},
|
||||
addToReadList () {
|
||||
this.$store.dispatch('dialogAddBooksToReadList', this.book)
|
||||
},
|
||||
async markRead () {
|
||||
const readProgress = { completed: true } as ReadProgressUpdateDto
|
||||
await this.$komgaBooks.updateReadProgress(this.book.id, readProgress)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list dense>
|
||||
<v-list-item @click="promptDeleteCollection"
|
||||
class="list-warning">
|
||||
<v-list-item-title>Delete</v-list-item-title>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list dense>
|
||||
<v-list-item @click="scan">
|
||||
<v-list-item-title>Scan library files</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
|
|
|||
57
komga-webui/src/components/menus/ReadListActionsMenu.vue
Normal file
57
komga-webui/src/components/menus/ReadListActionsMenu.vue
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-menu offset-y v-if="isAdmin" v-model="menuState">
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on" @click.prevent="">
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item @click="promptDeleteReadList"
|
||||
class="list-warning">
|
||||
<v-list-item-title>Delete</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReadListActionsMenu',
|
||||
data: function () {
|
||||
return {
|
||||
menuState: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
readList: {
|
||||
type: Object as () => ReadListDto,
|
||||
required: true,
|
||||
},
|
||||
menu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
menuState (val) {
|
||||
this.$emit('update:menu', val)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isAdmin (): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
promptDeleteReadList () {
|
||||
this.$store.dispatch('dialogDeleteReadList', this.readList)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
@import "../../styles/list-warning.css";
|
||||
</style>
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list dense>
|
||||
<v-list-item @click="analyze" v-if="isAdmin">
|
||||
<v-list-item-title>Analyze</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
|
|
|||
|
|
@ -39,3 +39,7 @@ export function seriesThumbnailUrl (seriesId: string): string {
|
|||
export function collectionThumbnailUrl (collectionId: string): string {
|
||||
return `${urls.originNoSlash}/api/v1/collections/${collectionId}/thumbnail`
|
||||
}
|
||||
|
||||
export function readListThumbnailUrl (readListId: string): string {
|
||||
return `${urls.originNoSlash}/api/v1/readlists/${readListId}/thumbnail`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import httpPlugin from './plugins/http.plugin'
|
|||
import komgaBooks from './plugins/komga-books.plugin'
|
||||
import komgaClaim from './plugins/komga-claim.plugin'
|
||||
import komgaCollections from './plugins/komga-collections.plugin'
|
||||
import komgaReadLists from './plugins/komga-readlists.plugin'
|
||||
import komgaFileSystem from './plugins/komga-filesystem.plugin'
|
||||
import komgaLibraries from './plugins/komga-libraries.plugin'
|
||||
import komgaReferential from './plugins/komga-referential.plugin'
|
||||
|
|
@ -30,6 +31,7 @@ Vue.use(httpPlugin)
|
|||
Vue.use(komgaFileSystem, { http: Vue.prototype.$http })
|
||||
Vue.use(komgaSeries, { http: Vue.prototype.$http })
|
||||
Vue.use(komgaCollections, { http: Vue.prototype.$http })
|
||||
Vue.use(komgaReadLists, { http: Vue.prototype.$http })
|
||||
Vue.use(komgaBooks, { http: Vue.prototype.$http })
|
||||
Vue.use(komgaReferential, { http: Vue.prototype.$http })
|
||||
Vue.use(komgaClaim, { http: Vue.prototype.$http })
|
||||
|
|
|
|||
17
komga-webui/src/plugins/komga-readlists.plugin.ts
Normal file
17
komga-webui/src/plugins/komga-readlists.plugin.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { AxiosInstance } from 'axios'
|
||||
import _Vue from 'vue'
|
||||
import KomgaReadListsService from '@/services/komga-readlists.service'
|
||||
|
||||
export default {
|
||||
install (
|
||||
Vue: typeof _Vue,
|
||||
{ http }: { http: AxiosInstance }) {
|
||||
Vue.prototype.$komgaReadLists = new KomgaReadListsService(http)
|
||||
},
|
||||
}
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$komgaReadLists: KomgaReadListsService;
|
||||
}
|
||||
}
|
||||
|
|
@ -85,12 +85,25 @@ const router = new Router({
|
|||
component: () => import(/* webpackChunkName: "browse-collections" */ './views/BrowseCollections.vue'),
|
||||
props: (route) => ({ libraryId: route.params.libraryId }),
|
||||
},
|
||||
{
|
||||
path: '/libraries/:libraryId/readlists',
|
||||
name: 'browse-readlists',
|
||||
beforeEnter: noLibraryGuard,
|
||||
component: () => import(/* webpackChunkName: "browse-readlists" */ './views/BrowseReadLists.vue'),
|
||||
props: (route) => ({ libraryId: route.params.libraryId }),
|
||||
},
|
||||
{
|
||||
path: '/collections/:collectionId',
|
||||
name: 'browse-collection',
|
||||
component: () => import(/* webpackChunkName: "browse-collection" */ './views/BrowseCollection.vue'),
|
||||
props: (route) => ({ collectionId: route.params.collectionId }),
|
||||
},
|
||||
{
|
||||
path: '/readlists/:readListId',
|
||||
name: 'browse-readlist',
|
||||
component: () => import(/* webpackChunkName: "browse-readlist" */ './views/BrowseReadList.vue'),
|
||||
props: (route) => ({ readListId: route.params.readListId }),
|
||||
},
|
||||
{
|
||||
path: '/series/:seriesId',
|
||||
name: 'browse-series',
|
||||
|
|
|
|||
|
|
@ -101,6 +101,18 @@ export default class KomgaBooksService {
|
|||
}
|
||||
}
|
||||
|
||||
async getReadLists (bookId: string): Promise<ReadListDto[]> {
|
||||
try {
|
||||
return (await this.http.get(`${API_BOOKS}/${bookId}/readlists`)).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve read lists'
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeBook (book: BookDto) {
|
||||
try {
|
||||
await this.http.post(`${API_BOOKS}/${book.id}/analyze`)
|
||||
|
|
|
|||
95
komga-webui/src/services/komga-readlists.service.ts
Normal file
95
komga-webui/src/services/komga-readlists.service.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { AxiosInstance } from 'axios'
|
||||
|
||||
const qs = require('qs')
|
||||
|
||||
const API_READLISTS = '/api/v1/readlists'
|
||||
|
||||
export default class KomgaReadListsService {
|
||||
private http: AxiosInstance
|
||||
|
||||
constructor (http: AxiosInstance) {
|
||||
this.http = http
|
||||
}
|
||||
|
||||
async getReadLists (libraryIds?: string[], pageRequest?: PageRequest, search?: string): Promise<Page<ReadListDto>> {
|
||||
try {
|
||||
const params = { ...pageRequest } as any
|
||||
if (libraryIds) params.library_id = libraryIds
|
||||
if (search) params.search = search
|
||||
|
||||
return (await this.http.get(API_READLISTS, {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, { indices: false }),
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve readLists'
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async getOneReadList (readListId: string): Promise<ReadListDto> {
|
||||
try {
|
||||
return (await this.http.get(`${API_READLISTS}/${readListId}`)).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve readList'
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async postReadList (readList: ReadListCreationDto): Promise<ReadListDto> {
|
||||
try {
|
||||
return (await this.http.post(API_READLISTS, readList)).data
|
||||
} catch (e) {
|
||||
let msg = `An error occurred while trying to add readList '${readList.name}'`
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async patchReadList (readListId: string, readList: ReadListUpdateDto) {
|
||||
try {
|
||||
await this.http.patch(`${API_READLISTS}/${readListId}`, readList)
|
||||
} catch (e) {
|
||||
let msg = `An error occurred while trying to update readList '${readListId}'`
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteReadList (readListId: string) {
|
||||
try {
|
||||
await this.http.delete(`${API_READLISTS}/${readListId}`)
|
||||
} catch (e) {
|
||||
let msg = `An error occurred while trying to delete readList '${readListId}'`
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async getBooks (readListId: string, pageRequest?: PageRequest): Promise<Page<BookDto>> {
|
||||
try {
|
||||
const params = { ...pageRequest }
|
||||
return (await this.http.get(`${API_READLISTS}/${readListId}/books`, {
|
||||
params: params,
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve books'
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,22 +5,34 @@ Vue.use(Vuex)
|
|||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
// collections
|
||||
addToCollectionSeries: {} as SeriesDto | SeriesDto[],
|
||||
addToCollectionDialog: false,
|
||||
editCollection: {} as CollectionDto,
|
||||
editCollectionDialog: false,
|
||||
deleteCollection: {} as CollectionDto,
|
||||
deleteCollectionDialog: false,
|
||||
// read lists
|
||||
addToReadListBooks: {} as BookDto | BookDto[],
|
||||
addToReadListDialog: false,
|
||||
editReadList: {} as ReadListDto,
|
||||
editReadListDialog: false,
|
||||
deleteReadList: {} as ReadListDto,
|
||||
deleteReadListDialog: false,
|
||||
// libraries
|
||||
editLibrary: {} as LibraryDto | undefined,
|
||||
editLibraryDialog: false,
|
||||
deleteLibrary: {} as LibraryDto,
|
||||
deleteLibraryDialog: false,
|
||||
// books
|
||||
updateBooks: {} as BookDto | BookDto[],
|
||||
updateBooksDialog: false,
|
||||
// series
|
||||
updateSeries: {} as SeriesDto | SeriesDto[],
|
||||
updateSeriesDialog: false,
|
||||
},
|
||||
mutations: {
|
||||
// Collections
|
||||
setAddToCollectionSeries (state, series) {
|
||||
state.addToCollectionSeries = series
|
||||
},
|
||||
|
|
@ -36,27 +48,49 @@ export default new Vuex.Store({
|
|||
setDeleteCollection (state, collection) {
|
||||
state.deleteCollection = collection
|
||||
},
|
||||
setDeleteCollectionDialog (state, dialog) {
|
||||
state.deleteCollectionDialog = dialog
|
||||
},
|
||||
// Read Lists
|
||||
setAddToReadListBooks (state, Book) {
|
||||
state.addToReadListBooks = Book
|
||||
},
|
||||
setAddToReadListDialog (state, dialog) {
|
||||
state.addToReadListDialog = dialog
|
||||
},
|
||||
setEditReadList (state, ReadList) {
|
||||
state.editReadList = ReadList
|
||||
},
|
||||
setEditReadListDialog (state, dialog) {
|
||||
state.editReadListDialog = dialog
|
||||
},
|
||||
setDeleteReadList (state, ReadList) {
|
||||
state.deleteReadList = ReadList
|
||||
},
|
||||
setDeleteReadListDialog (state, dialog) {
|
||||
state.deleteReadListDialog = dialog
|
||||
},
|
||||
// Libraries
|
||||
setEditLibrary (state, library) {
|
||||
state.editLibrary = library
|
||||
},
|
||||
setEditLibraryDialog (state, dialog) {
|
||||
state.editLibraryDialog = dialog
|
||||
},
|
||||
setDeleteCollectionDialog (state, dialog) {
|
||||
state.deleteCollectionDialog = dialog
|
||||
},
|
||||
setDeleteLibrary (state, library) {
|
||||
state.deleteLibrary = library
|
||||
},
|
||||
setDeleteLibraryDialog (state, dialog) {
|
||||
state.deleteLibraryDialog = dialog
|
||||
},
|
||||
// Books
|
||||
setUpdateBooks (state, books) {
|
||||
state.updateBooks = books
|
||||
},
|
||||
setUpdateBooksDialog (state, dialog) {
|
||||
state.updateBooksDialog = dialog
|
||||
},
|
||||
// Series
|
||||
setUpdateSeries (state, series) {
|
||||
state.updateSeries = series
|
||||
},
|
||||
|
|
@ -65,6 +99,7 @@ export default new Vuex.Store({
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
// collections
|
||||
dialogAddSeriesToCollection ({ commit }, series) {
|
||||
commit('setAddToCollectionSeries', series)
|
||||
commit('setAddToCollectionDialog', true)
|
||||
|
|
@ -86,6 +121,29 @@ export default new Vuex.Store({
|
|||
dialogDeleteCollectionDisplay ({ commit }, value) {
|
||||
commit('setDeleteCollectionDialog', value)
|
||||
},
|
||||
// read lists
|
||||
dialogAddBooksToReadList ({ commit }, books) {
|
||||
commit('setAddToReadListBooks', books)
|
||||
commit('setAddToReadListDialog', true)
|
||||
},
|
||||
dialogAddBooksToReadListDisplay ({ commit }, value) {
|
||||
commit('setAddToReadListDialog', value)
|
||||
},
|
||||
dialogEditReadList ({ commit }, readList) {
|
||||
commit('setEditReadList', readList)
|
||||
commit('setEditReadListDialog', true)
|
||||
},
|
||||
dialogEditReadListDisplay ({ commit }, value) {
|
||||
commit('setEditReadListDialog', value)
|
||||
},
|
||||
dialogDeleteReadList ({ commit }, readList) {
|
||||
commit('setDeleteReadList', readList)
|
||||
commit('setDeleteReadListDialog', true)
|
||||
},
|
||||
dialogDeleteReadListDisplay ({ commit }, value) {
|
||||
commit('setDeleteReadListDialog', value)
|
||||
},
|
||||
// libraries
|
||||
dialogAddLibrary ({ commit }) {
|
||||
commit('setEditLibrary', undefined)
|
||||
commit('setEditLibraryDialog', true)
|
||||
|
|
@ -104,6 +162,7 @@ export default new Vuex.Store({
|
|||
dialogDeleteLibraryDisplay ({ commit }, value) {
|
||||
commit('setDeleteLibraryDialog', value)
|
||||
},
|
||||
// books
|
||||
dialogUpdateBooks ({ commit }, books) {
|
||||
commit('setUpdateBooks', books)
|
||||
commit('setUpdateBooksDialog', true)
|
||||
|
|
@ -111,6 +170,7 @@ export default new Vuex.Store({
|
|||
dialogUpdateBooksDisplay ({ commit }, value) {
|
||||
commit('setUpdateBooksDialog', value)
|
||||
},
|
||||
// series
|
||||
dialogUpdateSeries ({ commit }, series) {
|
||||
commit('setUpdateSeries', series)
|
||||
commit('setUpdateSeriesDialog', true)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ interface EventCollectionDeleted {
|
|||
id: string
|
||||
}
|
||||
|
||||
interface EventReadListChanged {
|
||||
id: string
|
||||
}
|
||||
|
||||
interface EventReadListDeleted {
|
||||
id: string
|
||||
}
|
||||
|
||||
interface EventLibraryAdded {
|
||||
id: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ export const BOOK_CHANGED = 'book-changed'
|
|||
export const SERIES_CHANGED = 'series-changed'
|
||||
export const COLLECTION_DELETED = 'collection-deleted'
|
||||
export const COLLECTION_CHANGED = 'collection-changed'
|
||||
export const READLIST_DELETED = 'readlist-deleted'
|
||||
export const READLIST_CHANGED = 'readlist-changed'
|
||||
export const LIBRARY_ADDED = 'library-added'
|
||||
export const LIBRARY_CHANGED = 'library-changed'
|
||||
export const LIBRARY_DELETED = 'library-deleted'
|
||||
|
|
@ -32,6 +34,18 @@ export function collectionToEventCollectionDeleted (collection: CollectionDto):
|
|||
} as EventCollectionDeleted
|
||||
}
|
||||
|
||||
export function readListToEventReadListChanged (readList: ReadListDto): EventReadListChanged {
|
||||
return {
|
||||
id: readList.id,
|
||||
} as EventReadListChanged
|
||||
}
|
||||
|
||||
export function readListToEventReadListDeleted (readList: ReadListDto): EventReadListDeleted {
|
||||
return {
|
||||
id: readList.id,
|
||||
} as EventReadListDeleted
|
||||
}
|
||||
|
||||
export function libraryToEventLibraryAdded (library: LibraryDto): EventLibraryAdded {
|
||||
return {
|
||||
id: library.id,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { bookThumbnailUrl, collectionThumbnailUrl, seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { bookThumbnailUrl, collectionThumbnailUrl, readListThumbnailUrl, seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { RawLocation } from 'vue-router/types/router'
|
||||
|
||||
function plural (count: number, singular: string, plural: string) {
|
||||
|
|
@ -6,11 +6,13 @@ function plural (count: number, singular: string, plural: string) {
|
|||
}
|
||||
|
||||
export enum ItemTypes {
|
||||
BOOK, SERIES, COLLECTION
|
||||
BOOK, SERIES, COLLECTION, READLIST
|
||||
}
|
||||
|
||||
export function createItem (item: BookDto | SeriesDto | CollectionDto): Item<BookDto | SeriesDto | CollectionDto> {
|
||||
if ('seriesIds' in item) {
|
||||
export function createItem (item: BookDto | SeriesDto | CollectionDto | ReadListDto): Item<BookDto | SeriesDto | CollectionDto | ReadListDto> {
|
||||
if ('bookIds' in item) {
|
||||
return new ReadListItem(item)
|
||||
} else if ('seriesIds' in item) {
|
||||
return new CollectionItem(item)
|
||||
} else if ('seriesId' in item) {
|
||||
return new BookItem(item)
|
||||
|
|
@ -116,3 +118,26 @@ export class CollectionItem extends Item<CollectionDto> {
|
|||
return { name: 'browse-collection', params: { collectionId: this.item.id.toString() } }
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadListItem extends Item<ReadListDto> {
|
||||
thumbnailUrl (): string {
|
||||
return readListThumbnailUrl(this.item.id)
|
||||
}
|
||||
|
||||
type (): ItemTypes {
|
||||
return ItemTypes.READLIST
|
||||
}
|
||||
|
||||
title (): string {
|
||||
return this.item.name
|
||||
}
|
||||
|
||||
body (): string {
|
||||
const c = this.item.bookIds.length
|
||||
return `<span>${c} Books</span>`
|
||||
}
|
||||
|
||||
to (): RawLocation {
|
||||
return { name: 'browse-readlist', params: { readListId: this.item.id.toString() } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ interface LibraryCreationDto {
|
|||
importComicInfoBook: boolean,
|
||||
importComicInfoSeries: boolean,
|
||||
importComicInfoCollection: boolean,
|
||||
importComicInfoReadList: boolean,
|
||||
importEpubBook: boolean,
|
||||
importEpubSeries: boolean,
|
||||
importLocalArtwork: boolean,
|
||||
|
|
@ -17,6 +18,7 @@ interface LibraryUpdateDto {
|
|||
importComicInfoBook: boolean,
|
||||
importComicInfoSeries: boolean,
|
||||
importComicInfoCollection: boolean,
|
||||
importComicInfoReadList: boolean,
|
||||
importEpubBook: boolean,
|
||||
importEpubSeries: boolean,
|
||||
importLocalArtwork: boolean,
|
||||
|
|
@ -31,6 +33,7 @@ interface LibraryDto {
|
|||
importComicInfoBook: boolean,
|
||||
importComicInfoSeries: boolean,
|
||||
importComicInfoCollection: boolean,
|
||||
importComicInfoReadList: boolean,
|
||||
importEpubBook: boolean,
|
||||
importEpubSeries: boolean,
|
||||
importLocalArtwork: boolean,
|
||||
|
|
|
|||
18
komga-webui/src/types/komga-readlists.ts
Normal file
18
komga-webui/src/types/komga-readlists.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
interface ReadListDto {
|
||||
id: string,
|
||||
name: string,
|
||||
filtered: boolean,
|
||||
bookIds: string[],
|
||||
createdDate: string,
|
||||
lastModifiedDate: string
|
||||
}
|
||||
|
||||
interface ReadListCreationDto {
|
||||
name: string,
|
||||
bookIds: string[]
|
||||
}
|
||||
|
||||
interface ReadListUpdateDto {
|
||||
name?: string,
|
||||
bookIds?: string[]
|
||||
}
|
||||
1
komga-webui/src/types/library.ts
Normal file
1
komga-webui/src/types/library.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const LIBRARIES_ALL = 'all'
|
||||
|
|
@ -89,8 +89,7 @@
|
|||
<badge v-if="book.metadata.ageRating">{{ book.metadata.ageRating }}+</badge>
|
||||
</v-col>
|
||||
<v-col cols="auto" v-if="book.metadata.releaseDate">
|
||||
{{ book.metadata.releaseDate | moment
|
||||
('MMMM DD, YYYY') }}
|
||||
{{ book.metadata.releaseDate | moment('MMMM DD, YYYY') }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
|
@ -121,6 +120,18 @@
|
|||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="$vuetify.breakpoint.name !== 'xs'">
|
||||
<v-col>
|
||||
<read-lists-expansion-panels :read-lists="readLists"/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="$vuetify.breakpoint.name === 'xs'">
|
||||
<v-col class="pt-0 py-1">
|
||||
<read-lists-expansion-panels :read-lists="readLists"/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
|
@ -199,10 +210,11 @@ import { bookFileUrl, bookThumbnailUrl } from '@/functions/urls'
|
|||
import { ReadStatus } from '@/types/enum-books'
|
||||
import { BOOK_CHANGED, LIBRARY_DELETED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import ReadListsExpansionPanels from '@/components/ReadListsExpansionPanels.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseBook',
|
||||
components: { ToolbarSticky, Badge, ItemCard, BookActionsMenu },
|
||||
components: { ToolbarSticky, Badge, ItemCard, BookActionsMenu, ReadListsExpansionPanels },
|
||||
data: () => {
|
||||
return {
|
||||
book: {} as BookDto,
|
||||
|
|
@ -210,6 +222,7 @@ export default Vue.extend({
|
|||
siblings: [] as BookDto[],
|
||||
siblingPrevious: {} as BookDto,
|
||||
siblingNext: {} as BookDto,
|
||||
readLists: [] as ReadListDto[],
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
|
|
@ -291,6 +304,7 @@ export default Vue.extend({
|
|||
this.book = await this.$komgaBooks.getBook(bookId)
|
||||
this.series = await this.$komgaSeries.getOneSeries(this.book.seriesId)
|
||||
this.siblings = (await this.$komgaSeries.getBooks(this.book.seriesId, { unpaged: true } as PageRequest)).content
|
||||
this.readLists = await this.$komgaBooks.getReadLists(this.bookId)
|
||||
|
||||
if (this.$_.has(this.book, 'metadata.title')) {
|
||||
document.title = `Komga - ${getBookTitleCompact(this.book.metadata.title, this.series.metadata.title)}`
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
|||
import { COLLECTION_CHANGED, COLLECTION_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
|
||||
import { LIBRARIES_ALL } from '@/types/library'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseCollection',
|
||||
|
|
@ -205,7 +206,7 @@ export default Vue.extend({
|
|||
this.$store.dispatch('dialogEditCollection', this.collection)
|
||||
},
|
||||
afterDelete () {
|
||||
this.$router.push({ name: 'browse-collections', params: { libraryId: '0' } })
|
||||
this.$router.push({ name: 'browse-collections', params: { libraryId: LIBRARIES_ALL } })
|
||||
},
|
||||
reloadSeries (event: EventSeriesChanged) {
|
||||
if (this.series.some(s => s.id === event.id)) this.loadCollection(this.collectionId)
|
||||
|
|
|
|||
|
|
@ -43,12 +43,12 @@ import ItemBrowser from '@/components/ItemBrowser.vue'
|
|||
import LibraryNavigation from '@/components/LibraryNavigation.vue'
|
||||
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import { COLLECTION_CHANGED, LIBRARY_CHANGED } from '@/types/events'
|
||||
import { COLLECTION_CHANGED, COLLECTION_DELETED, LIBRARY_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import { Location } from 'vue-router'
|
||||
import { LIBRARIES_ALL } from '@/types/library'
|
||||
|
||||
const cookiePageSize = 'pagesize'
|
||||
const all = 'all'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseCollections',
|
||||
|
|
@ -75,15 +75,17 @@ export default Vue.extend({
|
|||
props: {
|
||||
libraryId: {
|
||||
type: String,
|
||||
default: all,
|
||||
default: LIBRARIES_ALL,
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadCollections)
|
||||
this.$eventHub.$on(COLLECTION_DELETED, this.reloadCollections)
|
||||
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$eventHub.$off(COLLECTION_CHANGED, this.reloadCollections)
|
||||
this.$eventHub.$off(COLLECTION_DELETED, this.reloadCollections)
|
||||
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary)
|
||||
},
|
||||
mounted () {
|
||||
|
|
@ -189,7 +191,7 @@ export default Vue.extend({
|
|||
size: this.pageSize,
|
||||
} as PageRequest
|
||||
|
||||
const lib = libraryId !== all ? [libraryId] : undefined
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
const collectionsPage = await this.$komgaCollections.getCollections(lib, pageRequest)
|
||||
|
||||
this.totalPages = collectionsPage.totalPages
|
||||
|
|
@ -197,7 +199,7 @@ export default Vue.extend({
|
|||
this.collections = collectionsPage.content
|
||||
},
|
||||
getLibraryLazy (libraryId: string): LibraryDto | undefined {
|
||||
if (libraryId !== all) {
|
||||
if (libraryId !== LIBRARIES_ALL) {
|
||||
return this.$store.getters.getLibraryById(libraryId)
|
||||
} else {
|
||||
return undefined
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@
|
|||
@edit="editMultipleSeries"
|
||||
/>
|
||||
|
||||
<library-navigation v-if="collectionsCount > 0"
|
||||
:libraryId="libraryId"
|
||||
/>
|
||||
<library-navigation :libraryId="libraryId"/>
|
||||
|
||||
<v-container fluid>
|
||||
<empty-state
|
||||
|
|
@ -82,12 +80,12 @@ import SortMenuButton from '@/components/SortMenuButton.vue'
|
|||
import { parseQueryFilter, parseQuerySort } from '@/functions/query-params'
|
||||
import { ReadStatus } from '@/types/enum-books'
|
||||
import { SeriesStatus } from '@/types/enum-series'
|
||||
import { COLLECTION_CHANGED, LIBRARY_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import { LIBRARY_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import { Location } from 'vue-router'
|
||||
import { LIBRARIES_ALL } from '@/types/library'
|
||||
|
||||
const cookiePageSize = 'pagesize'
|
||||
const all = 'all'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseLibraries',
|
||||
|
|
@ -133,13 +131,12 @@ export default Vue.extend({
|
|||
filterUnwatch: null as any,
|
||||
pageUnwatch: null as any,
|
||||
pageSizeUnwatch: null as any,
|
||||
collectionsCount: 0,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
libraryId: {
|
||||
type: String,
|
||||
default: all,
|
||||
default: LIBRARIES_ALL,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -153,13 +150,11 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
created () {
|
||||
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadCollections)
|
||||
this.$eventHub.$on(SERIES_CHANGED, this.reloadSeries)
|
||||
this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted)
|
||||
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$eventHub.$off(COLLECTION_CHANGED, this.reloadCollections)
|
||||
this.$eventHub.$off(SERIES_CHANGED, this.reloadSeries)
|
||||
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted)
|
||||
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary)
|
||||
|
|
@ -188,7 +183,6 @@ export default Vue.extend({
|
|||
this.totalPages = 1
|
||||
this.totalElements = null
|
||||
this.series = []
|
||||
this.collectionsCount = 0
|
||||
|
||||
this.loadLibrary(to.params.libraryId)
|
||||
|
||||
|
|
@ -238,7 +232,7 @@ export default Vue.extend({
|
|||
libraryDeleted (event: EventLibraryDeleted) {
|
||||
if (event.id === this.libraryId) {
|
||||
this.$router.push({ name: 'home' })
|
||||
} else if (this.libraryId === all) {
|
||||
} else if (this.libraryId === LIBRARIES_ALL) {
|
||||
this.loadLibrary(this.libraryId)
|
||||
}
|
||||
},
|
||||
|
|
@ -278,25 +272,19 @@ export default Vue.extend({
|
|||
|
||||
this.setWatches()
|
||||
},
|
||||
reloadCollections () {
|
||||
this.loadLibrary(this.libraryId)
|
||||
},
|
||||
reloadSeries (event: EventSeriesChanged) {
|
||||
if (this.libraryId === all || event.libraryId === this.libraryId) {
|
||||
if (this.libraryId === LIBRARIES_ALL || event.libraryId === this.libraryId) {
|
||||
this.loadPage(this.libraryId, this.page, this.sortActive)
|
||||
}
|
||||
},
|
||||
reloadLibrary (event: EventLibraryChanged) {
|
||||
if (this.libraryId === all || event.id === this.libraryId) {
|
||||
if (this.libraryId === LIBRARIES_ALL || event.id === this.libraryId) {
|
||||
this.loadLibrary(this.libraryId)
|
||||
}
|
||||
},
|
||||
async loadLibrary (libraryId: string) {
|
||||
this.library = this.getLibraryLazy(libraryId)
|
||||
|
||||
const lib = libraryId !== all ? [libraryId] : undefined
|
||||
this.collectionsCount = (await this.$komgaCollections.getCollections(lib, { size: 1 })).totalElements
|
||||
|
||||
await this.loadPage(libraryId, this.page, this.sortActive)
|
||||
},
|
||||
updateRoute () {
|
||||
|
|
@ -323,7 +311,7 @@ export default Vue.extend({
|
|||
pageRequest.sort = [`${sort.key},${sort.order}`]
|
||||
}
|
||||
|
||||
const requestLibraryId = libraryId !== all ? libraryId : undefined
|
||||
const requestLibraryId = libraryId !== LIBRARIES_ALL ? libraryId : undefined
|
||||
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters.status, this.filters.readStatus)
|
||||
|
||||
this.totalPages = seriesPage.totalPages
|
||||
|
|
|
|||
212
komga-webui/src/views/BrowseReadList.vue
Normal file
212
komga-webui/src/views/BrowseReadList.vue
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
<template>
|
||||
<div v-if="readList">
|
||||
<toolbar-sticky v-if="!editElements && selectedBooks.length === 0">
|
||||
|
||||
<read-list-actions-menu v-if="readList"
|
||||
:read-list="readList"
|
||||
/>
|
||||
|
||||
<v-toolbar-title v-if="readList">
|
||||
<span>{{ readList.name }}</span>
|
||||
<badge class="mx-4">{{ readList.bookIds.length }}</badge>
|
||||
</v-toolbar-title>
|
||||
|
||||
<v-spacer/>
|
||||
|
||||
<v-btn icon @click="startEditElements" v-if="isAdmin">
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-icon v-on="on">mdi-playlist-edit</v-icon>
|
||||
</template>
|
||||
<span>Edit elements</span>
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="editReadList" v-if="isAdmin">
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-icon v-on="on">mdi-pencil</v-icon>
|
||||
</template>
|
||||
<span>Edit read list</span>
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
</toolbar-sticky>
|
||||
|
||||
<books-multi-select-bar
|
||||
v-model="selectedBooks"
|
||||
@unselect-all="selectedBooks = []"
|
||||
@mark-read="markSelectedRead"
|
||||
@mark-unread="markSelectedUnread"
|
||||
@add-to-readlist="addToReadList"
|
||||
@edit="editMultipleBooks"
|
||||
/>
|
||||
|
||||
<!-- Edit elements sticky bar -->
|
||||
<v-scroll-y-transition hide-on-leave>
|
||||
<toolbar-sticky v-if="editElements" :elevation="5" color="base">
|
||||
<v-btn icon @click="cancelEditElements">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon color="primary" @click="doEditElements" :disabled="books.length === 0">
|
||||
<v-icon>mdi-check</v-icon>
|
||||
</v-btn>
|
||||
|
||||
</toolbar-sticky>
|
||||
</v-scroll-y-transition>
|
||||
|
||||
<v-container fluid>
|
||||
|
||||
<item-browser
|
||||
:items.sync="books"
|
||||
:selected.sync="selectedBooks"
|
||||
:edit-function="editSingleBook"
|
||||
:draggable="editElements"
|
||||
:deletable="editElements"
|
||||
/>
|
||||
|
||||
</v-container>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import { BOOK_CHANGED, READLIST_CHANGED, READLIST_DELETED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import ReadListActionsMenu from '@/components/menus/ReadListActionsMenu.vue'
|
||||
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseReadList',
|
||||
components: {
|
||||
ToolbarSticky,
|
||||
ItemBrowser,
|
||||
ReadListActionsMenu,
|
||||
Badge,
|
||||
BooksMultiSelectBar,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
readList: undefined as ReadListDto | undefined,
|
||||
books: [] as BookDto[],
|
||||
booksCopy: [] as BookDto[],
|
||||
selectedBooks: [] as BookDto[],
|
||||
editElements: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
readListId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedBooks (val: BookDto[]) {
|
||||
val.forEach(s => {
|
||||
let index = this.books.findIndex(x => x.id === s.id)
|
||||
if (index !== -1) {
|
||||
this.books.splice(index, 1, s)
|
||||
}
|
||||
index = this.booksCopy.findIndex(x => x.id === s.id)
|
||||
if (index !== -1) {
|
||||
this.booksCopy.splice(index, 1, s)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$eventHub.$on(READLIST_CHANGED, this.readListChanged)
|
||||
this.$eventHub.$on(READLIST_DELETED, this.afterDelete)
|
||||
this.$eventHub.$on(BOOK_CHANGED, this.reloadBook)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$eventHub.$off(READLIST_CHANGED, this.readListChanged)
|
||||
this.$eventHub.$off(READLIST_DELETED, this.afterDelete)
|
||||
this.$eventHub.$off(BOOK_CHANGED, this.reloadBook)
|
||||
},
|
||||
mounted () {
|
||||
this.loadReadList(this.readListId)
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
if (to.params.readListId !== from.params.readListId) {
|
||||
// reset
|
||||
this.books = []
|
||||
this.editElements = false
|
||||
|
||||
this.loadReadList(to.params.readListId)
|
||||
}
|
||||
|
||||
next()
|
||||
},
|
||||
computed: {
|
||||
isAdmin (): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
readListChanged (event: EventReadListChanged) {
|
||||
if (event.id === this.readListId) {
|
||||
this.loadReadList(this.readListId)
|
||||
}
|
||||
},
|
||||
async loadReadList (readListId: string) {
|
||||
this.readList = await this.$komgaReadLists.getOneReadList(readListId)
|
||||
this.books = (await this.$komgaReadLists.getBooks(readListId, { unpaged: true } as PageRequest)).content
|
||||
this.booksCopy = [...this.books]
|
||||
},
|
||||
editSingleBook (book: BookDto) {
|
||||
this.$store.dispatch('dialogUpdateBooks', book)
|
||||
},
|
||||
editMultipleBooks () {
|
||||
this.$store.dispatch('dialogUpdateBooks', this.selectedBooks)
|
||||
},
|
||||
async markSelectedRead () {
|
||||
await Promise.all(this.selectedBooks.map(b =>
|
||||
this.$komgaBooks.updateReadProgress(b.id, { completed: true } as ReadProgressUpdateDto),
|
||||
))
|
||||
this.selectedBooks = await Promise.all(this.selectedBooks.map(b =>
|
||||
this.$komgaBooks.getBook(b.id),
|
||||
))
|
||||
},
|
||||
async markSelectedUnread () {
|
||||
await Promise.all(this.selectedBooks.map(b =>
|
||||
this.$komgaBooks.deleteReadProgress(b.id),
|
||||
))
|
||||
this.selectedBooks = await Promise.all(this.selectedBooks.map(b =>
|
||||
this.$komgaBooks.getBook(b.id),
|
||||
))
|
||||
},
|
||||
addToReadList () {
|
||||
this.$store.dispatch('dialogAddBooksToReadList', this.selectedBooks)
|
||||
},
|
||||
startEditElements () {
|
||||
this.editElements = true
|
||||
},
|
||||
cancelEditElements () {
|
||||
this.editElements = false
|
||||
this.books = [...this.booksCopy]
|
||||
},
|
||||
doEditElements () {
|
||||
this.editElements = false
|
||||
const update = {
|
||||
bookIds: this.books.map(x => x.id),
|
||||
} as ReadListUpdateDto
|
||||
this.$komgaReadLists.patchReadList(this.readListId, update)
|
||||
this.loadReadList(this.readListId)
|
||||
},
|
||||
editReadList () {
|
||||
this.$store.dispatch('dialogEditReadList', this.readList)
|
||||
},
|
||||
afterDelete () {
|
||||
this.$router.push({ name: 'browse-readlists', params: { libraryId: 'all' } })
|
||||
},
|
||||
reloadBook (event: EventBookChanged) {
|
||||
if (this.books.some(b => b.id === event.id)) this.loadReadList(this.readListId)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
213
komga-webui/src/views/BrowseReadLists.vue
Normal file
213
komga-webui/src/views/BrowseReadLists.vue
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<template>
|
||||
<div :style="$vuetify.breakpoint.name === 'xs' ? 'margin-bottom: 56px' : undefined">
|
||||
<toolbar-sticky>
|
||||
<!-- Action menu -->
|
||||
<library-actions-menu v-if="library"
|
||||
:library="library"/>
|
||||
|
||||
<v-toolbar-title>
|
||||
<span>{{ library ? library.name : 'All libraries' }}</span>
|
||||
<badge class="ml-4">{{ totalElements }}</badge>
|
||||
</v-toolbar-title>
|
||||
|
||||
<v-spacer/>
|
||||
|
||||
<page-size-select v-model="pageSize"/>
|
||||
</toolbar-sticky>
|
||||
|
||||
<library-navigation :libraryId="libraryId"/>
|
||||
|
||||
<v-container fluid>
|
||||
<v-pagination
|
||||
v-if="totalPages > 1"
|
||||
v-model="page"
|
||||
:total-visible="paginationVisible"
|
||||
:length="totalPages"
|
||||
/>
|
||||
|
||||
<item-browser
|
||||
:items="readLists"
|
||||
:selectable="false"
|
||||
:edit-function="editSingle"
|
||||
/>
|
||||
|
||||
</v-container>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import LibraryNavigation from '@/components/LibraryNavigation.vue'
|
||||
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import { LIBRARY_CHANGED, READLIST_CHANGED, READLIST_DELETED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import { Location } from 'vue-router'
|
||||
import { LIBRARIES_ALL } from '@/types/library'
|
||||
|
||||
const cookiePageSize = 'pagesize'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseReadLists',
|
||||
components: {
|
||||
LibraryActionsMenu,
|
||||
ToolbarSticky,
|
||||
LibraryNavigation,
|
||||
ItemBrowser,
|
||||
Badge,
|
||||
PageSizeSelect,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
library: undefined as LibraryDto | undefined,
|
||||
readLists: [] as ReadListDto[],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
totalPages: 1,
|
||||
totalElements: null as number | null,
|
||||
pageUnwatch: null as any,
|
||||
pageSizeUnwatch: null as any,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
libraryId: {
|
||||
type: String,
|
||||
default: LIBRARIES_ALL,
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$eventHub.$on(READLIST_CHANGED, this.reloadElements)
|
||||
this.$eventHub.$on(READLIST_DELETED, this.reloadElements)
|
||||
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$eventHub.$off(READLIST_CHANGED, this.reloadElements)
|
||||
this.$eventHub.$off(READLIST_DELETED, this.reloadElements)
|
||||
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary)
|
||||
},
|
||||
mounted () {
|
||||
if (this.$cookies.isKey(cookiePageSize)) {
|
||||
this.pageSize = Number(this.$cookies.get(cookiePageSize))
|
||||
}
|
||||
|
||||
// restore from query param
|
||||
if (this.$route.query.page) this.page = Number(this.$route.query.page)
|
||||
if (this.$route.query.pageSize) this.pageSize = Number(this.$route.query.pageSize)
|
||||
|
||||
this.loadLibrary(this.libraryId)
|
||||
|
||||
this.setWatches()
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
if (to.params.libraryId !== from.params.libraryId) {
|
||||
// reset
|
||||
this.page = 1
|
||||
this.totalPages = 1
|
||||
this.totalElements = null
|
||||
this.readLists = []
|
||||
|
||||
this.loadLibrary(to.params.libraryId)
|
||||
}
|
||||
|
||||
next()
|
||||
},
|
||||
computed: {
|
||||
isAdmin (): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
paginationVisible (): number {
|
||||
switch (this.$vuetify.breakpoint.name) {
|
||||
case 'xs':
|
||||
return 5
|
||||
case 'sm':
|
||||
case 'md':
|
||||
return 10
|
||||
case 'lg':
|
||||
case 'xl':
|
||||
default:
|
||||
return 15
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setWatches () {
|
||||
this.pageSizeUnwatch = this.$watch('pageSize', (val) => {
|
||||
this.$cookies.set(cookiePageSize, val, Infinity)
|
||||
this.updateRouteAndReload()
|
||||
})
|
||||
|
||||
this.pageUnwatch = this.$watch('page', (val) => {
|
||||
this.updateRoute()
|
||||
this.loadPage(this.libraryId, val)
|
||||
})
|
||||
},
|
||||
unsetWatches () {
|
||||
this.pageUnwatch()
|
||||
this.pageSizeUnwatch()
|
||||
},
|
||||
updateRouteAndReload () {
|
||||
this.unsetWatches()
|
||||
|
||||
this.page = 1
|
||||
|
||||
this.updateRoute()
|
||||
this.loadPage(this.libraryId, this.page)
|
||||
|
||||
this.setWatches()
|
||||
},
|
||||
updateRoute () {
|
||||
this.$router.replace({
|
||||
name: this.$route.name,
|
||||
params: { libraryId: this.$route.params.libraryId },
|
||||
query: {
|
||||
page: `${this.page}`,
|
||||
pageSize: `${this.pageSize}`,
|
||||
},
|
||||
} as Location).catch((_: any) => {
|
||||
})
|
||||
},
|
||||
reloadElements () {
|
||||
this.loadLibrary(this.libraryId)
|
||||
},
|
||||
reloadLibrary (event: EventLibraryChanged) {
|
||||
if (event.id === this.libraryId) {
|
||||
this.loadLibrary(this.libraryId)
|
||||
}
|
||||
},
|
||||
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() } })
|
||||
}
|
||||
},
|
||||
async loadPage (libraryId: string, page: number) {
|
||||
const pageRequest = {
|
||||
page: page - 1,
|
||||
size: this.pageSize,
|
||||
} as PageRequest
|
||||
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
const elementsPage = await this.$komgaReadLists.getReadLists(lib, pageRequest)
|
||||
|
||||
this.totalPages = elementsPage.totalPages
|
||||
this.totalElements = elementsPage.totalElements
|
||||
this.readLists = elementsPage.content
|
||||
},
|
||||
getLibraryLazy (libraryId: string): LibraryDto | undefined {
|
||||
if (libraryId !== LIBRARIES_ALL) {
|
||||
return this.$store.getters.getLibraryById(libraryId)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
editSingle (element: ReadListDto) {
|
||||
this.$store.dispatch('dialogEditReadList', element)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -44,6 +44,7 @@
|
|||
@unselect-all="selectedBooks = []"
|
||||
@mark-read="markSelectedRead"
|
||||
@mark-unread="markSelectedUnread"
|
||||
@add-to-readlist="addToReadList"
|
||||
@edit="editMultipleBooks"
|
||||
/>
|
||||
|
||||
|
|
@ -133,7 +134,7 @@ import SortMenuButton from '@/components/SortMenuButton.vue'
|
|||
import { parseQueryFilter, parseQuerySort } from '@/functions/query-params'
|
||||
import { seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { ReadStatus } from '@/types/enum-books'
|
||||
import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import { BOOK_CHANGED, LIBRARY_DELETED, READLIST_CHANGED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import { Location } from 'vue-router'
|
||||
|
||||
|
|
@ -218,11 +219,13 @@ export default Vue.extend({
|
|||
},
|
||||
created () {
|
||||
this.$eventHub.$on(SERIES_CHANGED, this.reloadSeries)
|
||||
this.$eventHub.$on(READLIST_CHANGED, this.reloadSeries)
|
||||
this.$eventHub.$on(BOOK_CHANGED, this.reloadBooks)
|
||||
this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$eventHub.$off(SERIES_CHANGED, this.reloadSeries)
|
||||
this.$eventHub.$off(READLIST_CHANGED, this.reloadSeries)
|
||||
this.$eventHub.$off(BOOK_CHANGED, this.reloadBooks)
|
||||
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted)
|
||||
},
|
||||
|
|
@ -357,6 +360,9 @@ export default Vue.extend({
|
|||
editMultipleBooks () {
|
||||
this.$store.dispatch('dialogUpdateBooks', this.selectedBooks)
|
||||
},
|
||||
addToReadList () {
|
||||
this.$store.dispatch('dialogAddBooksToReadList', this.selectedBooks)
|
||||
},
|
||||
async markSelectedRead () {
|
||||
await Promise.all(this.selectedBooks.map(b =>
|
||||
this.$komgaBooks.updateReadProgress(b.id, { completed: true }),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
@unselect-all="selectedBooks = []"
|
||||
@mark-read="markSelectedBooksRead"
|
||||
@mark-unread="markSelectedBooksUnread"
|
||||
@add-to-readlist="addToReadList"
|
||||
@edit="editMultipleBooks"
|
||||
/>
|
||||
|
||||
|
|
@ -259,6 +260,9 @@ export default Vue.extend({
|
|||
editMultipleBooks () {
|
||||
this.$store.dispatch('dialogUpdateBooks', this.selectedBooks)
|
||||
},
|
||||
addToReadList () {
|
||||
this.$store.dispatch('dialogAddBooksToReadList', this.selectedBooks)
|
||||
},
|
||||
async markSelectedBooksRead () {
|
||||
await Promise.all(this.selectedBooks.map(b =>
|
||||
this.$komgaBooks.updateReadProgress(b.id, { completed: true }),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
@unselect-all="selectedBooks = []"
|
||||
@mark-read="markSelectedBooksRead"
|
||||
@mark-unread="markSelectedBooksUnread"
|
||||
@add-to-readlist="addToReadList"
|
||||
@edit="editMultipleBooks"
|
||||
/>
|
||||
|
||||
|
|
@ -79,6 +80,20 @@
|
|||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
<horizontal-scroller v-if="readLists.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Read Lists</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="readLists"
|
||||
nowrap
|
||||
:edit-function="singleEditReadList"
|
||||
:selectable="false"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
</template>
|
||||
</v-container>
|
||||
|
||||
|
|
@ -92,7 +107,15 @@ import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
|||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import { BOOK_CHANGED, COLLECTION_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import {
|
||||
BOOK_CHANGED,
|
||||
COLLECTION_CHANGED,
|
||||
COLLECTION_DELETED,
|
||||
LIBRARY_DELETED,
|
||||
READLIST_CHANGED,
|
||||
READLIST_DELETED,
|
||||
SERIES_CHANGED,
|
||||
} from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
|
|
@ -110,6 +133,7 @@ export default Vue.extend({
|
|||
series: [] as SeriesDto[],
|
||||
books: [] as BookDto[],
|
||||
collections: [] as CollectionDto[],
|
||||
readLists: [] as ReadListDto[],
|
||||
pageSize: 50,
|
||||
loading: false,
|
||||
selectedSeries: [] as SeriesDto[],
|
||||
|
|
@ -121,12 +145,18 @@ export default Vue.extend({
|
|||
this.$eventHub.$on(SERIES_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$on(BOOK_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$on(COLLECTION_DELETED, this.reloadResults)
|
||||
this.$eventHub.$on(READLIST_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$on(READLIST_DELETED, this.reloadResults)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$eventHub.$off(LIBRARY_DELETED, this.reloadResults)
|
||||
this.$eventHub.$off(SERIES_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$off(BOOK_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$off(COLLECTION_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$off(COLLECTION_DELETED, this.reloadResults)
|
||||
this.$eventHub.$off(READLIST_CHANGED, this.reloadResults)
|
||||
this.$eventHub.$off(READLIST_DELETED, this.reloadResults)
|
||||
},
|
||||
watch: {
|
||||
'$route.query.q': {
|
||||
|
|
@ -163,7 +193,7 @@ export default Vue.extend({
|
|||
return this.selectedSeries.length === 0 && this.selectedBooks.length === 0
|
||||
},
|
||||
emptyResults (): boolean {
|
||||
return !this.loading && this.series.length === 0 && this.books.length === 0 && this.collections.length === 0
|
||||
return !this.loading && this.series.length === 0 && this.books.length === 0 && this.collections.length === 0 && this.readLists.length === 0
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -176,6 +206,9 @@ export default Vue.extend({
|
|||
singleEditCollection (collection: CollectionDto) {
|
||||
this.$store.dispatch('dialogEditCollection', collection)
|
||||
},
|
||||
singleEditReadList (readList: ReadListDto) {
|
||||
this.$store.dispatch('dialogEditReadList', readList)
|
||||
},
|
||||
async markSelectedSeriesRead () {
|
||||
await Promise.all(this.selectedSeries.map(s =>
|
||||
this.$komgaSeries.markAsRead(s.id),
|
||||
|
|
@ -195,6 +228,9 @@ export default Vue.extend({
|
|||
addToCollection () {
|
||||
this.$store.dispatch('dialogAddSeriesToCollection', this.selectedSeries)
|
||||
},
|
||||
addToReadList () {
|
||||
this.$store.dispatch('dialogAddBooksToReadList', this.selectedBooks)
|
||||
},
|
||||
editMultipleSeries () {
|
||||
this.$store.dispatch('dialogUpdateSeries', this.selectedSeries)
|
||||
},
|
||||
|
|
@ -227,12 +263,14 @@ export default Vue.extend({
|
|||
this.series = (await this.$komgaSeries.getSeries(undefined, { size: this.pageSize }, search)).content
|
||||
this.books = (await this.$komgaBooks.getBooks(undefined, { size: this.pageSize }, search)).content
|
||||
this.collections = (await this.$komgaCollections.getCollections(undefined, { size: this.pageSize }, search)).content
|
||||
this.readLists = (await this.$komgaReadLists.getReadLists(undefined, { size: this.pageSize }, search)).content
|
||||
|
||||
this.loading = false
|
||||
} else {
|
||||
this.series = []
|
||||
this.books = []
|
||||
this.collections = []
|
||||
this.readLists = []
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue