mirror of
https://github.com/gotson/komga.git
synced 2026-04-22 15:00:59 +02:00
parent
4892945ddf
commit
c8e4a462a2
25 changed files with 311 additions and 82 deletions
|
|
@ -80,6 +80,7 @@
|
|||
import Vue from 'vue'
|
||||
import {COLLECTION_ADDED, COLLECTION_DELETED, READLIST_ADDED, READLIST_DELETED} from '@/types/events'
|
||||
import {LIBRARIES_ALL} from '@/types/library'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'LibraryNavigation',
|
||||
|
|
@ -107,6 +108,14 @@ export default Vue.extend({
|
|||
},
|
||||
immediate: true,
|
||||
},
|
||||
'$store.getters.getLibrariesPinned': {
|
||||
handler(val) {
|
||||
if (this.libraryId === LIBRARIES_ALL) {
|
||||
this.loadCollectionCounts(this.libraryId)
|
||||
this.loadReadListCounts(this.libraryId)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$eventHub.$on(COLLECTION_ADDED, this.collectionAdded)
|
||||
|
|
@ -142,12 +151,12 @@ export default Vue.extend({
|
|||
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] : this.$store.getters.getLibrariesPinned.map((it: LibraryDto) => it.id)
|
||||
this.$komgaCollections.getCollections(lib, {size: 0})
|
||||
.then(v => this.collectionsCount = v.totalElements)
|
||||
},
|
||||
async loadReadListCounts(libraryId: string) {
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : this.$store.getters.getLibrariesPinned.map((it: LibraryDto) => it.id)
|
||||
await this.$komgaReadLists.getReadLists(lib, {size: 0})
|
||||
.then(v => this.readListsCount = v.totalElements)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@
|
|||
import {UserRoles} from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import {ERROR, ErrorEvent} from '@/types/events'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import ThumbnailCard from '@/components/ThumbnailCard.vue'
|
||||
import DropZone from '@/components/DropZone.vue'
|
||||
|
||||
|
|
@ -152,9 +151,6 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
getErrorsName(): string {
|
||||
if (this.form.name === '') return this.$t('common.required').toString()
|
||||
if (this.form.name?.toLowerCase() !== this.collection.name?.toLowerCase() && this.collections.some(e => e.name.toLowerCase() === this.form.name.toLowerCase())) {
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@
|
|||
import {UserRoles} from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import {ERROR, ErrorEvent} from '@/types/events'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import DropZone from '@/components/DropZone.vue'
|
||||
import ThumbnailCard from '@/components/ThumbnailCard.vue'
|
||||
import {ReadListDto, ReadListThumbnailDto, ReadListUpdateDto} from '@/types/komga-readlists'
|
||||
|
|
@ -167,9 +166,6 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
getErrorsName(): string {
|
||||
if (this.form.name === '') return this.$t('common.required').toString()
|
||||
if (this.form.name?.toLowerCase() !== this.readList.name?.toLowerCase() && this.readLists.some(e => e.name.toLowerCase() === this.form.name.toLowerCase())) {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@
|
|||
import {UserRoles} from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import {ERROR} from '@/types/events'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import {UserDto, UserUpdateDto} from '@/types/komga-users'
|
||||
|
||||
export default Vue.extend({
|
||||
|
|
@ -79,9 +78,6 @@ export default Vue.extend({
|
|||
value: x,
|
||||
}))
|
||||
},
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dialogReset(user: UserDto) {
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ export default Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
return this.$store.getters.getLibraries
|
||||
},
|
||||
ageRestrictionsAvailable(): any[] {
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -1,32 +1,40 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-menu offset-y v-if="isAdmin">
|
||||
<v-menu offset-y>
|
||||
<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="scan(false)">
|
||||
<v-list-item v-if="!library.unpinned" @click="unpin">
|
||||
<v-list-item-title>{{ $t('menu.unpin') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="library.unpinned" @click="pin">
|
||||
<v-list-item-title>{{ $t('menu.pin') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="scan(false)" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.scan_library_files') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="scan(true)" class="list-warning">
|
||||
<v-list-item @click="scan(true)" class="list-warning" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.scan_library_files_deep') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="confirmAnalyzeModal = true">
|
||||
<v-list-item @click="confirmAnalyzeModal = true" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.analyze') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="confirmRefreshMetadataModal = true">
|
||||
<v-list-item @click="confirmRefreshMetadataModal = true" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.refresh_metadata') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="confirmEmptyTrash = true">
|
||||
<v-list-item @click="confirmEmptyTrash = true" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.empty_trash') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="edit">
|
||||
<v-list-item @click="edit" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.edit') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="promptDeleteLibrary"
|
||||
class="list-danger">
|
||||
class="list-danger"
|
||||
v-if="isAdmin"
|
||||
>
|
||||
<v-list-item-title>{{ $t('menu.delete') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
|
@ -61,6 +69,7 @@
|
|||
import Vue from 'vue'
|
||||
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import {ClientSettingLibraryUpdate} from '@/types/komga-clientsettings'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'LibraryActionsMenu',
|
||||
|
|
@ -84,6 +93,22 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
unpin() {
|
||||
this.$store.dispatch('updateLibrarySetting', {
|
||||
libraryId: this.library.id,
|
||||
patch: {
|
||||
unpinned: true,
|
||||
},
|
||||
} as ClientSettingLibraryUpdate)
|
||||
},
|
||||
pin() {
|
||||
this.$store.dispatch('updateLibrarySetting', {
|
||||
libraryId: this.library.id,
|
||||
patch: {
|
||||
unpinned: false,
|
||||
},
|
||||
} as ClientSettingLibraryUpdate)
|
||||
},
|
||||
scan(scanDeep: boolean) {
|
||||
this.$komgaLibraries.scanLibrary(this.library, scanDeep)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@
|
|||
"locale_rtl": "false",
|
||||
"lock_all": "Lock all",
|
||||
"media": "Media",
|
||||
"more": "More",
|
||||
"n_selected": "{count} selected",
|
||||
"nothing_to_show": "Nothing to show",
|
||||
"ok": "OK",
|
||||
|
|
@ -261,6 +262,7 @@
|
|||
"password": "Password",
|
||||
"pdf": "PDF",
|
||||
"pending_tasks": "No pending tasks | 1 pending task | {count} pending tasks",
|
||||
"pinned_libraries": "Pinned Libraries",
|
||||
"publisher": "Publisher",
|
||||
"read": "Read",
|
||||
"read_on": "Read on {date}",
|
||||
|
|
@ -892,10 +894,12 @@
|
|||
"empty_trash": "Empty trash",
|
||||
"mark_read": "Mark as read",
|
||||
"mark_unread": "Mark as unread",
|
||||
"pin": "Pin",
|
||||
"refresh_metadata": "Refresh metadata",
|
||||
"scan_library_files": "Scan library files",
|
||||
"scan_library_files_deep": "Scan library files (deep)",
|
||||
"select_all": "Select all"
|
||||
"select_all": "Select all",
|
||||
"unpin": "Unpin"
|
||||
},
|
||||
"metrics": {
|
||||
"library_books": "Books per library",
|
||||
|
|
@ -914,6 +918,10 @@
|
|||
"libraries": "Libraries",
|
||||
"logout": "Log Out"
|
||||
},
|
||||
"no_libraries_pinned": {
|
||||
"title": "No pinned libraries",
|
||||
"subtitle": "You can pin a library from the 3-dots menu"
|
||||
},
|
||||
"page_not_found": {
|
||||
"go_back_to_home_page": "Go back to home page",
|
||||
"page_does_not_exist": "The page you are looking for doesn't exist.",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ Vue.use(Chartkick.use(Chart))
|
|||
|
||||
Vue.use(httpPlugin)
|
||||
Vue.use(logger)
|
||||
Vue.use(komgaSettings, {store: store, http: Vue.prototype.$http})
|
||||
Vue.use(komgaFileSystem, {http: Vue.prototype.$http})
|
||||
Vue.use(komgaSeries, {http: Vue.prototype.$http})
|
||||
Vue.use(komgaCollections, {http: Vue.prototype.$http})
|
||||
|
|
@ -80,7 +81,6 @@ Vue.use(komgaMetrics, {http: Vue.prototype.$http})
|
|||
Vue.use(komgaHistory, {http: Vue.prototype.$http})
|
||||
Vue.use(komgaAnnouncements, {http: Vue.prototype.$http})
|
||||
Vue.use(komgaReleases, {http: Vue.prototype.$http})
|
||||
Vue.use(komgaSettings, {store: store, http: Vue.prototype.$http})
|
||||
Vue.use(komgaFonts, {http: Vue.prototype.$http})
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
|
|
|||
|
|
@ -11,35 +11,47 @@ const vuexModule: Module<any, any> = {
|
|||
libraries: [] as LibraryDto[],
|
||||
},
|
||||
getters: {
|
||||
getLibraryById: (state) => (id: number) => {
|
||||
return state.libraries.find((l: any) => l.id === id)
|
||||
getLibraries(state, getters) {
|
||||
const settings = getters.getClientSettingsLibraries
|
||||
return state.libraries
|
||||
.map((it: LibraryDto) => Object.assign({}, it, settings[it.id]))
|
||||
.sort((a: LibraryDto, b: LibraryDto) => a.name.toLowerCase() > b.name.toLowerCase())
|
||||
},
|
||||
getLibraryById: (state, getters) => (id: number) => {
|
||||
return getters.getLibraries.find((l: any) => l.id === id)
|
||||
},
|
||||
getLibrariesPinned(state, getters) {
|
||||
return getters.getLibraries.filter((it: LibraryDto) => !it.unpinned)
|
||||
},
|
||||
getLibrariesUnpinned(state, getters) {
|
||||
return getters.getLibraries.filter((it: LibraryDto) => it.unpinned)
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setLibraries (state, libraries) {
|
||||
setLibraries(state, libraries) {
|
||||
state.libraries = libraries
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async getLibraries ({ commit }) {
|
||||
async getLibraries({commit}) {
|
||||
commit('setLibraries', await service.getLibraries())
|
||||
},
|
||||
async postLibrary ({ dispatch }, library) {
|
||||
async postLibrary({dispatch}, library) {
|
||||
await service.postLibrary(library)
|
||||
},
|
||||
async updateLibrary ({ dispatch }, { libraryId, library }) {
|
||||
async updateLibrary({dispatch}, {libraryId, library}) {
|
||||
await service.updateLibrary(libraryId, library)
|
||||
},
|
||||
async deleteLibrary ({ dispatch }, library) {
|
||||
async deleteLibrary({dispatch}, library) {
|
||||
await service.deleteLibrary(library)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
install (
|
||||
install(
|
||||
Vue: typeof _Vue,
|
||||
{ store, http }: { store: any, http: AxiosInstance }) {
|
||||
{store, http}: { store: any, http: AxiosInstance }) {
|
||||
service = new KomgaLibrariesService(http)
|
||||
Vue.prototype.$komgaLibraries = service
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import {AxiosInstance} from 'axios'
|
|||
import _Vue from 'vue'
|
||||
import KomgaSettingsService from '@/services/komga-settings.service'
|
||||
import {Module} from 'vuex'
|
||||
import {ClientSettingDto} from '@/types/komga-clientsettings'
|
||||
import {
|
||||
CLIENT_SETTING,
|
||||
ClientSettingDto,
|
||||
ClientSettingLibrary,
|
||||
ClientSettingLibraryUpdate,
|
||||
ClientSettingUserUpdateDto,
|
||||
} from '@/types/komga-clientsettings'
|
||||
|
||||
let service: KomgaSettingsService
|
||||
|
||||
|
|
@ -15,6 +21,14 @@ const vuexModule: Module<any, any> = {
|
|||
getClientSettings(state): Record<string, ClientSettingDto> {
|
||||
return {...state.clientSettingsGlobal, ...state.clientSettingsUser}
|
||||
},
|
||||
getClientSettingsLibraries(state): Record<string, ClientSettingLibrary> {
|
||||
let settings: Record<string, ClientSettingLibrary> = {}
|
||||
try {
|
||||
settings = JSON.parse(state.clientSettingsUser[CLIENT_SETTING.WEBUI_LIBRARIES]?.value)
|
||||
} catch (e) {
|
||||
}
|
||||
return settings
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setClientSettingsGlobal(state, settings) {
|
||||
|
|
@ -31,6 +45,16 @@ const vuexModule: Module<any, any> = {
|
|||
async getClientSettingsUser({commit}) {
|
||||
commit('setClientSettingsUser', await service.getClientSettingsUser())
|
||||
},
|
||||
async updateLibrarySetting({dispatch, getters}, update: ClientSettingLibraryUpdate) {
|
||||
const all = getters.getClientSettingsLibraries
|
||||
all[update.libraryId] = Object.assign({}, all[update.libraryId], update.patch)
|
||||
const newSettings = {} as Record<string, ClientSettingUserUpdateDto>
|
||||
newSettings[CLIENT_SETTING.WEBUI_LIBRARIES] = {
|
||||
value: JSON.stringify(all),
|
||||
}
|
||||
await service.updateClientSettingUser(newSettings)
|
||||
dispatch('getClientSettingsUser')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@ const noLibraryGuard = (to: any, from: any, next: any) => {
|
|||
} else next()
|
||||
}
|
||||
|
||||
const noLibraryNorPinGuard = (to: any, from: any, next: any) => {
|
||||
if (lStore.state.komgaLibraries.libraries.length === 0) {
|
||||
next({name: 'welcome'})
|
||||
} else if (lStore.getters.getLibrariesPinned.length === 0) {
|
||||
next({name: 'no-pins'})
|
||||
} else next()
|
||||
}
|
||||
|
||||
const getLibraryRoute = (libraryId: string) => {
|
||||
switch ((lStore.getters.getLibraryRoute(libraryId) as LIBRARY_ROUTE)) {
|
||||
case LIBRARY_ROUTE.COLLECTIONS:
|
||||
|
|
@ -57,10 +65,15 @@ const router = new Router({
|
|||
name: 'welcome',
|
||||
component: () => import(/* webpackChunkName: "welcome" */ './views/WelcomeView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/no-pins',
|
||||
name: 'no-pins',
|
||||
component: () => import(/* webpackChunkName: "no-pins" */ './views/NoPinnedLibraries.vue'),
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
beforeEnter: noLibraryGuard,
|
||||
beforeEnter: noLibraryNorPinGuard,
|
||||
component: () => import(/* webpackChunkName: "dashboard" */ './views/DashboardView.vue'),
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,14 +53,15 @@ export default class KomgaBooksService {
|
|||
}
|
||||
}
|
||||
|
||||
async getBooksOnDeck(libraryId?: string, pageRequest?: PageRequest): Promise<Page<BookDto>> {
|
||||
async getBooksOnDeck(libraryIds?: string[], pageRequest?: PageRequest): Promise<Page<BookDto>> {
|
||||
try {
|
||||
const params = {...pageRequest} as any
|
||||
if (libraryId) {
|
||||
params.library_id = libraryId
|
||||
if (libraryIds) {
|
||||
params.library_id = libraryIds
|
||||
}
|
||||
return (await this.http.get(`${API_BOOKS}/ondeck`, {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve books on deck'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import {AxiosInstance} from 'axios'
|
||||
import {AuthorDto, BookDto} from '@/types/komga-books'
|
||||
import {GroupCountDto, SeriesDto, SeriesMetadataUpdateDto, SeriesThumbnailDto} from '@/types/komga-series'
|
||||
import {SeriesSearch} from '@/types/komga-search'
|
||||
|
||||
|
|
@ -41,13 +40,14 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async getNewSeries(libraryId?: string, oneshot?: boolean, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
async getNewSeries(libraryIds?: string[], oneshot?: boolean, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
try {
|
||||
const params = {...pageRequest} as any
|
||||
if (libraryId) params.library_id = libraryId
|
||||
if (libraryIds) params.library_id = libraryIds
|
||||
if (oneshot !== undefined) params.oneshot = oneshot
|
||||
return (await this.http.get(`${API_SERIES}/new`, {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve new series'
|
||||
|
|
@ -58,13 +58,14 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async getUpdatedSeries(libraryId?: string, oneshot?: boolean, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
async getUpdatedSeries(libraryIds?: string[], oneshot?: boolean, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
try {
|
||||
const params = {...pageRequest} as any
|
||||
if (libraryId) params.library_id = libraryId
|
||||
if (libraryIds) params.library_id = libraryIds
|
||||
if (oneshot !== undefined) params.oneshot = oneshot
|
||||
return (await this.http.get(`${API_SERIES}/updated`, {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve updated series'
|
||||
|
|
|
|||
|
|
@ -17,4 +17,15 @@ export enum CLIENT_SETTING {
|
|||
WEBUI_OAUTH2_AUTO_LOGIN = 'webui.oauth2.auto_login',
|
||||
WEBUI_POSTER_STRETCH = 'webui.poster.stretch',
|
||||
WEBUI_POSTER_BLUR_UNREAD = 'webui.poster.blur_unread',
|
||||
WEBUI_LIBRARIES = 'webui.libraries',
|
||||
}
|
||||
|
||||
export interface ClientSettingLibrary {
|
||||
unpinned?: boolean,
|
||||
order?: number,
|
||||
}
|
||||
|
||||
export interface ClientSettingLibraryUpdate {
|
||||
libraryId: string,
|
||||
patch: ClientSettingLibrary,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ export interface LibraryDto {
|
|||
analyzeDimensions: boolean,
|
||||
oneshotsDirectory: string,
|
||||
unavailable: boolean,
|
||||
|
||||
// custom fields
|
||||
unpinned: boolean,
|
||||
order: number,
|
||||
}
|
||||
|
||||
export interface LibraryCreationDto {
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ export default Vue.extend({
|
|||
},
|
||||
async resetParams(route: any, collectionId: string) {
|
||||
// load dynamic filters
|
||||
this.$set(this.filterOptions, 'library', this.$store.state.komgaLibraries.libraries.map((x: LibraryDto) => ({
|
||||
this.$set(this.filterOptions, 'library', this.$store.getters.getLibraries.map((x: LibraryDto) => ({
|
||||
name: x.name,
|
||||
value: x.id,
|
||||
})))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<libraries-actions-menu v-else/>
|
||||
|
||||
<v-toolbar-title>
|
||||
<span>{{ library ? library.name : $t('common.all_libraries') }}</span>
|
||||
<span>{{ toolbarTitle }}</span>
|
||||
<v-chip label class="mx-4" v-if="totalElements">
|
||||
<span style="font-size: 1.1rem">{{ totalElements }}</span>
|
||||
</v-chip>
|
||||
|
|
@ -87,7 +87,6 @@ export default Vue.extend({
|
|||
},
|
||||
data: () => {
|
||||
return {
|
||||
library: undefined as LibraryDto | undefined,
|
||||
collections: [] as CollectionDto[],
|
||||
selectedCollections: [] as CollectionDto[],
|
||||
page: 1,
|
||||
|
|
@ -104,6 +103,14 @@ export default Vue.extend({
|
|||
default: LIBRARIES_ALL,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$store.getters.getLibrariesPinned': {
|
||||
handler(val) {
|
||||
if (this.libraryId === LIBRARIES_ALL)
|
||||
this.loadLibrary(this.libraryId)
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$eventHub.$on(COLLECTION_ADDED, this.reloadCollections)
|
||||
this.$eventHub.$on(COLLECTION_CHANGED, this.reloadCollections)
|
||||
|
|
@ -142,6 +149,14 @@ export default Vue.extend({
|
|||
next()
|
||||
},
|
||||
computed: {
|
||||
library(): LibraryDto | undefined {
|
||||
return this.getLibraryLazy(this.libraryId)
|
||||
},
|
||||
toolbarTitle(): string {
|
||||
if (this.library) return this.library.name
|
||||
else if (this.$store.getters.getLibrariesPinned.length > 0) return this.$t('common.pinned_libraries').toString()
|
||||
else return this.$t('common.all_libraries').toString()
|
||||
},
|
||||
isAdmin(): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
|
|
@ -205,7 +220,6 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
async loadLibrary(libraryId: string) {
|
||||
this.library = this.getLibraryLazy(libraryId)
|
||||
if (this.library != undefined) document.title = `Komga - ${this.library.name}`
|
||||
await this.loadPage(libraryId, this.page)
|
||||
|
||||
|
|
@ -221,7 +235,7 @@ export default Vue.extend({
|
|||
size: this.pageSize,
|
||||
} as PageRequest
|
||||
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : this.$store.getters.getLibrariesPinned.map(it => it.id)
|
||||
const collectionsPage = await this.$komgaCollections.getCollections(lib, pageRequest)
|
||||
|
||||
this.totalPages = collectionsPage.totalPages
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<libraries-actions-menu v-else/>
|
||||
|
||||
<v-toolbar-title>
|
||||
<span>{{ library ? library.name : $t('common.all_libraries') }}</span>
|
||||
<span>{{ toolbarTitle }}</span>
|
||||
<v-chip label class="mx-4" v-if="totalElements">
|
||||
<span style="font-size: 1.1rem">{{ totalElements }}</span>
|
||||
</v-chip>
|
||||
|
|
@ -168,7 +168,8 @@ import {ItemContext} from '@/types/items'
|
|||
import {
|
||||
BookSearch,
|
||||
SearchConditionAgeRating,
|
||||
SearchConditionAllOfSeries, SearchConditionAnyOfBook,
|
||||
SearchConditionAllOfSeries,
|
||||
SearchConditionAnyOfBook,
|
||||
SearchConditionAnyOfSeries,
|
||||
SearchConditionAuthor,
|
||||
SearchConditionComplete,
|
||||
|
|
@ -229,7 +230,6 @@ export default Vue.extend({
|
|||
},
|
||||
data: function () {
|
||||
return {
|
||||
library: undefined as LibraryDto | undefined,
|
||||
series: [] as SeriesDto[],
|
||||
seriesGroups: [] as GroupCountDto[],
|
||||
alphabeticalNavigation: ['ALL', '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
|
||||
|
|
@ -266,6 +266,14 @@ export default Vue.extend({
|
|||
default: LIBRARIES_ALL,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$store.getters.getLibrariesPinned': {
|
||||
handler(val) {
|
||||
if (this.libraryId === LIBRARIES_ALL)
|
||||
this.loadLibrary(this.libraryId)
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$eventHub.$on(SERIES_ADDED, this.seriesChanged)
|
||||
this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
|
||||
|
|
@ -319,6 +327,14 @@ export default Vue.extend({
|
|||
next()
|
||||
},
|
||||
computed: {
|
||||
library(): LibraryDto | undefined {
|
||||
return this.getLibraryLazy(this.libraryId)
|
||||
},
|
||||
toolbarTitle(): string {
|
||||
if (this.library) return this.library.name
|
||||
else if (this.$store.getters.getLibrariesPinned.length > 0) return this.$t('common.pinned_libraries').toString()
|
||||
else return this.$t('common.all_libraries').toString()
|
||||
},
|
||||
symbolCondition(): SearchConditionSeries | undefined {
|
||||
if (this.selectedSymbol === 'ALL') return undefined
|
||||
if (this.selectedSymbol === '#') return new SearchConditionAllOfSeries(
|
||||
|
|
@ -633,7 +649,6 @@ export default Vue.extend({
|
|||
if (this.series.some(b => b.id === event.seriesId)) this.reloadPage()
|
||||
},
|
||||
async loadLibrary(libraryId: string) {
|
||||
this.library = this.getLibraryLazy(libraryId)
|
||||
if (this.library != undefined) document.title = `Komga - ${this.library.name}`
|
||||
|
||||
await this.loadPage(libraryId, this.page, this.sortActive, this.symbolCondition)
|
||||
|
|
@ -671,6 +686,11 @@ export default Vue.extend({
|
|||
|
||||
const conditions = [] as SearchConditionSeries[]
|
||||
if (libraryId !== LIBRARIES_ALL) conditions.push(new SearchConditionLibraryId(new SearchOperatorIs(libraryId)))
|
||||
else {
|
||||
conditions.push(new SearchConditionAnyOfSeries(
|
||||
this.$store.getters.getLibrariesPinned.map((it: LibraryDto) => new SearchConditionLibraryId(new SearchOperatorIs(it.id))),
|
||||
))
|
||||
}
|
||||
if (this.filters.status && this.filters.status.length > 0) this.filtersMode?.status?.allOf ? conditions.push(new SearchConditionAllOfSeries(this.filters.status)) : conditions.push(new SearchConditionAnyOfSeries(this.filters.status))
|
||||
if (this.filters.readStatus && this.filters.readStatus.length > 0) conditions.push(new SearchConditionAnyOfSeries(this.filters.readStatus))
|
||||
if (this.filters.genre && this.filters.genre.length > 0) this.filtersMode?.genre?.allOf ? conditions.push(new SearchConditionAllOfSeries(this.filters.genre)) : conditions.push(new SearchConditionAnyOfSeries(this.filters.genre))
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ export default Vue.extend({
|
|||
},
|
||||
async resetParams(route: any, readListId: string) {
|
||||
// load dynamic filters
|
||||
this.$set(this.filterOptions, 'library', this.$store.state.komgaLibraries.libraries.map((x: LibraryDto) => ({
|
||||
this.$set(this.filterOptions, 'library', this.$store.getters.getLibraries.map((x: LibraryDto) => ({
|
||||
name: x.name,
|
||||
value: x.id,
|
||||
})))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<libraries-actions-menu v-else/>
|
||||
|
||||
<v-toolbar-title>
|
||||
<span>{{ library ? library.name : $t('common.all_libraries') }}</span>
|
||||
<span>{{ toolbarTitle }}</span>
|
||||
<v-chip label class="mx-4" v-if="totalElements">
|
||||
<span style="font-size: 1.1rem">{{ totalElements }}</span>
|
||||
</v-chip>
|
||||
|
|
@ -88,7 +88,6 @@ export default Vue.extend({
|
|||
},
|
||||
data: () => {
|
||||
return {
|
||||
library: undefined as LibraryDto | undefined,
|
||||
readLists: [] as ReadListDto[],
|
||||
selectedReadLists: [] as ReadListDto[],
|
||||
page: 1,
|
||||
|
|
@ -105,6 +104,14 @@ export default Vue.extend({
|
|||
default: LIBRARIES_ALL,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$store.getters.getLibrariesPinned': {
|
||||
handler(val) {
|
||||
if (this.libraryId === LIBRARIES_ALL)
|
||||
this.loadLibrary(this.libraryId)
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$eventHub.$on(READLIST_ADDED, this.reloadElements)
|
||||
this.$eventHub.$on(READLIST_CHANGED, this.reloadElements)
|
||||
|
|
@ -143,6 +150,14 @@ export default Vue.extend({
|
|||
next()
|
||||
},
|
||||
computed: {
|
||||
library(): LibraryDto | undefined {
|
||||
return this.getLibraryLazy(this.libraryId)
|
||||
},
|
||||
toolbarTitle(): string {
|
||||
if (this.library) return this.library.name
|
||||
else if (this.$store.getters.getLibrariesPinned.length > 0) return this.$t('common.pinned_libraries').toString()
|
||||
else return this.$t('common.all_libraries').toString()
|
||||
},
|
||||
isAdmin(): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
|
|
@ -206,7 +221,6 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
async loadLibrary(libraryId: string) {
|
||||
this.library = this.getLibraryLazy(libraryId)
|
||||
if (this.library != undefined) document.title = `Komga - ${this.library.name}`
|
||||
await this.loadPage(libraryId, this.page)
|
||||
|
||||
|
|
@ -222,7 +236,7 @@ export default Vue.extend({
|
|||
size: this.pageSize,
|
||||
} as PageRequest
|
||||
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : this.$store.getters.getLibrariesPinned.map(it => it.id)
|
||||
const elementsPage = await this.$komgaReadLists.getReadLists(lib, pageRequest)
|
||||
|
||||
this.totalPages = elementsPage.totalPages
|
||||
|
|
|
|||
|
|
@ -235,10 +235,15 @@ import {PageLoader} from '@/types/pageLoader'
|
|||
import {ItemContext} from '@/types/items'
|
||||
import {
|
||||
BookSearch,
|
||||
SearchConditionAllOfBook, SearchConditionAnyOfBook,
|
||||
SearchConditionAllOfBook,
|
||||
SearchConditionAnyOfBook,
|
||||
SearchConditionAnyOfSeries,
|
||||
SearchConditionBook,
|
||||
SearchConditionLibraryId,
|
||||
SearchConditionReadStatus, SearchConditionReleaseDate, SearchConditionSeriesId, SearchOperatorAfter,
|
||||
SearchConditionReadStatus,
|
||||
SearchConditionReleaseDate,
|
||||
SearchConditionSeriesId,
|
||||
SearchOperatorAfter,
|
||||
SearchOperatorIs,
|
||||
} from '@/types/komga-search'
|
||||
|
||||
|
|
@ -257,7 +262,6 @@ export default Vue.extend({
|
|||
return {
|
||||
ItemContext,
|
||||
loading: false,
|
||||
library: undefined as LibraryDto | undefined,
|
||||
loaderNewSeries: undefined as unknown as PageLoader<SeriesDto>,
|
||||
loaderUpdatedSeries: undefined as unknown as PageLoader<SeriesDto>,
|
||||
loaderLatestBooks: undefined as unknown as PageLoader<BookDto>,
|
||||
|
|
@ -299,7 +303,7 @@ export default Vue.extend({
|
|||
route: LIBRARY_ROUTE.RECOMMENDED,
|
||||
})
|
||||
this.setupLoaders(this.libraryId)
|
||||
this.loadAll(this.libraryId)
|
||||
this.loadAll()
|
||||
},
|
||||
props: {
|
||||
libraryId: {
|
||||
|
|
@ -310,7 +314,11 @@ export default Vue.extend({
|
|||
watch: {
|
||||
libraryId(val) {
|
||||
this.setupLoaders(val)
|
||||
this.loadAll(val)
|
||||
this.loadAll()
|
||||
},
|
||||
libraryIds() {
|
||||
this.setupLoaders(this.libraryId)
|
||||
this.loadAll()
|
||||
},
|
||||
'$store.state.komgaLibraries.libraries': {
|
||||
handler(val) {
|
||||
|
|
@ -318,8 +326,19 @@ export default Vue.extend({
|
|||
else this.reload()
|
||||
},
|
||||
},
|
||||
'$store.getters.getLibrariesPinned': {
|
||||
handler(val) {
|
||||
if (val.length === 0) this.$router.push({name: 'no-pins'})
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
library(): LibraryDto | undefined {
|
||||
return this.getLibraryLazy(this.libraryId)
|
||||
},
|
||||
libraryIds(): string[] {
|
||||
return this.libraryId !== LIBRARIES_ALL ? [this.libraryId] : this.$store.getters.getLibrariesPinned.map((it: LibraryDto) => it.id)
|
||||
},
|
||||
isAdmin(): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
|
|
@ -346,8 +365,8 @@ export default Vue.extend({
|
|||
async scrollChanged(loader: PageLoader<any>, percent: number) {
|
||||
if (percent > 0.95) await loader.loadNext()
|
||||
},
|
||||
getRequestLibraryId(libraryId: string): string | undefined {
|
||||
return libraryId !== LIBRARIES_ALL ? libraryId : undefined
|
||||
getRequestLibraryId(libraryId: string): string[] {
|
||||
return libraryId !== LIBRARIES_ALL ? [libraryId] : this.$store.getters.getLibrariesPinned.map((it: LibraryDto) => it.id)
|
||||
},
|
||||
seriesChanged(event: SeriesSseDto) {
|
||||
if (this.libraryId === LIBRARIES_ALL || event.libraryId === this.libraryId) {
|
||||
|
|
@ -371,11 +390,15 @@ export default Vue.extend({
|
|||
else if (this.loaderNewSeries?.items.some(s => s.id === event.seriesId)) this.reload()
|
||||
},
|
||||
reload: throttle(function (this: any) {
|
||||
this.loadAll(this.libraryId, true)
|
||||
this.loadAll(true)
|
||||
}, 5000),
|
||||
setupLoaders(libraryId: string) {
|
||||
const requestLibraries = this.getRequestLibraryId(libraryId)
|
||||
const baseBookConditions = [] as SearchConditionBook[]
|
||||
if (libraryId !== LIBRARIES_ALL) baseBookConditions.push(new SearchConditionLibraryId(new SearchOperatorIs(libraryId)))
|
||||
if (requestLibraries)
|
||||
baseBookConditions.push(new SearchConditionAnyOfSeries(
|
||||
requestLibraries.map((it: string) => new SearchConditionLibraryId(new SearchOperatorIs(it))),
|
||||
))
|
||||
|
||||
this.loaderInProgressBooks = new PageLoader<BookDto>(
|
||||
{sort: ['readProgress.readDate,desc']},
|
||||
|
|
@ -385,7 +408,7 @@ export default Vue.extend({
|
|||
)
|
||||
this.loaderOnDeckBooks = new PageLoader<BookDto>(
|
||||
{},
|
||||
(pageable: PageRequest) => this.$komgaBooks.getBooksOnDeck(this.getRequestLibraryId(libraryId), pageable),
|
||||
(pageable: PageRequest) => this.$komgaBooks.getBooksOnDeck(requestLibraries, pageable),
|
||||
)
|
||||
this.loaderLatestBooks = new PageLoader<BookDto>(
|
||||
{sort: ['createdDate,desc']},
|
||||
|
|
@ -408,16 +431,15 @@ export default Vue.extend({
|
|||
|
||||
this.loaderNewSeries = new PageLoader<SeriesDto>(
|
||||
{},
|
||||
(pageable: PageRequest) => this.$komgaSeries.getNewSeries(this.getRequestLibraryId(libraryId), false, pageable),
|
||||
(pageable: PageRequest) => this.$komgaSeries.getNewSeries(requestLibraries, false, pageable),
|
||||
)
|
||||
this.loaderUpdatedSeries = new PageLoader<SeriesDto>(
|
||||
{},
|
||||
(pageable: PageRequest) => this.$komgaSeries.getUpdatedSeries(this.getRequestLibraryId(libraryId), false, pageable),
|
||||
(pageable: PageRequest) => this.$komgaSeries.getUpdatedSeries(requestLibraries, false, pageable),
|
||||
)
|
||||
},
|
||||
loadAll(libraryId: string, reload: boolean = false) {
|
||||
loadAll(reload: boolean = false) {
|
||||
this.loading = true
|
||||
this.library = this.getLibraryLazy(libraryId)
|
||||
if (this.library != undefined) document.title = `Komga - ${this.library.name}`
|
||||
this.selectedSeries = []
|
||||
this.selectedBooks = []
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@
|
|||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item v-for="(l, index) in libraries"
|
||||
<!-- PINNED LIBRARIES -->
|
||||
<v-list-item v-for="(l, index) in librariesPinned"
|
||||
:key="index"
|
||||
:to="{name:'libraries', params: {libraryId: l.id}}"
|
||||
>
|
||||
|
|
@ -94,11 +95,38 @@
|
|||
>{{ $t('common.unavailable') }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action v-if="isAdmin" class="ma-0">
|
||||
<v-list-item-action class="ma-0">
|
||||
<library-actions-menu :library="l"/>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
|
||||
<!-- UNPINNED LIBRARIES -->
|
||||
<v-list-group no-action
|
||||
sub-group
|
||||
v-if="librariesUnpinned.length > 0"
|
||||
>
|
||||
<template v-slot:activator>
|
||||
<v-list-item-title>{{ $t('common.more') }}</v-list-item-title>
|
||||
</template>
|
||||
|
||||
<v-list-item v-for="(l, index) in librariesUnpinned"
|
||||
:key="index"
|
||||
:to="{name:'libraries', params: {libraryId: l.id}}"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ l.name }}</v-list-item-title>
|
||||
<v-list-item-subtitle
|
||||
v-if="l.unavailable"
|
||||
class="error--text caption"
|
||||
>{{ $t('common.unavailable') }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action class="ma-0">
|
||||
<library-actions-menu :library="l"/>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<!-- IMPORT -->
|
||||
<v-list-group v-if="isAdmin"
|
||||
prepend-icon="mdi-import"
|
||||
|
|
@ -400,7 +428,13 @@ export default Vue.extend({
|
|||
return this.$store.state.komgaSse.taskCountByType
|
||||
},
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
return this.$store.getters.getLibraries
|
||||
},
|
||||
librariesPinned(): LibraryDto[] {
|
||||
return this.$store.getters.getLibrariesPinned
|
||||
},
|
||||
librariesUnpinned(): LibraryDto[] {
|
||||
return this.$store.getters.getLibrariesUnpinned
|
||||
},
|
||||
isAdmin(): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export default Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
filterLibrariesOptions(): object[] {
|
||||
return this.$store.state.komgaLibraries.libraries.map(x => ({
|
||||
return this.$store.getters.getLibraries.map(x => ({
|
||||
text: x.name,
|
||||
value: x.id,
|
||||
}))
|
||||
|
|
|
|||
35
komga-webui/src/views/NoPinnedLibraries.vue
Normal file
35
komga-webui/src/views/NoPinnedLibraries.vue
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<div class="pa-6">
|
||||
<empty-state
|
||||
:title="$t('no_libraries_pinned.title')"
|
||||
:sub-title="$t('no_libraries_pinned.subtitle')"
|
||||
icon="mdi-pin-off"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'NoPinnedLibraries',
|
||||
components: {EmptyState},
|
||||
mounted() {
|
||||
if (this.$store.getters.getLibrariesPinned.length !== 0) {
|
||||
this.$router.push({name: 'dashboard'})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$store.getters.getLibrariesPinned': {
|
||||
handler(val) {
|
||||
if (val.length !== 0) this.$router.push({name: 'dashboard'})
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -43,7 +43,6 @@
|
|||
import Vue from 'vue'
|
||||
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
|
||||
import {ERROR, ErrorEvent, NOTIFICATION, NotificationEvent} from '@/types/events'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import jsFileDownloader from 'js-file-downloader'
|
||||
import urls from '@/functions/urls'
|
||||
|
||||
|
|
@ -53,11 +52,6 @@ export default Vue.extend({
|
|||
data: () => ({
|
||||
modalStopServer: false,
|
||||
}),
|
||||
computed: {
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async cancelAllTasks() {
|
||||
const count = await this.$komgaTasks.deleteAllTasks()
|
||||
|
|
|
|||
Loading…
Reference in a new issue