feat(webui): the UI is now dynamic to events from the server

closes #124
This commit is contained in:
Gauthier Roebroeck 2021-06-21 14:53:06 +08:00
parent 691c7f0071
commit a707fd3594
37 changed files with 741 additions and 330 deletions

View file

@ -6,6 +6,8 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import {Theme} from "@/types/themes"; import {Theme} from "@/types/themes";
import {LIBRARY_ADDED, LIBRARY_CHANGED, LIBRARY_DELETED} from "@/types/events";
import {LibrarySseDto} from "@/types/komga-sse";
const cookieLocale = 'locale' const cookieLocale = 'locale'
const cookieTheme = 'theme' const cookieTheme = 'theme'
@ -73,9 +75,18 @@ export default Vue.extend({
this.$cookies.keys() this.$cookies.keys()
.filter(x => x.startsWith('collection.filter') || x.startsWith('library.filter') || x.startsWith('library.sort')) .filter(x => x.startsWith('collection.filter') || x.startsWith('library.filter') || x.startsWith('library.sort'))
.forEach(x => this.$cookies.remove(x)) .forEach(x => this.$cookies.remove(x))
this.$eventHub.$on(LIBRARY_ADDED, this.reloadLibraries)
this.$eventHub.$on(LIBRARY_DELETED, this.reloadLibraries)
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibraries)
}, },
beforeDestroy() { beforeDestroy() {
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.systemThemeChange) window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.systemThemeChange)
this.$eventHub.$off(LIBRARY_ADDED, this.reloadLibraries)
this.$eventHub.$off(LIBRARY_DELETED, this.reloadLibraries)
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibraries)
}, },
watch: { watch: {
"$store.state.persistedState.locale": { "$store.state.persistedState.locale": {
@ -117,6 +128,9 @@ export default Vue.extend({
break break
} }
}, },
reloadLibraries(event: LibrarySseDto) {
this.$store.dispatch('getLibraries')
},
}, },
}) })
</script> </script>

View file

@ -3,39 +3,31 @@
<collection-add-to-dialog <collection-add-to-dialog
v-model="addToCollectionDialog" v-model="addToCollectionDialog"
:series="addToCollectionSeries" :series="addToCollectionSeries"
@added="collectionAdded"
@created="collectionAdded"
/> />
<collection-edit-dialog <collection-edit-dialog
v-model="editCollectionDialog" v-model="editCollectionDialog"
:collection="editCollection" :collection="editCollection"
@updated="collectionUpdated"
/> />
<collection-delete-dialog <collection-delete-dialog
v-model="deleteCollectionDialog" v-model="deleteCollectionDialog"
:collection="deleteCollection" :collection="deleteCollection"
@deleted="collectionDeleted"
/> />
<read-list-add-to-dialog <read-list-add-to-dialog
v-model="addToReadListDialog" v-model="addToReadListDialog"
:books="addToReadListBooks" :books="addToReadListBooks"
@added="readListAdded"
@created="readListAdded"
/> />
<read-list-edit-dialog <read-list-edit-dialog
v-model="editReadListDialog" v-model="editReadListDialog"
:read-list="editReadList" :read-list="editReadList"
@updated="readListUpdated"
/> />
<read-list-delete-dialog <read-list-delete-dialog
v-model="deleteReadListDialog" v-model="deleteReadListDialog"
:read-list="deleteReadList" :read-list="deleteReadList"
@deleted="readListDeleted"
/> />
<library-edit-dialog <library-edit-dialog
@ -46,19 +38,16 @@
<library-delete-dialog <library-delete-dialog
v-model="deleteLibraryDialog" v-model="deleteLibraryDialog"
:library="deleteLibrary" :library="deleteLibrary"
@deleted="libraryDeleted"
/> />
<edit-books-dialog <edit-books-dialog
v-model="updateBooksDialog" v-model="updateBooksDialog"
:books="updateBooks" :books="updateBooks"
@updated="bookUpdated"
/> />
<edit-series-dialog <edit-series-dialog
v-model="updateSeriesDialog" v-model="updateSeriesDialog"
:series="updateSeries" :series="updateSeries"
@updated="seriesUpdated"
/> />
</div> </div>
@ -72,27 +61,11 @@ import EditBooksDialog from '@/components/dialogs/EditBooksDialog.vue'
import EditSeriesDialog from '@/components/dialogs/EditSeriesDialog.vue' import EditSeriesDialog from '@/components/dialogs/EditSeriesDialog.vue'
import LibraryDeleteDialog from '@/components/dialogs/LibraryDeleteDialog.vue' import LibraryDeleteDialog from '@/components/dialogs/LibraryDeleteDialog.vue'
import LibraryEditDialog from '@/components/dialogs/LibraryEditDialog.vue' import LibraryEditDialog from '@/components/dialogs/LibraryEditDialog.vue'
import {
BOOK_CHANGED,
bookToEventBookChanged,
COLLECTION_CHANGED,
COLLECTION_DELETED,
collectionToEventCollectionChanged,
collectionToEventCollectionDeleted,
LIBRARY_DELETED,
libraryToEventLibraryDeleted,
READLIST_CHANGED,
READLIST_DELETED,
readListToEventReadListChanged,
readListToEventReadListDeleted,
SERIES_CHANGED,
seriesToEventSeriesChanged,
} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import ReadListAddToDialog from '@/components/dialogs/ReadListAddToDialog.vue' import ReadListAddToDialog from '@/components/dialogs/ReadListAddToDialog.vue'
import ReadListDeleteDialog from '@/components/dialogs/ReadListDeleteDialog.vue' import ReadListDeleteDialog from '@/components/dialogs/ReadListDeleteDialog.vue'
import ReadListEditDialog from '@/components/dialogs/ReadListEditDialog.vue' import ReadListEditDialog from '@/components/dialogs/ReadListEditDialog.vue'
import { BookDto } from '@/types/komga-books' import {BookDto} from '@/types/komga-books'
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
export default Vue.extend({ export default Vue.extend({
@ -226,49 +199,6 @@ export default Vue.extend({
return this.$store.state.updateSeries return this.$store.state.updateSeries
}, },
}, },
methods: {
collectionAdded (collection: CollectionDto) {
if (Array.isArray(this.addToCollectionSeries)) {
this.addToCollectionSeries.forEach(s => {
this.$eventHub.$emit(SERIES_CHANGED, seriesToEventSeriesChanged(s))
})
} else {
this.$eventHub.$emit(SERIES_CHANGED, seriesToEventSeriesChanged(this.addToCollectionSeries))
}
this.$eventHub.$emit(COLLECTION_CHANGED, collectionToEventCollectionChanged(collection))
},
collectionUpdated () {
this.$eventHub.$emit(COLLECTION_CHANGED, collectionToEventCollectionChanged(this.editCollection))
},
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))
},
bookUpdated (book: BookDto) {
this.$eventHub.$emit(BOOK_CHANGED, bookToEventBookChanged(book))
},
seriesUpdated (series: SeriesDto) {
this.$eventHub.$emit(SERIES_CHANGED, seriesToEventSeriesChanged(series))
},
},
}) })
</script> </script>

View file

