-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #784 from nextcloud/feat/pdf-viewer
feat(viewer): add PDF viewer
- Loading branch information
Showing
4 changed files
with
120 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |