web/satellite: implement .txt preview
This change allows .txt files to be previewed in the legacy UI's file browser. References #6426 Change-Id: If0267695f07e6ea1738377527827c1e386fb668f
This commit is contained in:
parent
4ba2703783
commit
ff16d2fa02
@ -0,0 +1,60 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="file-preview-placeholder">
|
||||
<p class="file-preview-placeholder__key">{{ file?.Key || '' }}</p>
|
||||
<p class="file-preview-placeholder__label">No preview available</p>
|
||||
<VButton
|
||||
icon="download"
|
||||
:label="`Download (${prettyBytes(file?.Size || 0)})`"
|
||||
:on-press="() => emit('download')"
|
||||
width="188px"
|
||||
height="52px"
|
||||
border-radius="10px"
|
||||
font-size="14px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
import { BrowserObject } from '@/store/modules/objectBrowserStore';
|
||||
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
file: BrowserObject;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
download: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.file-preview-placeholder {
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&__key {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: var(--c-white);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--c-white);
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -8,8 +8,8 @@
|
||||
<LogoIcon class="gallery__header__logo" />
|
||||
<SmallLogoIcon class="gallery__header__small-logo" />
|
||||
<div class="gallery__header__name">
|
||||
<ImageIcon v-if="previewIsImage" />
|
||||
<VideoIcon v-else-if="previewIsAudio || previewIsVideo" />
|
||||
<ImageIcon v-if="previewType === PreviewType.Image" />
|
||||
<VideoIcon v-else-if="previewType === PreviewType.Audio || previewType === PreviewType.Video" />
|
||||
<EmptyIcon v-else />
|
||||
<p class="gallery__header__name__label" :title="file?.Key || ''">{{ file?.Key || '' }}</p>
|
||||
</div>
|
||||
@ -57,40 +57,31 @@
|
||||
<ArrowIcon class="gallery__main__left-arrow" @click="onPrevious" />
|
||||
<VLoader v-if="isLoading" class="gallery__main__loader" width="100px" height="100px" is-white />
|
||||
<div v-else class="gallery__main__preview">
|
||||
<text-file-preview v-if="previewType === PreviewType.Text" :src="objectPreviewUrl">
|
||||
<file-preview-placeholder :file="file" @download="download" />
|
||||
</text-file-preview>
|
||||
<img
|
||||
v-if="previewIsImage && !isLoading"
|
||||
v-else-if="previewType === PreviewType.Image"
|
||||
:src="objectPreviewUrl"
|
||||
class="gallery__main__preview__item"
|
||||
aria-roledescription="image-preview"
|
||||
alt="preview"
|
||||
>
|
||||
<video
|
||||
v-if="previewIsVideo && !isLoading"
|
||||
v-else-if="previewType === PreviewType.Video"
|
||||
controls
|
||||
:src="objectPreviewUrl"
|
||||
class="gallery__main__preview__item"
|
||||
aria-roledescription="video-preview"
|
||||
/>
|
||||
<audio
|
||||
v-if="previewIsAudio && !isLoading"
|
||||
v-else-if="previewType === PreviewType.Audio"
|
||||
controls
|
||||
:src="objectPreviewUrl"
|
||||
class="gallery__main__preview__item"
|
||||
aria-roledescription="audio-preview"
|
||||
/>
|
||||
<div v-if="placeHolderDisplayable || previewAndMapFailed" class="gallery__main__preview__empty">
|
||||
<p class="gallery__main__preview__empty__key">{{ file?.Key || '' }}</p>
|
||||
<p class="gallery__main__preview__empty__label">No preview available</p>
|
||||
<VButton
|
||||
icon="download"
|
||||
:label="`Download (${prettyBytes(file?.Size || 0)})`"
|
||||
:on-press="download"
|
||||
width="188px"
|
||||
height="52px"
|
||||
border-radius="10px"
|
||||
font-size="14px"
|
||||
/>
|
||||
</div>
|
||||
<file-preview-placeholder v-else :file="file" @download="download" />
|
||||
<div class="gallery__main__preview__buttons">
|
||||
<ArrowIcon class="gallery__main__preview__buttons__left-arrow" @click="onPrevious" />
|
||||
<ArrowIcon @click="onNext" />
|
||||
@ -114,7 +105,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, h, onBeforeMount, onMounted, ref, Teleport, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
import { BrowserObject, PreviewCache, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
@ -123,7 +113,7 @@ import { useNotify } from '@/utils/hooks';
|
||||
import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
import { useLinksharing } from '@/composables/useLinksharing';
|
||||
import { RouteConfig } from '@/types/router';
|
||||
import { ShareType } from '@/types/browser';
|
||||
import { EXTENSION_PREVIEW_TYPES, PreviewType, ShareType } from '@/types/browser';
|
||||
|
||||
import ButtonIcon from '@/components/browser/galleryView/ButtonIcon.vue';
|
||||
import OptionsDropdown from '@/components/browser/galleryView/OptionsDropdown.vue';
|
||||
@ -132,7 +122,8 @@ import ShareModal from '@/components/modals/ShareModal.vue';
|
||||
import DetailsModal from '@/components/browser/galleryView/modals/Details.vue';
|
||||
import DistributionModal from '@/components/browser/galleryView/modals/Distribution.vue';
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import TextFilePreview from '@/components/browser/galleryView/TextFilePreview.vue';
|
||||
import FilePreviewPlaceholder from '@/components/browser/galleryView/FilePreviewPlaceholder.vue';
|
||||
|
||||
import LogoIcon from '@/../static/images/logo.svg';
|
||||
import SmallLogoIcon from '@/../static/images/smallLogo.svg';
|
||||
@ -192,13 +183,6 @@ const filePath = computed((): string => {
|
||||
return obStore.state.objectPathForModal;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the extension of the current file.
|
||||
*/
|
||||
const extension = computed((): string | undefined => {
|
||||
return filePath.value.split('.').pop();
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns bucket name from store.
|
||||
*/
|
||||
@ -206,54 +190,6 @@ const bucket = computed((): string => {
|
||||
return bucketsStore.state.fileComponentBucketName;
|
||||
});
|
||||
|
||||
/**
|
||||
* Check to see if the current file is an image file.
|
||||
*/
|
||||
const previewIsImage = computed((): boolean => {
|
||||
if (!extension.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ['bmp', 'svg', 'jpg', 'jpeg', 'png', 'ico', 'gif'].includes(
|
||||
extension.value.toLowerCase(),
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check to see if the current file is a video file.
|
||||
*/
|
||||
const previewIsVideo = computed((): boolean => {
|
||||
if (!extension.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ['m4v', 'mp4', 'webm', 'mov', 'mkv'].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 ['mp3', 'wav', 'ogg'].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);
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns current path without object key.
|
||||
*/
|
||||
@ -261,6 +197,23 @@ const currentPath = computed((): string => {
|
||||
return decodeURIComponent(route.path.replace(RouteConfig.Buckets.with(RouteConfig.UploadFile).path, ''));
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the type of object being previewed.
|
||||
*/
|
||||
const previewType = computed<PreviewType>(() => {
|
||||
if (previewAndMapFailed.value) return PreviewType.None;
|
||||
|
||||
const dotIdx = file.value.Key.lastIndexOf('.');
|
||||
if (dotIdx === -1) return PreviewType.None;
|
||||
|
||||
const ext = file.value.Key.toLowerCase().slice(dotIdx + 1);
|
||||
for (const [exts, previewType] of EXTENSION_PREVIEW_TYPES) {
|
||||
if (exts.includes(ext)) return previewType;
|
||||
}
|
||||
|
||||
return PreviewType.None;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the object map url for the file being displayed.
|
||||
*/
|
||||
@ -629,30 +582,6 @@ watch(filePath, () => {
|
||||
}
|
||||
}
|
||||
|
||||
&__empty {
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&__key {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: var(--c-white);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--c-white);
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: none;
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<VLoader v-if="isLoading" class="text-file-preview__loader" width="100px" height="100px" is-white />
|
||||
<div v-else-if="!isError" class="text-file-preview__container">
|
||||
<div class="text-file-preview__container__content">{{ content }}</div>
|
||||
</div>
|
||||
<slot v-else />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
import { useLoading } from '@/composables/useLoading';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
|
||||
const { isLoading, withLoading } = useLoading();
|
||||
const notify = useNotify();
|
||||
|
||||
const props = defineProps<{
|
||||
src: string;
|
||||
}>();
|
||||
|
||||
const content = ref<string>('');
|
||||
const isError = ref<boolean>(false);
|
||||
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.text-file-preview {
|
||||
|
||||
&__container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--c-white);
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
&__loader {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -77,3 +77,20 @@ export enum ShareType {
|
||||
Folder = 'Folder',
|
||||
Bucket = 'Bucket',
|
||||
}
|
||||
|
||||
export enum PreviewType {
|
||||
None,
|
||||
Text,
|
||||
Image,
|
||||
Video,
|
||||
Audio,
|
||||
PDF,
|
||||
}
|
||||
|
||||
export const EXTENSION_PREVIEW_TYPES = 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],
|
||||
[['pdf'], PreviewType.PDF],
|
||||
]);
|
||||
|
@ -54,19 +54,11 @@ import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { useLinksharing } from '@/composables/useLinksharing';
|
||||
import { EXTENSION_PREVIEW_TYPES, PreviewType } from '@/types/browser';
|
||||
|
||||
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,
|
||||
PDF,
|
||||
}
|
||||
|
||||
const obStore = useObjectBrowserStore();
|
||||
const bucketsStore = useBucketsStore();
|
||||
const notify = useNotify();
|
||||
@ -75,14 +67,6 @@ const { generateObjectPreviewAndMapURL } = useLinksharing();
|
||||
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],
|
||||
[['pdf'], PreviewType.PDF],
|
||||
]);
|
||||
|
||||
const props = defineProps<{
|
||||
file: BrowserObject,
|
||||
active: boolean, // whether this item is visible
|
||||
@ -132,7 +116,7 @@ const previewType = computed<PreviewType>(() => {
|
||||
if (dotIdx === -1) return PreviewType.None;
|
||||
|
||||
const ext = props.file.Key.toLowerCase().slice(dotIdx + 1);
|
||||
for (const [exts, previewType] of extInfos) {
|
||||
for (const [exts, previewType] of EXTENSION_PREVIEW_TYPES) {
|
||||
if (exts.includes(ext)) return previewType;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user