web/satellite/vuetify-poc: implement .txt preview

This change allows .txt files to be previewed in the Vuetify UI's
file browser.

References #6426

Change-Id: Ib84ca562d47a413af17890af160542da65425016
This commit is contained in:
Jeremy Wharton 2023-10-23 22:11:32 -05:00 committed by Storj Robot
parent 40e43826a9
commit f319af5a35
5 changed files with 113 additions and 43 deletions

View File

@ -66,48 +66,46 @@
</v-btn>
</template>
</v-toolbar>
<div class="flex-grow-1">
<v-carousel
ref="carousel"
v-model="constCarouselIndex"
tabindex="0"
hide-delimiters
show-arrows="hover"
class="h-100 no-outline"
@keydown.right="onNext"
@keydown.left="onPrevious"
>
<template #prev>
<v-btn
v-if="files.length > 1"
color="default"
class="rounded-circle"
icon
@click="onPrevious"
>
<v-icon icon="mdi-chevron-left" size="x-large" />
</v-btn>
</template>
<template #next>
<v-btn
v-if="files.length > 1"
color="default"
class="rounded-circle"
icon
@click="onNext"
>
<v-icon icon="mdi-chevron-right" size="x-large" />
</v-btn>
</template>
<v-carousel
ref="carousel"
v-model="constCarouselIndex"
tabindex="0"
hide-delimiters
show-arrows="hover"
class="h-100 no-outline"
@keydown.right="onNext"
@keydown.left="onPrevious"
>
<template #prev>
<v-btn
v-if="files.length > 1"
color="default"
class="rounded-circle"
icon
@click="onPrevious"
>
<v-icon icon="mdi-chevron-left" size="x-large" />
</v-btn>
</template>
<template #next>
<v-btn
v-if="files.length > 1"
color="default"
class="rounded-circle"
icon
@click="onNext"
>
<v-icon icon="mdi-chevron-right" size="x-large" />
</v-btn>
</template>
<v-carousel-item v-for="(file, i) in files" :key="file.Key">
<!-- v-carousel will mount all items at the same time -->
<!-- so :active will tell file-preview-item if it is the current. -->
<!-- If it is, it'll load the preview. -->
<file-preview-item :active="i === fileIndex" :file="file" @download="download" />
</v-carousel-item>
</v-carousel>
</div>
<v-carousel-item v-for="(file, i) in files" :key="file.Key">
<!-- v-carousel will mount all items at the same time -->
<!-- so :active will tell file-preview-item if it is the current. -->
<!-- If it is, it'll load the preview. -->
<file-preview-item :active="i === fileIndex" :file="file" @download="download" />
</v-carousel-item>
</v-carousel>
</v-card>
</v-dialog>

View File

@ -5,6 +5,9 @@
<v-container v-if="isLoading" class="fill-height flex-column justify-center align-center mt-n16">
<v-progress-circular indeterminate />
</v-container>
<text-file-preview v-else-if="previewType === PreviewType.Text" :src="objectPreviewUrl">
<file-preview-placeholder :file="file" @download="emit('download')" />
</text-file-preview>
<v-container v-else-if="previewType === PreviewType.Video" class="fill-height flex-column justify-center align-center">
<video
controls
@ -39,7 +42,7 @@
<file-preview-placeholder :file="file" @download="emit('download')" />
</object>
</v-container>
<file-preview-placeholder v-else class="mt-n16" :file="file" @download="emit('download')" />
<file-preview-placeholder v-else :file="file" @download="emit('download')" />
</template>
<script setup lang="ts">
@ -53,9 +56,11 @@ import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames
import { useLinksharing } from '@/composables/useLinksharing';
import FilePreviewPlaceholder from '@poc/components/dialogs/filePreviewComponents/FilePreviewPlaceholder.vue';
import TextFilePreview from '@poc/components/dialogs/filePreviewComponents/TextFilePreview.vue';
enum PreviewType {
None,
Text,
Image,
Video,
Audio,
@ -71,6 +76,7 @@ const isLoading = ref<boolean>(false);
const previewAndMapFailed = ref<boolean>(false);
const extInfos = new Map<string[], PreviewType>([
[['txt'], PreviewType.Text],
[['bmp', 'svg', 'jpg', 'jpeg', 'png', 'ico', 'gif'], PreviewType.Image],
[['m4v', 'mp4', 'webm', 'mov', 'mkv'], PreviewType.Video],
[['m4a', 'mp3', 'wav', 'ogg'], PreviewType.Audio],

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<div class="h-100 w-100 d-flex flex-column align-center justify-center">
<div class="h-100 w-100 mt-n16 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

View File

@ -0,0 +1,55 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div v-if="isLoading" class="w-100 h-100 d-flex align-center justify-center">
<v-progress-circular indeterminate />
</div>
<v-container v-else-if="!isError" class="w-100 h-100 overflow-y-auto">
<v-sheet
:theme="isDark ? 'dark' : 'light'"
:color="isDark ? 'rgba(0, 0, 0, 0.3)' : undefined"
:border="isDark"
class="w-100 pa-4 mx-auto break-word"
max-width="calc(100% - 144px)"
min-height="100%"
>
<code>{{ content }}</code>
</v-sheet>
</v-container>
<slot v-else />
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useTheme } from 'vuetify';
import { VProgressCircular, VContainer, VSheet } from 'vuetify/components';
import { useLoading } from '@/composables/useLoading';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
const theme = useTheme();
const { isLoading, withLoading } = useLoading();
const notify = useNotify();
const props = defineProps<{
src: string;
}>();
const content = ref<string>('');
const isError = ref<boolean>(false);
const isDark = computed(() => theme.global.current.value.dark);
onMounted(async () => {
await withLoading(async () => {
try {
content.value = await (await fetch(props.src)).text();
} catch (error) {
notify.error(`Error fetching object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
isError.value = true;
}
});
});
</script>

View File

@ -282,3 +282,14 @@ table {
.text-cursor-pointer {
cursor: pointer;
}
.break-word {
word-break: break-word;
}
// Vuetify has a bug where any element with the attribute "disabled" forces the default cursor
// even if the value of this attribute is "false".
// This causes text in window items to appear to be unselectable.
[disabled=false] {
cursor: unset;
}