Skip to content

Commit

Permalink
feat(webui): mark books as read or unread
Browse files Browse the repository at this point in the history
available from the book details screen, and from the series screen (for multiple books)

related to #25
  • Loading branch information
gotson committed Jun 2, 2020
1 parent 17c80cd commit 24c994f
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 11 deletions.
24 changes: 23 additions & 1 deletion komga-webui/src/components/ItemCard.vue
Expand Up @@ -12,6 +12,7 @@
lazy-src="../assets/cover.svg"
aspect-ratio="0.7071"
>
<div class="unread" v-if="isUnread"/>
<v-fade-transition>
<v-overlay
v-if="hover || selected || preselect"
Expand All @@ -36,6 +37,13 @@
</v-icon>
</v-overlay>
</v-fade-transition>
<v-progress-linear
v-if="isInProgress"
:value="readProgressPercentage"
color="orange"
height="6"
style="position: absolute; bottom: 0"
/>
</v-img>
<!-- Description-->
<v-card-subtitle
Expand All @@ -52,8 +60,10 @@
</template>

<script lang="ts">
import { getReadProgress, getReadProgressPercentage } from '@/functions/book-progress'
import { ReadProgress } from '@/types/enum-books'
import { createItem, Item } from '@/types/items'
import Vue from 'vue'
import { BookItem, createItem, Item } from '@/types/items'
export default Vue.extend({
name: 'ItemCard',
Expand Down Expand Up @@ -112,6 +122,18 @@ export default Vue.extend({
body (): string {
return this.computedItem.body()
},
isInProgress (): boolean {
if ('seriesId' in this.item) return getReadProgress(this.item) === ReadProgress.IN_PROGRESS
return false
},
isUnread (): boolean {
if ('seriesId' in this.item) return getReadProgress(this.item) === ReadProgress.UNREAD
return false
},
readProgressPercentage (): number {
if ('seriesId' in this.item) return getReadProgressPercentage(this.item)
return 0
},
},
methods: {
onClick () {
Expand Down
15 changes: 15 additions & 0 deletions komga-webui/src/functions/book-progress.ts
@@ -0,0 +1,15 @@
import { ReadProgress } from '@/types/enum-books'

export function getReadProgress (book: BookDto): ReadProgress {
if (book.readProgress?.completed) return ReadProgress.READ
if (book.readProgress?.completed === false) return ReadProgress.IN_PROGRESS
return ReadProgress.UNREAD
}

export function getReadProgressPercentage (book: BookDto): number {
if (book.readProgress?.completed) return 100
if (book.readProgress?.completed === false) {
return book.readProgress?.page / book.media.pagesCount * 100
}
return 0
}
24 changes: 24 additions & 0 deletions komga-webui/src/services/komga-books.service.ts
Expand Up @@ -119,4 +119,28 @@ export default class KomgaBooksService {
throw new Error(msg)
}
}

async updateReadProgress (bookId: number, readProgress: ReadProgressUpdateDto) {
try {
await this.http.patch(`${API_BOOKS}/${bookId}/read-progress`, readProgress)
} catch (e) {
let msg = `An error occurred while trying to update read progress`
if (e.response.data.message) {
msg += `: ${e.response.data.message}`
}
throw new Error(msg)
}
}

async deleteReadProgress (bookId: number) {
try {
await this.http.delete(`${API_BOOKS}/${bookId}/read-progress`)
} catch (e) {
let msg = `An error occurred while trying to delete read progress`
if (e.response.data.message) {
msg += `: ${e.response.data.message}`
}
throw new Error(msg)
}
}
}
10 changes: 10 additions & 0 deletions komga-webui/src/styles/unread-triangle.css
@@ -0,0 +1,10 @@
.unread {
border-left: 25px solid transparent;
border-right: 25px solid orange;
border-bottom: 25px solid transparent;
height: 0;
width: 0;
position: absolute;
right: 0;
z-index: 2;
}
6 changes: 6 additions & 0 deletions komga-webui/src/types/enum-books.ts
Expand Up @@ -11,3 +11,9 @@ export enum MediaStatus {
ERROR = 'ERROR',
UNSUPPORTED = 'UNSUPPORTED'
}

export enum ReadProgress {
UNREAD = 'UNREAD',
READ = 'READ',
IN_PROGRESS = 'IN_PROGRESS'
}
15 changes: 14 additions & 1 deletion komga-webui/src/types/komga-books.ts
Expand Up @@ -8,7 +8,8 @@ interface BookDto {
sizeBytes: number,
size: string,
media: MediaDto,
metadata: BookMetadataDto
metadata: BookMetadataDto,
readProgress?: ReadProgressDto
}

interface MediaDto {
Expand Down Expand Up @@ -47,6 +48,13 @@ interface BookMetadataDto {
authorsLock: boolean,
}

interface ReadProgressDto {
page: number,
completed: boolean,
created: string,
lastModified: string
}

interface BookMetadataUpdateDto {
title?: string,
titleLock?: boolean,
Expand All @@ -73,6 +81,11 @@ interface AuthorDto {
role: string
}

interface ReadProgressUpdateDto {
page?: number,
completed?: boolean
}

interface BookFormat {
type: string,
color: string
Expand Down
50 changes: 46 additions & 4 deletions komga-webui/src/views/BrowseBook.vue
Expand Up @@ -29,6 +29,16 @@
<v-list-item @click="refreshMetadata()">
<v-list-item-title>Refresh metadata</v-list-item-title>
</v-list-item>
<v-list-item
v-if="!isRead"
@click="markRead()">
<v-list-item-title>Mark as read</v-list-item-title>
</v-list-item>
<v-list-item
v-if="!isUnread"
@click="markUnread()">
<v-list-item-title>Mark as unread</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</toolbar-sticky>
Expand All @@ -43,6 +53,7 @@
max-height="300"
max-width="212"
>
<div class="unread" v-if="isUnread"/>
<v-fade-transition>
<v-overlay
v-if="hover && book.media.status === 'READY'"
Expand All @@ -58,6 +69,13 @@
</v-btn>
</v-overlay>
</v-fade-transition>
<v-progress-linear
v-if="isInProgress"
:value="readProgressPercentage"
color="orange"
height="6"
style="position: absolute; bottom: 0"
/>
</v-img>
</template>
</v-hover>
Expand Down Expand Up @@ -175,14 +193,16 @@
</template>

<script lang="ts">
import Badge from '@/components/Badge.vue'
import EditBooksDialog from '@/components/EditBooksDialog.vue'
import ToolbarSticky from '@/components/ToolbarSticky.vue'
import { groupAuthorsByRolePlural } from '@/functions/authors'
import { getBookFormatFromMediaType } from '@/functions/book-format'
import { getReadProgress, getReadProgressPercentage } from '@/functions/book-progress'
import { getBookTitleCompact } from '@/functions/book-title'
import { bookFileUrl, bookThumbnailUrl } from '@/functions/urls'
import { ReadProgress } from '@/types/enum-books'
import Vue from 'vue'
import { getBookTitleCompact } from '@/functions/book-title'
import Badge from '@/components/Badge.vue'
import EditBooksDialog from '@/components/EditBooksDialog.vue'
import { groupAuthorsByRolePlural } from '@/functions/authors'
export default Vue.extend({
name: 'BrowseBook',
Expand Down Expand Up @@ -234,6 +254,18 @@ export default Vue.extend({
authorsByRole (): any {
return groupAuthorsByRolePlural(this.book.metadata.authors)
},
isRead (): boolean {
return getReadProgress(this.book) === ReadProgress.READ
},
isUnread (): boolean {
return getReadProgress(this.book) === ReadProgress.UNREAD
},
isInProgress (): boolean {
return getReadProgress(this.book) === ReadProgress.IN_PROGRESS
},
readProgressPercentage (): number {
return getReadProgressPercentage(this.book)
},
},
methods: {
analyze () {
Expand All @@ -242,9 +274,19 @@ export default Vue.extend({
refreshMetadata () {
this.$komgaBooks.refreshMetadata(this.book)
},
async markRead () {
const readProgress = { completed: true } as ReadProgressUpdateDto
await this.$komgaBooks.updateReadProgress(this.book.id, readProgress)
this.book = await this.$komgaBooks.getBook(this.bookId)
},
async markUnread () {
await this.$komgaBooks.deleteReadProgress(this.book.id)
this.book = await this.$komgaBooks.getBook(this.bookId)
},
},
})
</script>

<style scoped>
@import "../styles/unread-triangle.css";
</style>
35 changes: 30 additions & 5 deletions komga-webui/src/views/BrowseSeries.vue
Expand Up @@ -55,6 +55,14 @@

<v-spacer/>

<v-btn @click="markSelectedRead()">
Mark as read
</v-btn>

<v-btn @click="markSelectedUnread()">
Mark as unread
</v-btn>

<v-btn icon @click="dialogEditBooks = true" v-if="isAdmin">
<v-icon>mdi-pencil</v-icon>
</v-btn>
Expand Down Expand Up @@ -96,7 +104,8 @@
</v-row>

<v-divider class="my-4"/>
<item-browser :items="books" :selected.sync="selected" :edit-function="this.singleEdit" class="px-6" @update="updateVisible"></item-browser>
<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"/>
Expand All @@ -105,15 +114,15 @@

<script lang="ts">
import Badge from '@/components/Badge.vue'
import EditBooksDialog from '@/components/EditBooksDialog.vue'
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
import ItemBrowser from '@/components/ItemBrowser.vue'
import SortMenuButton from '@/components/SortMenuButton.vue'
import ToolbarSticky from '@/components/ToolbarSticky.vue'
import Vue from 'vue'
import { parseQuerySort } from '@/functions/query-params'
import { seriesThumbnailUrl } from '@/functions/urls'
import { LoadState } from '@/types/common'
import EditBooksDialog from '@/components/EditBooksDialog.vue'
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
import ItemBrowser from '@/components/ItemBrowser.vue'
import Vue from 'vue'
export default Vue.extend({
name: 'BrowseSeries',
Expand Down Expand Up @@ -304,6 +313,22 @@ export default Vue.extend({
this.editBookSingle = book
this.dialogEditBookSingle = true
},
async markSelectedRead () {
await Promise.all(this.selectedBooks.map(b =>
this.$komgaBooks.updateReadProgress(b.id, { completed: true }),
))
this.selectedBooks = await Promise.all(this.selectedBooks.map(b =>
this.$komgaBooks.getBook(b.id),
))
},
async markSelectedUnread () {
await Promise.all(this.selectedBooks.map(b =>
this.$komgaBooks.deleteReadProgress(b.id),
))
this.selectedBooks = await Promise.all(this.selectedBooks.map(b =>
this.$komgaBooks.getBook(b.id),
))
},
},
})
</script>
Expand Down

0 comments on commit 24c994f

Please sign in to comment.