web/satellite{/vuetify-poc}: implement CSV file previewing

This change allows .csv files to be previewed in the file browser.

Resolves #6426

Change-Id: Ib93ebb417b8f69231ed2b36b8258ad91c6a1ba4b
This commit is contained in:
Jeremy Wharton 2023-10-25 23:52:32 -05:00 committed by Storj Robot
parent 42e1b088c2
commit 405491e8d0
9 changed files with 200 additions and 0 deletions

View File

@ -18,6 +18,7 @@
"@stripe/stripe-js": "2.1.0", "@stripe/stripe-js": "2.1.0",
"bip39-english": "2.5.0", "bip39-english": "2.5.0",
"chart.js": "4.2.1", "chart.js": "4.2.1",
"papaparse": "5.4.1",
"pinia": "2.0.23", "pinia": "2.0.23",
"pretty-bytes": "5.6.0", "pretty-bytes": "5.6.0",
"qrcode": "1.5.3", "qrcode": "1.5.3",
@ -31,6 +32,7 @@
"devDependencies": { "devDependencies": {
"@types/filesystem": "0.0.32", "@types/filesystem": "0.0.32",
"@types/node": "18.17.1", "@types/node": "18.17.1",
"@types/papaparse": "5.3.10",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.0",
"@typescript-eslint/eslint-plugin": "5.59.5", "@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5", "@typescript-eslint/parser": "5.59.5",
@ -2571,6 +2573,15 @@
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
"dev": true "dev": true
}, },
"node_modules/@types/papaparse": {
"version": "5.3.10",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.10.tgz",
"integrity": "sha512-mS1Fta/xJ9EDYmAvpeWzcV9Gr0cOl1ClpW7di9+wSUNDIDO55tBtyXg97O7K+Syrd9rDEmuejM2iqmJIJ1SO5g==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qrcode": { "node_modules/@types/qrcode": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.0.tgz",
@ -7259,6 +7270,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/papaparse": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
"integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",

View File

@ -26,6 +26,7 @@
"@stripe/stripe-js": "2.1.0", "@stripe/stripe-js": "2.1.0",
"bip39-english": "2.5.0", "bip39-english": "2.5.0",
"chart.js": "4.2.1", "chart.js": "4.2.1",
"papaparse": "5.4.1",
"pinia": "2.0.23", "pinia": "2.0.23",
"pretty-bytes": "5.6.0", "pretty-bytes": "5.6.0",
"qrcode": "1.5.3", "qrcode": "1.5.3",
@ -39,6 +40,7 @@
"devDependencies": { "devDependencies": {
"@types/filesystem": "0.0.32", "@types/filesystem": "0.0.32",
"@types/node": "18.17.1", "@types/node": "18.17.1",
"@types/papaparse": "5.3.10",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.0",
"@typescript-eslint/eslint-plugin": "5.59.5", "@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5", "@typescript-eslint/parser": "5.59.5",

View File

@ -160,6 +160,7 @@ onBeforeUnmount((): void => {
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 4px; width: 4px;
height: 4px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
@ -171,4 +172,8 @@ onBeforeUnmount((): void => {
border-radius: 6px; border-radius: 6px;
height: 5px; height: 5px;
} }
::-webkit-scrollbar-corner {
background-color: transparent;
}
</style> </style>

View File

@ -0,0 +1,84 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VLoader v-if="isLoading" class="csv-file-preview__loader" width="100px" height="100px" is-white />
<div v-else-if="!isError" class="csv-file-preview__container">
<table>
<tr v-for="(row, rowIdx) in items" :key="rowIdx">
<td v-for="(col, colIdx) in row" :key="colIdx">
{{ col }}
</td>
</tr>
</table>
</div>
<slot v-else />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Papa, { ParseResult } from 'papaparse';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import VLoader from '@/components/common/VLoader.vue';
const notify = useNotify();
const props = defineProps<{
src: string;
}>();
const items = ref<string[][]>([]);
const isLoading = ref<boolean>(true);
const isError = ref<boolean>(false);
onMounted(() => {
Papa.parse(props.src, {
download: true,
worker: true,
header: false,
skipEmptyLines: true,
complete: (results: ParseResult<string[]>) => {
if (results) items.value = results.data;
isLoading.value = false;
},
error: (error: Error) => {
if (isError.value) return;
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
isError.value = true;
},
});
});
</script>
<style scoped lang="scss">
.csv-file-preview {
&__container {
width: 100%;
max-height: 100%;
padding: 16px;
box-sizing: border-box;
overflow: auto;
align-self: flex-start;
table {
min-width: 100%;
border-collapse: collapse;
td {
background: var(--c-white);
border: 1px solid var(--c-grey-3);
padding: 6px 10px;
white-space: nowrap;
}
}
}
&__loader {
align-items: center;
}
}
</style>

View File

@ -60,6 +60,9 @@
<text-file-preview v-if="previewType === PreviewType.Text" :src="objectPreviewUrl"> <text-file-preview v-if="previewType === PreviewType.Text" :src="objectPreviewUrl">
<file-preview-placeholder :file="file" @download="download" /> <file-preview-placeholder :file="file" @download="download" />
</text-file-preview> </text-file-preview>
<c-s-v-file-preview v-else-if="previewType === PreviewType.CSV" :src="objectPreviewUrl">
<file-preview-placeholder :file="file" @download="download" />
</c-s-v-file-preview>
<img <img
v-else-if="previewType === PreviewType.Image" v-else-if="previewType === PreviewType.Image"
:src="objectPreviewUrl" :src="objectPreviewUrl"
@ -123,6 +126,7 @@ import DetailsModal from '@/components/browser/galleryView/modals/Details.vue';
import DistributionModal from '@/components/browser/galleryView/modals/Distribution.vue'; import DistributionModal from '@/components/browser/galleryView/modals/Distribution.vue';
import VLoader from '@/components/common/VLoader.vue'; import VLoader from '@/components/common/VLoader.vue';
import TextFilePreview from '@/components/browser/galleryView/TextFilePreview.vue'; import TextFilePreview from '@/components/browser/galleryView/TextFilePreview.vue';
import CSVFilePreview from '@/components/browser/galleryView/CSVFilePreview.vue';
import FilePreviewPlaceholder from '@/components/browser/galleryView/FilePreviewPlaceholder.vue'; import FilePreviewPlaceholder from '@/components/browser/galleryView/FilePreviewPlaceholder.vue';
import LogoIcon from '@/../static/images/logo.svg'; import LogoIcon from '@/../static/images/logo.svg';

View File

@ -81,6 +81,7 @@ export enum ShareType {
export enum PreviewType { export enum PreviewType {
None, None,
Text, Text,
CSV,
Image, Image,
Video, Video,
Audio, Audio,
@ -89,6 +90,7 @@ export enum PreviewType {
export const EXTENSION_PREVIEW_TYPES = new Map<string[], PreviewType>([ export const EXTENSION_PREVIEW_TYPES = new Map<string[], PreviewType>([
[['txt'], PreviewType.Text], [['txt'], PreviewType.Text],
[['csv'], PreviewType.CSV],
[['bmp', 'svg', 'jpg', 'jpeg', 'png', 'ico', 'gif'], PreviewType.Image], [['bmp', 'svg', 'jpg', 'jpeg', 'png', 'ico', 'gif'], PreviewType.Image],
[['m4v', 'mp4', 'webm', 'mov', 'mkv'], PreviewType.Video], [['m4v', 'mp4', 'webm', 'mov', 'mkv'], PreviewType.Video],
[['m4a', 'mp3', 'wav', 'ogg'], PreviewType.Audio], [['m4a', 'mp3', 'wav', 'ogg'], PreviewType.Audio],

View File

@ -0,0 +1,74 @@
// 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 mt-n16">
<v-progress-circular indeterminate />
</div>
<v-container v-else-if="!isError" class="w-100 max-h-100 overflow-auto">
<table :class="`v-theme--${theme.global.name.value}`">
<tr v-for="(row, rowIdx) in items" :key="rowIdx">
<td v-for="(col, colIdx) in row" :key="colIdx">
{{ col }}
</td>
</tr>
</table>
</v-container>
<slot v-else />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useTheme } from 'vuetify';
import { VProgressCircular, VContainer } from 'vuetify/components';
import Papa, { ParseResult } from 'papaparse';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
const theme = useTheme();
const notify = useNotify();
const props = defineProps<{
src: string;
}>();
const items = ref<string[][]>([]);
const isLoading = ref<boolean>(true);
const isError = ref<boolean>(false);
onMounted(() => {
Papa.parse(props.src, {
download: true,
worker: true,
header: false,
skipEmptyLines: true,
complete: (results: ParseResult<string[]>) => {
if (results) items.value = results.data;
isLoading.value = false;
},
error: (error: Error) => {
if (isError.value) return;
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
isError.value = true;
},
});
});
</script>
<style scoped lang="scss">
table {
min-width: 100%;
color: rgb(var(--v-theme-on-background));
border-collapse: collapse;
td {
background: rgb(var(--v-theme-surface));
padding: 6px 10px;
white-space: nowrap;
/* stylelint-disable-next-line color-function-notation */
border: 1px solid rgba(var(--v-border-color),var(--v-border-opacity));
}
}
</style>

View File

@ -8,6 +8,9 @@
<text-file-preview v-else-if="previewType === PreviewType.Text" :src="objectPreviewUrl"> <text-file-preview v-else-if="previewType === PreviewType.Text" :src="objectPreviewUrl">
<file-preview-placeholder :file="file" @download="emit('download')" /> <file-preview-placeholder :file="file" @download="emit('download')" />
</text-file-preview> </text-file-preview>
<c-s-v-file-preview v-else-if="previewType === PreviewType.CSV" :src="objectPreviewUrl">
<file-preview-placeholder :file="file" @download="emit('download')" />
</c-s-v-file-preview>
<v-container v-else-if="previewType === PreviewType.Video" 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
@ -58,6 +61,7 @@ import { EXTENSION_PREVIEW_TYPES, PreviewType } from '@/types/browser';
import FilePreviewPlaceholder from '@poc/components/dialogs/filePreviewComponents/FilePreviewPlaceholder.vue'; import FilePreviewPlaceholder from '@poc/components/dialogs/filePreviewComponents/FilePreviewPlaceholder.vue';
import TextFilePreview from '@poc/components/dialogs/filePreviewComponents/TextFilePreview.vue'; import TextFilePreview from '@poc/components/dialogs/filePreviewComponents/TextFilePreview.vue';
import CSVFilePreview from '@poc/components/dialogs/filePreviewComponents/CSVFilePreview.vue';
const obStore = useObjectBrowserStore(); const obStore = useObjectBrowserStore();
const bucketsStore = useBucketsStore(); const bucketsStore = useBucketsStore();

View File

@ -243,6 +243,7 @@ table {
// Scrollbar // Scrollbar
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 4px; width: 4px;
height: 4px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background-color: rgb(var(--v-theme-background)); background-color: rgb(var(--v-theme-background));
@ -252,6 +253,9 @@ table {
border-radius: 2px; border-radius: 2px;
min-height: 5px; min-height: 5px;
} }
::-webkit-scrollbar-corner {
background-color: transparent;
}
// Alerts // Alerts
.v-alert-title { .v-alert-title {
@ -278,6 +282,11 @@ table {
position: relative !important; position: relative !important;
} }
// Sizing
.max-h-100 {
max-height: 100%;
}
// text styles // text styles
.text-cursor-pointer { .text-cursor-pointer {
cursor: pointer; cursor: pointer;