diff --git a/komga-webui/src/functions/file.ts b/komga-webui/src/functions/file.ts index 04164ea3c..e1fa24441 100644 --- a/komga-webui/src/functions/file.ts +++ b/komga-webui/src/functions/file.ts @@ -1,7 +1,7 @@ import filesize from 'filesize' -export async function getFileFromUrl(url: string, name: string = url, defaultType = 'image/jpeg') { - const response = await fetch(url) +export async function getFileFromUrl(url: string, name: string = url, defaultType = 'image/jpeg', fetchOptions = {}) { + const response = await fetch(url, fetchOptions) const data = await response.blob() return new File([data], name, { type: data.type || defaultType, diff --git a/komga-webui/src/functions/resize-image.ts b/komga-webui/src/functions/resize-image.ts new file mode 100644 index 000000000..ff7037962 --- /dev/null +++ b/komga-webui/src/functions/resize-image.ts @@ -0,0 +1,45 @@ +function getCanvasBlob(canvas: HTMLCanvasElement): Promise { + return new Promise((resolve) => { + canvas.toBlob((blob: Blob | null) => { resolve(blob) }) + }) +} + +export async function resizeImageFile(imageFile: File, maxWidth: number = 500, maxHeight: number = 500): Promise { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + if (ctx !== null) { + const img = new Image() + img.src = URL.createObjectURL(imageFile) + await img.decode() + + let width = img.width + let height = img.height + + // Calculate width and height of the smaller image + if (width > height) { + if (width > maxWidth) { + height = height * (maxWidth / width) + width = maxWidth + } + } else { + if (height > maxHeight) { + width = width * (maxHeight / height) + height = maxHeight + } + } + + canvas.width = width + canvas.height = height + // Release blob data to save browser memory + URL.revokeObjectURL(img.src) + ctx.drawImage(img, 0, 0, width, height) + const blob = await getCanvasBlob(canvas) + if (blob !== null) { + return new File([blob], 'poster', {lastModified: Date.now()}) + } + } + // If we have not returned before here, resizing has failed for some reason. + // Maybe the browser doesn't support 2D canvas or image loading was not possible. + // Just return the original image. + return imageFile +} diff --git a/komga-webui/src/locales/en.json b/komga-webui/src/locales/en.json index 4b5c3c774..7d2cace93 100644 --- a/komga-webui/src/locales/en.json +++ b/komga-webui/src/locales/en.json @@ -89,6 +89,9 @@ "move_next": "Click or press \"Next\" again to move to the next book.", "move_next_exit": "Click or press \"Next\" again to exit the reader.", "move_previous": "Click or press \"Previous\" again to move to the previous book.", + "notification_poster_set_book": "Book poster is now set to the current page.", + "notification_poster_set_readlist": "Read list poster is now set to the current page.", + "notification_poster_set_series": "Series poster is now set to the current page.", "paged_reader_layout": { "double": "Double pages", "double_no_cover": "Double pages (no cover)", @@ -104,6 +107,9 @@ "width": "Fit width", "width_shrink_only": "Fit width (shrink only)" }, + "set_current_page_as_book_poster": "Set page as poster for book", + "set_current_page_as_readlist_poster": "Set page as poster for read list", + "set_current_page_as_series_poster": "Set page as poster for series", "settings": { "always_fullscreen": "Always Full Screen", "animate_page_transitions": "Animate page transitions", diff --git a/komga-webui/src/views/BookReader.vue b/komga-webui/src/views/BookReader.vue index 5057b8f58..7b0803399 100644 --- a/komga-webui/src/views/BookReader.vue +++ b/komga-webui/src/views/BookReader.vue @@ -63,6 +63,15 @@ {{ $t('bookreader.download_current_page') }} + + {{ $t('bookreader.set_current_page_as_book_poster') }} + + + {{ $t('bookreader.set_current_page_as_series_poster') }} + + + {{ $t('bookreader.set_current_page_as_readlist_poster') }} + @@ -307,6 +316,8 @@ import ShortcutHelpDialog from '@/components/dialogs/ShortcutHelpDialog.vue' import {getBookTitleCompact} from '@/functions/book-title' import {checkImageSupport, ImageFeature} from '@/functions/check-image' import {bookPageUrl} from '@/functions/urls' +import {getFileFromUrl} from '@/functions/file' +import {resizeImageFile} from '@/functions/resize-image' import {ReadingDirection} from '@/types/enum-books' import Vue from 'vue' import {Location} from 'vue-router' @@ -327,6 +338,7 @@ import {Context, ContextOrigin} from '@/types/context' import {SeriesDto} from '@/types/komga-series' import jsFileDownloader from 'js-file-downloader' import screenfull from 'screenfull' +import {ItemTypes} from '@/types/items' export default Vue.extend({ name: 'BookReader', @@ -340,6 +352,7 @@ export default Vue.extend({ }, data: function () { return { + ItemTypes, screenfull, fullscreenIcon: 'mdi-fullscreen', book: {} as BookDto, @@ -857,6 +870,24 @@ export default Vue.extend({ forceDesktopMode: true, }) }, + async setCurrentPageAsPoster(type: ItemTypes) { + const imageFile = await getFileFromUrl(this.currentPage.url, 'poster', 'image/jpeg', {credentials: 'include'}) + const newImageFile = await resizeImageFile(imageFile) + switch (type) { + case ItemTypes.BOOK: + await this.$komgaBooks.uploadThumbnail(this.book.id, newImageFile, true) + this.sendNotification(`${this.$t('bookreader.notification_poster_set_book')}`) + break + case ItemTypes.SERIES: + await this.$komgaSeries.uploadThumbnail(this.series.id, newImageFile, true) + this.sendNotification(`${this.$t('bookreader.notification_poster_set_series')}`) + break + case ItemTypes.READLIST: + await this.$komgaReadLists.uploadThumbnail(this.context.id, newImageFile, true) + this.sendNotification(`${this.$t('bookreader.notification_poster_set_readlist')}`) + break + } + }, }, })