mirror of
https://github.com/gotson/komga.git
synced 2025-12-21 16:03:03 +01:00
fix(webui): refactor Cards to a single dynamic component (#148)
this removes the badges on Series and Book cards. For Series it's duplicated information with the card showing the number of books. For Books it's information that is more technical and not needed in the overview, and still available in the book detailed view.
This commit is contained in:
parent
1de0d8491b
commit
74a9f7e628
11 changed files with 431 additions and 145 deletions
6
komga-webui/package-lock.json
generated
6
komga-webui/package-lock.json
generated
|
|
@ -16415,9 +16415,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.2.tgz",
|
||||
"integrity": "sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==",
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
|
||||
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
"sass-loader": "^8.0.2",
|
||||
"ts-jest": "^25.2.1",
|
||||
"typeface-roboto": "0.0.75",
|
||||
"typescript": "^3.8.2",
|
||||
"typescript": "^3.8.3",
|
||||
"vue-cli-plugin-vuetify": "^2.0.5",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuetify-loader": "^1.4.3"
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@
|
|||
<span class="white--text pa-1 px-2 subtitle-2"
|
||||
:style="{background: format.color, position: 'absolute', right: 0}"
|
||||
>
|
||||
{{ format.type }}
|
||||
{{ format.type }} 1
|
||||
</span>
|
||||
|
||||
<span v-if="book.media.status !== 'READY'"
|
||||
class="white--text pa-1 px-2 subtitle-2"
|
||||
:style="{background: statusColor, position: 'absolute', bottom: 0, width: `${width}px`}"
|
||||
>
|
||||
{{ book.media.status }}
|
||||
{{ book.media.status }} 2
|
||||
</span>
|
||||
|
||||
<v-fade-transition>
|
||||
116
komga-webui/src/components/ItemBrowser.vue
Normal file
116
komga-webui/src/components/ItemBrowser.vue
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<v-item-group multiple v-model="selectedItems">
|
||||
<v-row justify="start" ref="content" v-resize="onResize" v-if="hasItems">
|
||||
<v-skeleton-loader v-for="(item, index) in items"
|
||||
:key="index"
|
||||
:width="itemWidth"
|
||||
:height="itemHeight"
|
||||
justify-self="start"
|
||||
:loading="item === null"
|
||||
type="card, text"
|
||||
class="ma-3 mx-2"
|
||||
:data-index="index"
|
||||
v-intersect="onElementIntersect"
|
||||
|
||||
>
|
||||
<v-item v-slot:default="{ toggle, active }" :value="$_.get(item, 'id', 0)">
|
||||
<slot name="item" v-bind:data="{ toggle, active, item, index, itemWidth, preselect: shouldPreselect(), editItem }">
|
||||
<item-card
|
||||
:item="item"
|
||||
:width="itemWidth"
|
||||
:selected="active"
|
||||
:preselect="shouldPreselect()"
|
||||
:onEdit="editItem"
|
||||
:onSelected="toggle"
|
||||
></item-card>
|
||||
</slot>
|
||||
</v-item>
|
||||
</v-skeleton-loader>
|
||||
</v-row>
|
||||
<v-row v-else justify="center">
|
||||
<slot name="empty"></slot>
|
||||
</v-row>
|
||||
</v-item-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { computeCardWidth } from '@/functions/grid-utilities'
|
||||
import ItemCard from '@/components/ItemCard.vue'
|
||||
import mixins from 'vue-typed-mixins'
|
||||
import VisibleElements from '@/mixins/VisibleElements'
|
||||
|
||||
export default mixins(VisibleElements).extend({
|
||||
name: 'ItemBrowser',
|
||||
components: { ItemCard },
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
editFunction: {
|
||||
type: Function,
|
||||
},
|
||||
resizeFunction: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
selectedItems: [],
|
||||
width: 150,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
series: {
|
||||
handler () {
|
||||
this.visibleElements = []
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selectedItems: {
|
||||
handler () {
|
||||
this.$emit('update:selected', this.selectedItems)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selected: {
|
||||
handler () {
|
||||
this.selectedItems = this.selected as []
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasItems (): boolean {
|
||||
return this.items.length > 0
|
||||
},
|
||||
itemWidth (): number {
|
||||
return this.width
|
||||
},
|
||||
itemHeight (): number {
|
||||
return this.width / 0.7071 + 116
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
shouldPreselect (): boolean {
|
||||
return this.selectedItems.length > 0
|
||||
},
|
||||
editItem (item: any) {
|
||||
this.editFunction(item)
|
||||
},
|
||||
onResize () {
|
||||
const content = this.$refs.content as HTMLElement
|
||||
this.width = computeCardWidth(content.clientWidth, this.$vuetify.breakpoint.name)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
158
komga-webui/src/components/ItemCard.vue
Normal file
158
komga-webui/src/components/ItemCard.vue
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<v-hover :disabled="disableHover">
|
||||
<template v-slot:default="{ hover }">
|
||||
<v-card
|
||||
:width="width"
|
||||
@click="onClick"
|
||||
:ripple="false"
|
||||
>
|
||||
<!-- Thumbnail-->
|
||||
<v-img
|
||||
:src="thumbnailUrl"
|
||||
lazy-src="../assets/cover.svg"
|
||||
aspect-ratio="0.7071"
|
||||
>
|
||||
<v-fade-transition>
|
||||
<v-overlay
|
||||
v-if="hover || selected || preselect"
|
||||
absolute
|
||||
:opacity="hover ? 0.3 : 0"
|
||||
:class="`${hover ? 'item-border-darken' : selected ? 'item-border' : 'item-border-transparent'} overlay-full`"
|
||||
>
|
||||
<v-icon v-if="onSelected"
|
||||
:color="selected ? 'secondary' : ''"
|
||||
style="position: absolute; top: 5px; left: 10px"
|
||||
@click.stop="selectItem"
|
||||
>
|
||||
{{ selected || (preselect && hover) ? 'mdi-checkbox-marked-circle' : 'mdi-checkbox-blank-circle-outline'
|
||||
}}
|
||||
</v-icon>
|
||||
|
||||
<v-icon v-if="!selected && !preselect && onEdit"
|
||||
style="position: absolute; bottom: 10px; left: 10px"
|
||||
@click.stop="editItem"
|
||||
>
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
</v-overlay>
|
||||
</v-fade-transition>
|
||||
</v-img>
|
||||
<!-- Description-->
|
||||
<v-card-subtitle
|
||||
v-line-clamp="2"
|
||||
v-bind="subtitleProps"
|
||||
v-html="title"
|
||||
>
|
||||
</v-card-subtitle>
|
||||
<v-card-text class="px-2" v-html="body">
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-hover>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { BookItem, createItem, Item } from '@/types/items'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ItemCard',
|
||||
props: {
|
||||
item: {
|
||||
type: Object as () => BookDto | SeriesDto,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: 150,
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
preselect: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
onSelected: {
|
||||
type: Function,
|
||||
default: undefined,
|
||||
required: false,
|
||||
},
|
||||
onEdit: {
|
||||
type: Function,
|
||||
default: undefined,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
overlay (): boolean {
|
||||
return this.onEdit !== undefined || this.onSelected !== undefined
|
||||
},
|
||||
computedItem (): Item<BookDto | SeriesDto> {
|
||||
return createItem(this.item)
|
||||
},
|
||||
disableHover (): boolean {
|
||||
return !this.overlay
|
||||
},
|
||||
thumbnailUrl (): string {
|
||||
return this.computedItem.thumbnailUrl()
|
||||
},
|
||||
title (): string {
|
||||
return this.computedItem.title()
|
||||
},
|
||||
subtitleProps (): Object {
|
||||
return this.computedItem.subtitleProps()
|
||||
},
|
||||
body (): string {
|
||||
return this.computedItem.body()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onClick () {
|
||||
if (this.preselect && this.onSelected !== undefined) {
|
||||
this.selectItem()
|
||||
} else {
|
||||
this.goto()
|
||||
}
|
||||
},
|
||||
goto () {
|
||||
this.computedItem.goto(this.$router)
|
||||
},
|
||||
selectItem () {
|
||||
if (this.onSelected !== undefined) {
|
||||
this.onSelected()
|
||||
}
|
||||
},
|
||||
editItem () {
|
||||
if (this.onEdit !== undefined) {
|
||||
this.onEdit(this.item)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.item-border {
|
||||
border: 3px solid var(--v-secondary-base);
|
||||
}
|
||||
|
||||
.item-border-transparent {
|
||||
border: 3px solid transparent;
|
||||
}
|
||||
|
||||
.item-border-darken {
|
||||
border: 3px solid var(--v-secondary-darken2);
|
||||
}
|
||||
|
||||
.overlay-full .v-overlay__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -14,6 +14,7 @@ const visibleElements = Vue.extend({
|
|||
} else {
|
||||
this.$_.pull(this.visibleElements, elementIndex)
|
||||
}
|
||||
this.$emit('update', this.visibleElements)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
82
komga-webui/src/types/items.ts
Normal file
82
komga-webui/src/types/items.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { bookThumbnailUrl, seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { VueRouter } from 'vue-router/types/router'
|
||||
|
||||
function plural (count: number, singular: string, plural: string) {
|
||||
return `${count} ${count === 1 ? singular : plural}`
|
||||
}
|
||||
|
||||
export function createItem (item: BookDto | SeriesDto): Item<BookDto | SeriesDto> {
|
||||
if ('seriesId' in item) {
|
||||
return new BookItem(item)
|
||||
} else if ('libraryId' in item) {
|
||||
return new SeriesItem(item)
|
||||
} else {
|
||||
throw new Error('The given item type is not known!')
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Item<T> {
|
||||
item: T;
|
||||
|
||||
constructor (item: T) {
|
||||
this.item = item
|
||||
}
|
||||
|
||||
subtitleProps (): Object {
|
||||
return {
|
||||
style: 'word-break: normal !important; height: 4em',
|
||||
'class': 'pa-2 pb-1 text--primary',
|
||||
title: this.title(),
|
||||
}
|
||||
}
|
||||
|
||||
abstract thumbnailUrl(): string
|
||||
|
||||
abstract title(): string
|
||||
|
||||
abstract body(): string
|
||||
|
||||
abstract goto(router: VueRouter): void
|
||||
}
|
||||
|
||||
export class BookItem extends Item<BookDto> {
|
||||
thumbnailUrl (): string {
|
||||
return bookThumbnailUrl(this.item.id)
|
||||
}
|
||||
|
||||
title (): string {
|
||||
const m = this.item.metadata
|
||||
return `#${m.number} - ${m.title}`
|
||||
}
|
||||
|
||||
body (): string {
|
||||
let c = this.item.media.pagesCount
|
||||
return `
|
||||
<div>${this.item.size}</div>
|
||||
<div>${plural(c, 'Page', 'Pages')}</div>
|
||||
`
|
||||
}
|
||||
|
||||
goto (router: VueRouter): void {
|
||||
router.push({ name: 'browse-book', params: { bookId: this.item.id.toString() } })
|
||||
}
|
||||
}
|
||||
|
||||
export class SeriesItem extends Item<SeriesDto> {
|
||||
thumbnailUrl (): string {
|
||||
return seriesThumbnailUrl(this.item.id)
|
||||
}
|
||||
|
||||
title (): string {
|
||||
return this.item.metadata.title
|
||||
}
|
||||
|
||||
body (): string {
|
||||
let c = this.item.booksCount
|
||||
return `<span>${plural(c, 'Book', 'Books')}</span>`
|
||||
}
|
||||
|
||||
goto (router: VueRouter): void {
|
||||
router.push({ name: 'browse-series', params: { seriesId: this.item.id.toString() } })
|
||||
}
|
||||
}
|
||||
|
|
@ -69,65 +69,49 @@
|
|||
:series.sync="editSeriesSingle"
|
||||
/>
|
||||
|
||||
<v-item-group multiple v-model="selected">
|
||||
<v-container fluid class="px-6">
|
||||
<v-row justify="start" ref="content" v-resize="updateCardWidth" v-if="totalElements !== 0">
|
||||
<v-skeleton-loader v-for="(s, i) in series"
|
||||
:key="i"
|
||||
:width="cardWidth"
|
||||
:height="cardWidth / .7071 + 94"
|
||||
justify-self="start"
|
||||
:loading="s === null"
|
||||
type="card, text"
|
||||
class="ma-3 mx-2"
|
||||
v-intersect="onElementIntersect"
|
||||
:data-index="i"
|
||||
>
|
||||
<v-item v-slot:default="{ active, toggle }" :value="$_.get(s, 'id', 0)">
|
||||
<card-series :series="s"
|
||||
:width="cardWidth"
|
||||
:selected="active"
|
||||
:select="toggle"
|
||||
:preSelect="selected.length > 0"
|
||||
:edit="singleEdit"
|
||||
/>
|
||||
</v-item>
|
||||
</v-skeleton-loader>
|
||||
</v-row>
|
||||
|
||||
<!-- Empty state if filter returns no books -->
|
||||
<v-row justify="center" v-else>
|
||||
<empty-state title="The active filter has no matches"
|
||||
sub-title="Use the menu above to change the active filter"
|
||||
icon="mdi-book-multiple"
|
||||
icon-color="secondary"
|
||||
>
|
||||
<v-btn @click="filterStatus = []">Clear filter</v-btn>
|
||||
</empty-state>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-item-group>
|
||||
<item-browser :items="series"
|
||||
:selected.sync="selected"
|
||||
:edit-function="this.singleEdit"
|
||||
class="px-6"
|
||||
@update="updateVisible"
|
||||
>
|
||||
<template #empty v-if="filterStatus.length > 0">
|
||||
<empty-state title="The active filter has no matches"
|
||||
sub-title="Use the menu above to change the active filter"
|
||||
icon="mdi-book-multiple"
|
||||
icon-color="secondary"
|
||||
>
|
||||
<v-btn @click="filterStatus = []">Clear filter</v-btn>
|
||||
</empty-state>
|
||||
</template>
|
||||
<template #empty v-else>
|
||||
<empty-state title="There are no current libraries"
|
||||
sub-title="Use the + button on the side bar to add a library"
|
||||
icon="mdi-book-multiple"
|
||||
icon-color="secondary"
|
||||
>
|
||||
</empty-state>
|
||||
</template>
|
||||
</item-browser>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import CardSeries from '@/components/CardSeries.vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
|
||||
import LibraryActionsMenu from '@/components/LibraryActionsMenu.vue'
|
||||
import SortMenuButton from '@/components/SortMenuButton.vue'
|
||||
import ToolbarSticky from '@/components/ToolbarSticky.vue'
|
||||
import { computeCardWidth } from '@/functions/grid-utilities'
|
||||
import { parseQuerySort } from '@/functions/query-params'
|
||||
import VisibleElements from '@/mixins/VisibleElements'
|
||||
import { LoadState } from '@/types/common'
|
||||
import mixins from 'vue-typed-mixins'
|
||||
import { SeriesStatus } from '@/types/enum-series'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default mixins(VisibleElements).extend({
|
||||
export default Vue.extend({
|
||||
name: 'BrowseLibraries',
|
||||
components: { LibraryActionsMenu, CardSeries, EmptyState, ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog },
|
||||
components: { LibraryActionsMenu, EmptyState, ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, ItemBrowser },
|
||||
data: () => {
|
||||
return {
|
||||
library: undefined as LibraryDto | undefined,
|
||||
|
|
@ -146,7 +130,6 @@ export default mixins(VisibleElements).extend({
|
|||
sortDefault: { key: 'metadata.titleSort', order: 'asc' } as SortActive,
|
||||
filterStatus: [] as string[],
|
||||
SeriesStatus,
|
||||
cardWidth: 150,
|
||||
sortUnwatch: null as any,
|
||||
filterUnwatch: null as any,
|
||||
selected: [],
|
||||
|
|
@ -161,21 +144,6 @@ export default mixins(VisibleElements).extend({
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
async visibleElements (val) {
|
||||
for (const i of val) {
|
||||
const pageNumber = Math.floor(i / this.pageSize)
|
||||
if (this.pagesState[pageNumber] === undefined || this.pagesState[pageNumber] === LoadState.NotLoaded) {
|
||||
this.processPage(await this.loadPage(pageNumber, this.libraryId))
|
||||
}
|
||||
}
|
||||
|
||||
const max = this.$_.max(val) as number | undefined
|
||||
const index = (max === undefined ? 0 : max).toString()
|
||||
|
||||
if (this.$route.params.index !== index) {
|
||||
this.updateRoute(index)
|
||||
}
|
||||
},
|
||||
selected (val: number[]) {
|
||||
this.selectedSeries = val.map(id => this.series.find(s => s.id === id))
|
||||
.filter(x => x !== undefined) as SeriesDto[]
|
||||
|
|
@ -242,6 +210,21 @@ export default mixins(VisibleElements).extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
async updateVisible (val: []) {
|
||||
for (const i of val) {
|
||||
const pageNumber = Math.floor(i / this.pageSize)
|
||||
if (this.pagesState[pageNumber] === undefined || this.pagesState[pageNumber] === LoadState.NotLoaded) {
|
||||
this.processPage(await this.loadPage(pageNumber, this.libraryId))
|
||||
}
|
||||
}
|
||||
|
||||
const max = this.$_.max(val) as number | undefined
|
||||
const index = (max === undefined ? 0 : max).toString()
|
||||
|
||||
if (this.$route.params.index !== index) {
|
||||
this.updateRoute(index)
|
||||
}
|
||||
},
|
||||
setWatches () {
|
||||
this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload)
|
||||
this.filterUnwatch = this.$watch('filterStatus', this.updateRouteAndReload)
|
||||
|
|
@ -254,10 +237,6 @@ export default mixins(VisibleElements).extend({
|
|||
this.updateRoute()
|
||||
this.reloadData(this.libraryId)
|
||||
},
|
||||
updateCardWidth () {
|
||||
const content = this.$refs.content as HTMLElement
|
||||
this.cardWidth = computeCardWidth(content.clientWidth, this.$vuetify.breakpoint.name)
|
||||
},
|
||||
parseQuerySortOrDefault (querySort: any): SortActive {
|
||||
return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault)
|
||||
},
|
||||
|
|
@ -267,7 +246,6 @@ export default mixins(VisibleElements).extend({
|
|||
reloadData (libraryId: number, countItem?: number) {
|
||||
this.totalElements = null
|
||||
this.pagesState = []
|
||||
this.visibleElements = []
|
||||
this.series = Array(countItem || this.pageSize).fill(null)
|
||||
this.loadInitialData(libraryId)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -96,36 +96,8 @@
|
|||
</v-row>
|
||||
|
||||
<v-divider class="my-4"/>
|
||||
|
||||
<v-item-group multiple v-model="selected">
|
||||
<v-row justify="start" ref="content" v-resize="updateCardWidth">
|
||||
|
||||
<v-skeleton-loader v-for="(b, i) in books"
|
||||
:key="i"
|
||||
:width="cardWidth"
|
||||
:height="cardWidth / .7071 + 116"
|
||||
justify-self="start"
|
||||
:loading="b === null"
|
||||
type="card, text"
|
||||
class="ma-3 mx-2"
|
||||
v-intersect="onElementIntersect"
|
||||
:data-index="i"
|
||||
>
|
||||
<v-item v-slot:default="{ active, toggle }" :value="$_.get(b, 'id', 0)">
|
||||
<card-book :book="b"
|
||||
:width="cardWidth"
|
||||
:selected="active"
|
||||
:select="toggle"
|
||||
:preSelect="selected.length > 0"
|
||||
:edit="singleEdit"
|
||||
/>
|
||||
</v-item>
|
||||
</v-skeleton-loader>
|
||||
|
||||
</v-row>
|
||||
</v-item-group>
|
||||
<item-browser :items="books" :selected.sync="selected" :edit-function="this.singleEdit" class="px-6" @update="updateVisible"></item-browser>
|
||||
</v-container>
|
||||
|
||||
<edit-series-dialog v-model="dialogEdit"
|
||||
:series.sync="series"/>
|
||||
</div>
|
||||
|
|
@ -133,21 +105,19 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import CardBook from '@/components/CardBook.vue'
|
||||
import SortMenuButton from '@/components/SortMenuButton.vue'
|
||||
import ToolbarSticky from '@/components/ToolbarSticky.vue'
|
||||
import { computeCardWidth } from '@/functions/grid-utilities'
|
||||
import Vue from 'vue'
|
||||
import { parseQuerySort } from '@/functions/query-params'
|
||||
import { seriesThumbnailUrl } from '@/functions/urls'
|
||||
import VisibleElements from '@/mixins/VisibleElements'
|
||||
import { LoadState } from '@/types/common'
|
||||
import mixins from 'vue-typed-mixins'
|
||||
import EditBooksDialog from '@/components/EditBooksDialog.vue'
|
||||
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
|
||||
export default mixins(VisibleElements).extend({
|
||||
export default Vue.extend({
|
||||
name: 'BrowseSeries',
|
||||
components: { CardBook, ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, EditBooksDialog },
|
||||
components: { ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, EditBooksDialog, ItemBrowser },
|
||||
data: () => {
|
||||
return {
|
||||
series: {} as SeriesDto,
|
||||
|
|
@ -163,7 +133,6 @@ export default mixins(VisibleElements).extend({
|
|||
}] as SortOption[],
|
||||
sortActive: {} as SortActive,
|
||||
sortDefault: { key: 'metadata.numberSort', order: 'asc' } as SortActive,
|
||||
cardWidth: 150,
|
||||
dialogEdit: false,
|
||||
sortUnwatch: null as any,
|
||||
selected: [],
|
||||
|
|
@ -189,21 +158,6 @@ export default mixins(VisibleElements).extend({
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
async visibleElements (val) {
|
||||
for (const i of val) {
|
||||
const pageNumber = Math.floor(i / this.pageSize)
|
||||
if (this.pagesState[pageNumber] === undefined || this.pagesState[pageNumber] === LoadState.NotLoaded) {
|
||||
this.processPage(await this.loadPage(pageNumber, this.seriesId))
|
||||
}
|
||||
}
|
||||
|
||||
const max = this.$_.max(val) as number | undefined
|
||||
const index = (max === undefined ? 0 : max).toString()
|
||||
|
||||
if (this.$route.params.index !== index) {
|
||||
this.updateRoute(index)
|
||||
}
|
||||
},
|
||||
series (val) {
|
||||
if (this.$_.has(val, 'metadata.title')) {
|
||||
document.title = `Komga - ${val.metadata.title}`
|
||||
|
|
@ -268,6 +222,21 @@ export default mixins(VisibleElements).extend({
|
|||
next()
|
||||
},
|
||||
methods: {
|
||||
async updateVisible (val: []) {
|
||||
for (const i of val) {
|
||||
const pageNumber = Math.floor(i / this.pageSize)
|
||||
if (this.pagesState[pageNumber] === undefined || this.pagesState[pageNumber] === LoadState.NotLoaded) {
|
||||
this.processPage(await this.loadPage(pageNumber, this.seriesId))
|
||||
}
|
||||
}
|
||||
|
||||
const max = this.$_.max(val) as number | undefined
|
||||
const index = (max === undefined ? 0 : max).toString()
|
||||
|
||||
if (this.$route.params.index !== index) {
|
||||
this.updateRoute(index)
|
||||
}
|
||||
},
|
||||
setWatches () {
|
||||
this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload)
|
||||
},
|
||||
|
|
@ -281,10 +250,6 @@ export default mixins(VisibleElements).extend({
|
|||
async loadSeries () {
|
||||
this.series = await this.$komgaSeries.getOneSeries(this.seriesId)
|
||||
},
|
||||
updateCardWidth () {
|
||||
const content = this.$refs.content as HTMLElement
|
||||
this.cardWidth = computeCardWidth(content.clientWidth, this.$vuetify.breakpoint.name)
|
||||
},
|
||||
parseQuerySortOrDefault (querySort: any): SortActive {
|
||||
return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault)
|
||||
},
|
||||
|
|
@ -301,7 +266,6 @@ export default mixins(VisibleElements).extend({
|
|||
reloadData (seriesId: number, countItem?: number) {
|
||||
this.totalElements = null
|
||||
this.pagesState = []
|
||||
this.visibleElements = []
|
||||
this.books = Array(countItem || this.pageSize).fill(null)
|
||||
this.loadInitialData(seriesId)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,11 +23,7 @@
|
|||
height="306.14"
|
||||
class="ma-2 card"
|
||||
/>
|
||||
<card-series v-else
|
||||
:series="s"
|
||||
class="ma-2 card"
|
||||
:edit="singleEditSeries"
|
||||
/>
|
||||
<item-card v-else class="ma-2 card" :item="s" :on-edit="singleEditSeries"/>
|
||||
</div>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
|
@ -48,11 +44,7 @@
|
|||
height="306.14"
|
||||
class="ma-2 card"
|
||||
/>
|
||||
<card-series v-else
|
||||
:series="s"
|
||||
class="ma-2 card"
|
||||
:edit="singleEditSeries"
|
||||
/>
|
||||
<item-card v-else class="ma-2 card" :item="s" :on-edit="singleEditSeries"/>
|
||||
</div>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
|
@ -74,11 +66,7 @@
|
|||
height="328.13"
|
||||
class="ma-2 card"
|
||||
/>
|
||||
<card-book v-else
|
||||
:book="b"
|
||||
class="ma-2 card"
|
||||
:edit="singleEditBook"
|
||||
/>
|
||||
<item-card v-else class="ma-2 card" :item="b" :on-edit="singleEditBook"/>
|
||||
</div>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
|
@ -87,16 +75,15 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import CardBook from '@/components/CardBook.vue'
|
||||
import CardSeries from '@/components/CardSeries.vue'
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import Vue from 'vue'
|
||||
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
|
||||
import EditBooksDialog from '@/components/EditBooksDialog.vue'
|
||||
import ItemCard from '@/components/ItemCard.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Dashboard',
|
||||
components: { CardSeries, CardBook, HorizontalScroller, EditSeriesDialog, EditBooksDialog },
|
||||
components: { ItemCard, HorizontalScroller, EditSeriesDialog, EditBooksDialog },
|
||||
data: () => {
|
||||
const pageSize = 20
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in a new issue