Skip to content

Commit d210100

Browse files
feat(etno): add pdf media viewer
1 parent a1a07f7 commit d210100

10 files changed

Lines changed: 506 additions & 18 deletions

File tree

apps/etno/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
"lint:eslint": "eslint . --fix --cache"
1616
},
1717
"dependencies": {
18+
"@embedpdf/core": "^2.14.0",
19+
"@embedpdf/engines": "^2.14.0",
20+
"@embedpdf/plugin-document-manager": "^2.14.0",
21+
"@embedpdf/plugin-render": "^2.14.0",
22+
"@embedpdf/plugin-scroll": "^2.14.0",
23+
"@embedpdf/plugin-viewport": "^2.14.0",
24+
"@embedpdf/plugin-zoom": "^2.14.0",
1825
"@metafori/components": "*",
1926
"@tailwindcss/vite": "^4.1.18",
2027
"axios": "^1.13.6",

apps/etno/src/components/DetailViewers/BaseViewer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="flex-1 border-r border-neutral-200">
2+
<div class="flex-1 border-r border-neutral-200 lg:h-[calc(100vh-3.5rem)] lg:overflow-auto lg:sticky lg:top-14">
33
<slot />
44
</div>
55
</template>

apps/etno/src/components/DetailViewers/PdfViewer.vue

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<template>
2+
<BaseViewer>
3+
<div
4+
v-if="isLoading || !engine"
5+
class="flex justify-center items-center h-full"
6+
>
7+
{{ $t('detail.viewer.pdf.loading') }}
8+
</div>
9+
<EmbedPDF
10+
v-else
11+
v-slot="{ activeDocumentId }"
12+
:engine="engine"
13+
:plugins="plugins"
14+
>
15+
<DocumentContent
16+
v-if="activeDocumentId"
17+
v-slot="{ isLoaded }"
18+
:document-id="activeDocumentId"
19+
>
20+
<div class="flex flex-col h-full w-full">
21+
<template v-if="isLoaded">
22+
<PdfViewerToolbar
23+
:document-id="activeDocumentId"
24+
/>
25+
<Viewport
26+
:document-id="activeDocumentId"
27+
class="bg-neutral-100 flex-1"
28+
>
29+
<Scroller :document-id="activeDocumentId">
30+
<template #default="{ page }">
31+
<div
32+
:style="{ width: page.width + 'px', height: page.height + 'px' }"
33+
class="border border-neutral-200 bg-white"
34+
>
35+
<RenderLayer
36+
:document-id="activeDocumentId"
37+
:page-index="page.pageIndex"
38+
/>
39+
</div>
40+
</template>
41+
</Scroller>
42+
</Viewport>
43+
</template>
44+
</div>
45+
</DocumentContent>
46+
</EmbedPDF>
47+
</BaseViewer>
48+
</template>
49+
50+
<script setup lang="ts">
51+
import { computed } from 'vue';
52+
import { usePdfiumEngine } from '@embedpdf/engines/vue';
53+
import { EmbedPDF } from '@embedpdf/core/vue';
54+
import { createPluginRegistration } from '@embedpdf/core';
55+
import { ViewportPluginPackage, Viewport } from '@embedpdf/plugin-viewport/vue';
56+
import { ScrollPluginPackage, Scroller } from '@embedpdf/plugin-scroll/vue';
57+
import {
58+
DocumentContent,
59+
DocumentManagerPluginPackage,
60+
} from '@embedpdf/plugin-document-manager/vue';
61+
import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/vue';
62+
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/vue';
63+
64+
import BaseViewer from '../BaseViewer.vue';
65+
import PdfViewerToolbar from './PdfViewerToolbar.vue';
66+
67+
const props = defineProps<{
68+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
69+
detail?: any;
70+
}>();
71+
72+
const { engine, isLoading } = usePdfiumEngine();
73+
74+
const plugins = computed(() => {
75+
const url = props.detail?.media?.documents?.[0]?.url;
76+
77+
if (!url) {
78+
return [];
79+
}
80+
81+
return [
82+
createPluginRegistration(DocumentManagerPluginPackage, {
83+
initialDocuments: [{ url }],
84+
}),
85+
createPluginRegistration(ViewportPluginPackage),
86+
createPluginRegistration(ScrollPluginPackage),
87+
createPluginRegistration(RenderPluginPackage),
88+
createPluginRegistration(ZoomPluginPackage),
89+
];
90+
});
91+
</script>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<template>
2+
<div class="relative bg-white border-b border-neutral-200 px-4 py-2 flex items-center justify-end">
3+
<!-- Zoom Controls -->
4+
<div class="flex items-center gap-2 absolute left-1/2 -translate-x-1/2">
5+
<BaseButton
6+
variant="secondary"
7+
size="small"
8+
@click="zoom?.zoomOut()"
9+
>
10+
<BaseIcon icon="zoomOut" />
11+
</BaseButton>
12+
<BaseButton
13+
variant="secondary"
14+
size="small"
15+
@click="zoom?.zoomIn()"
16+
>
17+
<BaseIcon icon="zoomIn" />
18+
</BaseButton>
19+
</div>
20+
21+
<!-- Page Navigation -->
22+
<div class="flex items-center gap-2">
23+
<BaseButton
24+
variant="secondary"
25+
size="small"
26+
:disabled="!scrollState.totalPages || scrollState.currentPage <= 1"
27+
@click="scroll?.scrollToPreviousPage()"
28+
>
29+
<BaseIcon icon="caretLeft" />
30+
</BaseButton>
31+
32+
<span class="text-sm text-neutral-500 min-w-16 text-center">
33+
{{ scrollState.currentPage }} / {{ scrollState.totalPages }}
34+
</span>
35+
36+
<BaseButton
37+
variant="secondary"
38+
size="small"
39+
:disabled="!scrollState.totalPages || scrollState.currentPage >= scrollState.totalPages"
40+
@click="scroll?.scrollToNextPage()"
41+
>
42+
<BaseIcon icon="caretRight" />
43+
</BaseButton>
44+
</div>
45+
</div>
46+
</template>
47+
48+
<script setup lang="ts">
49+
import { useScroll } from '@embedpdf/plugin-scroll/vue';
50+
import { useZoom } from '@embedpdf/plugin-zoom/vue';
51+
52+
import { BaseButton, BaseIcon } from '@metafori/components';
53+
54+
const props = defineProps<{
55+
documentId: string;
56+
}>();
57+
58+
const { provides: scroll, state: scrollState } = useScroll(() => props.documentId);
59+
const { provides: zoom } = useZoom(() => props.documentId);
60+
</script>

apps/etno/src/i18n/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@
4545
"title": "Detail"
4646
},
4747
"viewer": {
48-
"label": "Viewer"
48+
"label": "Viewer",
49+
"pdf": {
50+
"loading": "Loading PDF..."
51+
}
4952
},
5053
"section": {
5154
"basicInfo": "Basic information",

apps/etno/src/i18n/sk.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@
4545
"title": "Detail"
4646
},
4747
"viewer": {
48-
"label": "Viewer"
48+
"label": "Viewer",
49+
"pdf": {
50+
"loading": "Načítavanie PDF..."
51+
}
4952
},
5053
"section": {
5154
"basicInfo": "Základné údaje",

apps/etno/src/views/DetailView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ import ImageViewer from '@/components/DetailViewers/ImageViewer.vue';
209209
import MapViewer from '@/components/DetailViewers/MapViewer.vue';
210210
import VideoViewer from '@/components/DetailViewers/VideoViewer.vue';
211211
import AudioViewer from '@/components/DetailViewers/AudioViewer.vue';
212-
import PdfViewer from '@/components/DetailViewers/PdfViewer.vue';
212+
import PdfViewer from '@/components/DetailViewers/PdfViewer/PdfViewer.vue';
213213
import TranscriptViewer from '@/components/DetailViewers/TranscriptViewer.vue';
214214
215215
import { detailPanelOpen } from '@/store';

0 commit comments

Comments
 (0)