web/satellite/vuetify-poc: support PDF previewing

This change allows supported platforms to preview PDFs in the object
browser.

Resolves #6397

Change-Id: I6078914f0ddf5f620514e27e51d24ba8c11a2786
This commit is contained in:
Jeremy Wharton 2023-10-09 20:57:51 -05:00 committed by Storj Robot
parent 7038ddef7f
commit 4e0f062cb5
2 changed files with 74 additions and 68 deletions

View File

@ -5,7 +5,7 @@
<v-container v-if="isLoading" class="fill-height flex-column justify-center align-center mt-n16"> <v-container v-if="isLoading" class="fill-height flex-column justify-center align-center mt-n16">
<v-progress-circular indeterminate /> <v-progress-circular indeterminate />
</v-container> </v-container>
<v-container v-else-if="previewIsVideo" class="fill-height flex-column justify-center align-center"> <v-container v-else-if="previewType === PreviewType.Video" class="fill-height flex-column justify-center align-center">
<video <video
controls controls
:src="objectPreviewUrl" :src="objectPreviewUrl"
@ -13,14 +13,14 @@
aria-roledescription="video-preview" aria-roledescription="video-preview"
/> />
</v-container> </v-container>
<v-container v-else-if="previewIsAudio" class="fill-height flex-column justify-center align-center"> <v-container v-else-if="previewType === PreviewType.Audio" class="fill-height flex-column justify-center align-center">
<audio <audio
controls controls
:src="objectPreviewUrl" :src="objectPreviewUrl"
aria-roledescription="audio-preview" aria-roledescription="audio-preview"
/> />
</v-container> </v-container>
<v-container v-else-if="previewIsImage" class="fill-height flex-column justify-center align-center"> <v-container v-else-if="previewType === PreviewType.Image" class="fill-height flex-column justify-center align-center">
<img <img
v-if="objectPreviewUrl" v-if="objectPreviewUrl"
:src="objectPreviewUrl" :src="objectPreviewUrl"
@ -29,54 +29,62 @@
alt="preview" alt="preview"
> >
</v-container> </v-container>
<v-container v-else-if="placeHolderDisplayable || previewAndMapFailed" class="fill-height flex-column justify-center align-center mt-n16"> <v-container v-else-if="previewType === PreviewType.PDF" class="fill-height flex-column justify-center align-center">
<p class="mb-5">{{ file?.Key || '' }}</p> <object
<p class="text-h5 mb-5 font-weight-bold">No preview available</p> :data="objectPreviewUrl"
<v-btn type="application/pdf"
@click="() => emits('download')" aria-roledescription="pdf-preview"
class="h-100 w-100"
> >
<template #prepend> <file-preview-placeholder :file="file" @download="emit('download')" />
<img src="@poc/assets/icon-download.svg" width="22" alt="Download"> </object>
</template>
{{ `Download (${prettyBytes(file?.Size || 0)})` }}
</v-btn>
</v-container> </v-container>
<file-preview-placeholder v-else class="mt-n16" :file="file" @download="emit('download')" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { VBtn, VContainer, VProgressCircular } from 'vuetify/components'; import { VContainer, VProgressCircular } from 'vuetify/components';
import { useRoute } from 'vue-router';
import prettyBytes from 'pretty-bytes';
import { useAppStore } from '@/store/modules/appStore';
import { BrowserObject, PreviewCache, useObjectBrowserStore } from '@/store/modules/objectBrowserStore'; import { BrowserObject, PreviewCache, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
import { useBucketsStore } from '@/store/modules/bucketsStore'; import { useBucketsStore } from '@/store/modules/bucketsStore';
import { useNotify } from '@/utils/hooks'; import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames'; import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useLinksharing } from '@/composables/useLinksharing'; import { useLinksharing } from '@/composables/useLinksharing';
const appStore = useAppStore(); import FilePreviewPlaceholder from '@poc/components/dialogs/filePreviewComponents/FilePreviewPlaceholder.vue';
enum PreviewType {
None,
Image,
Video,
Audio,
PDF,
}
const obStore = useObjectBrowserStore(); const obStore = useObjectBrowserStore();
const bucketsStore = useBucketsStore(); const bucketsStore = useBucketsStore();
const notify = useNotify(); const notify = useNotify();
const { generateObjectPreviewAndMapURL } = useLinksharing(); const { generateObjectPreviewAndMapURL } = useLinksharing();
const route = useRoute();
const isLoading = ref<boolean>(false); const isLoading = ref<boolean>(false);
const previewAndMapFailed = ref<boolean>(false); const previewAndMapFailed = ref<boolean>(false);
const imgExts = ['bmp', 'svg', 'jpg', 'jpeg', 'png', 'ico', 'gif']; const extInfos = new Map<string[], PreviewType>([
const videoExts = ['m4v', 'mp4', 'webm', 'mov', 'mkv']; [['bmp', 'svg', 'jpg', 'jpeg', 'png', 'ico', 'gif'], PreviewType.Image],
const audioExts = ['m4a', 'mp3', 'wav', 'ogg']; [['m4v', 'mp4', 'webm', 'mov', 'mkv'], PreviewType.Video],
[['m4a', 'mp3', 'wav', 'ogg'], PreviewType.Audio],
[['pdf'], PreviewType.PDF],
]);
const props = defineProps<{ const props = defineProps<{
file: BrowserObject, file: BrowserObject,
active: boolean, // whether this item is visible active: boolean, // whether this item is visible
}>(); }>();
const emits = defineEmits(['download']); const emit = defineEmits<{
download: [];
}>();
/** /**
* Returns object preview URLs cache from store. * Returns object preview URLs cache from store.
@ -109,54 +117,20 @@ const encodedFilePath = computed((): string => {
}); });
/** /**
* Get the extension of the current file. * Returns the type of object being previewed.
*/ */
const extension = computed((): string | undefined => { const previewType = computed<PreviewType>(() => {
return props.file.Key.split('.').pop(); if (previewAndMapFailed.value) return PreviewType.None;
});
/** const dotIdx = props.file.Key.lastIndexOf('.');
* Check to see if the current file is an image file. if (dotIdx === -1) return PreviewType.None;
*/
const previewIsImage = computed((): boolean => { const ext = props.file.Key.toLowerCase().slice(dotIdx + 1);
if (!extension.value) { for (const [exts, previewType] of extInfos) {
return false; if (exts.includes(ext)) return previewType;
} }
return imgExts.includes(extension.value.toLowerCase()); return PreviewType.None;
});
/**
* Check to see if the current file is a video file.
*/
const previewIsVideo = computed((): boolean => {
if (!extension.value) {
return false;
}
return videoExts.includes(extension.value.toLowerCase());
});
/**
* Check to see if the current file is an audio file.
*/
const previewIsAudio = computed((): boolean => {
if (!extension.value) {
return false;
}
return audioExts.includes(extension.value.toLowerCase());
});
/**
* Check to see if the current file is neither an image file, video file, or audio file.
*/
const placeHolderDisplayable = computed((): boolean => {
return [
previewIsImage.value,
previewIsVideo.value,
previewIsAudio.value,
].every((value) => !value);
}); });
/** /**

View File

@ -0,0 +1,32 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="h-100 w-100 d-flex flex-column align-center justify-center">
<p class="mb-5">{{ file?.Key || '' }}</p>
<p class="text-h5 mb-5 font-weight-bold">No preview available</p>
<v-btn
@click="() => emits('download')"
>
<template #prepend>
<img src="@poc/assets/icon-download.svg" width="22" alt="Download">
</template>
{{ `Download (${prettyBytes(file?.Size || 0)})` }}
</v-btn>
</div>
</template>
<script setup lang="ts">
import { VBtn } from 'vuetify/components';
import prettyBytes from 'pretty-bytes';
import { BrowserObject } from '@/store/modules/objectBrowserStore';
const props = defineProps<{
file: BrowserObject,
}>();
const emits = defineEmits<{
download: [];
}>();
</script>