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