Skip to content

Commit

Permalink
web/satellite/vuetify-poc: add image preview to browser card view
Browse files Browse the repository at this point in the history
This change adds image preview to the browser card view.

Issue: #6427

Change-Id: Iab8110fb3fa70fea29a98d8f96bac9b357dd401d
  • Loading branch information
wilfred-asomanii authored and Storj Robot committed Nov 3, 2023
1 parent 52b3ffd commit 8150620
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 36 deletions.
Expand Up @@ -43,10 +43,7 @@ import { computed, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { VBtn, VCard, VCardText, VCol, VDivider, VRow } from 'vuetify/components';
import {
BrowserObject,
useObjectBrowserStore,
} from '@/store/modules/objectBrowserStore';
import { BrowserObject, PreviewCache, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
Expand All @@ -55,6 +52,7 @@ import { useConfigStore } from '@/store/modules/configStore';
import { LocalData } from '@/utils/localData';
import { useAppStore } from '@/store/modules/appStore';
import { BrowserObjectTypeInfo, BrowserObjectWrapper, EXTENSION_INFOS, FILE_INFO, FOLDER_INFO } from '@/types/browser';
import { useLinksharing } from '@/composables/useLinksharing';
import FilePreviewDialog from '@poc/components/dialogs/FilePreviewDialog.vue';
import DeleteFileDialog from '@poc/components/dialogs/DeleteFileDialog.vue';
Expand All @@ -80,6 +78,8 @@ const appStore = useAppStore();
const notify = useNotify();
const router = useRouter();
const { generateObjectPreviewAndMapURL } = useLinksharing();
const isFetching = ref<boolean>(false);
const search = ref<string>('');
const selected = ref([]);
Expand All @@ -90,6 +90,13 @@ const fileToShare = ref<BrowserObject | null>(null);
const isShareDialogShown = ref<boolean>(false);
const isFileGuideShown = ref<boolean>(false);
/**
* Returns object preview URLs cache from store.
*/
const cachedObjectPreviewURLs = computed((): Map<string, PreviewCache> => {
return obStore.state.cachedObjectPreviewURLs;
});
/**
* Returns the name of the selected bucket.
*/
Expand Down Expand Up @@ -198,6 +205,60 @@ function onShareClick(file: BrowserObject): void {
isShareDialogShown.value = true;
}
/**
* Get the object preview url.
*/
async function fetchPreviewUrl(file : BrowserObject) {
let url = '';
try {
url = await generateObjectPreviewAndMapURL(bucketsStore.state.fileComponentBucketName, file.path + file.Key);
} catch (error) {
error.message = `Unable to get file preview URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.FILE_BROWSER_ENTRY);
}
if (!url) {
return;
}
const filePath = encodeURIComponent(`${bucketName.value}/${file.path}${file.Key}`);
obStore.cacheObjectPreviewURL(filePath, { url, lastModified: file.LastModified.getTime() });
}
/**
* Try to find current object's url in cache.
*/
function findCachedURL(file: BrowserObject): string | undefined {
const filePath = encodeURIComponent(`${bucketName.value}/${file.path}${file.Key}`);
const cache = cachedObjectPreviewURLs.value.get(filePath);
if (!cache) return undefined;
if (cache.lastModified !== file.LastModified.getTime()) {
obStore.removeFromObjectPreviewCache(filePath);
return undefined;
}
return cache.url;
}
/**
* Loads object URL from cache or generates new URL.
*/
async function processFilePath(file: BrowserObjectWrapper) {
if (file.browserObject.type === 'folder') return;
if (file.typeInfo.title !== 'Image') return;
const url = findCachedURL(file.browserObject);
if (!url) {
await fetchPreviewUrl(file.browserObject);
}
}
watch(filePath, fetchFiles, { immediate: true });
watch(() => props.forceEmpty, v => !v && fetchFiles());
watch(allFiles, async (files: BrowserObjectWrapper[]) => {
for (const file of files) {
await processFilePath(file);
}
});
</script>
81 changes: 60 additions & 21 deletions web/satellite/vuetify-poc/src/components/FileCard.vue
Expand Up @@ -4,12 +4,18 @@
<template>
<v-card variant="flat" :border="true" rounded="xlg">
<div class="h-100 d-flex flex-column justify-space-between">
<v-container v-if="isLoading" class="fill-height flex-column justify-center align-center mt-n16">
<v-progress-circular indeterminate />
</v-container>
<v-img
v-if="objectPreviewUrl && previewType === PreviewType.Image"
:src="objectPreviewUrl"
alt="preview"
height="200px"
cover
@click="emit('previewClick', item.browserObject)"
/>
<div
v-else
class="d-flex flex-column justify-center align-center file-icon-container"
@click="emit('previewClick', item.browserObject)"
>
<img
:src="item.typeInfo.icon"
Expand All @@ -26,10 +32,10 @@
</a>
</v-card-title>
<v-card-subtitle>
{{ getFormattedSize(item.browserObject) }}
{{ item.browserObject.type === 'folder' ? '&nbsp;': getFormattedSize(item.browserObject) }}
</v-card-subtitle>
<v-card-subtitle>
{{ getFormattedDate(item.browserObject) }}
{{ item.browserObject.type === 'folder' ? '&nbsp;': getFormattedDate(item.browserObject) }}
</v-card-subtitle>
</v-card-item>
<v-card-text class="flex-grow-0">
Expand All @@ -47,24 +53,16 @@

<script setup lang="ts">
import { useRouter } from 'vue-router';
import {
VCard,
VCardItem,
VCardSubtitle,
VCardText,
VCardTitle,
VContainer,
VDivider,
VProgressCircular,
} from 'vuetify/components';
import { VCard, VCardItem, VCardSubtitle, VCardText, VCardTitle, VDivider, VImg } from 'vuetify/components';
import { computed } from 'vue';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useNotify } from '@/utils/hooks';
import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
import { BrowserObject, PreviewCache, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import { Size } from '@/utils/bytesSize';
import { useLoading } from '@/composables/useLoading';
import { EXTENSION_PREVIEW_TYPES, PreviewType } from '@/types/browser';
import BrowserRowActions from '@poc/components/BrowserRowActions.vue';
Expand All @@ -88,8 +86,6 @@ const obStore = useObjectBrowserStore();
const router = useRouter();
const notify = useNotify();
const { isLoading, withLoading } = useLoading();
const props = defineProps<{
item: BrowserObjectWrapper,
}>();
Expand All @@ -100,11 +96,55 @@ const emit = defineEmits<{
shareClick: [BrowserObject];
}>();
/**
* Returns object preview URL from cache.
*/
const objectPreviewUrl = computed((): string => {
const cache = cachedObjectPreviewURLs.value.get(encodedFilePath.value);
const url = cache?.url || '';
return `${url}?view=1`;
});
/**
* Returns object preview URLs cache from store.
*/
const cachedObjectPreviewURLs = computed((): Map<string, PreviewCache> => {
return obStore.state.cachedObjectPreviewURLs;
});
/**
* Retrieve the encoded filepath.
*/
const encodedFilePath = computed((): string => {
return encodeURIComponent(`${bucket.value}/${props.item.browserObject.path}${props.item.browserObject.Key}`);
});
/**
* Returns bucket name from store.
*/
const bucket = computed((): string => {
return bucketsStore.state.fileComponentBucketName;
});
/**
* Returns the type of object being previewed.
*/
const previewType = computed<PreviewType>(() => {
const dotIdx = props.item.browserObject.Key.lastIndexOf('.');
if (dotIdx === -1) return PreviewType.None;
const ext = props.item.browserObject.Key.toLowerCase().slice(dotIdx + 1);
for (const [exts, previewType] of EXTENSION_PREVIEW_TYPES) {
if (exts.includes(ext)) return previewType;
}
return PreviewType.None;
});
/**
* Returns the string form of the file's size.
*/
function getFormattedSize(file: BrowserObject): string {
if (file.type === 'folder') return '---';
const size = new Size(file.Size);
return `${size.formattedBytes} ${size.label}`;
}
Expand All @@ -113,7 +153,6 @@ function getFormattedSize(file: BrowserObject): string {
* Returns the string form of the file's last modified date.
*/
function getFormattedDate(file: BrowserObject): string {
if (file.type === 'folder') return '---';
const date = file.LastModified;
return date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
}
Expand Down
29 changes: 18 additions & 11 deletions web/satellite/vuetify-poc/src/views/Bucket.vue
Expand Up @@ -87,17 +87,23 @@
density="comfortable"
class="pa-1"
>
<v-btn
size="small"
rounded="xl"
active-class="active"
:active="isCardView"
aria-label="Toggle Cards View"
@click="isCardView = true"
>
<icon-card-view />
Cards
</v-btn>
<v-tooltip location="top">
<template #activator="{ props }">
<v-btn
size="small"
rounded="xl"
active-class="active"
:active="isCardView"
aria-label="Toggle Cards View"
v-bind="props"
@click="isCardView = true"
>
<icon-card-view />
Cards
</v-btn>
</template>
Using cards will preview images, which will add up to your Download.
</v-tooltip>
<v-btn
size="small"
rounded="xl"
Expand Down Expand Up @@ -138,6 +144,7 @@ import {
VSpacer,
VDivider,
VBtnToggle,
VTooltip,
} from 'vuetify/components';
import { useDisplay } from 'vuetify';
Expand Down

0 comments on commit 8150620

Please sign in to comment.