feat: add inline PDF viewer for task attachments (#2541)
This commit is contained in:
parent
33d607714d
commit
f5752b97e9
|
|
@ -37,6 +37,7 @@ import {
|
|||
faEyeSlash,
|
||||
faFile,
|
||||
faFileImage,
|
||||
faFilePdf,
|
||||
faFillDrip,
|
||||
faFilter,
|
||||
faForward,
|
||||
|
|
@ -111,6 +112,7 @@ library.add(faSquareCheck)
|
|||
library.add(faTable)
|
||||
library.add(faFile)
|
||||
library.add(faFileImage)
|
||||
library.add(faFilePdf)
|
||||
library.add(faCheckSquare)
|
||||
library.add(faStrikethrough)
|
||||
library.add(faCode)
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@
|
|||
<Icon icon="trash-alt" />
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="editEnabled && canPreview(a)"
|
||||
v-if="editEnabled && canPreviewImage(a)"
|
||||
v-tooltip="task.coverImageAttachmentId === a.id
|
||||
? $t('task.attachment.unsetAsCover')
|
||||
: $t('task.attachment.setAsCover')"
|
||||
|
|
@ -168,6 +168,19 @@
|
|||
alt=""
|
||||
>
|
||||
</Modal>
|
||||
|
||||
<!-- Attachment PDF modal -->
|
||||
<Modal
|
||||
:enabled="attachmentPdfBlobUrl !== null"
|
||||
:wide="true"
|
||||
@close="attachmentPdfBlobUrl = null"
|
||||
>
|
||||
<iframe
|
||||
v-if="attachmentPdfBlobUrl"
|
||||
:src="attachmentPdfBlobUrl"
|
||||
class="pdf-preview-iframe"
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -180,7 +193,7 @@ import ProgressBar from '@/components/misc/ProgressBar.vue'
|
|||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
import {canPreviewImage, canPreviewPdf} from '@/models/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
|
|
@ -365,10 +378,13 @@ async function deleteAttachment() {
|
|||
}
|
||||
|
||||
const attachmentImageBlobUrl = ref<string | null>(null)
|
||||
const attachmentPdfBlobUrl = ref<string | null>(null)
|
||||
|
||||
async function viewOrDownload(attachment: IAttachment) {
|
||||
if (canPreview(attachment)) {
|
||||
if (canPreviewImage(attachment)) {
|
||||
attachmentImageBlobUrl.value = await attachmentService.getBlobUrl(attachment)
|
||||
} else if (canPreviewPdf(attachment)) {
|
||||
attachmentPdfBlobUrl.value = await attachmentService.getBlobUrl(attachment)
|
||||
} else {
|
||||
downloadAttachment(attachment)
|
||||
}
|
||||
|
|
@ -576,6 +592,15 @@ defineExpose({
|
|||
block-size: 100%;
|
||||
}
|
||||
|
||||
.pdf-preview-iframe {
|
||||
inline-size: 100%;
|
||||
max-inline-size: calc(100% - 4rem);
|
||||
block-size: calc(100vh - var(--modal-content-spacing-tablet));
|
||||
border: none;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.is-task-cover {
|
||||
background: var(--primary);
|
||||
color: var(--white);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,17 @@
|
|||
alt="Attachment preview"
|
||||
>
|
||||
|
||||
<!-- PDF icon -->
|
||||
<div
|
||||
v-else-if="isPdf"
|
||||
class="icon-wrapper"
|
||||
>
|
||||
<Icon
|
||||
size="6x"
|
||||
icon="file-pdf"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fallback -->
|
||||
<div
|
||||
v-else
|
||||
|
|
@ -19,10 +30,10 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, shallowReactive, watchEffect} from 'vue'
|
||||
import {computed, ref, shallowReactive, watchEffect} from 'vue'
|
||||
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
import {canPreviewImage, canPreviewPdf} from '@/models/attachment'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: IAttachment
|
||||
|
|
@ -30,9 +41,10 @@ const props = defineProps<{
|
|||
|
||||
const attachmentService = shallowReactive(new AttachmentService())
|
||||
const blobUrl = ref<string | undefined>(undefined)
|
||||
const isPdf = computed(() => props.modelValue && canPreviewPdf(props.modelValue))
|
||||
|
||||
watchEffect(async () => {
|
||||
if (props.modelValue && canPreview(props.modelValue)) {
|
||||
if (props.modelValue && canPreviewImage(props.modelValue)) {
|
||||
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue, PREVIEW_SIZE.MD)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,9 +6,18 @@ import type { IFile } from '@/modelTypes/IFile'
|
|||
import type { IAttachment } from '@/modelTypes/IAttachment'
|
||||
|
||||
export const SUPPORTED_IMAGE_SUFFIX = ['.jpeg', '.jpg', '.png', '.bmp', '.gif']
|
||||
export const SUPPORTED_PDF_SUFFIX = ['.pdf']
|
||||
|
||||
export function canPreviewImage(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
|
||||
export function canPreviewPdf(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_PDF_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
|
||||
export function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
return canPreviewImage(attachment) || canPreviewPdf(attachment)
|
||||
}
|
||||
|
||||
export default class AttachmentModel extends AbstractModel<IAttachment> implements IAttachment {
|
||||
|
|
|
|||
Loading…
Reference in New Issue