diff options
author | pacien | 2022-11-28 03:13:01 +0100 |
---|---|---|
committer | GitHub | 2022-11-28 03:13:01 +0100 |
commit | b0b3f99f8078e7bc003fe7e2d60c7524954f7dfe (patch) | |
tree | a4af5f2863477924a7a37c2fda155f61b2212a15 /viewer/src/views | |
parent | 8e0cda290d85d0a126093c9950c8030cfcb9d800 (diff) | |
parent | d84f0f48c9b1dc73ec20a1cf5c31feeb744aa3d9 (diff) | |
download | ldgallery-b0b3f99f8078e7bc003fe7e2d60c7524954f7dfe.tar.gz |
Merge pull request #348 from ldgallery/p_viewer_epub
viewer: render EPUB ebooks
Diffstat (limited to 'viewer/src/views')
-rw-r--r-- | viewer/src/views/GalleryNavigation.vue | 2 | ||||
-rw-r--r-- | viewer/src/views/item_handlers/async/AsyncEpubViewer.vue | 180 | ||||
-rw-r--r-- | viewer/src/views/item_handlers/async/index.ts | 23 |
3 files changed, 205 insertions, 0 deletions
diff --git a/viewer/src/views/GalleryNavigation.vue b/viewer/src/views/GalleryNavigation.vue index 0869aaf..b342c52 100644 --- a/viewer/src/views/GalleryNavigation.vue +++ b/viewer/src/views/GalleryNavigation.vue | |||
@@ -44,6 +44,7 @@ import { computedEager } from '@vueuse/shared'; | |||
44 | import { computed, watchEffect } from 'vue'; | 44 | import { computed, watchEffect } from 'vue'; |
45 | import { useI18n } from 'vue-i18n'; | 45 | import { useI18n } from 'vue-i18n'; |
46 | import GallerySearch from './GallerySearch.vue'; | 46 | import GallerySearch from './GallerySearch.vue'; |
47 | import { EpubViewer } from './item_handlers/async'; | ||
47 | import AudioViewer from './item_handlers/AudioViewer.vue'; | 48 | import AudioViewer from './item_handlers/AudioViewer.vue'; |
48 | import DirectoryViewer from './item_handlers/DirectoryViewer.vue'; | 49 | import DirectoryViewer from './item_handlers/DirectoryViewer.vue'; |
49 | import DownloadViewer from './item_handlers/DownloadViewer.vue'; | 50 | import DownloadViewer from './item_handlers/DownloadViewer.vue'; |
@@ -67,6 +68,7 @@ const COMPONENT_BY_TYPE: Record<ItemType, unknown> = { | |||
67 | plaintext: PlainTextViewer, | 68 | plaintext: PlainTextViewer, |
68 | markdown: MarkdownViewer, | 69 | markdown: MarkdownViewer, |
69 | pdf: PdfViewer, | 70 | pdf: PdfViewer, |
71 | epub: EpubViewer, | ||
70 | video: VideoViewer, | 72 | video: VideoViewer, |
71 | audio: AudioViewer, | 73 | audio: AudioViewer, |
72 | other: DownloadViewer, | 74 | other: DownloadViewer, |
diff --git a/viewer/src/views/item_handlers/async/AsyncEpubViewer.vue b/viewer/src/views/item_handlers/async/AsyncEpubViewer.vue new file mode 100644 index 0000000..b5c0cb4 --- /dev/null +++ b/viewer/src/views/item_handlers/async/AsyncEpubViewer.vue | |||
@@ -0,0 +1,180 @@ | |||
1 | <!-- | ||
2 | -- ldgallery - A static generator which turns a collection of tagged | ||
3 | -- pictures into a searchable web gallery. | ||
4 | -- | ||
5 | -- Copyright (C) 2022 Pacien TRAN-GIRARD | ||
6 | -- | ||
7 | -- This program is free software: you can redistribute it and/or modify | ||
8 | -- it under the terms of the GNU Affero General Public License as | ||
9 | -- published by the Free Software Foundation, either version 3 of the | ||
10 | -- License, or (at your option) any later version. | ||
11 | -- | ||
12 | -- This program is distributed in the hope that it will be useful, | ||
13 | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | -- GNU Affero General Public License for more details. | ||
16 | -- | ||
17 | -- You should have received a copy of the GNU Affero General Public License | ||
18 | -- along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
19 | --> | ||
20 | |||
21 | <template> | ||
22 | <div :class="$style.container"> | ||
23 | <div | ||
24 | ref="view" | ||
25 | :class="$style.epubView" | ||
26 | class="scrollbar" | ||
27 | /> | ||
28 | |||
29 | <ul | ||
30 | v-if="prevSection || nextSection" | ||
31 | :class="$style.navBar" | ||
32 | > | ||
33 | <li> | ||
34 | <LdLink | ||
35 | v-if="prevSection" | ||
36 | @click="goToPrevSection" | ||
37 | > | ||
38 | <fa-icon | ||
39 | :icon="faSquareCaretLeft" | ||
40 | size="lg" | ||
41 | alt="«" | ||
42 | /> | ||
43 | {{ prevSectionLabel }} | ||
44 | </LdLink> | ||
45 | </li> | ||
46 | |||
47 | <li> | ||
48 | {{ currSectionLabel }} | ||
49 | </li> | ||
50 | |||
51 | <li> | ||
52 | <LdLink | ||
53 | v-if="nextSection" | ||
54 | @click="goToNextSection" | ||
55 | > | ||
56 | {{ nextSectionLabel }} | ||
57 | <fa-icon | ||
58 | :icon="faSquareCaretRight" | ||
59 | size="lg" | ||
60 | alt="»" | ||
61 | /> | ||
62 | </LdLink> | ||
63 | </li> | ||
64 | </ul> | ||
65 | </div> | ||
66 | </template> | ||
67 | |||
68 | <script setup lang="ts"> | ||
69 | import { EPUBItem } from '@/@types/gallery'; | ||
70 | import { useItemResource } from '@/services/ui/ldItemResourceUrl'; | ||
71 | import { useUiStore } from '@/store/uiStore'; | ||
72 | import ePub, { Rendition } from 'epubjs'; | ||
73 | import { SpineItem } from 'epubjs/types/section'; | ||
74 | import { computed, PropType, Ref, ref, toRef, watch } from 'vue'; | ||
75 | import { useI18n } from 'vue-i18n'; | ||
76 | import LdLink from '@/components/LdLink.vue'; | ||
77 | import { | ||
78 | faSquareCaretLeft, | ||
79 | faSquareCaretRight, | ||
80 | } from '@fortawesome/free-solid-svg-icons'; | ||
81 | |||
82 | const { t } = useI18n(); | ||
83 | const uiStore = useUiStore(); | ||
84 | |||
85 | const props = defineProps({ | ||
86 | item: { type: Object as PropType<EPUBItem>, required: true }, | ||
87 | }); | ||
88 | |||
89 | const { itemResourceUrl } = useItemResource(toRef(props, 'item')); | ||
90 | |||
91 | const view = ref<HTMLDivElement>(); | ||
92 | const rendition = ref<Rendition>(); | ||
93 | const currSection = ref<SpineItem>(); | ||
94 | const prevSection = ref<SpineItem>(); | ||
95 | const nextSection = ref<SpineItem>(); | ||
96 | |||
97 | const book = computed(() => ePub(itemResourceUrl.value)); | ||
98 | |||
99 | watch([book, view], ([book, view]) => { | ||
100 | if (!view) return; | ||
101 | view.innerHTML = ''; | ||
102 | rendition.value = book.renderTo(view, { | ||
103 | flow: 'scrolled-doc', | ||
104 | width: '100%', | ||
105 | }); | ||
106 | }); | ||
107 | |||
108 | watch(rendition, async(rendition, oldRendition) => { | ||
109 | if (!rendition) return; | ||
110 | oldRendition?.off('rendered', updateNavigation); | ||
111 | await rendition.display(); | ||
112 | rendition.on('rendered', updateNavigation); | ||
113 | }); | ||
114 | |||
115 | watch(() => uiStore.fullWidth, () => { | ||
116 | // Simulate a window resize to force EPub to resize the container | ||
117 | setTimeout(() => window.dispatchEvent(new Event('resize'))); | ||
118 | }); | ||
119 | |||
120 | function updateNavigation(currentSection: SpineItem) { | ||
121 | currSection.value = currentSection; | ||
122 | prevSection.value = currentSection.prev(); | ||
123 | nextSection.value = currentSection.next(); | ||
124 | } | ||
125 | |||
126 | const currSectionLabel = computed(() => getSectionTitle(currSection) ?? ''); | ||
127 | const prevSectionLabel = computed(() => | ||
128 | getSectionTitle(prevSection) ?? t('epubViewer.previousSection')); | ||
129 | const nextSectionLabel = computed(() => | ||
130 | getSectionTitle(nextSection) ?? t('epubViewer.nextSection')); | ||
131 | |||
132 | function getSectionTitle(section: Ref<SpineItem | undefined>): string | null { | ||
133 | if (!section.value?.href) return null; | ||
134 | return book.value?.navigation.get(section.value.href).label; | ||
135 | } | ||
136 | |||
137 | function goToPrevSection() { | ||
138 | rendition.value?.prev(); | ||
139 | } | ||
140 | |||
141 | function goToNextSection() { | ||
142 | rendition.value?.next(); | ||
143 | } | ||
144 | </script> | ||
145 | |||
146 | <style lang="scss" module> | ||
147 | @import "~@/assets/scss/theme"; | ||
148 | |||
149 | .container { | ||
150 | display: flex; | ||
151 | flex-direction: column; | ||
152 | height: 100%; | ||
153 | background-color: $viewer-epub-background; | ||
154 | } | ||
155 | |||
156 | .epubView { | ||
157 | flex: 1; | ||
158 | overflow-x: hidden; | ||
159 | } | ||
160 | |||
161 | .navBar { | ||
162 | display: flex; | ||
163 | flex-direction: row; | ||
164 | list-style-type: none; | ||
165 | margin: 0; | ||
166 | padding: .75em; | ||
167 | |||
168 | background-color: $panel-bottom-bgcolor; | ||
169 | color: $panel-bottom-txtcolor; | ||
170 | |||
171 | > li { | ||
172 | flex: 1; | ||
173 | text-align: center; | ||
174 | |||
175 | > a { | ||
176 | padding: .5em; | ||
177 | } | ||
178 | } | ||
179 | } | ||
180 | </style> | ||
diff --git a/viewer/src/views/item_handlers/async/index.ts b/viewer/src/views/item_handlers/async/index.ts new file mode 100644 index 0000000..d4ca996 --- /dev/null +++ b/viewer/src/views/item_handlers/async/index.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | /* ldgallery - A static generator which turns a collection of tagged | ||
2 | -- pictures into a searchable web gallery. | ||
3 | -- | ||
4 | -- Copyright (C) 2022 Pacien TRAN-GIRARD | ||
5 | -- | ||
6 | -- This program is free software: you can redistribute it and/or modify | ||