Skip to content

Commit

Permalink
Merge pull request #784 from nextcloud/feat/pdf-viewer
Browse files Browse the repository at this point in the history
feat(viewer): add PDF viewer
  • Loading branch information
ShGKme authored Sep 4, 2024
2 parents 6b6f6a2 + 4702c7a commit 3cdd10b
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/talk/renderer/Viewer/Viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export async function createViewer() {
const { default: ViewerApp } = await import('./ViewerApp.vue')
const { default: ViewerHandlerImages } = await import('./ViewerHandlerImages.vue')
const { default: ViewerHandlerVideos } = await import('./ViewerHandlerVideos.vue')
const { default: ViewerHandlerPdf } = await import('./ViewerHandlerPdf.vue')

const Viewer = {
availableHandlers: [{
Expand Down Expand Up @@ -44,6 +45,11 @@ export async function createViewer() {
'video/x-matroska',
],
component: ViewerHandlerVideos,
}, {
id: 'pdf',
group: 'document',
mimes: ['application/pdf'],
component: ViewerHandlerPdf,
}],

open(...args) {
Expand Down
28 changes: 28 additions & 0 deletions src/talk/renderer/Viewer/ViewerHandlerPdf.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup>
import { computed } from 'vue'
import { toRef } from '@vueuse/core'
import { useFileContent } from './viewer.composables.ts'
import ViewerHandlerBase from './ViewerHandlerBase.vue'
const props = defineProps({
file: {
type: Object,
required: true,
},
})
const { content, loading, error } = useFileContent(toRef(() => props.file.filename), 'binary')
const src = computed(() => content.value ? URL.createObjectURL(content.value) : undefined)
</script>

<template>
<ViewerHandlerBase :loading="loading" :error="error">
<iframe v-if="src" :src="src" />
</ViewerHandlerBase>
</template>
54 changes: 54 additions & 0 deletions src/talk/renderer/Viewer/viewer.composables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { ref, watchEffect } from 'vue'
import type { Ref } from 'vue'
import { toValue } from '@vueuse/core'
import type { MaybeRefOrGetter } from '@vueuse/core'
import { fetchFileContent } from './viewer.service.ts'

type FileContentComposableResult<T> = {
content: Ref<T>,
loading: Ref<boolean>,
error: Ref<boolean | string>,
}

export function useFileContent(filename: MaybeRefOrGetter<string>, format: 'binary'): FileContentComposableResult<Blob | null>
export function useFileContent(filename: MaybeRefOrGetter<string>, format: 'text'): FileContentComposableResult<string | null>
/**
* Fetch file content with reactive state in a watch effect
* @param filename - Path to user's file, e.g. '/Talk/file.txt'
* @param format - Format of the file content to be returned. Binary is returned as Blob
*/
export function useFileContent(filename: MaybeRefOrGetter<string>, format: 'text' | 'binary'): FileContentComposableResult<Blob | string | null> {
const content: Ref<Blob | string | null> = ref(null)
const loading: Ref<boolean> = ref(false)
const error: Ref<boolean | string> = ref(false)

watchEffect(async () => {
loading.value = true
content.value = null
error.value = false
try {
// Help TS to infer the type from overloads
content.value = format === 'text'
? await fetchFileContent(toValue(filename), format)
: await fetchFileContent(toValue(filename), format)
} catch (e) {
console.error('Failed to fetch file content', e)
// TODO: We can parse error from e.response, but it is not translated
// Or we can add messages for different status codes but it seems
// that it's always 404 if file cannot be loaded for any reason
error.value = true
}
loading.value = false
})

return {
content,
loading,
error,
}
}
32 changes: 32 additions & 0 deletions src/talk/renderer/Viewer/viewer.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { davGetClient, davRemoteURL, davRootPath } from '@nextcloud/files'
import type { FileStat } from 'webdav' // eslint-disable-line n/no-extraneous-import -- not exported from @nextcloud/files

export async function fetchFileContent(filename: string, format: 'text'): Promise<string>
export async function fetchFileContent(filename: string, format: 'binary'): Promise<Blob>
/**
* Fetch file content
* @param filename - Path to user's file, e.g. '/Talk/file.txt'
* @param format - Format of the file content to be returned. 'binary' is returned as Blob
*/
export async function fetchFileContent(filename: string, format: 'text' | 'binary'): Promise<string | Blob> {
const webDavClient = davGetClient(davRemoteURL + davRootPath)

// Get the MIME type of the file for the binary file to generate a correct Blob later
let mimeType: string
if (format === 'binary') {
const stat = await webDavClient.stat(filename) as FileStat
mimeType = stat.mime
}

// Fetch file content
type FileContent = typeof format extends 'binary' ? ArrayBuffer : string
const content = await webDavClient.getFileContents(filename, { format }) as FileContent

// Return the content in the requested format
return format === 'binary' ? new Blob([content], { type: mimeType }) : content
}

0 comments on commit 3cdd10b

Please sign in to comment.