@ -13,6 +13,7 @@
lazy-src="../assets/cover.svg" lazy-src="../assets/cover.svg"
aspect-ratio="0.7071" aspect-ratio="0.7071"
contain contain
@error="thumbnailError = true"
> >
<!-- unread tick for book --> <!-- unread tick for book -->
<div class="unread" v-if="isUnread"/> <div class="unread" v-if="isUnread"/>
@ -39,7 +40,8 @@
:style="'position: absolute; top: 5px; ' + ($vuetify.rtl ? 'right' : 'left') + ': 10px'" :style="'position: absolute; top: 5px; ' + ($vuetify.rtl ? 'right' : 'left') + ': 10px'"
@click.stop="selectItem" @click.stop="selectItem"
> >
{{ selected || (preselect && hover) ? 'mdi-checkbox-marked-circle' : 'mdi-checkbox-blank-circle-outline' {{
selected || (preselect && hover) ? 'mdi-checkbox-marked-circle' : 'mdi-checkbox-blank-circle-outline'
}} }}
</v-icon> </v-icon>
@ -126,10 +128,12 @@ import {RawLocation} from 'vue-router'
import ReadListActionsMenu from '@/components/menus/ReadListActionsMenu.vue' import ReadListActionsMenu from '@/components/menus/ReadListActionsMenu.vue'
import {BookDto} from '@/types/komga-books' import {BookDto} from '@/types/komga-books'
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
import {THUMBNAILBOOK_ADDED, THUMBNAILSERIES_ADDED} from "@/types/events";
import {ThumbnailBookSseDto, ThumbnailSeriesSseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'ItemCard', name: 'ItemCard',
components: { BookActionsMenu, SeriesActionsMenu, CollectionActionsMenu, ReadListActionsMenu }, components: {BookActionsMenu, SeriesActionsMenu, CollectionActionsMenu, ReadListActionsMenu},
props: { props: {
item: { item: {
type: Object as () => BookDto | SeriesDto | CollectionDto | ReadListDto, type: Object as () => BookDto | SeriesDto | CollectionDto | ReadListDto,
@ -182,79 +186,101 @@ export default Vue.extend({
return { return {
ItemTypes, ItemTypes,
actionMenuState: false, actionMenuState: false,
thumbnailError: false,
thumbnailCacheBust: '',
} }
}, },
created() {
this.$eventHub.$on(THUMBNAILBOOK_ADDED, this.thumbnailBookAdded)
this.$eventHub.$on(THUMBNAILSERIES_ADDED, this.thumbnailSeriesAdded)
},
beforeDestroy() {
this.$eventHub.$off(THUMBNAILBOOK_ADDED, this.thumbnailBookAdded)
this.$eventHub.$off(THUMBNAILSERIES_ADDED, this.thumbnailSeriesAdded)
},
computed: { computed: {
canReadPages (): boolean { canReadPages(): boolean {
return this.$store.getters.mePageStreaming && this.computedItem.type() === ItemTypes.BOOK return this.$store.getters.mePageStreaming && this.computedItem.type() === ItemTypes.BOOK
}, },
overlay (): boolean { overlay(): boolean {
return this.onEdit !== undefined || this.onSelected !== undefined || this.bookReady || this.canReadPages || this.actionMenu return this.onEdit !== undefined || this.onSelected !== undefined || this.bookReady || this.canReadPages || this.actionMenu
}, },
computedItem (): Item<BookDto | SeriesDto | CollectionDto | ReadListDto> { computedItem(): Item<BookDto | SeriesDto | CollectionDto | ReadListDto> {
return createItem(this.item) return createItem(this.item)
}, },
disableHover (): boolean { disableHover(): boolean {
return !this.overlay return !this.overlay
}, },
thumbnailUrl (): string { thumbnailUrl(): string {
return this.computedItem.thumbnailUrl() return this.computedItem.thumbnailUrl() + this.thumbnailCacheBust
}, },
title (): string { title(): string {
return this.computedItem.title() return this.computedItem.title()
}, },
subtitleProps (): Object { subtitleProps(): Object {
return this.computedItem.subtitleProps() return this.computedItem.subtitleProps()
}, },
body (): string { body(): string {
return this.computedItem.body() return this.computedItem.body()
}, },
isInProgress (): boolean { isInProgress(): boolean {
if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgress(this.item as BookDto) === ReadStatus.IN_PROGRESS if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgress(this.item as BookDto) === ReadStatus.IN_PROGRESS
return false return false
}, },
isUnread (): boolean { isUnread(): boolean {
if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgress(this.item as BookDto) === ReadStatus.UNREAD if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgress(this.item as BookDto) === ReadStatus.UNREAD
return false return false
}, },
unreadCount (): number | undefined { unreadCount(): number | undefined {
if (this.computedItem.type() === ItemTypes.SERIES) return (this.item as SeriesDto).booksUnreadCount + (this.item as SeriesDto).booksInProgressCount if (this.computedItem.type() === ItemTypes.SERIES) return (this.item as SeriesDto).booksUnreadCount + (this.item as SeriesDto).booksInProgressCount
return undefined return undefined
}, },
readProgressPercentage (): number { readProgressPercentage(): number {
if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgressPercentage(this.item as BookDto) if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgressPercentage(this.item as BookDto)
return 0 return 0
}, },
bookReady (): boolean { bookReady(): boolean {
if (this.computedItem.type() === ItemTypes.BOOK) { if (this.computedItem.type() === ItemTypes.BOOK) {
return (this.item as BookDto).media.status === 'READY' return (this.item as BookDto).media.status === 'READY'
} }
return false return false
}, },
to (): RawLocation { to(): RawLocation {
return this.computedItem.to() return this.computedItem.to()
}, },
fabTo (): RawLocation { fabTo(): RawLocation {
return this.computedItem.fabTo() return this.computedItem.fabTo()
}, },
}, },
methods: { methods: {
onClick () { thumbnailBookAdded(event: ThumbnailBookSseDto) {
if (this.thumbnailError &&
((this.computedItem.type() === ItemTypes.BOOK && event.bookId === this.item.id) || (this.computedItem.type() === ItemTypes.SERIES && event.seriesId === this.item.id))
) {
this.thumbnailCacheBust = '?' + this.$_.random(1000)
}
},
thumbnailSeriesAdded(event: ThumbnailSeriesSseDto) {
if (this.thumbnailError && (this.computedItem.type() === ItemTypes.SERIES && event.seriesId === this.item.id)) {
this.thumbnailCacheBust = '?' + this.$_.random(1000)
}
},
onClick() {
if (this.preselect && this.onSelected !== undefined) { if (this.preselect && this.onSelected !== undefined) {
this.selectItem() this.selectItem()
} else if (!this.noLink) { } else if (!this.noLink) {
this.goto() this.goto()
} }
}, },
goto () { goto() {
this.$router.push(this.computedItem.to()) this.$router.push(this.computedItem.to())
}, },
selectItem () { selectItem() {
if (this.onSelected !== undefined) { if (this.onSelected !== undefined) {
this.onSelected() this.onSelected()
} }
}, },
editItem () { editItem() {
if (this.onEdit !== undefined) { if (this.onEdit !== undefined) {
this.onEdit(this.item) this.onEdit(this.item)
} }

View file

@ -77,7 +77,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import {COLLECTION_CHANGED, READLIST_CHANGED} from '@/types/events' import {COLLECTION_ADDED, COLLECTION_DELETED, READLIST_ADDED, READLIST_DELETED} from '@/types/events'
import {LIBRARIES_ALL} from '@/types/library' import {LIBRARIES_ALL} from '@/types/library'
export default Vue.extend({ export default Vue.extend({
@ -101,18 +101,23 @@ export default Vue.extend({
watch: { watch: {
libraryId: { libraryId: {
handler(val) { handler(val) {
this.loadCounts(val) this.loadReadListCounts(val)
this.loadCollectionCounts(val)
}, },
immediate: true, immediate: true,
}, },
}, },
created() { created() {
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadCounts) this.$eventHub.$on(COLLECTION_ADDED, this.collectionAdded)
this.$eventHub.$on(READLIST_CHANGED, this.reloadCounts) this.$eventHub.$on(COLLECTION_DELETED, this.collectionDeleted)
this.$eventHub.$on(READLIST_ADDED, this.readListAdded)
this.$eventHub.$on(READLIST_DELETED, this.readListDeleted)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventHub.$off(COLLECTION_CHANGED, this.reloadCounts) this.$eventHub.$off(COLLECTION_ADDED, this.collectionAdded)
this.$eventHub.$off(READLIST_CHANGED, this.reloadCounts) this.$eventHub.$off(COLLECTION_DELETED, this.collectionDeleted)
this.$eventHub.$off(READLIST_ADDED, this.readListAdded)
this.$eventHub.$off(READLIST_DELETED, this.readListDeleted)
}, },
computed: { computed: {
showRecommended(): boolean { showRecommended(): boolean {
@ -123,15 +128,27 @@ export default Vue.extend({
}, },
}, },
methods: { methods: {
reloadCounts() { readListAdded() {
this.loadCounts(this.libraryId) if(this.readListsCount === 0) this.loadReadListCounts(this.libraryId)
}, },
async loadCounts(libraryId: string) { readListDeleted() {
if(this.readListsCount === 1) this.loadReadListCounts(this.libraryId)
},
collectionAdded() {
if(this.collectionsCount === 0) this.loadCollectionCounts(this.libraryId)
},
collectionDeleted() {
if(this.collectionsCount === 1) this.loadCollectionCounts(this.libraryId)
},
async loadCollectionCounts(libraryId: string) {
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
this.$komgaCollections.getCollections(lib, {size: 0}) this.$komgaCollections.getCollections(lib, {size: 0})
.then(v => this.collectionsCount = v.totalElements) .then(v => this.collectionsCount = v.totalElements)
},
async loadReadListCounts(libraryId: string) {
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
await this.$komgaReadLists.getReadLists(lib, {size: 0}) await this.$komgaReadLists.getReadLists(lib, {size: 0})
.then(v => this.readListsCount = v.totalElements) .then(v => this.readListsCount = v.totalElements)
}, },
}, },
}) })

View file

@ -0,0 +1,117 @@
<template>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
bottom
multi-line
vertical
:timeout="snackbar.timeout"
>
<p>{{ snackbar.text }}</p>
<p>{{ snackbar.text2 }}</p>
<template v-slot:action="{ attrs }">
<v-btn
v-if="snackbar.goTo"
color="secondary"
text
v-bind="attrs"
@click="snackbar.goTo.click"
>
{{ snackbar.goTo.text }}
</v-btn>
<v-btn
text
v-bind="attrs"
@click="close"
>{{ $t('common.dismiss') }}</v-btn>
</template>
</v-snackbar>
</template>
<script lang="ts">
import Vue from 'vue'
import {BOOK_IMPORTED} from "@/types/events";
import {convertErrorCodes} from "@/functions/error-codes";
import {BookImportSseDto} from "@/types/komga-sse";
export default Vue.extend({
name: 'Toaster',
data: function () {
return {
queue: [] as any[],
snackbar: {
show: false,
text: '',
text2: '',
color: undefined,
timeout: 5000,
goTo: {
text: '',
click: () => {
},
},
},
}
},
created() {
this.$eventHub.$on(BOOK_IMPORTED, this.bookImported)
},
beforeDestroy() {
this.$eventHub.$off(BOOK_IMPORTED, this.bookImported)
},
watch: {
'snackbar.show'(val) {
if (!val) {
setTimeout(() => this.next(), 1000)
}
},
queue(val) {
if (val.length > 0) {
this.next()
}
},
},
methods: {
close() {
this.snackbar.show = false
},
next() {
if (this.snackbar.show) {
return
}
if (this.queue.length > 0) {
const snack = this.queue.shift()
this.snackbar.text = snack.text
this.snackbar.text2 = snack.text2
this.snackbar.goTo = snack.goTo
this.snackbar.color = snack.color
this.snackbar.show = true
}
},
async bookImported(event: BookImportSseDto) {
if (event.success && event.bookId) {
const book = await this.$komgaBooks.getBook(event.bookId)
this.queue.push({
text: this.$t('book_import.notification.import_successful', {book: book.metadata.title}).toString(),
text2: this.$t('book_import.notification.source_file', {file: event.sourceFile}).toString(),
goTo: {
text: this.$t('book_import.notification.go_to_book').toString(),
click: () => this.$router.push({name: 'browse-book', params: {bookId: book.id}}),
},
})
} else {
this.queue.push({
text: this.$t('book_import.notification.import_failure', {file: event.sourceFile}).toString(),
text2: convertErrorCodes(event.message || ''),
color: 'error',
})
}
},
},
})
</script>
<style scoped>
</style>

View file

@ -155,7 +155,6 @@ export default Vue.extend({
try { try {
await this.$komgaCollections.patchCollection(collection.id, toUpdate) await this.$komgaCollections.patchCollection(collection.id, toUpdate)
this.$emit('added', collection)
this.dialogClose() this.dialogClose()
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
@ -170,7 +169,6 @@ export default Vue.extend({
try { try {
const created = await this.$komgaCollections.postCollection(toCreate) const created = await this.$komgaCollections.postCollection(toCreate)
this.$emit('created', created)
this.dialogClose() this.dialogClose()
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)

View file

@ -95,7 +95,6 @@ export default Vue.extend({
async deleteCollection() { async deleteCollection() {
try { try {
await this.$komgaCollections.deleteCollection(this.collection.id) await this.$komgaCollections.deleteCollection(this.collection.id)
this.$emit('deleted', true)
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
} }

View file

@ -138,7 +138,6 @@ export default Vue.extend({
} as CollectionUpdateDto } as CollectionUpdateDto
await this.$komgaCollections.patchCollection(this.collection.id, update) await this.$komgaCollections.patchCollection(this.collection.id, update)
this.$emit('updated', true)
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
} }

View file

@ -532,7 +532,6 @@ export default Vue.extend({
for (const b of toUpdate) { for (const b of toUpdate) {
try { try {
await this.$komgaBooks.updateMetadata(b.id, metadata) await this.$komgaBooks.updateMetadata(b.id, metadata)
this.$emit('updated', b)
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
} }

View file

@ -591,7 +591,6 @@ export default Vue.extend({
for (const s of toUpdate) { for (const s of toUpdate) {
try { try {
await this.$komgaSeries.updateMetadata(s.id, metadata) await this.$komgaSeries.updateMetadata(s.id, metadata)
this.$emit('updated', s)
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
} }

View file

@ -94,7 +94,6 @@ export default Vue.extend({
async deleteLibrary() { async deleteLibrary() {
try { try {
await this.$store.dispatch('deleteLibrary', this.library) await this.$store.dispatch('deleteLibrary', this.library)
this.$emit('deleted', true)
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
} }

View file

@ -245,7 +245,6 @@
<script lang="ts"> <script lang="ts">
import FileBrowserDialog from '@/components/dialogs/FileBrowserDialog.vue' import FileBrowserDialog from '@/components/dialogs/FileBrowserDialog.vue'
import {LIBRARY_ADDED, LIBRARY_CHANGED, libraryToEventLibraryChanged} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {required} from 'vuelidate/lib/validators' import {required} from 'vuelidate/lib/validators'
@ -435,10 +434,8 @@ export default Vue.extend({
try { try {
if (this.library) { if (this.library) {
await this.$store.dispatch('updateLibrary', {libraryId: this.library.id, library: library}) await this.$store.dispatch('updateLibrary', {libraryId: this.library.id, library: library})
this.$eventHub.$emit(LIBRARY_CHANGED, libraryToEventLibraryChanged(this.library))
} else { } else {
await this.$store.dispatch('postLibrary', library) await this.$store.dispatch('postLibrary', library)
this.$eventHub.$emit(LIBRARY_ADDED)
} }
this.dialogClose() this.dialogClose()
} catch (e) { } catch (e) {

View file

@ -151,7 +151,6 @@ export default Vue.extend({
try { try {
await this.$komgaReadLists.patchReadList(readList.id, toUpdate) await this.$komgaReadLists.patchReadList(readList.id, toUpdate)
this.$emit('added', readList)
this.dialogClose() this.dialogClose()
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
@ -165,7 +164,6 @@ export default Vue.extend({
try { try {
const created = await this.$komgaReadLists.postReadList(toCreate) const created = await this.$komgaReadLists.postReadList(toCreate)
this.$emit('created', created)
this.dialogClose() this.dialogClose()
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)

View file

@ -93,7 +93,6 @@ export default Vue.extend({
async delete() { async delete() {
try { try {
await this.$komgaReadLists.deleteReadList(this.readList.id) await this.$komgaReadLists.deleteReadList(this.readList.id)
this.$emit('deleted', true)
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
} }

View file

@ -122,7 +122,6 @@ export default Vue.extend({
} as ReadListUpdateDto } as ReadListUpdateDto
await this.$komgaReadLists.patchReadList(this.readList.id, update) await this.$komgaReadLists.patchReadList(this.readList.id, update)
this.$emit('updated', true)
} catch (e) { } catch (e) {
this.showSnack(e.message) this.showSnack(e.message)
} }

View file

@ -29,7 +29,6 @@
<script lang="ts"> <script lang="ts">
import {getReadProgress} from '@/functions/book-progress' import {getReadProgress} from '@/functions/book-progress'
import {ReadStatus} from '@/types/enum-books' import {ReadStatus} from '@/types/enum-books'
import {BOOK_CHANGED, bookToEventBookChanged} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {BookDto, ReadProgressUpdateDto} from '@/types/komga-books' import {BookDto, ReadProgressUpdateDto} from '@/types/komga-books'
@ -79,11 +78,9 @@ export default Vue.extend({
async markRead () { async markRead () {
const readProgress = { completed: true } as ReadProgressUpdateDto const readProgress = { completed: true } as ReadProgressUpdateDto
await this.$komgaBooks.updateReadProgress(this.book.id, readProgress) await this.$komgaBooks.updateReadProgress(this.book.id, readProgress)
this.$eventHub.$emit(BOOK_CHANGED, bookToEventBookChanged(this.book))
}, },
async markUnread () { async markUnread () {
await this.$komgaBooks.deleteReadProgress(this.book.id) await this.$komgaBooks.deleteReadProgress(this.book.id)
this.$eventHub.$emit(BOOK_CHANGED, bookToEventBookChanged(this.book))
}, },
}, },
}) })

View file

@ -27,7 +27,6 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import {SERIES_CHANGED, seriesToEventSeriesChanged} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
@ -76,11 +75,11 @@ export default Vue.extend({
}, },
async markRead () { async markRead () {
await this.$komgaSeries.markAsRead(this.series.id) await this.$komgaSeries.markAsRead(this.series.id)
this.$eventHub.$emit(SERIES_CHANGED, seriesToEventSeriesChanged(this.series)) // this.$eventHub.$emit(SERIES_CHANGED, seriesToEventSeriesChanged(this.series))
}, },
async markUnread () { async markUnread () {
await this.$komgaSeries.markAsUnread(this.series.id) await this.$komgaSeries.markAsUnread(this.series.id)
this.$eventHub.$emit(SERIES_CHANGED, seriesToEventSeriesChanged(this.series)) // this.$eventHub.$emit(SERIES_CHANGED, seriesToEventSeriesChanged(this.series))
}, },
}, },
}) })

View file

@ -39,6 +39,12 @@
"field_import_path": "Import from folder", "field_import_path": "Import from folder",
"info_part1": "This screen lets you import files that are outside your existing libraries. You can only import files into existing Series, in which case Komga will move or copy the files into the directory of the chosen Series.", "info_part1": "This screen lets you import files that are outside your existing libraries. You can only import files into existing Series, in which case Komga will move or copy the files into the directory of the chosen Series.",
"info_part2": "If you choose a number for a book, and a book already exists with that number, then you will be able to compare the 2 books. If you decide to import the book, Komga will upgrade the existing book with the new one, effectively replacing the old file with the new.", "info_part2": "If you choose a number for a book, and a book already exists with that number, then you will be able to compare the 2 books. If you decide to import the book, Komga will upgrade the existing book with the new one, effectively replacing the old file with the new.",
"notification": {
"go_to_book": "Go to book",
"import_failure": "Failed to import book: {file}",
"import_successful": "Book imported successfully: {book}",
"source_file": "Source file: {file}"
},
"row": { "row": {
"error_analyze_first": "Book needs to be analyzed first", "error_analyze_first": "Book needs to be analyzed first",
"error_choose_series": "Choose a series", "error_choose_series": "Choose a series",
@ -159,6 +165,7 @@
"collections": "Collections", "collections": "Collections",
"create": "Create", "create": "Create",
"delete": "Delete", "delete": "Delete",
"dismiss": "Dismiss",
"download": "Download", "download": "Download",
"email": "Email", "email": "Email",
"filter_no_matches": "The active filter has no matches", "filter_no_matches": "The active filter has no matches",
@ -177,12 +184,13 @@
"read": "Read", "read": "Read",
"readlists": "Read Lists", "readlists": "Read Lists",
"required": "Required", "required": "Required",
"reset_filters": "Reset filters",
"roles": "Roles", "roles": "Roles",
"series": "Series", "series": "Series",
"tags": "Tags", "tags": "Tags",
"use_filter_panel_to_change_filter": "Use the filter panel to change the active filter", "use_filter_panel_to_change_filter": "Use the filter panel to change the active filter",
"year": "year", "year": "year",
"reset_filters": "Reset filters" "pending_tasks": "No pending tasks | 1 pending task | {count} pending tasks"
}, },
"dashboard": { "dashboard": {
"keep_reading": "Keep Reading", "keep_reading": "Keep Reading",
@ -469,7 +477,12 @@
"ERR_1014": "No match for book number within series", "ERR_1014": "No match for book number within series",
"ERR_1015": "Error while deserializing ComicRack ReadingList", "ERR_1015": "Error while deserializing ComicRack ReadingList",
"ERR_1016": "Directory not accessible or not a directory", "ERR_1016": "Directory not accessible or not a directory",
"ERR_1017": "Cannot scan folder that is part of an existing library" "ERR_1017": "Cannot scan folder that is part of an existing library",
"ERR_1018": "File not found",
"ERR_1019": "Cannot import file that is part of an existing library",
"ERR_1020": "Book to upgrade does not belong to provided series",
"ERR_1021": "Destination file already exists",
"ERR_1022": "Newly imported book could not be scanned"
}, },
"filter": { "filter": {
"age_rating": "age rating", "age_rating": "age rating",

View file

@ -18,12 +18,16 @@ import komgaReferential from './plugins/komga-referential.plugin'
import komgaSeries from './plugins/komga-series.plugin' import komgaSeries from './plugins/komga-series.plugin'
import komgaUsers from './plugins/komga-users.plugin' import komgaUsers from './plugins/komga-users.plugin'
import komgaTransientBooks from './plugins/komga-transientbooks.plugin' import komgaTransientBooks from './plugins/komga-transientbooks.plugin'
import komgaSse from './plugins/komga-sse.plugin'
import vuetify from './plugins/vuetify' import vuetify from './plugins/vuetify'
import './public-path' import './public-path'
import router from './router' import router from './router'
import store from './store' import store from './store'
import i18n from './i18n' import i18n from './i18n'
Vue.prototype.$_ = _
Vue.prototype.$eventHub = new Vue()
Vue.use(Vuelidate) Vue.use(Vuelidate)
Vue.use(lineClamp) Vue.use(lineClamp)
Vue.use(VueCookies) Vue.use(VueCookies)
@ -39,10 +43,9 @@ Vue.use(komgaClaim, {http: Vue.prototype.$http})
Vue.use(komgaTransientBooks, {http: Vue.prototype.$http}) Vue.use(komgaTransientBooks, {http: Vue.prototype.$http})
Vue.use(komgaUsers, {store: store, http: Vue.prototype.$http}) Vue.use(komgaUsers, {store: store, http: Vue.prototype.$http})
Vue.use(komgaLibraries, {store: store, http: Vue.prototype.$http}) Vue.use(komgaLibraries, {store: store, http: Vue.prototype.$http})
Vue.use(komgaSse, {eventHub: Vue.prototype.$eventHub, store: store})
Vue.use(actuator, {http: Vue.prototype.$http}) Vue.use(actuator, {http: Vue.prototype.$http})
Vue.prototype.$_ = _
Vue.prototype.$eventHub = new Vue()
Vue.config.productionTip = false Vue.config.productionTip = false

View file

@ -1,7 +1,7 @@
import KomgaLibrariesService from '@/services/komga-libraries.service' import KomgaLibrariesService from '@/services/komga-libraries.service'
import { AxiosInstance } from 'axios' import {AxiosInstance} from 'axios'
import _Vue from 'vue' import _Vue from 'vue'
import { Module } from 'vuex/types' import {Module} from 'vuex/types'
let service: KomgaLibrariesService let service: KomgaLibrariesService
@ -25,15 +25,12 @@ const vuexModule: Module<any, any> = {
}, },
async postLibrary ({ dispatch }, library) { async postLibrary ({ dispatch }, library) {
await service.postLibrary(library) await service.postLibrary(library)
await dispatch('getLibraries')
}, },
async updateLibrary ({ dispatch }, { libraryId, library }) { async updateLibrary ({ dispatch }, { libraryId, library }) {
await service.updateLibrary(libraryId, library) await service.updateLibrary(libraryId, library)
await dispatch('getLibraries')
}, },
async deleteLibrary ({ dispatch }, library) { async deleteLibrary ({ dispatch }, library) {
await service.deleteLibrary(library) await service.deleteLibrary(library)
await dispatch('getLibraries')
}, },
}, },
} }

View file

@ -0,0 +1,31 @@
import _Vue from 'vue'
import KomgaSseService from "@/services/komga-sse.service"
import {Module} from "vuex";
const vuexModule: Module<any, any> = {
state: {
taskCount: 0,
},
mutations: {
setTaskCount (state, val) {
state.taskCount = val
},
},
}
export default {
install(
Vue: typeof _Vue,
{eventHub, store}: { eventHub: _Vue, store: any },
) {
store.registerModule('komgaSse', vuexModule)
Vue.prototype.$komgaSse = new KomgaSseService(eventHub, store)
},
}
declare module 'vue/types/vue' {
interface Vue {
$komgaSse: KomgaSseService;
}
}

View file

@ -0,0 +1,99 @@
import urls from '@/functions/urls'
import {
BOOK_ADDED,
BOOK_CHANGED,
BOOK_DELETED,
BOOK_IMPORTED,
COLLECTION_ADDED,
COLLECTION_CHANGED,
COLLECTION_DELETED,
LIBRARY_ADDED,
LIBRARY_CHANGED,
LIBRARY_DELETED,
READLIST_ADDED,
READLIST_CHANGED,
READLIST_DELETED,
READPROGRESS_CHANGED,
READPROGRESS_DELETED,
SERIES_ADDED,
SERIES_CHANGED,
SERIES_DELETED,
THUMBNAILBOOK_ADDED,
THUMBNAILSERIES_ADDED,
} from "@/types/events";
import Vue from "vue";
import {TaskQueueSseDto} from "@/types/komga-sse";
const API_SSE = '/sse/v1/events'
export default class KomgaSseService {
private eventSource: EventSource | undefined
private eventHub: Vue
private store: any
constructor(eventHub: Vue, store: any) {
this.eventHub = eventHub
this.store = store
this.eventHub.$watch(
() => this.store.getters.authenticated,
(val) => {
if (val) this.connect()
else this.disconnect()
})
}
connect() {
this.eventSource = new EventSource(urls.originNoSlash + API_SSE, {withCredentials: true})
// Libraries
this.eventSource.addEventListener('LibraryAdded', (event: any) => this.emit(LIBRARY_ADDED, event))
this.eventSource.addEventListener('LibraryChanged', (event: any) => this.emit(LIBRARY_CHANGED, event))
this.eventSource.addEventListener('LibraryDeleted', (event: any) => this.emit(LIBRARY_DELETED, event))
// Series
this.eventSource.addEventListener('SeriesAdded', (event: any) => this.emit(SERIES_ADDED, event))
this.eventSource.addEventListener('SeriesChanged', (event: any) => this.emit(SERIES_CHANGED, event))
this.eventSource.addEventListener('SeriesDeleted', (event: any) => this.emit(SERIES_DELETED, event))
// Books
this.eventSource.addEventListener('BookAdded', (event: any) => this.emit(BOOK_ADDED, event))
this.eventSource.addEventListener('BookChanged', (event: any) => this.emit(BOOK_CHANGED, event))
this.eventSource.addEventListener('BookDeleted', (event: any) => this.emit(BOOK_DELETED, event))
this.eventSource.addEventListener('BookImported', (event: any) => this.emit(BOOK_IMPORTED, event))
// Collections
this.eventSource.addEventListener('CollectionAdded', (event: any) => this.emit(COLLECTION_ADDED, event))
this.eventSource.addEventListener('CollectionChanged', (event: any) => this.emit(COLLECTION_CHANGED, event))
this.eventSource.addEventListener('CollectionDeleted', (event: any) => this.emit(COLLECTION_DELETED, event))
// Read Lists
this.eventSource.addEventListener('ReadListAdded', (event: any) => this.emit(READLIST_ADDED, event))
this.eventSource.addEventListener('ReadListChanged', (event: any) => this.emit(READLIST_CHANGED, event))
this.eventSource.addEventListener('ReadListDeleted', (event: any) => this.emit(READLIST_DELETED, event))
// Read Progress
this.eventSource.addEventListener('ReadProgressChanged', (event: any) => this.emit(READPROGRESS_CHANGED, event))
this.eventSource.addEventListener('ReadProgressDeleted', (event: any) => this.emit(READPROGRESS_DELETED, event))
// Thumbnails
this.eventSource.addEventListener('ThumbnailBookAdded', (event: any) => this.emit(THUMBNAILBOOK_ADDED, event))
this.eventSource.addEventListener('ThumbnailSeriesAdded', (event: any) => this.emit(THUMBNAILSERIES_ADDED, event))
this.eventSource.addEventListener('TaskQueueStatus', (event: any) => this.updateTaskCount(event))
}
disconnect() {
this.eventSource?.close()
}
private emit(name: string, event: any) {
this.eventHub.$emit(name, JSON.parse(event.data))
}
private updateTaskCount(event: any) {
const data = JSON.parse(event.data) as TaskQueueSseDto
this.store.commit('setTaskCount', data.count)
}
}

View file

@ -23,7 +23,7 @@ export enum ReadStatus {
} }
export function replaceCompositeReadStatus(list: string[]): string[] { export function replaceCompositeReadStatus(list: string[]): string[] {
if(list.includes(ReadStatus.UNREAD_AND_IN_PROGRESS)){ if(list?.includes(ReadStatus.UNREAD_AND_IN_PROGRESS)){
return [...without(list, ReadStatus.UNREAD_AND_IN_PROGRESS), ReadStatus.UNREAD, ReadStatus.IN_PROGRESS] return [...without(list, ReadStatus.UNREAD_AND_IN_PROGRESS), ReadStatus.UNREAD, ReadStatus.IN_PROGRESS]
} }
else return list else return list

View file

@ -1,37 +0,0 @@
interface EventBookChanged {
id: string,
seriesId: string
}
interface EventSeriesChanged {
id: string,
libraryId: string
}
interface EventCollectionChanged {
id: string
}
interface EventCollectionDeleted {
id: string
}
interface EventReadListChanged {
id: string
}
interface EventReadListDeleted {
id: string
}
interface EventLibraryAdded {
id: string
}
interface EventLibraryChanged {
id: string
}
interface EventLibraryDeleted {
id: string
}

View file

@ -1,68 +1,26 @@
import { BookDto } from '@/types/komga-books'
import {SeriesDto} from "@/types/komga-series";
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_ADDED = 'library-added'
export const LIBRARY_CHANGED = 'library-changed' export const LIBRARY_CHANGED = 'library-changed'
export const LIBRARY_DELETED = 'library-deleted' export const LIBRARY_DELETED = 'library-deleted'
export function bookToEventBookChanged (book: BookDto): EventBookChanged { export const BOOK_ADDED = 'book-added'
return { export const BOOK_CHANGED = 'book-changed'
id: book.id, export const BOOK_DELETED = 'book-deleted'
seriesId: book.seriesId, export const BOOK_IMPORTED = 'book-imported'
} as EventBookChanged
}
export function seriesToEventSeriesChanged (series: SeriesDto): EventSeriesChanged { export const SERIES_ADDED = 'series-added'
return { export const SERIES_CHANGED = 'series-changed'
id: series.id, export const SERIES_DELETED = 'series-deleted'
libraryId: series.libraryId,
} as EventSeriesChanged
}
export function collectionToEventCollectionChanged (collection: CollectionDto): EventCollectionChanged { export const COLLECTION_ADDED = 'collection-added'
return { export const COLLECTION_CHANGED = 'collection-changed'
id: collection.id, export const COLLECTION_DELETED = 'collection-deleted'
} as EventCollectionChanged
}
export function collectionToEventCollectionDeleted (collection: CollectionDto): EventCollectionDeleted { export const READLIST_ADDED = 'readlist-added'
return { export const READLIST_CHANGED = 'readlist-changed'
id: collection.id, export const READLIST_DELETED = 'readlist-deleted'
} as EventCollectionDeleted
}
export function readListToEventReadListChanged (readList: ReadListDto): EventReadListChanged { export const READPROGRESS_CHANGED = 'readprogress-changed'
return { export const READPROGRESS_DELETED = 'readprogress-deleted'
id: readList.id,
} as EventReadListChanged
}
export function readListToEventReadListDeleted (readList: ReadListDto): EventReadListDeleted { export const THUMBNAILBOOK_ADDED = 'thumbnailbook-added'
return { export const THUMBNAILSERIES_ADDED = 'thumbnailbook-added'
id: readList.id,
} as EventReadListDeleted
}
export function libraryToEventLibraryAdded (library: LibraryDto): EventLibraryAdded {
return {
id: library.id,
} as EventLibraryAdded
}
export function libraryToEventLibraryChanged (library: LibraryDto): EventLibraryChanged {
return {
id: library.id,
} as EventLibraryChanged
}
export function libraryToEventLibraryDeleted (library: LibraryDto): EventLibraryDeleted {
return {
id: library.id,
} as EventLibraryDeleted
}

View file

@ -0,0 +1,49 @@
export interface LibrarySseDto {
libraryId: string,
}
export interface SeriesSseDto {
seriesId: string,
libraryId: string,
}
export interface BookSseDto {
bookId: string,
seriesId: string,
libraryId: string,
}
export interface CollectionSseDto {
collectionId: string,
seriesIds: string[],
}
export interface ReadListSseDto {
readListId: string,
bookIds: string[],
}
export interface ReadProgressSseDto {
bookId: string,
userId: string,
}
export interface ThumbnailBookSseDto {
bookId: string,
seriesId: string,
}
export interface ThumbnailSeriesSseDto {
seriesId: string,
}
export interface TaskQueueSseDto {
count: number,
}
export interface BookImportSseDto {
bookId?: string,
sourceFile: string,
success: boolean,
message?: string,
}

View file

@ -322,7 +322,16 @@ import {getReadProgress, getReadProgressPercentage} from '@/functions/book-progr
import {getBookTitleCompact} from '@/functions/book-title' import {getBookTitleCompact} from '@/functions/book-title'
import {bookFileUrl, bookThumbnailUrl} from '@/functions/urls' import {bookFileUrl, bookThumbnailUrl} from '@/functions/urls'
import {ReadStatus} from '@/types/enum-books' import {ReadStatus} from '@/types/enum-books'
import {BOOK_CHANGED, LIBRARY_DELETED} from '@/types/events' import {
BOOK_CHANGED,
BOOK_DELETED,
LIBRARY_DELETED,
READLIST_ADDED,
READLIST_CHANGED,
READLIST_DELETED,
READPROGRESS_CHANGED,
READPROGRESS_DELETED,
} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import ReadListsExpansionPanels from '@/components/ReadListsExpansionPanels.vue' import ReadListsExpansionPanels from '@/components/ReadListsExpansionPanels.vue'
import {BookDto, BookFormat} from '@/types/komga-books' import {BookDto, BookFormat} from '@/types/komga-books'
@ -333,6 +342,7 @@ import VueHorizontal from "vue-horizontal";
import {authorRoles} from "@/types/author-roles"; import {authorRoles} from "@/types/author-roles";
import {convertErrorCodes} from "@/functions/error-codes"; import {convertErrorCodes} from "@/functions/error-codes";
import RtlIcon from "@/components/RtlIcon.vue"; import RtlIcon from "@/components/RtlIcon.vue";
import {BookSseDto, LibrarySseDto, ReadListSseDto, ReadProgressSseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'BrowseBook', name: 'BrowseBook',
@ -351,12 +361,24 @@ export default Vue.extend({
}, },
async created() { async created() {
this.loadBook(this.bookId) this.loadBook(this.bookId)
this.$eventHub.$on(BOOK_CHANGED, this.reloadBook) this.$eventHub.$on(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$on(BOOK_DELETED, this.bookDeleted)
this.$eventHub.$on(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$on(READPROGRESS_DELETED, this.readProgressChanged)
this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted)
this.$eventHub.$on(READLIST_ADDED, this.readListChanged)
this.$eventHub.$on(READLIST_CHANGED, this.readListChanged)
this.$eventHub.$on(READLIST_DELETED, this.readListChanged)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventHub.$off(BOOK_CHANGED, this.reloadBook) this.$eventHub.$off(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$off(BOOK_DELETED, this.bookDeleted)
this.$eventHub.$off(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$off(READPROGRESS_DELETED, this.readProgressChanged)
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted)
this.$eventHub.$off(READLIST_ADDED, this.readListChanged)
this.$eventHub.$off(READLIST_CHANGED, this.readListChanged)
this.$eventHub.$off(READLIST_DELETED, this.readListChanged)
}, },
props: { props: {
bookId: { bookId: {
@ -434,13 +456,27 @@ export default Vue.extend({
}, },
}, },
methods: { methods: {
libraryDeleted(event: EventLibraryDeleted) { libraryDeleted(event: LibrarySseDto) {
if (event.id === this.book.libraryId) { if (event.libraryId === this.book.libraryId) {
this.$router.push({name: 'home'}) this.$router.push({name: 'home'})
} }
}, },
reloadBook(event: EventBookChanged) { readListChanged(event: ReadListSseDto) {
if (event.id === this.bookId) this.loadBook(this.bookId) if(event.bookIds.includes(this.bookId) || this.readLists.map(x => x.id).includes(event.readListId)){
this.$komgaBooks.getReadLists(this.bookId)
.then(v => this.readLists = v)
}
},
bookChanged(event: BookSseDto) {
if (event.bookId === this.bookId) this.loadBook(this.bookId)
},
bookDeleted(event: BookSseDto) {
if (event.bookId === this.bookId){
this.$router.push({name:'browse-series', params: {seriesId: this.series.id }})
}
},
readProgressChanged(event: ReadProgressSseDto){
if (event.bookId === this.bookId) this.loadBook(this.bookId)
}, },
async loadBook(bookId: string) { async loadBook(bookId: string) {
this.book = await this.$komgaBooks.getBook(bookId) this.book = await this.$komgaBooks.getBook(bookId)

View file

@ -114,7 +114,7 @@
import CollectionActionsMenu from '@/components/menus/CollectionActionsMenu.vue' import CollectionActionsMenu from '@/components/menus/CollectionActionsMenu.vue'
import ItemBrowser from '@/components/ItemBrowser.vue' import ItemBrowser from '@/components/ItemBrowser.vue'
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue' import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
import {COLLECTION_CHANGED, COLLECTION_DELETED, SERIES_CHANGED} from '@/types/events' import {COLLECTION_CHANGED, COLLECTION_DELETED, SERIES_CHANGED, SERIES_DELETED} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue' import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
import {LIBRARIES_ALL} from '@/types/library' import {LIBRARIES_ALL} from '@/types/library'
@ -130,6 +130,7 @@ import {parseQueryParam} from '@/functions/query-params'
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
import {authorRoles} from "@/types/author-roles"; import {authorRoles} from "@/types/author-roles";
import {AuthorDto} from "@/types/komga-books"; import {AuthorDto} from "@/types/komga-books";
import {CollectionSseDto, SeriesSseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'BrowseCollection', name: 'BrowseCollection',
@ -186,13 +187,15 @@ export default Vue.extend({
}, },
created() { created() {
this.$eventHub.$on(COLLECTION_CHANGED, this.collectionChanged) this.$eventHub.$on(COLLECTION_CHANGED, this.collectionChanged)
this.$eventHub.$on(COLLECTION_DELETED, this.afterDelete) this.$eventHub.$on(COLLECTION_DELETED, this.collectionDeleted)
this.$eventHub.$on(SERIES_CHANGED, this.reloadSeries) this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$on(SERIES_DELETED, this.seriesChanged)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventHub.$off(COLLECTION_CHANGED, this.collectionChanged) this.$eventHub.$off(COLLECTION_CHANGED, this.collectionChanged)
this.$eventHub.$off(COLLECTION_DELETED, this.afterDelete) this.$eventHub.$off(COLLECTION_DELETED, this.collectionDeleted)
this.$eventHub.$off(SERIES_CHANGED, this.reloadSeries) this.$eventHub.$off(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$off(SERIES_DELETED, this.seriesChanged)
}, },
async mounted() { async mounted() {
await this.resetParams(this.$route, this.collectionId) await this.resetParams(this.$route, this.collectionId)
@ -345,11 +348,16 @@ export default Vue.extend({
unsetWatches() { unsetWatches() {
this.filterUnwatch() this.filterUnwatch()
}, },
collectionChanged(event: EventCollectionChanged) { collectionChanged(event: CollectionSseDto) {
if (event.id === this.collectionId) { if (event.collectionId === this.collectionId) {
this.loadCollection(this.collectionId) this.loadCollection(this.collectionId)
} }
}, },
collectionDeleted(event: CollectionSseDto) {
if(event.collectionId === this.collectionId) {
this.$router.push({name: 'browse-collections', params: {libraryId: LIBRARIES_ALL}})
}
},
updateRouteAndReload() { updateRouteAndReload() {
this.unsetWatches() this.unsetWatches()
@ -431,11 +439,8 @@ export default Vue.extend({
editCollection() { editCollection() {
this.$store.dispatch('dialogEditCollection', this.collection) this.$store.dispatch('dialogEditCollection', this.collection)
}, },
afterDelete() { seriesChanged(event: SeriesSseDto) {
this.$router.push({name: 'browse-collections', params: {libraryId: LIBRARIES_ALL}}) if (this.series.some(s => s.id === event.seriesId)) this.loadCollection(this.collectionId)
},
reloadSeries(event: EventSeriesChanged) {
if (this.series.some(s => s.id === event.id)) this.loadCollection(this.collectionId)
}, },
}, },
}) })

View file

@ -54,10 +54,11 @@ import ItemBrowser from '@/components/ItemBrowser.vue'
import LibraryNavigation from '@/components/LibraryNavigation.vue' import LibraryNavigation from '@/components/LibraryNavigation.vue'
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue' import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
import PageSizeSelect from '@/components/PageSizeSelect.vue' import PageSizeSelect from '@/components/PageSizeSelect.vue'
import {COLLECTION_CHANGED, COLLECTION_DELETED, LIBRARY_CHANGED} from '@/types/events' import {COLLECTION_ADDED, COLLECTION_CHANGED, COLLECTION_DELETED, LIBRARY_CHANGED} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {Location} from 'vue-router' import {Location} from 'vue-router'
import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library' import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library'
import {LibrarySseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'BrowseCollections', name: 'BrowseCollections',
@ -87,11 +88,13 @@ export default Vue.extend({
}, },
}, },
created() { created() {
this.$eventHub.$on(COLLECTION_ADDED, this.reloadCollections)
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadCollections) this.$eventHub.$on(COLLECTION_CHANGED, this.reloadCollections)
this.$eventHub.$on(COLLECTION_DELETED, this.reloadCollections) this.$eventHub.$on(COLLECTION_DELETED, this.reloadCollections)
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary) this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventHub.$off(COLLECTION_ADDED, this.reloadCollections)
this.$eventHub.$off(COLLECTION_CHANGED, this.reloadCollections) this.$eventHub.$off(COLLECTION_CHANGED, this.reloadCollections)
this.$eventHub.$off(COLLECTION_DELETED, this.reloadCollections) this.$eventHub.$off(COLLECTION_DELETED, this.reloadCollections)
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary) this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary)
@ -179,8 +182,8 @@ export default Vue.extend({
reloadCollections() { reloadCollections() {
this.loadLibrary(this.libraryId) this.loadLibrary(this.libraryId)
}, },
reloadLibrary(event: EventLibraryChanged) { reloadLibrary(event: LibrarySseDto) {
if (event.id === this.libraryId) { if (event.libraryId === this.libraryId) {
this.loadLibrary(this.libraryId) this.loadLibrary(this.libraryId)
} }
}, },

View file

@ -112,7 +112,7 @@ import PageSizeSelect from '@/components/PageSizeSelect.vue'
import {parseQueryParam, parseQuerySort} from '@/functions/query-params' import {parseQueryParam, parseQuerySort} from '@/functions/query-params'
import {ReadStatus, replaceCompositeReadStatus} from '@/types/enum-books' import {ReadStatus, replaceCompositeReadStatus} from '@/types/enum-books'
import {SeriesStatus, SeriesStatusKeyValue} from '@/types/enum-series' import {SeriesStatus, SeriesStatusKeyValue} from '@/types/enum-series'
import {LIBRARY_CHANGED, LIBRARY_DELETED, SERIES_CHANGED} from '@/types/events' import {LIBRARY_CHANGED, LIBRARY_DELETED, SERIES_ADDED, SERIES_CHANGED, SERIES_DELETED} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {Location} from 'vue-router' import {Location} from 'vue-router'
import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library' import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library'
@ -124,6 +124,7 @@ import {mergeFilterParams, sortOrFilterActive, toNameValue} from '@/functions/fi
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
import {AuthorDto} from "@/types/komga-books"; import {AuthorDto} from "@/types/komga-books";
import {authorRoles} from "@/types/author-roles"; import {authorRoles} from "@/types/author-roles";
import {LibrarySseDto, SeriesSseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'BrowseLibraries', name: 'BrowseLibraries',
@ -184,14 +185,18 @@ export default Vue.extend({
}, },
}, },
created() { created() {
this.$eventHub.$on(SERIES_CHANGED, this.reloadSeries) this.$eventHub.$on(SERIES_ADDED, this.seriesChanged)
this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$on(SERIES_DELETED, this.seriesChanged)
this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted)
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary) this.$eventHub.$on(LIBRARY_CHANGED, this.libraryChanged)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventHub.$off(SERIES_CHANGED, this.reloadSeries) this.$eventHub.$off(SERIES_ADDED, this.seriesChanged)
this.$eventHub.$off(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$off(SERIES_DELETED, this.seriesChanged)
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted)
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary) this.$eventHub.$off(LIBRARY_CHANGED, this.libraryChanged)
}, },
async mounted() { async mounted() {
this.$store.commit('setLibraryRoute', {id: this.libraryId, route: LIBRARY_ROUTE.BROWSE}) this.$store.commit('setLibraryRoute', {id: this.libraryId, route: LIBRARY_ROUTE.BROWSE})
@ -365,8 +370,8 @@ export default Vue.extend({
}) })
return validFilter return validFilter
}, },
libraryDeleted(event: EventLibraryDeleted) { libraryDeleted(event: LibrarySseDto) {
if (event.id === this.libraryId) { if (event.libraryId === this.libraryId) {
this.$router.push({name: 'home'}) this.$router.push({name: 'home'})
} else if (this.libraryId === LIBRARIES_ALL) { } else if (this.libraryId === LIBRARIES_ALL) {
this.loadLibrary(this.libraryId) this.loadLibrary(this.libraryId)
@ -407,13 +412,13 @@ export default Vue.extend({
this.setWatches() this.setWatches()
}, },
reloadSeries(event: EventSeriesChanged) { seriesChanged(event: SeriesSseDto) {
if (this.libraryId === LIBRARIES_ALL || event.libraryId === this.libraryId) { if (this.libraryId === LIBRARIES_ALL || event.libraryId === this.libraryId) {
this.loadPage(this.libraryId, this.page, this.sortActive) this.loadPage(this.libraryId, this.page, this.sortActive)
} }
}, },
reloadLibrary(event: EventLibraryChanged) { libraryChanged(event: LibrarySseDto) {
if (this.libraryId === LIBRARIES_ALL || event.id === this.libraryId) { if (this.libraryId === LIBRARIES_ALL || event.libraryId === this.libraryId) {
this.loadLibrary(this.libraryId) this.loadLibrary(this.libraryId)
} }
}, },

View file

@ -76,12 +76,20 @@
<script lang="ts"> <script lang="ts">
import ItemBrowser from '@/components/ItemBrowser.vue' import ItemBrowser from '@/components/ItemBrowser.vue'
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue' import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
import {BOOK_CHANGED, READLIST_CHANGED, READLIST_DELETED} from '@/types/events' import {
BOOK_CHANGED,
BOOK_DELETED,
READLIST_CHANGED,
READLIST_DELETED,
READPROGRESS_CHANGED,
READPROGRESS_DELETED,
} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import ReadListActionsMenu from '@/components/menus/ReadListActionsMenu.vue' import ReadListActionsMenu from '@/components/menus/ReadListActionsMenu.vue'
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue' import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
import {BookDto, ReadProgressUpdateDto} from '@/types/komga-books' import {BookDto, ReadProgressUpdateDto} from '@/types/komga-books'
import {ContextOrigin} from '@/types/context' import {ContextOrigin} from '@/types/context'
import {BookSseDto, ReadListSseDto, ReadProgressSseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'BrowseReadList', name: 'BrowseReadList',
@ -122,13 +130,19 @@ export default Vue.extend({
}, },
created () { created () {
this.$eventHub.$on(READLIST_CHANGED, this.readListChanged) this.$eventHub.$on(READLIST_CHANGED, this.readListChanged)
this.$eventHub.$on(READLIST_DELETED, this.afterDelete) this.$eventHub.$on(READLIST_DELETED, this.readListDeleted)
this.$eventHub.$on(BOOK_CHANGED, this.reloadBook) this.$eventHub.$on(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$on(BOOK_DELETED, this.bookChanged)
this.$eventHub.$on(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$on(READPROGRESS_DELETED, this.readProgressChanged)
}, },
beforeDestroy () { beforeDestroy () {
this.$eventHub.$off(READLIST_CHANGED, this.readListChanged) this.$eventHub.$off(READLIST_CHANGED, this.readListChanged)
this.$eventHub.$off(READLIST_DELETED, this.afterDelete) this.$eventHub.$off(READLIST_DELETED, this.readListDeleted)
this.$eventHub.$off(BOOK_CHANGED, this.reloadBook) this.$eventHub.$off(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$off(BOOK_DELETED, this.bookChanged)
this.$eventHub.$off(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$off(READPROGRESS_DELETED, this.readProgressChanged)
}, },
mounted () { mounted () {
this.loadReadList(this.readListId) this.loadReadList(this.readListId)
@ -150,11 +164,16 @@ export default Vue.extend({
}, },
}, },
methods: { methods: {
readListChanged (event: EventReadListChanged) { readListChanged (event: ReadListSseDto) {
if (event.id === this.readListId) { if (event.readListId === this.readListId) {
this.loadReadList(this.readListId) this.loadReadList(this.readListId)
} }
}, },
readListDeleted (event: ReadListSseDto) {
if (event.readListId === this.readListId) {
this.$router.push({name: 'browse-readlists', params: {libraryId: 'all'}})
}
},
async loadReadList (readListId: string) { async loadReadList (readListId: string) {
this.$komgaReadLists.getOneReadList(readListId) this.$komgaReadLists.getOneReadList(readListId)
.then(v => this.readList = v) .then(v => this.readList = v)
@ -206,11 +225,11 @@ export default Vue.extend({
editReadList () { editReadList () {
this.$store.dispatch('dialogEditReadList', this.readList) this.$store.dispatch('dialogEditReadList', this.readList)
}, },
afterDelete () { bookChanged (event: BookSseDto) {
this.$router.push({ name: 'browse-readlists', params: { libraryId: 'all' } }) if (this.books.some(b => b.id === event.bookId)) this.loadReadList(this.readListId)
}, },
reloadBook (event: EventBookChanged) { readProgressChanged(event: ReadProgressSseDto){
if (this.books.some(b => b.id === event.id)) this.loadReadList(this.readListId) if (this.books.some(b => b.id === event.bookId)) this.loadReadList(this.readListId)
}, },
}, },
}) })

View file

@ -54,10 +54,11 @@ import ItemBrowser from '@/components/ItemBrowser.vue'
import LibraryNavigation from '@/components/LibraryNavigation.vue' import LibraryNavigation from '@/components/LibraryNavigation.vue'
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue' import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
import PageSizeSelect from '@/components/PageSizeSelect.vue' import PageSizeSelect from '@/components/PageSizeSelect.vue'
import {LIBRARY_CHANGED, READLIST_CHANGED, READLIST_DELETED} from '@/types/events' import {LIBRARY_CHANGED, READLIST_ADDED, READLIST_CHANGED, READLIST_DELETED} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {Location} from 'vue-router' import {Location} from 'vue-router'
import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library' import {LIBRARIES_ALL, LIBRARY_ROUTE} from '@/types/library'
import {LibrarySseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'BrowseReadLists', name: 'BrowseReadLists',
@ -87,11 +88,13 @@ export default Vue.extend({
}, },
}, },
created () { created () {
this.$eventHub.$on(READLIST_ADDED, this.reloadElements)
this.$eventHub.$on(READLIST_CHANGED, this.reloadElements) this.$eventHub.$on(READLIST_CHANGED, this.reloadElements)
this.$eventHub.$on(READLIST_DELETED, this.reloadElements) this.$eventHub.$on(READLIST_DELETED, this.reloadElements)
this.$eventHub.$on(LIBRARY_CHANGED, this.reloadLibrary) 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_CHANGED, this.reloadElements)
this.$eventHub.$off(READLIST_DELETED, this.reloadElements) this.$eventHub.$off(READLIST_DELETED, this.reloadElements)
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary) this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary)
@ -179,8 +182,8 @@ export default Vue.extend({
reloadElements () { reloadElements () {
this.loadLibrary(this.libraryId) this.loadLibrary(this.libraryId)
}, },
reloadLibrary (event: EventLibraryChanged) { reloadLibrary (event: LibrarySseDto) {
if (event.id === this.libraryId) { if (event.libraryId === this.libraryId) {
this.loadLibrary(this.libraryId) this.loadLibrary(this.libraryId)
} }
}, },

View file

@ -381,7 +381,19 @@ import PageSizeSelect from '@/components/PageSizeSelect.vue'
import {parseQueryParam, parseQueryParamAndFilter, parseQuerySort} from '@/functions/query-params' import {parseQueryParam, parseQueryParamAndFilter, parseQuerySort} from '@/functions/query-params'
import {seriesFileUrl, seriesThumbnailUrl} from '@/functions/urls' import {seriesFileUrl, seriesThumbnailUrl} from '@/functions/urls'
import {ReadStatus, replaceCompositeReadStatus} from '@/types/enum-books' import {ReadStatus, replaceCompositeReadStatus} from '@/types/enum-books'
import {BOOK_CHANGED, LIBRARY_DELETED, READLIST_CHANGED, SERIES_CHANGED} from '@/types/events' import {
BOOK_ADDED,
BOOK_CHANGED,
BOOK_DELETED,
COLLECTION_ADDED,
COLLECTION_CHANGED,
COLLECTION_DELETED,
LIBRARY_DELETED,
READPROGRESS_CHANGED,
READPROGRESS_DELETED,
SERIES_CHANGED,
SERIES_DELETED,
} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {Location} from 'vue-router' import {Location} from 'vue-router'
import {AuthorDto, BookDto} from '@/types/komga-books' import {AuthorDto, BookDto} from '@/types/komga-books'
@ -397,6 +409,8 @@ import ReadMore from "@/components/ReadMore.vue";
import {authorRoles, authorRolesSeries} from "@/types/author-roles"; import {authorRoles, authorRolesSeries} from "@/types/author-roles";
import VueHorizontal from "vue-horizontal"; import VueHorizontal from "vue-horizontal";
import RtlIcon from "@/components/RtlIcon.vue"; import RtlIcon from "@/components/RtlIcon.vue";
import {throttle} from "lodash";
import {BookSseDto, CollectionSseDto, LibrarySseDto, ReadProgressSseDto, SeriesSseDto} from "@/types/komga-sse";
const tags = require('language-tags') const tags = require('language-tags')
@ -542,16 +556,30 @@ export default Vue.extend({
}, },
}, },
created() { created() {
this.$eventHub.$on(SERIES_CHANGED, this.reloadSeries) this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$on(READLIST_CHANGED, this.reloadSeries) this.$eventHub.$on(SERIES_DELETED, this.seriesDeleted)
this.$eventHub.$on(BOOK_CHANGED, this.reloadBooks) this.$eventHub.$on(BOOK_ADDED, this.bookChanged)
this.$eventHub.$on(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$on(BOOK_DELETED, this.bookChanged)
this.$eventHub.$on(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$on(READPROGRESS_DELETED, this.readProgressChanged)
this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted)
this.$eventHub.$on(COLLECTION_ADDED, this.collectionChanged)
this.$eventHub.$on(COLLECTION_CHANGED, this.collectionChanged)
this.$eventHub.$on(COLLECTION_DELETED, this.collectionChanged)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventHub.$off(SERIES_CHANGED, this.reloadSeries) this.$eventHub.$off(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$off(READLIST_CHANGED, this.reloadSeries) this.$eventHub.$off(SERIES_DELETED, this.seriesDeleted)
this.$eventHub.$off(BOOK_CHANGED, this.reloadBooks) this.$eventHub.$off(BOOK_ADDED, this.bookChanged)
this.$eventHub.$off(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$off(BOOK_DELETED, this.bookChanged)
this.$eventHub.$off(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$off(READPROGRESS_DELETED, this.readProgressChanged)
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted)
this.$eventHub.$off(COLLECTION_ADDED, this.collectionChanged)
this.$eventHub.$off(COLLECTION_CHANGED, this.collectionChanged)
this.$eventHub.$off(COLLECTION_DELETED, this.collectionChanged)
}, },
async mounted() { async mounted() {
this.pageSize = this.$store.state.persistedState.browsingPageSize || this.pageSize this.pageSize = this.$store.state.persistedState.browsingPageSize || this.pageSize
@ -636,22 +664,41 @@ export default Vue.extend({
this.setWatches() this.setWatches()
}, },
libraryDeleted(event: EventLibraryDeleted) { libraryDeleted(event: LibrarySseDto) {
if (event.id === this.series.libraryId) { if (event.libraryId === this.series.libraryId) {
this.$router.push({name: 'home'}) this.$router.push({name: 'home'})
} }
}, },
reloadSeries(event: EventSeriesChanged) { seriesChanged(event: SeriesSseDto) {
if (event.id === this.seriesId) this.loadSeries(this.seriesId) if (event.seriesId === this.seriesId)
this.$komgaSeries.getOneSeries(this.seriesId)
.then(v => this.series = v)
}, },
reloadBooks(event: EventBookChanged) { seriesDeleted(event: SeriesSseDto) {
if (event.seriesId === this.seriesId) this.loadSeries(this.seriesId) if (event.seriesId === this.seriesId) {
this.$router.push({name: 'browse-libraries', params: {libraryId: this.series.libraryId}})
}
}, },
bookChanged(event: BookSseDto) {
if (event.seriesId === this.seriesId) this.reloadPage()
},
readProgressChanged(event: ReadProgressSseDto) {
if (this.books.some(b => b.id === event.bookId)) this.reloadPage()
},
collectionChanged(event: CollectionSseDto) {
if (event.seriesIds.includes(this.seriesId) || this.collections.map(x => x.id).includes(event.collectionId)) {
this.$komgaSeries.getCollections(this.seriesId)
.then(v => this.collections = v)
}
},
reloadPage: throttle(function (this: any) {
this.loadPage(this.seriesId, this.page, this.sortActive)
}, 5000),
async loadSeries(seriesId: string) { async loadSeries(seriesId: string) {
this.$komgaSeries.getOneSeries(seriesId) this.$komgaSeries.getOneSeries(seriesId)
.then(v => this.series = v) .then(v => this.series = v)
this.$komgaSeries.getCollections(seriesId) this.$komgaSeries.getCollections(seriesId)
.then(v => this.collections = v) .then(v => this.collections = v)
await this.loadPage(seriesId, this.page, this.sortActive) await this.loadPage(seriesId, this.page, this.sortActive)
}, },

View file

@ -17,7 +17,8 @@
</toolbar-sticky> </toolbar-sticky>
<library-navigation v-if="individualLibrary && $vuetify.breakpoint.name === 'xs'" :libraryId="libraryId" bottom-navigation/> <library-navigation v-if="individualLibrary && $vuetify.breakpoint.name === 'xs'" :libraryId="libraryId"
bottom-navigation/>
<series-multi-select-bar <series-multi-select-bar
v-model="selectedSeries" v-model="selectedSeries"
@ -134,10 +135,21 @@ import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
import LibraryNavigation from '@/components/LibraryNavigation.vue' import LibraryNavigation from '@/components/LibraryNavigation.vue'
import {ReadStatus} from '@/types/enum-books' import {ReadStatus} from '@/types/enum-books'
import {BookDto} from '@/types/komga-books' import {BookDto} from '@/types/komga-books'
import {BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED} from '@/types/events' import {
BOOK_ADDED,
BOOK_CHANGED,
BOOK_DELETED,
READPROGRESS_CHANGED,
READPROGRESS_DELETED,
SERIES_ADDED,
SERIES_CHANGED,
SERIES_DELETED,
} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
import {LIBRARIES_ALL, LIBRARY_ROUTE} from "@/types/library"; import {LIBRARIES_ALL, LIBRARY_ROUTE} from "@/types/library";
import {throttle} from 'lodash'
import {BookSseDto, ReadProgressSseDto, SeriesSseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'Dashboard', name: 'Dashboard',
@ -164,17 +176,30 @@ export default Vue.extend({
} }
}, },
created() { created() {
this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$on(SERIES_ADDED, this.seriesChanged)
this.$eventHub.$on(SERIES_CHANGED, this.reload) this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$on(BOOK_CHANGED, this.reload) this.$eventHub.$on(SERIES_DELETED, this.seriesChanged)
this.$eventHub.$on(BOOK_ADDED, this.bookChanged)
this.$eventHub.$on(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$on(BOOK_DELETED, this.bookChanged)
this.$eventHub.$on(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$on(READPROGRESS_DELETED, this.readProgressChanged)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted) this.$eventHub.$off(SERIES_ADDED, this.seriesChanged)
this.$eventHub.$off(SERIES_CHANGED, this.reload) this.$eventHub.$off(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$off(BOOK_CHANGED, this.reload) this.$eventHub.$off(SERIES_DELETED, this.seriesChanged)
this.$eventHub.$off(BOOK_ADDED, this.bookChanged)
this.$eventHub.$off(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$off(BOOK_DELETED, this.bookChanged)
this.$eventHub.$off(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$off(READPROGRESS_DELETED, this.readProgressChanged)
}, },
mounted() { mounted() {
if(this.individualLibrary) this.$store.commit('setLibraryRoute', {id: this.libraryId, route: LIBRARY_ROUTE.RECOMMENDED}) if (this.individualLibrary) this.$store.commit('setLibraryRoute', {
id: this.libraryId,
route: LIBRARY_ROUTE.RECOMMENDED,
})
this.reload() this.reload()
}, },
props: { props: {
@ -193,6 +218,12 @@ export default Vue.extend({
libraryId(val) { libraryId(val) {
this.loadAll(val) this.loadAll(val)
}, },
'$store.state.komgaLibraries.libraries': {
handler(val){
if(val.length === 0) this.$router.push({name: 'welcome'})
else this.reload()
},
},
}, },
computed: { computed: {
fixedCardWidth(): number { fixedCardWidth(): number {
@ -213,16 +244,24 @@ export default Vue.extend({
getRequestLibraryId(libraryId: string): string | undefined { getRequestLibraryId(libraryId: string): string | undefined {
return libraryId !== LIBRARIES_ALL ? libraryId : undefined return libraryId !== LIBRARIES_ALL ? libraryId : undefined
}, },
libraryDeleted() { seriesChanged(event: SeriesSseDto) {
if (this.$store.state.komgaLibraries.libraries.length === 0) { if (this.libraryId === LIBRARIES_ALL || event.libraryId === this.libraryId) {
this.$router.push({name: 'welcome'})
} else {
this.reload() this.reload()
} }
}, },
reload() { bookChanged(event: BookSseDto){
this.loadAll(this.libraryId) if (this.libraryId === LIBRARIES_ALL || event.libraryId === this.libraryId) {
this.reload()
}
}, },
readProgressChanged(event: ReadProgressSseDto){
if (this.inProgressBooks.some(b => b.id === event.bookId)) this.reload()
else if (this.latestBooks.some(b => b.id === event.bookId)) this.reload()
else if (this.onDeckBooks.some(b => b.id === event.bookId)) this.reload()
},
reload: throttle(function(this: any) {
this.loadAll(this.libraryId)
}, 5000),
loadAll(libraryId: string) { loadAll(libraryId: string) {
this.library = this.getLibraryLazy(libraryId) this.library = this.getLibraryLazy(libraryId)
this.selectedSeries = [] this.selectedSeries = []

View file

@ -20,6 +20,21 @@
Komga Komga
</v-list-item-title> </v-list-item-title>
</v-list-item-content> </v-list-item-content>
<v-tooltip left>
<template v-slot:activator="{ on }">
<v-progress-linear
:active="taskCount > 0"
indeterminate
absolute
bottom
height="2"
color="secondary"
v-on="on"
/>
</template>
<span>{{ $tc('common.pending_tasks', taskCount) }}</span>
</v-tooltip>
</v-list-item> </v-list-item>
<v-divider/> <v-divider/>
@ -141,6 +156,7 @@
<v-main class="fill-height"> <v-main class="fill-height">
<dialogs/> <dialogs/>
<toaster/>
<router-view/> <router-view/>
</v-main> </v-main>
</div> </div>
@ -152,11 +168,12 @@ import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
import SearchBox from '@/components/SearchBox.vue' import SearchBox from '@/components/SearchBox.vue'
import {Theme} from '@/types/themes' import {Theme} from '@/types/themes'
import Vue from 'vue' import Vue from 'vue'
import {LIBRARIES_ALL} from "@/types/library"; import {LIBRARIES_ALL} from "@/types/library"
import Toaster from "@/components/Toaster.vue"
export default Vue.extend({ export default Vue.extend({
name: 'home', name: 'home',
components: {LibraryActionsMenu, SearchBox, Dialogs}, components: {Toaster, LibraryActionsMenu, SearchBox, Dialogs},
data: function () { data: function () {
return { return {
LIBRARIES_ALL, LIBRARIES_ALL,
@ -171,6 +188,9 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
taskCount(): number {
return this.$store.state.komgaSse.taskCount
},
libraries(): LibraryDto[] { libraries(): LibraryDto[] {
return this.$store.state.komgaLibraries.libraries return this.$store.state.komgaLibraries.libraries
}, },

View file

@ -110,15 +110,20 @@ import ItemBrowser from '@/components/ItemBrowser.vue'
import {BookDto} from '@/types/komga-books' import {BookDto} from '@/types/komga-books'
import { import {
BOOK_CHANGED, BOOK_CHANGED,
BOOK_DELETED,
COLLECTION_CHANGED, COLLECTION_CHANGED,
COLLECTION_DELETED, COLLECTION_DELETED,
LIBRARY_DELETED, LIBRARY_DELETED,
READLIST_CHANGED, READLIST_CHANGED,
READLIST_DELETED, READLIST_DELETED,
READPROGRESS_CHANGED,
READPROGRESS_DELETED,
SERIES_CHANGED, SERIES_CHANGED,
SERIES_DELETED,
} from '@/types/events' } from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
import {BookSseDto, CollectionSseDto, ReadListSseDto, ReadProgressSseDto, SeriesSseDto} from "@/types/komga-sse";
export default Vue.extend({ export default Vue.extend({
name: 'Search', name: 'Search',
@ -144,21 +149,29 @@ export default Vue.extend({
}, },
created () { created () {
this.$eventHub.$on(LIBRARY_DELETED, this.reloadResults) this.$eventHub.$on(LIBRARY_DELETED, this.reloadResults)
this.$eventHub.$on(SERIES_CHANGED, this.reloadResults) this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$on(BOOK_CHANGED, this.reloadResults) this.$eventHub.$on(SERIES_DELETED, this.seriesChanged)
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadResults) this.$eventHub.$on(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$on(COLLECTION_DELETED, this.reloadResults) this.$eventHub.$on(BOOK_DELETED, this.bookChanged)
this.$eventHub.$on(READLIST_CHANGED, this.reloadResults) this.$eventHub.$on(COLLECTION_CHANGED, this.collectionChanged)
this.$eventHub.$on(READLIST_DELETED, this.reloadResults) this.$eventHub.$on(COLLECTION_DELETED, this.collectionChanged)
this.$eventHub.$on(READLIST_CHANGED, this.readListChanged)
this.$eventHub.$on(READLIST_DELETED, this.readListChanged)
this.$eventHub.$on(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$on(READPROGRESS_DELETED, this.readProgressChanged)
}, },
beforeDestroy () { beforeDestroy () {
this.$eventHub.$off(LIBRARY_DELETED, this.reloadResults) this.$eventHub.$off(LIBRARY_DELETED, this.reloadResults)
this.$eventHub.$off(SERIES_CHANGED, this.reloadResults) this.$eventHub.$off(SERIES_CHANGED, this.seriesChanged)
this.$eventHub.$off(BOOK_CHANGED, this.reloadResults) this.$eventHub.$off(SERIES_DELETED, this.seriesChanged)
this.$eventHub.$off(COLLECTION_CHANGED, this.reloadResults) this.$eventHub.$off(BOOK_CHANGED, this.bookChanged)
this.$eventHub.$off(COLLECTION_DELETED, this.reloadResults) this.$eventHub.$off(BOOK_DELETED, this.bookChanged)
this.$eventHub.$off(READLIST_CHANGED, this.reloadResults) this.$eventHub.$off(COLLECTION_CHANGED, this.collectionChanged)
this.$eventHub.$off(READLIST_DELETED, this.reloadResults) this.$eventHub.$off(COLLECTION_DELETED, this.collectionChanged)
this.$eventHub.$off(READLIST_CHANGED, this.readListChanged)
this.$eventHub.$off(READLIST_DELETED, this.readListChanged)
this.$eventHub.$off(READPROGRESS_CHANGED, this.readProgressChanged)
this.$eventHub.$off(READPROGRESS_DELETED, this.readProgressChanged)
}, },
watch: { watch: {
'$route.query.q': { '$route.query.q': {
@ -199,6 +212,31 @@ export default Vue.extend({
}, },
}, },
methods: { methods: {
seriesChanged(event: SeriesSseDto){
if(this.series.map(x => x.id).includes(event.seriesId)){
this.reloadResults()
}
},
bookChanged(event: BookSseDto){
if(this.books.map(x => x.id).includes(event.bookId)){
this.reloadResults()
}
},
readProgressChanged(event: ReadProgressSseDto){
if(this.books.map(x => x.id).includes(event.bookId)){
this.reloadResults()
}
},
collectionChanged (event: CollectionSseDto) {
if (this.collections.map(x => x.id).includes(event.collectionId)) {
this.reloadResults()
}
},
readListChanged (event: ReadListSseDto) {
if (this.readLists.map(x => x.id).includes(event.readListId)) {
this.reloadResults()
}
},
singleEditSeries (series: SeriesDto) { singleEditSeries (series: SeriesDto) {
this.$store.dispatch('dialogUpdateSeries', series) this.$store.dispatch('dialogUpdateSeries', series)
}, },

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="ma-3"> <div class="pa-6">
<v-row align="center" justify="center"> <v-row align="center" justify="center">
<v-img src="../assets/logo.svg" <v-img src="../assets/logo.svg"
max-width="400" max-width="400"
@ -16,7 +16,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {LIBRARY_ADDED} from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
export default Vue.extend({ export default Vue.extend({
@ -26,21 +25,19 @@ export default Vue.extend({
return this.$store.getters.meAdmin return this.$store.getters.meAdmin
}, },
}, },
created () {
this.$eventHub.$on(LIBRARY_ADDED, this.libraryAdded)
},
beforeDestroy () {
this.$eventHub.$off(LIBRARY_ADDED, this.libraryAdded)
},
mounted () { mounted () {
if (this.$store.state.komgaLibraries.libraries.length !== 0) { if (this.$store.state.komgaLibraries.libraries.length !== 0) {
this.$router.push({ name: 'dashboard' }) this.$router.push({ name: 'dashboard' })
} }
}, },
methods: { watch: {
libraryAdded () { '$store.state.komgaLibraries.libraries': {
this.$router.push({ name: 'dashboard' }) handler(val){
if(val.length !== 0) this.$router.push({name: 'dashboard'})
},
}, },
},
methods: {
addLibrary () { addLibrary () {
this.$store.dispatch('dialogAddLibrary') this.$store.dispatch('dialogAddLibrary')
}, },