fix(webui): lazy load collections on browse series

also adjusted layout for smaller screens
This commit is contained in:
Gauthier Roebroeck 2020-06-29 11:37:26 +08:00
commit d89533ded6
5 changed files with 140 additions and 92 deletions

View 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>

View file

@ -14,24 +14,25 @@
:class="flexClass" :class="flexClass"
> >
<v-item <v-item
v-for="item in localItems" v-for="item in localItems"
:key="item.id" :key="item.id"
class="my-3 mx-2" class="my-2 mx-2"
v-slot:default="{ toggle, active }" :value="item" v-slot:default="{ toggle, active }" :value="item"
> >
<slot name="item"> <slot name="item">
<div style="position: relative" <div style="position: relative"
:class="draggable ? 'draggable-item' : undefined" :class="draggable ? 'draggable-item' : undefined"
> >
<item-card <item-card
class="item-card" class="item-card"
:item="item" :item="item"
:width="itemWidth" :width="itemWidth"
:selected="active" :selected="active"
:no-link="draggable || deletable" :no-link="draggable || deletable"
:preselect="shouldPreselect" :preselect="shouldPreselect"
:onEdit="(draggable || deletable) ? undefined : editFunction" :onEdit="(draggable || deletable) ? undefined : editFunction"
:onSelected="(draggable || deletable) ? undefined : selectable ? toggle: undefined" :onSelected="(draggable || deletable) ? undefined : selectable ? toggle: undefined"
:action-menu="actionMenu"
></item-card> ></item-card>
<v-slide-y-reverse-transition> <v-slide-y-reverse-transition>
@ -84,6 +85,10 @@ export default Vue.extend({
type: Array, type: Array,
required: true, required: true,
}, },
fixedItemWidth: {
type: Number,
required: false,
},
selectable: { selectable: {
type: Boolean, type: Boolean,
default: true, default: true,
@ -110,6 +115,10 @@ export default Vue.extend({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
actionMenu: {
type: Boolean,
default: true,
},
}, },
data: () => { data: () => {
return { return {
@ -152,7 +161,7 @@ export default Vue.extend({
return this.items.length > 0 return this.items.length > 0
}, },
itemWidth (): number { itemWidth (): number {
return this.width return this.fixedItemWidth ? this.fixedItemWidth : this.width
}, },
shouldPreselect (): boolean { shouldPreselect (): boolean {
return this.selectedItems.length > 0 return this.selectedItems.length > 0

View file

@ -74,65 +74,21 @@
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row v-if="$vuetify.breakpoint.name !== 'xs'">
<v-col> <v-col>
<v-expansion-panels v-model="collectionPanel" v-if="$vuetify.breakpoint.name !== 'xs'"> <collections-expansion-panels :collections="collections"/>
<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> </v-col>
</v-row> </v-row>
</v-col> </v-col>
</v-row> </v-row>
<v-row v-if="$vuetify.breakpoint.name === 'xs'"> <v-row v-if="$vuetify.breakpoint.name === 'xs'">
<v-expansion-panels v-model="collectionPanel"> <v-col class="pt-0 py-1">
<v-expansion-panel v-for="(c, i) in collections" <collections-expansion-panels :collections="collections"/>
:key="i" </v-col>
>
<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-row> </v-row>
<v-divider class="my-4"/> <v-divider class="my-1"/>
<empty-state <empty-state
v-if="totalPages === 0" v-if="totalPages === 0"
@ -164,21 +120,21 @@
<script lang="ts"> <script lang="ts">
import Badge from '@/components/Badge.vue' 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 EmptyState from '@/components/EmptyState.vue'
import FilterMenuButton from '@/components/FilterMenuButton.vue' import FilterMenuButton from '@/components/FilterMenuButton.vue'
import HorizontalScroller from '@/components/HorizontalScroller.vue'
import ItemBrowser from '@/components/ItemBrowser.vue' import ItemBrowser from '@/components/ItemBrowser.vue'
import ItemCard from '@/components/ItemCard.vue' import ItemCard from '@/components/ItemCard.vue'
import PageSizeSelect from '@/components/PageSizeSelect.vue'
import SeriesActionsMenu from '@/components/menus/SeriesActionsMenu.vue' import SeriesActionsMenu from '@/components/menus/SeriesActionsMenu.vue'
import PageSizeSelect from '@/components/PageSizeSelect.vue'
import SortMenuButton from '@/components/SortMenuButton.vue' import SortMenuButton from '@/components/SortMenuButton.vue'
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
import { parseQueryFilter, parseQuerySort } from '@/functions/query-params' import { parseQueryFilter, parseQuerySort } from '@/functions/query-params'
import { seriesThumbnailUrl } from '@/functions/urls' import { seriesThumbnailUrl } from '@/functions/urls'
import { ReadStatus } from '@/types/enum-books' import { ReadStatus } from '@/types/enum-books'
import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events' import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
import Vue from 'vue' import Vue from 'vue'
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
const cookiePageSize = 'pagesize' const cookiePageSize = 'pagesize'
@ -192,10 +148,10 @@ export default Vue.extend({
ItemBrowser, ItemBrowser,
PageSizeSelect, PageSizeSelect,
SeriesActionsMenu, SeriesActionsMenu,
HorizontalScroller,
ItemCard, ItemCard,
EmptyState, EmptyState,
BooksMultiSelectBar, BooksMultiSelectBar,
CollectionsExpansionPanels,
}, },
data: () => { data: () => {
return { return {
@ -219,8 +175,6 @@ export default Vue.extend({
pageUnwatch: null as any, pageUnwatch: null as any,
pageSizeUnwatch: null as any, pageSizeUnwatch: null as any,
collections: [] as CollectionDto[], collections: [] as CollectionDto[],
collectionsContent: [] as any[][],
collectionPanel: -1,
} }
}, },
computed: { computed: {
@ -294,7 +248,6 @@ export default Vue.extend({
this.totalElements = null this.totalElements = null
this.books = [] this.books = []
this.collections = [] this.collections = []
this.collectionPanel = -1
this.loadSeries(Number(to.params.seriesId)) this.loadSeries(Number(to.params.seriesId))
@ -348,9 +301,6 @@ export default Vue.extend({
async loadSeries (seriesId: number) { async loadSeries (seriesId: number) {
this.series = await this.$komgaSeries.getOneSeries(seriesId) this.series = await this.$komgaSeries.getOneSeries(seriesId)
this.collections = await this.$komgaSeries.getCollections(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) await this.loadPage(seriesId, this.page, this.sortActive)
}, },
parseQuerySortOrDefault (querySort: any): SortActive { parseQuerySortOrDefault (querySort: any): SortActive {

View file

@ -17,7 +17,7 @@
@edit="editMultipleBooks" @edit="editMultipleBooks"
/> />
<v-container fluid class="px-6"> <v-container fluid>
<empty-state v-if="allEmpty" <empty-state v-if="allEmpty"
title="Nothing to show" title="Nothing to show"
icon="mdi-help-circle" icon="mdi-help-circle"
@ -25,7 +25,7 @@
> >
</empty-state> </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> <template v-slot:prepend>
<div class="title">Keep Reading</div> <div class="title">Keep Reading</div>
</template> </template>
@ -35,11 +35,12 @@
:edit-function="singleEditBook" :edit-function="singleEditBook"
:selected.sync="selectedBooks" :selected.sync="selectedBooks"
:selectable="selectedSeries.length === 0" :selectable="selectedSeries.length === 0"
:fixed-item-width="fixedCardWidth"
/> />
</template> </template>
</horizontal-scroller> </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> <template v-slot:prepend>
<div class="title">On Deck</div> <div class="title">On Deck</div>
</template> </template>
@ -49,11 +50,12 @@
:edit-function="singleEditBook" :edit-function="singleEditBook"
:selected.sync="selectedBooks" :selected.sync="selectedBooks"
:selectable="selectedSeries.length === 0" :selectable="selectedSeries.length === 0"
:fixed-item-width="fixedCardWidth"
/> />
</template> </template>
</horizontal-scroller> </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> <template v-slot:prepend>
<div class="title">Recently Added Series</div> <div class="title">Recently Added Series</div>
</template> </template>
@ -63,11 +65,12 @@
:edit-function="singleEditSeries" :edit-function="singleEditSeries"
:selected.sync="selectedSeries" :selected.sync="selectedSeries"
:selectable="selectedBooks.length === 0" :selectable="selectedBooks.length === 0"
:fixed-item-width="fixedCardWidth"
/> />
</template> </template>
</horizontal-scroller> </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> <template v-slot:prepend>
<div class="title">Recently Updated Series</div> <div class="title">Recently Updated Series</div>
</template> </template>
@ -77,11 +80,12 @@
:edit-function="singleEditSeries" :edit-function="singleEditSeries"
:selected.sync="selectedSeries" :selected.sync="selectedSeries"
:selectable="selectedBooks.length === 0" :selectable="selectedBooks.length === 0"
:fixed-item-width="fixedCardWidth"
/> />
</template> </template>
</horizontal-scroller> </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> <template v-slot:prepend>
<div class="title">Recently Added Books</div> <div class="title">Recently Added Books</div>
</template> </template>
@ -91,6 +95,7 @@
:edit-function="singleEditBook" :edit-function="singleEditBook"
:selected.sync="selectedBooks" :selected.sync="selectedBooks"
:selectable="selectedSeries.length === 0" :selectable="selectedSeries.length === 0"
:fixed-item-width="fixedCardWidth"
/> />
</template> </template>
</horizontal-scroller> </horizontal-scroller>
@ -99,14 +104,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
import EmptyState from '@/components/EmptyState.vue' import EmptyState from '@/components/EmptyState.vue'
import HorizontalScroller from '@/components/HorizontalScroller.vue' import HorizontalScroller from '@/components/HorizontalScroller.vue'
import ItemBrowser from '@/components/ItemBrowser.vue'
import { ReadStatus } from '@/types/enum-books' import { ReadStatus } from '@/types/enum-books'
import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events' import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
import Vue from 'vue' 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({ export default Vue.extend({
name: 'Dashboard', name: 'Dashboard',
@ -150,6 +155,9 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
fixedCardWidth (): number {
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
},
allEmpty (): boolean { allEmpty (): boolean {
return this.newSeries.length === 0 && return this.newSeries.length === 0 &&
this.updatedSeries.length === 0 && this.updatedSeries.length === 0 &&

View file

@ -23,7 +23,7 @@
@edit="editMultipleBooks" @edit="editMultipleBooks"
/> />
<v-container fluid class="px-6"> <v-container fluid>
<empty-state <empty-state
v-if="emptyResults" v-if="emptyResults"
title="The search returned no results" title="The search returned no results"
@ -35,7 +35,7 @@
</empty-state> </empty-state>
<template v-else> <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> <template v-slot:prepend>
<div class="title">Series</div> <div class="title">Series</div>
</template> </template>
@ -45,11 +45,12 @@
:edit-function="singleEditSeries" :edit-function="singleEditSeries"
:selected.sync="selectedSeries" :selected.sync="selectedSeries"
:selectable="selectedBooks.length === 0" :selectable="selectedBooks.length === 0"
:fixed-item-width="fixedCardWidth"
/> />
</template> </template>
</horizontal-scroller> </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> <template v-slot:prepend>
<div class="title">Books</div> <div class="title">Books</div>
</template> </template>
@ -59,16 +60,22 @@
:edit-function="singleEditBook" :edit-function="singleEditBook"
:selected.sync="selectedBooks" :selected.sync="selectedBooks"
:selectable="selectedSeries.length === 0" :selectable="selectedSeries.length === 0"
:fixed-item-width="fixedCardWidth"
/> />
</template> </template>
</horizontal-scroller> </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> <template v-slot:prepend>
<div class="title">Collections</div> <div class="title">Collections</div>
</template> </template>
<template v-slot:content> <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> </template>
</horizontal-scroller> </horizontal-scroller>
@ -79,14 +86,14 @@
</template> </template>
<script lang="ts"> <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 BooksMultiSelectBar from '@/components/bars/BooksMultiSelectBar.vue'
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.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({ export default Vue.extend({
name: 'Search', name: 'Search',
@ -147,6 +154,9 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
fixedCardWidth (): number {
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
},
showToolbar (): boolean { showToolbar (): boolean {
return this.selectedSeries.length === 0 && this.selectedBooks.length === 0 return this.selectedSeries.length === 0 && this.selectedBooks.length === 0
}, },