web/satellite/vuetify-poc: add image preview to browser card view
This change adds image preview to the browser card view. Issue: #6427 Change-Id: Iab8110fb3fa70fea29a98d8f96bac9b357dd401d
This commit is contained in:
parent
52b3ffd8c3
commit
81506203c4
@ -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';
|
||||
@ -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';
|
||||
@ -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([]);
|
||||
@ -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.
|
||||
*/
|
||||
@ -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>
|
@ -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"
|
||||
@ -26,10 +32,10 @@
|
||||
</a>
|
||||
</v-card-title>
|
||||
<v-card-subtitle>
|
||||
{{ getFormattedSize(item.browserObject) }}
|
||||
{{ item.browserObject.type === 'folder' ? ' ': getFormattedSize(item.browserObject) }}
|
||||
</v-card-subtitle>
|
||||
<v-card-subtitle>
|
||||
{{ getFormattedDate(item.browserObject) }}
|
||||
{{ item.browserObject.type === 'folder' ? ' ': getFormattedDate(item.browserObject) }}
|
||||
</v-card-subtitle>
|
||||
</v-card-item>
|
||||
<v-card-text class="flex-grow-0">
|
||||
@ -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';
|
||||
|
||||
@ -88,8 +86,6 @@ const obStore = useObjectBrowserStore();
|
||||
const router = useRouter();
|
||||
const notify = useNotify();
|
||||
|
||||
const { isLoading, withLoading } = useLoading();
|
||||
|
||||
const props = defineProps<{
|
||||
item: BrowserObjectWrapper,
|
||||
}>();
|
||||
@ -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}`;
|
||||
}
|
||||
@ -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' });
|
||||
}
|
||||
|
@ -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"
|
||||
@ -138,6 +144,7 @@ import {
|
||||
VSpacer,
|
||||
VDivider,
|
||||
VBtnToggle,
|
||||
VTooltip,
|
||||
} from 'vuetify/components';
|
||||
import { useDisplay } from 'vuetify';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user