mirror of
https://github.com/gotson/komga.git
synced 2025-12-22 00:13:30 +01:00
fix(webui): lazy load collections on browse series
also adjusted layout for smaller screens
This commit is contained in:
parent
938c9239d6
commit
d89533ded6
5 changed files with 142 additions and 94 deletions
71
komga-webui/src/components/CollectionsExpansionPanels.vue
Normal file
71
komga-webui/src/components/CollectionsExpansionPanels.vue
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<v-expansion-panels v-model="collectionPanel">
|
||||
<v-expansion-panel v-for="(c, index) in collections"
|
||||
:key="index"
|
||||
>
|
||||
<v-expansion-panel-header>{{ c.name }} collection</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<horizontal-scroller>
|
||||
<template v-slot:prepend>
|
||||
<router-link class="overline"
|
||||
:to="{name: 'browse-collection', params: {collectionId: c.id}}"
|
||||
>Manage collection
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="collectionsContent[index]"
|
||||
nowrap
|
||||
:selectable="false"
|
||||
:action-menu="false"
|
||||
:fixed-item-width="100"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'CollectionsExpansionPanels',
|
||||
components: {
|
||||
HorizontalScroller,
|
||||
ItemBrowser,
|
||||
},
|
||||
props: {
|
||||
collections: {
|
||||
type: Array as () => CollectionDto[],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
collectionPanel: undefined as number | undefined,
|
||||
collectionsContent: [[]] as any[],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
collections: {
|
||||
handler (val) {
|
||||
this.collectionPanel = undefined
|
||||
this.collectionsContent = [...Array(val.length)].map(elem => new Array(0))
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
async collectionPanel (val) {
|
||||
if (val !== undefined) {
|
||||
const collId = this.collections[val].id
|
||||
if (this.$_.isEmpty(this.collectionsContent[val])) {
|
||||
const content = (await this.$komgaCollections.getSeries(collId, { unpaged: true } as PageRequest)).content
|
||||
this.collectionsContent.splice(val, 1, content)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -14,24 +14,25 @@
|
|||
:class="flexClass"
|
||||
>
|
||||
<v-item
|
||||
v-for="item in localItems"
|
||||
:key="item.id"
|
||||
class="my-3 mx-2"
|
||||
v-slot:default="{ toggle, active }" :value="item"
|
||||
v-for="item in localItems"
|
||||
:key="item.id"
|
||||
class="my-2 mx-2"
|
||||
v-slot:default="{ toggle, active }" :value="item"
|
||||
>
|
||||
<slot name="item">
|
||||
<div style="position: relative"
|
||||
:class="draggable ? 'draggable-item' : undefined"
|
||||
>
|
||||
<item-card
|
||||
class="item-card"
|
||||
:item="item"
|
||||
:width="itemWidth"
|
||||
:selected="active"
|
||||
:no-link="draggable || deletable"
|
||||
:preselect="shouldPreselect"
|
||||
:onEdit="(draggable || deletable) ? undefined : editFunction"
|
||||
:onSelected="(draggable || deletable) ? undefined : selectable ? toggle: undefined"
|
||||
class="item-card"
|
||||
:item="item"
|
||||
:width="itemWidth"
|
||||
:selected="active"
|
||||
:no-link="draggable || deletable"
|
||||
:preselect="shouldPreselect"
|
||||
:onEdit="(draggable || deletable) ? undefined : editFunction"
|
||||
:onSelected="(draggable || deletable) ? undefined : selectable ? toggle: undefined"
|
||||
:action-menu="actionMenu"
|
||||
></item-card>
|
||||
|
||||
<v-slide-y-reverse-transition>
|
||||
|
|
@ -84,6 +85,10 @@ export default Vue.extend({
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
fixedItemWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
selectable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
|
@ -110,6 +115,10 @@ export default Vue.extend({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
actionMenu: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
|
|
@ -152,7 +161,7 @@ export default Vue.extend({
|
|||
return this.items.length > 0
|
||||
},
|
||||
itemWidth (): number {
|
||||
return this.width
|
||||
return this.fixedItemWidth ? this.fixedItemWidth : this.width
|
||||
},
|
||||
shouldPreselect (): boolean {
|
||||
return this.selectedItems.length > 0
|
||||
|
|
|
|||
|
|
@ -74,65 +74,21 @@
|
|||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-row v-if="$vuetify.breakpoint.name !== 'xs'">
|
||||
<v-col>
|
||||
<v-expansion-panels v-model="collectionPanel" v-if="$vuetify.breakpoint.name !== 'xs'">
|
||||
<v-expansion-panel v-for="(c, i) in collections"
|
||||
:key="i"
|
||||
>
|
||||
<v-expansion-panel-header>{{ c.name }} collection</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<horizontal-scroller>
|
||||
<template v-slot:prepend>
|
||||
<router-link class="overline"
|
||||
:to="{name: 'browse-collection', params: {collectionId: c.id}}"
|
||||
>Manage collection
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<div v-for="(s, i) in collectionsContent[c.id]"
|
||||
:key="i"
|
||||
>
|
||||
<item-card class="ma-2 card" :item="s"/>
|
||||
</div>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
<collections-expansion-panels :collections="collections"/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="$vuetify.breakpoint.name === 'xs'">
|
||||
<v-expansion-panels v-model="collectionPanel">
|
||||
<v-expansion-panel v-for="(c, i) in collections"
|
||||
:key="i"
|
||||
>
|
||||
<v-expansion-panel-header>{{ c.name }} collection</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<horizontal-scroller>
|
||||
<template v-slot:prepend>
|
||||
<router-link class="overline"
|
||||
:to="{name: 'browse-collection', params: {collectionId: c.id}}"
|
||||
>Manage collection
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<div v-for="(s, i) in collectionsContent[c.id]"
|
||||
:key="i"
|
||||
>
|
||||
<item-card class="ma-2 card" :item="s"/>
|
||||
</div>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
<v-col class="pt-0 py-1">
|
||||
<collections-expansion-panels :collections="collections"/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider class="my-4"/>
|
||||
<v-divider class="my-1"/>
|
||||
|
||||
<empty-state
|
||||
v-if="totalPages === 0"
|
||||
|
|
@ -164,21 +120,21 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import CollectionsExpansionPanels from '@/components/CollectionsExpansionPanels.vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import FilterMenuButton from '@/components/FilterMenuButton.vue'
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import ItemCard from '@/components/ItemCard.vue'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import SeriesActionsMenu from '@/components/menus/SeriesActionsMenu.vue'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import SortMenuButton from '@/components/SortMenuButton.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import { parseQueryFilter, parseQuerySort } from '@/functions/query-params'
|
||||
import { seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { ReadStatus } from '@/types/enum-books'
|
||||
import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
|
||||
|
||||
const cookiePageSize = 'pagesize'
|
||||
|
||||
|
|
@ -192,10 +148,10 @@ export default Vue.extend({
|
|||
ItemBrowser,
|
||||
PageSizeSelect,
|
||||
SeriesActionsMenu,
|
||||
HorizontalScroller,
|
||||
ItemCard,
|
||||
EmptyState,
|
||||
BooksMultiSelectBar,
|
||||
CollectionsExpansionPanels,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
|
|
@ -219,8 +175,6 @@ export default Vue.extend({
|
|||
pageUnwatch: null as any,
|
||||
pageSizeUnwatch: null as any,
|
||||
collections: [] as CollectionDto[],
|
||||
collectionsContent: [] as any[][],
|
||||
collectionPanel: -1,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -294,7 +248,6 @@ export default Vue.extend({
|
|||
this.totalElements = null
|
||||
this.books = []
|
||||
this.collections = []
|
||||
this.collectionPanel = -1
|
||||
|
||||
this.loadSeries(Number(to.params.seriesId))
|
||||
|
||||
|
|
@ -348,9 +301,6 @@ export default Vue.extend({
|
|||
async loadSeries (seriesId: number) {
|
||||
this.series = await this.$komgaSeries.getOneSeries(seriesId)
|
||||
this.collections = await this.$komgaSeries.getCollections(seriesId)
|
||||
for (const c of this.collections) {
|
||||
this.collectionsContent[c.id] = (await this.$komgaCollections.getSeries(c.id, { unpaged: true } as PageRequest)).content
|
||||
}
|
||||
await this.loadPage(seriesId, this.page, this.sortActive)
|
||||
},
|
||||
parseQuerySortOrDefault (querySort: any): SortActive {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
@edit="editMultipleBooks"
|
||||
/>
|
||||
|
||||
<v-container fluid class="px-6">
|
||||
<v-container fluid>
|
||||
<empty-state v-if="allEmpty"
|
||||
title="Nothing to show"
|
||||
icon="mdi-help-circle"
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
>
|
||||
</empty-state>
|
||||
|
||||
<horizontal-scroller v-if="inProgressBooks.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="inProgressBooks.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Keep Reading</div>
|
||||
</template>
|
||||
|
|
@ -35,11 +35,12 @@
|
|||
:edit-function="singleEditBook"
|
||||
:selected.sync="selectedBooks"
|
||||
:selectable="selectedSeries.length === 0"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
<horizontal-scroller v-if="onDeckBooks.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="onDeckBooks.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">On Deck</div>
|
||||
</template>
|
||||
|
|
@ -49,11 +50,12 @@
|
|||
:edit-function="singleEditBook"
|
||||
:selected.sync="selectedBooks"
|
||||
:selectable="selectedSeries.length === 0"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
<horizontal-scroller v-if="newSeries.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="newSeries.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Recently Added Series</div>
|
||||
</template>
|
||||
|
|
@ -63,11 +65,12 @@
|
|||
:edit-function="singleEditSeries"
|
||||
:selected.sync="selectedSeries"
|
||||
:selectable="selectedBooks.length === 0"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
<horizontal-scroller v-if="updatedSeries.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="updatedSeries.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Recently Updated Series</div>
|
||||
</template>
|
||||
|
|
@ -77,11 +80,12 @@
|
|||
:edit-function="singleEditSeries"
|
||||
:selected.sync="selectedSeries"
|
||||
:selectable="selectedBooks.length === 0"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
<horizontal-scroller v-if="latestBooks.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="latestBooks.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Recently Added Books</div>
|
||||
</template>
|
||||
|
|
@ -91,6 +95,7 @@
|
|||
:edit-function="singleEditBook"
|
||||
:selected.sync="selectedBooks"
|
||||
:selectable="selectedSeries.length === 0"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
|
@ -99,14 +104,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
|
||||
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import { ReadStatus } from '@/types/enum-books'
|
||||
import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
|
||||
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Dashboard',
|
||||
|
|
@ -150,6 +155,9 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
fixedCardWidth (): number {
|
||||
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
|
||||
},
|
||||
allEmpty (): boolean {
|
||||
return this.newSeries.length === 0 &&
|
||||
this.updatedSeries.length === 0 &&
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
@edit="editMultipleBooks"
|
||||
/>
|
||||
|
||||
<v-container fluid class="px-6">
|
||||
<v-container fluid>
|
||||
<empty-state
|
||||
v-if="emptyResults"
|
||||
title="The search returned no results"
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</empty-state>
|
||||
|
||||
<template v-else>
|
||||
<horizontal-scroller v-if="series.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="series.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Series</div>
|
||||
</template>
|
||||
|
|
@ -45,11 +45,12 @@
|
|||
:edit-function="singleEditSeries"
|
||||
:selected.sync="selectedSeries"
|
||||
:selectable="selectedBooks.length === 0"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
<horizontal-scroller v-if="books.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="books.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Books</div>
|
||||
</template>
|
||||
|
|
@ -59,16 +60,22 @@
|
|||
:edit-function="singleEditBook"
|
||||
:selected.sync="selectedBooks"
|
||||
:selectable="selectedSeries.length === 0"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
<horizontal-scroller v-if="collections.length !== 0" class="my-4">
|
||||
<horizontal-scroller v-if="collections.length !== 0" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<div class="title">Collections</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<item-browser :items="collections" nowrap :edit-function="singleEditCollection" :selectable="false"/>
|
||||
<item-browser :items="collections"
|
||||
nowrap
|
||||
:edit-function="singleEditCollection"
|
||||
:selectable="false"
|
||||
:fixed-item-width="fixedCardWidth"
|
||||
/>
|
||||
</template>
|
||||
</horizontal-scroller>
|
||||
|
||||
|
|
@ -79,14 +86,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import { BOOK_CHANGED, COLLECTION_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
|
||||
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import { BOOK_CHANGED, COLLECTION_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Search',
|
||||
|
|
@ -147,6 +154,9 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
fixedCardWidth (): number {
|
||||
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
|
||||
},
|
||||
showToolbar (): boolean {
|
||||
return this.selectedSeries.length === 0 && this.selectedBooks.length === 0
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue