mirror of
https://github.com/gotson/komga.git
synced 2025-12-20 07:23:34 +01:00
parent
55df968651
commit
35bf05eb39
14 changed files with 210 additions and 71 deletions
|
|
@ -26,6 +26,7 @@
|
|||
<item-card
|
||||
class="item-card"
|
||||
:item="item"
|
||||
:item-context="itemContext"
|
||||
:width="itemWidth"
|
||||
:selected="active"
|
||||
:no-link="draggable || deletable"
|
||||
|
|
@ -76,6 +77,7 @@ import ItemCard from '@/components/ItemCard.vue'
|
|||
import {computeCardWidth} from '@/functions/grid-utilities'
|
||||
import Vue from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import {ItemContext} from '@/types/items'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ItemBrowser',
|
||||
|
|
@ -85,6 +87,10 @@ export default Vue.extend({
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
itemContext: {
|
||||
type: Array as () => ItemContext[],
|
||||
default: () => [],
|
||||
},
|
||||
fixedItemWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
|
|
|
|||
|
|
@ -101,14 +101,28 @@
|
|||
|
||||
<!-- Description-->
|
||||
<template v-if="!thumbnailOnly">
|
||||
<router-link :to="to" class="link-underline">
|
||||
<router-link v-if="!Array.isArray(title)" :to="title.to" class="link-underline" @click.native="$event.stopImmediatePropagation()">
|
||||
<v-card-subtitle
|
||||
v-line-clamp="2"
|
||||
v-bind="subtitleProps"
|
||||
v-html="title"
|
||||
>
|
||||
</v-card-subtitle>
|
||||
v-html="title.title"
|
||||
/>
|
||||
</router-link>
|
||||
<template v-if="Array.isArray(title)">
|
||||
<v-card-subtitle
|
||||
v-bind="subtitleProps"
|
||||
>
|
||||
<router-link
|
||||
v-for="(t, i) in title"
|
||||
:key="i"
|
||||
:to="t.to"
|
||||
@click.native="$event.stopImmediatePropagation()"
|
||||
class="link-underline text-truncate"
|
||||
v-html="t.title"
|
||||
style="display: block"
|
||||
/>
|
||||
</v-card-subtitle>
|
||||
</template>
|
||||
<v-card-text class="px-2" v-html="body">
|
||||
</v-card-text>
|
||||
</template>
|
||||
|
|
@ -123,17 +137,21 @@ import CollectionActionsMenu from '@/components/menus/CollectionActionsMenu.vue'
|
|||
import SeriesActionsMenu from '@/components/menus/SeriesActionsMenu.vue'
|
||||
import {getReadProgress, getReadProgressPercentage} from '@/functions/book-progress'
|
||||
import {ReadStatus} from '@/types/enum-books'
|
||||
import {createItem, Item, ItemTypes} from '@/types/items'
|
||||
import {createItem, Item, ItemContext, ItemTitle, ItemTypes} from '@/types/items'
|
||||
import Vue from 'vue'
|
||||
import {RawLocation} from 'vue-router'
|
||||
import ReadListActionsMenu from '@/components/menus/ReadListActionsMenu.vue'
|
||||
import {BookDto} from '@/types/komga-books'
|
||||
import {SeriesDto} from '@/types/komga-series'
|
||||
import {
|
||||
THUMBNAILBOOK_ADDED, THUMBNAILBOOK_DELETED,
|
||||
THUMBNAILCOLLECTION_ADDED, THUMBNAILCOLLECTION_DELETED,
|
||||
THUMBNAILREADLIST_ADDED, THUMBNAILREADLIST_DELETED,
|
||||
THUMBNAILSERIES_ADDED, THUMBNAILSERIES_DELETED,
|
||||
THUMBNAILBOOK_ADDED,
|
||||
THUMBNAILBOOK_DELETED,
|
||||
THUMBNAILCOLLECTION_ADDED,
|
||||
THUMBNAILCOLLECTION_DELETED,
|
||||
THUMBNAILREADLIST_ADDED,
|
||||
THUMBNAILREADLIST_DELETED,
|
||||
THUMBNAILSERIES_ADDED,
|
||||
THUMBNAILSERIES_DELETED,
|
||||
} from '@/types/events'
|
||||
import {
|
||||
ThumbnailBookSseDto,
|
||||
|
|
@ -151,6 +169,10 @@ export default Vue.extend({
|
|||
type: Object as () => BookDto | SeriesDto | CollectionDto | ReadListDto,
|
||||
required: true,
|
||||
},
|
||||
itemContext: {
|
||||
type: Array as () => ItemContext[],
|
||||
default: () => [],
|
||||
},
|
||||
// hide the bottom part of the card
|
||||
thumbnailOnly: {
|
||||
type: Boolean,
|
||||
|
|
@ -248,14 +270,14 @@ export default Vue.extend({
|
|||
thumbnailUrl(): string {
|
||||
return this.computedItem.thumbnailUrl() + this.thumbnailCacheBust
|
||||
},
|
||||
title(): string {
|
||||
return this.computedItem.title()
|
||||
title(): ItemTitle | ItemTitle[] {
|
||||
return this.computedItem.title(this.itemContext)
|
||||
},
|
||||
subtitleProps(): Object {
|
||||
return this.computedItem.subtitleProps()
|
||||
},
|
||||
body(): string {
|
||||
return this.computedItem.body()
|
||||
return this.computedItem.body(this.itemContext)
|
||||
},
|
||||
isInProgress(): boolean {
|
||||
if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgress(this.item as BookDto) === ReadStatus.IN_PROGRESS
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="readListsContent[index]"
|
||||
:item-context="[ItemContext.SHOW_SERIES]"
|
||||
nowrap
|
||||
:selectable="false"
|
||||
:action-menu="false"
|
||||
|
|
@ -31,6 +32,7 @@ import ItemBrowser from '@/components/ItemBrowser.vue'
|
|||
import Vue from 'vue'
|
||||
import {BookDto} from '@/types/komga-books'
|
||||
import {ContextOrigin} from '@/types/context'
|
||||
import {ItemContext} from '@/types/items'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReadListsExpansionPanels',
|
||||
|
|
@ -46,6 +48,7 @@ export default Vue.extend({
|
|||
},
|
||||
data: () => {
|
||||
return {
|
||||
ItemContext,
|
||||
readListPanel: undefined as number | undefined,
|
||||
readListsContent: [[]] as any[],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@
|
|||
"book_card": {
|
||||
"error": "Error",
|
||||
"unknown": "To be analyzed",
|
||||
"unsupported": "Unsupported"
|
||||
"unread": "Unread",
|
||||
"unsupported": "Unsupported",
|
||||
"no_release_date": "No release date"
|
||||
},
|
||||
"book_import": {
|
||||
"button_browse": "Browse",
|
||||
|
|
|
|||
|
|
@ -4,12 +4,27 @@ import {BookDto} from '@/types/komga-books'
|
|||
import {SeriesDto} from '@/types/komga-series'
|
||||
import i18n from '@/i18n'
|
||||
import {MediaStatus} from '@/types/enum-books'
|
||||
import {getFileSize} from '@/functions/file'
|
||||
|
||||
export enum ItemTypes {
|
||||
BOOK, SERIES, COLLECTION, READLIST
|
||||
}
|
||||
|
||||
export function createItem (item: BookDto | SeriesDto | CollectionDto | ReadListDto): Item<BookDto | SeriesDto | CollectionDto | ReadListDto> {
|
||||
export enum ItemContext {
|
||||
RELEASE_DATE = 'RELEASE_DATE',
|
||||
DATE_ADDED = 'DATE_ADDED',
|
||||
DATE_UPDATED = 'DATE_UPDATED',
|
||||
FILE_SIZE = 'FILE_SIZE',
|
||||
SHOW_SERIES = 'SHOW_SERIES',
|
||||
READ_DATE = 'READ_DATE',
|
||||
}
|
||||
|
||||
export interface ItemTitle {
|
||||
title: string,
|
||||
to: RawLocation,
|
||||
}
|
||||
|
||||
export function createItem(item: BookDto | SeriesDto | CollectionDto | ReadListDto): Item<BookDto | SeriesDto | CollectionDto | ReadListDto> {
|
||||
if ('bookIds' in item) {
|
||||
return new ReadListItem(item)
|
||||
} else if ('seriesIds' in item) {
|
||||
|
|
@ -26,149 +41,202 @@ export function createItem (item: BookDto | SeriesDto | CollectionDto | ReadList
|
|||
export abstract class Item<T> {
|
||||
item: T
|
||||
|
||||
constructor (item: T) {
|
||||
constructor(item: T) {
|
||||
this.item = item
|
||||
}
|
||||
|
||||
subtitleProps (): Object {
|
||||
subtitleProps(): Object {
|
||||
return {
|
||||
style: 'word-break: normal !important; height: 4em',
|
||||
'class': 'pa-2 pb-1 text--primary',
|
||||
title: this.title(),
|
||||
}
|
||||
}
|
||||
|
||||
abstract type (): ItemTypes
|
||||
abstract type(): ItemTypes
|
||||
|
||||
abstract thumbnailUrl (): string
|
||||
abstract thumbnailUrl(): string
|
||||
|
||||
abstract title (): string
|
||||
abstract title(context: ItemContext[]): ItemTitle | ItemTitle[]
|
||||
|
||||
abstract body (): string
|
||||
abstract body(context: ItemContext[]): string
|
||||
|
||||
abstract to (): RawLocation
|
||||
abstract to(): RawLocation
|
||||
|
||||
abstract fabTo (): RawLocation
|
||||
abstract fabTo(): RawLocation
|
||||
}
|
||||
|
||||
export class BookItem extends Item<BookDto> {
|
||||
thumbnailUrl (): string {
|
||||
thumbnailUrl(): string {
|
||||
return bookThumbnailUrl(this.item.id)
|
||||
}
|
||||
|
||||
type (): ItemTypes {
|
||||
type(): ItemTypes {
|
||||
return ItemTypes.BOOK
|
||||
}
|
||||
|
||||
title (): string {
|
||||
const m = this.item.metadata
|
||||
return `${m.number} - ${m.title}`
|
||||
}
|
||||
|
||||
body (): string {
|
||||
if(this.item.deleted) return `<div class="text-truncate error--text">${i18n.t('common.unavailable')}</div>`
|
||||
switch (this.item.media.status) {
|
||||
case MediaStatus.ERROR: return `<div class="text-truncate error--text">${i18n.t('book_card.error')}</div>`
|
||||
case MediaStatus.UNSUPPORTED: return `<div class="text-truncate warning--text">${i18n.t('book_card.unsupported')}</div>`
|
||||
case MediaStatus.UNKNOWN: return `<div class="text-truncate">${i18n.t('book_card.unknown')}</div>`
|
||||
default: return `<div class="text-truncate">${i18n.tc('common.pages_n', this.item.media.pagesCount)}</div>`
|
||||
title(context: ItemContext[]): ItemTitle | ItemTitle[] {
|
||||
if (context.includes(ItemContext.SHOW_SERIES))
|
||||
return [
|
||||
{
|
||||
title: `${this.item.seriesTitle}`,
|
||||
to: this.seriesTo(),
|
||||
},
|
||||
{
|
||||
title: `${this.item.metadata.number} - ${this.item.metadata.title}`,
|
||||
to: this.to(),
|
||||
},
|
||||
]
|
||||
return {
|
||||
title: `${this.item.metadata.number} - ${this.item.metadata.title}`,
|
||||
to: this.to(),
|
||||
}
|
||||
}
|
||||
|
||||
to (): RawLocation {
|
||||
body(context: ItemContext[] = []): string {
|
||||
if (this.item.deleted) return `<div class="text-truncate error--text">${i18n.t('common.unavailable')}</div>`
|
||||
switch (this.item.media.status) {
|
||||
case MediaStatus.ERROR:
|
||||
return `<div class="text-truncate error--text">${i18n.t('book_card.error')}</div>`
|
||||
case MediaStatus.UNSUPPORTED:
|
||||
return `<div class="text-truncate warning--text">${i18n.t('book_card.unsupported')}</div>`
|
||||
case MediaStatus.UNKNOWN:
|
||||
return `<div class="text-truncate">${i18n.t('book_card.unknown')}</div>`
|
||||
default:
|
||||
let text
|
||||
if (context.includes(ItemContext.RELEASE_DATE))
|
||||
text = this.item.metadata.releaseDate ? new Intl.DateTimeFormat(i18n.locale, {dateStyle: 'medium'} as Intl.DateTimeFormatOptions).format(new Date(this.item.metadata.releaseDate)) : i18n.t('book_card.no_release_date')
|
||||
else if (context.includes(ItemContext.DATE_ADDED))
|
||||
text = new Intl.DateTimeFormat(i18n.locale, {dateStyle: 'medium'} as Intl.DateTimeFormatOptions).format(new Date(this.item.created))
|
||||
else if (context.includes(ItemContext.READ_DATE))
|
||||
text = this.item.readProgress?.lastModified ? new Intl.DateTimeFormat(i18n.locale, {dateStyle: 'medium'} as Intl.DateTimeFormatOptions).format(new Date(this.item.readProgress?.lastModified)) : i18n.t('book_card.unread')
|
||||
else if (context.includes(ItemContext.FILE_SIZE))
|
||||
text = getFileSize(this.item.sizeBytes)
|
||||
else
|
||||
text = i18n.tc('common.pages_n', this.item.media.pagesCount)
|
||||
return `<div class="text-truncate">${text}</div>`
|
||||
}
|
||||
}
|
||||
|
||||
to(): RawLocation {
|
||||
return {
|
||||
name: 'browse-book',
|
||||
params: { bookId: this.item.id },
|
||||
query: { context: this.item?.context?.origin, contextId: this.item?.context?.id },
|
||||
params: {bookId: this.item.id},
|
||||
query: {context: this.item?.context?.origin, contextId: this.item?.context?.id},
|
||||
}
|
||||
}
|
||||
|
||||
fabTo (): RawLocation {
|
||||
seriesTo(): RawLocation {
|
||||
return {
|
||||
name: 'browse-series',
|
||||
params: {seriesId: this.item.seriesId},
|
||||
}
|
||||
}
|
||||
|
||||
fabTo(): RawLocation {
|
||||
return {
|
||||
name: 'read-book',
|
||||
params: { bookId: this.item.id },
|
||||
query: { context: this.item?.context?.origin, contextId: this.item?.context?.id },
|
||||
params: {bookId: this.item.id},
|
||||
query: {context: this.item?.context?.origin, contextId: this.item?.context?.id},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SeriesItem extends Item<SeriesDto> {
|
||||
thumbnailUrl (): string {
|
||||
thumbnailUrl(): string {
|
||||
return seriesThumbnailUrl(this.item.id)
|
||||
}
|
||||
|
||||
type (): ItemTypes {
|
||||
type(): ItemTypes {
|
||||
return ItemTypes.SERIES
|
||||
}
|
||||
|
||||
title (): string {
|
||||
return this.item.metadata.title
|
||||
title(context: ItemContext[]): ItemTitle | ItemTitle[] {
|
||||
return {
|
||||
title: this.item.metadata.title,
|
||||
to: this.to(),
|
||||
}
|
||||
}
|
||||
|
||||
body (): string {
|
||||
if(this.item.deleted) return `<div class="text-truncate error--text">${i18n.t('common.unavailable')}</div>`
|
||||
return `<span>${i18n.tc('common.books_n', this.item.booksCount)}</span>`
|
||||
body(context: ItemContext[] = []): string {
|
||||
if (this.item.deleted) return `<div class="text-truncate error--text">${i18n.t('common.unavailable')}</div>`
|
||||
|
||||
let text
|
||||
if (context.includes(ItemContext.RELEASE_DATE))
|
||||
text = this.item.booksMetadata.releaseDate ? new Intl.DateTimeFormat(i18n.locale, {dateStyle: 'medium'} as Intl.DateTimeFormatOptions).format(new Date(this.item.booksMetadata.releaseDate)) : i18n.t('book_card.no_release_date')
|
||||
else if (context.includes(ItemContext.DATE_ADDED))
|
||||
text = new Intl.DateTimeFormat(i18n.locale, {dateStyle: 'medium'} as Intl.DateTimeFormatOptions).format(new Date(this.item.created))
|
||||
else if (context.includes(ItemContext.DATE_UPDATED))
|
||||
text = new Intl.DateTimeFormat(i18n.locale, {dateStyle: 'medium'} as Intl.DateTimeFormatOptions).format(new Date(this.item.lastModified))
|
||||
else
|
||||
text = i18n.tc('common.books_n', this.item.booksCount)
|
||||
return `<div class="text-truncate">${text}</div>`
|
||||
}
|
||||
|
||||
to (): RawLocation {
|
||||
return { name: 'browse-series', params: { seriesId: this.item.id.toString() } }
|
||||
to(): RawLocation {
|
||||
return {name: 'browse-series', params: {seriesId: this.item.id.toString()}}
|
||||
}
|
||||
|
||||
fabTo (): RawLocation {
|
||||
fabTo(): RawLocation {
|
||||
return undefined as unknown as RawLocation
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionItem extends Item<CollectionDto> {
|
||||
thumbnailUrl (): string {
|
||||
thumbnailUrl(): string {
|
||||
return collectionThumbnailUrl(this.item.id)
|
||||
}
|
||||
|
||||
type (): ItemTypes {
|
||||
type(): ItemTypes {
|
||||
return ItemTypes.COLLECTION
|
||||
}
|
||||
|
||||
title (): string {
|
||||
return this.item.name
|
||||
title(context: ItemContext[]): ItemTitle | ItemTitle[] {
|
||||
return {
|
||||
title: this.item.name,
|
||||
to: this.to(),
|
||||
}
|
||||
}
|
||||
|
||||
body (): string {
|
||||
body(context: ItemContext[] = []): string {
|
||||
const c = this.item.seriesIds.length
|
||||
return `<span>${c} Series</span>`
|
||||
}
|
||||
|
||||
to (): RawLocation {
|
||||
return { name: 'browse-collection', params: { collectionId: this.item.id.toString() } }
|
||||
to(): RawLocation {
|
||||
return {name: 'browse-collection', params: {collectionId: this.item.id.toString()}}
|
||||
}
|
||||
|
||||
fabTo (): RawLocation {
|
||||
fabTo(): RawLocation {
|
||||
return undefined as unknown as RawLocation
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadListItem extends Item<ReadListDto> {
|
||||
thumbnailUrl (): string {
|
||||
thumbnailUrl(): string {
|
||||
return readListThumbnailUrl(this.item.id)
|
||||
}
|
||||
|
||||
type (): ItemTypes {
|
||||
type(): ItemTypes {
|
||||
return ItemTypes.READLIST
|
||||
}
|
||||
|
||||
title (): string {
|
||||
return this.item.name
|
||||
title(context: ItemContext[]): ItemTitle | ItemTitle[] {
|
||||
return {
|
||||
title: this.item.name,
|
||||
to: this.to(),
|
||||
}
|
||||
}
|
||||
|
||||
body (): string {
|
||||
body(context: ItemContext[] = []): string {
|
||||
const c = this.item.bookIds.length
|
||||
return `<span>${c} Books</span>`
|
||||
}
|
||||
|
||||
to (): RawLocation {
|
||||
return { name: 'browse-readlist', params: { readListId: this.item.id.toString() } }
|
||||
to(): RawLocation {
|
||||
return {name: 'browse-readlist', params: {readListId: this.item.id.toString()}}
|
||||
}
|
||||
|
||||
fabTo (): RawLocation {
|
||||
fabTo(): RawLocation {
|
||||
return undefined as unknown as RawLocation
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import {CopyMode} from '@/types/enum-books'
|
|||
export interface BookDto {
|
||||
id: string,
|
||||
seriesId: string,
|
||||
seriesTitle: string,
|
||||
libraryId: string,
|
||||
name: string,
|
||||
url: string,
|
||||
number: number,
|
||||
created: string,
|
||||
lastModified: string,
|
||||
sizeBytes: number,
|
||||
size: string,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export interface SeriesDto {
|
|||
libraryId: string,
|
||||
name: string,
|
||||
url: string,
|
||||
created: string,
|
||||
lastModified: string,
|
||||
booksCount: number,
|
||||
booksReadCount: number,
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
|
||||
<item-browser
|
||||
:items="series"
|
||||
:item-context="itemContext"
|
||||
:selected.sync="selectedSeries"
|
||||
:edit-function="isAdmin ? editSingleSeries : undefined"
|
||||
/>
|
||||
|
|
@ -155,6 +156,7 @@ import {LibrarySseDto, ReadProgressSeriesSseDto, SeriesSseDto} from '@/types/kom
|
|||
import {throttle} from 'lodash'
|
||||
import AlphabeticalNavigation from '@/components/AlphabeticalNavigation.vue'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import { ItemContext } from '@/types/items'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseLibraries',
|
||||
|
|
@ -262,6 +264,12 @@ export default Vue.extend({
|
|||
next()
|
||||
},
|
||||
computed: {
|
||||
itemContext(): ItemContext[] {
|
||||
if(this.sortActive.key === 'booksMetadata.releaseDate') return [ItemContext.RELEASE_DATE]
|
||||
if(this.sortActive.key === 'createdDate') return [ItemContext.DATE_ADDED]
|
||||
if(this.sortActive.key === 'lastModifiedDate') return [ItemContext.DATE_UPDATED]
|
||||
return []
|
||||
},
|
||||
sortOptions(): SortOption[] {
|
||||
return [
|
||||
{name: this.$t('sort.name').toString(), key: 'metadata.titleSort'},
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@
|
|||
|
||||
<item-browser
|
||||
:items.sync="books"
|
||||
:item-context="[ItemContext.SHOW_SERIES]"
|
||||
:selected.sync="selectedBooks"
|
||||
:edit-function="editSingleBook"
|
||||
:draggable="editElements"
|
||||
|
|
@ -149,6 +150,7 @@ import {LibraryDto} from '@/types/komga-libraries'
|
|||
import {mergeFilterParams, toNameValue} from '@/functions/filter'
|
||||
import {Location} from 'vue-router'
|
||||
import {readListFileUrl} from '@/functions/urls'
|
||||
import {ItemContext} from '@/types/items'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseReadList',
|
||||
|
|
@ -164,6 +166,7 @@ export default Vue.extend({
|
|||
},
|
||||
data: () => {
|
||||
return {
|
||||
ItemContext,
|
||||
readList: undefined as ReadListDto | undefined,
|
||||
books: [] as BookDto[],
|
||||
booksCopy: [] as BookDto[],
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@
|
|||
/>
|
||||
|
||||
<item-browser :items="books"
|
||||
:item-context="itemContext"
|
||||
:selected.sync="selectedBooks"
|
||||
:edit-function="isAdmin ? editSingleBook : undefined"
|
||||
/>
|
||||
|
|
@ -450,6 +451,7 @@ import VueHorizontal from 'vue-horizontal'
|
|||
import RtlIcon from '@/components/RtlIcon.vue'
|
||||
import {throttle} from 'lodash'
|
||||
import {BookSseDto, CollectionSseDto, LibrarySseDto, ReadProgressSseDto, SeriesSseDto} from '@/types/komga-sse'
|
||||
import {ItemContext} from '@/types/items'
|
||||
|
||||
const tags = require('language-tags')
|
||||
|
||||
|
|
@ -496,6 +498,12 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
itemContext(): ItemContext[] {
|
||||
if(this.sortActive.key === 'metadata.releaseDate') return [ItemContext.RELEASE_DATE]
|
||||
if(this.sortActive.key === 'createdDate') return [ItemContext.DATE_ADDED]
|
||||
if(this.sortActive.key === 'fileSize') return [ItemContext.FILE_SIZE]
|
||||
return []
|
||||
},
|
||||
sortOptions(): SortOption[] {
|
||||
return [
|
||||
{name: this.$t('sort.number').toString(), key: 'metadata.numberSort'},
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="loaderInProgressBooks.items"
|
||||
:item-context="[ItemContext.SHOW_SERIES]"
|
||||
nowrap
|
||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||
:selected.sync="selectedBooks"
|
||||
|
|
@ -80,6 +81,7 @@
|
|||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="loaderOnDeckBooks.items"
|
||||
:item-context="[ItemContext.SHOW_SERIES]"
|
||||
nowrap
|
||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||
:selected.sync="selectedBooks"
|
||||
|
|
@ -100,6 +102,7 @@
|
|||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="loaderRecentlyReleasedBooks.items"
|
||||
:item-context="[ItemContext.RELEASE_DATE, ItemContext.SHOW_SERIES]"
|
||||
nowrap
|
||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||
:selected.sync="selectedBooks"
|
||||
|
|
@ -120,6 +123,7 @@
|
|||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="loaderLatestBooks.items"
|
||||
:item-context="[ItemContext.SHOW_SERIES]"
|
||||
nowrap
|
||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||
:selected.sync="selectedBooks"
|
||||
|
|
@ -180,6 +184,7 @@
|
|||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="loaderRecentlyReadBooks.items"
|
||||
:item-context="[ItemContext.SHOW_SERIES, ItemContext.READ_DATE]"
|
||||
nowrap
|
||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||
:selected.sync="selectedBooks"
|
||||
|
|
@ -222,6 +227,7 @@ import {subMonths} from 'date-fns'
|
|||
import {BookSseDto, ReadProgressSeriesSseDto, ReadProgressSseDto, SeriesSseDto} from '@/types/komga-sse'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import {PageLoader} from '@/types/pageLoader'
|
||||
import {ItemContext} from '@/types/items'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'DashboardView',
|
||||
|
|
@ -236,6 +242,7 @@ export default Vue.extend({
|
|||
},
|
||||
data: () => {
|
||||
return {
|
||||
ItemContext,
|
||||
loading: false,
|
||||
library: undefined as LibraryDto | undefined,
|
||||
loaderNewSeries: undefined as unknown as PageLoader<SeriesDto>,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="loaderBooks.items"
|
||||
:item-context="[ItemContext.SHOW_SERIES]"
|
||||
nowrap
|
||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||
:selected.sync="selectedBooks"
|
||||
|
|
@ -173,6 +174,7 @@ import {
|
|||
} from '@/types/komga-sse'
|
||||
import {throttle} from 'lodash'
|
||||
import {PageLoader} from '@/types/pageLoader'
|
||||
import {ItemContext} from '@/types/items'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SearchView',
|
||||
|
|
@ -185,6 +187,7 @@ export default Vue.extend({
|
|||
},
|
||||
data: () => {
|
||||
return {
|
||||
ItemContext,
|
||||
loaderSeries: undefined as unknown as PageLoader<SeriesDto>,
|
||||
loaderBooks: undefined as unknown as PageLoader<BookDto>,
|
||||
loaderCollections: undefined as unknown as PageLoader<CollectionDto>,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class BookDtoDao(
|
|||
private val r = Tables.READ_PROGRESS
|
||||
private val a = Tables.BOOK_METADATA_AUTHOR
|
||||
private val s = Tables.SERIES
|
||||
private val sd = Tables.SERIES_METADATA
|
||||
private val rlb = Tables.READLIST_BOOK
|
||||
private val bt = Tables.BOOK_METADATA_TAG
|
||||
private val bl = Tables.BOOK_METADATA_LINK
|
||||
|
|
@ -282,11 +283,13 @@ class BookDtoDao(
|
|||
*m.fields(),
|
||||
*d.fields(),
|
||||
*r.fields(),
|
||||
sd.TITLE,
|
||||
).apply { if (joinConditions.selectReadListNumber) select(rlb.NUMBER) }
|
||||
.from(b)
|
||||
.leftJoin(m).on(b.ID.eq(m.BOOK_ID))
|
||||
.leftJoin(d).on(b.ID.eq(d.BOOK_ID))
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.leftJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID))
|
||||
.apply { if (joinConditions.tag) leftJoin(bt).on(b.ID.eq(bt.BOOK_ID)) }
|
||||
.apply { if (joinConditions.selectReadListNumber) leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) }
|
||||
.apply { if (joinConditions.author) leftJoin(a).on(b.ID.eq(a.BOOK_ID)) }
|
||||
|
|
@ -298,6 +301,7 @@ class BookDtoDao(
|
|||
val mr = rec.into(m)
|
||||
val dr = rec.into(d)
|
||||
val rr = rec.into(r)
|
||||
val seriesTitle = rec.into(sd.TITLE).component1()
|
||||
|
||||
val authors = dsl.selectFrom(a)
|
||||
.where(a.BOOK_ID.eq(br.id))
|
||||
|
|
@ -316,7 +320,7 @@ class BookDtoDao(
|
|||
.fetchInto(bl)
|
||||
.map { WebLinkDto(it.label, it.url) }
|
||||
|
||||
br.toDto(mr.toDto(), dr.toDto(authors, tags, links), if (rr.userId != null) rr.toDto() else null)
|
||||
br.toDto(mr.toDto(), dr.toDto(authors, tags, links), if (rr.userId != null) rr.toDto() else null, seriesTitle)
|
||||
}
|
||||
|
||||
private fun BookSearchWithReadProgress.toCondition(): Condition {
|
||||
|
|
@ -365,10 +369,11 @@ class BookDtoDao(
|
|||
val author: Boolean = false,
|
||||
)
|
||||
|
||||
private fun BookRecord.toDto(media: MediaDto, metadata: BookMetadataDto, readProgress: ReadProgressDto?) =
|
||||
private fun BookRecord.toDto(media: MediaDto, metadata: BookMetadataDto, readProgress: ReadProgressDto?, seriesTitle: String) =
|
||||
BookDto(
|
||||
id = id,
|
||||
seriesId = seriesId,
|
||||
seriesTitle = seriesTitle,
|
||||
libraryId = libraryId,
|
||||
name = name,
|
||||
url = URL(url).toFilePath(),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import java.time.LocalDateTime
|
|||
data class BookDto(
|
||||
val id: String,
|
||||
val seriesId: String,
|
||||
val seriesTitle: String,
|
||||
val libraryId: String,
|
||||
val name: String,
|
||||
val url: String,
|
||||
|
|
|
|||
Loading…
Reference in a new issue