web/satellite/vuetify-poc: allow for sharing files and folders

This change allows linksharing URLs to be generated for files and
folders within the Vuetify project's file browser.

Resolves #6111

Change-Id: I8cbe81b33cb5e35de0c34bba8ccc9175c727bd94
This commit is contained in:
Jeremy Wharton 2023-09-12 20:02:29 -05:00 committed by Storj Robot
parent 8f1682941e
commit ec8f3b4528
5 changed files with 63 additions and 20 deletions

View File

@ -59,7 +59,7 @@
</v-list-item> </v-list-item>
</template> </template>
<v-list-item density="comfortable" link rounded="lg"> <v-list-item density="comfortable" link rounded="lg" @click="() => emit('shareClick')">
<template #prepend> <template #prepend>
<icon-share bold /> <icon-share bold />
</template> </template>
@ -134,6 +134,7 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
deleteFolderClick: []; deleteFolderClick: [];
shareClick: [];
}>(); }>();
const isDownloading = ref<boolean>(false); const isDownloading = ref<boolean>(false);

View File

@ -67,6 +67,7 @@
<browser-row-actions <browser-row-actions
:file="item.raw.browserObject" :file="item.raw.browserObject"
@delete-folder-click="() => onDeleteFolderClick(item.raw.browserObject)" @delete-folder-click="() => onDeleteFolderClick(item.raw.browserObject)"
@share-click="() => onShareClick(item.raw.browserObject)"
/> />
</template> </template>
</v-data-table-row> </v-data-table-row>
@ -76,7 +77,18 @@
<file-preview-dialog v-model="previewDialog" /> <file-preview-dialog v-model="previewDialog" />
</v-card> </v-card>
<delete-folder-dialog v-if="folderToDelete" v-model="isDeleteFolderDialogShown" :folder="folderToDelete" /> <delete-folder-dialog
v-if="folderToDelete"
v-model="isDeleteFolderDialogShown"
:folder="folderToDelete"
@content-removed="folderToDelete = null"
/>
<share-dialog
v-model="isShareDialogShown"
:bucket-name="bucketName"
:file="fileToShare || undefined"
@content-removed="fileToShare = null"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -107,6 +119,7 @@ import { tableSizeOptions } from '@/types/common';
import BrowserRowActions from '@poc/components/BrowserRowActions.vue'; import BrowserRowActions from '@poc/components/BrowserRowActions.vue';
import FilePreviewDialog from '@poc/components/dialogs/FilePreviewDialog.vue'; import FilePreviewDialog from '@poc/components/dialogs/FilePreviewDialog.vue';
import DeleteFolderDialog from '@poc/components/dialogs/DeleteFolderDialog.vue'; import DeleteFolderDialog from '@poc/components/dialogs/DeleteFolderDialog.vue';
import ShareDialog from '@poc/components/dialogs/ShareDialog.vue';
import folderIcon from '@poc/assets/icon-folder-tonal.svg'; import folderIcon from '@poc/assets/icon-folder-tonal.svg';
import pdfIcon from '@poc/assets/icon-pdf-tonal.svg'; import pdfIcon from '@poc/assets/icon-pdf-tonal.svg';
@ -164,8 +177,10 @@ const search = ref<string>('');
const selected = ref([]); const selected = ref([]);
const previewDialog = ref<boolean>(false); const previewDialog = ref<boolean>(false);
const options = ref<TableOptions>(); const options = ref<TableOptions>();
const folderToDelete = ref<BrowserObject>(); const folderToDelete = ref<BrowserObject | null>(null);
const isDeleteFolderDialogShown = ref<boolean>(false); const isDeleteFolderDialogShown = ref<boolean>(false);
const fileToShare = ref<BrowserObject | null>(null);
const isShareDialogShown = ref<boolean>(false);
const sortBy = [{ key: 'name', order: 'asc' }]; const sortBy = [{ key: 'name', order: 'asc' }];
const headers = [ const headers = [
@ -405,6 +420,14 @@ function onDeleteFolderClick(folder: BrowserObject): void {
isDeleteFolderDialogShown.value = true; isDeleteFolderDialogShown.value = true;
} }
/**
* Handles Share button click event.
*/
function onShareClick(file: BrowserObject): void {
fileToShare.value = file;
isShareDialogShown.value = true;
}
watch(filePath, fetchFiles, { immediate: true }); watch(filePath, fetchFiles, { immediate: true });
watch(() => props.forceEmpty, v => !v && fetchFiles()); watch(() => props.forceEmpty, v => !v && fetchFiles());
</script> </script>

View File

@ -8,7 +8,7 @@
transition="fade-transition" transition="fade-transition"
:persistent="isLoading" :persistent="isLoading"
> >
<v-card rounded="xlg"> <v-card rounded="xlg" ref="innerContent">
<v-card-item class="pl-7 py-4"> <v-card-item class="pl-7 py-4">
<template #prepend> <template #prepend>
<v-sheet <v-sheet
@ -62,7 +62,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed, ref, Component, watch } from 'vue';
import { import {
VDialog, VDialog,
VCard, VCard,
@ -91,6 +91,7 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
'update:modelValue': [value: boolean], 'update:modelValue': [value: boolean],
'contentRemoved': [],
}>(); }>();
const model = computed<boolean>({ const model = computed<boolean>({
@ -104,6 +105,8 @@ const bucketsStore = useBucketsStore();
const { isLoading, withLoading } = useLoading(); const { isLoading, withLoading } = useLoading();
const notify = useNotify(); const notify = useNotify();
const innerContent = ref<Component | null>(null);
const filePath = computed<string>(() => bucketsStore.state.fileComponentPath); const filePath = computed<string>(() => bucketsStore.state.fileComponentPath);
async function onDeleteClick(): Promise<void> { async function onDeleteClick(): Promise<void> {
@ -120,4 +123,6 @@ async function onDeleteClick(): Promise<void> {
model.value = false; model.value = false;
}); });
} }
watch(innerContent, comp => !comp && emit('contentRemoved'));
</script> </script>

View File

@ -42,7 +42,7 @@
Download Download
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
<v-btn icon size="small" color="white"> <v-btn icon size="small" color="white" @click="isShareDialogShown = true">
<icon-share size="22" /> <icon-share size="22" />
<v-tooltip <v-tooltip
activator="parent" activator="parent"
@ -90,6 +90,8 @@
</v-carousel> </v-carousel>
</v-card> </v-card>
</v-dialog> </v-dialog>
<share-dialog v-model="isShareDialogShown" :bucket-name="bucketName" :file="currentFile" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -105,9 +107,7 @@ import {
VToolbarTitle, VToolbarTitle,
VTooltip, VTooltip,
} from 'vuetify/components'; } from 'vuetify/components';
import { useRoute } from 'vue-router';
import { useAppStore } from '@/store/modules/appStore';
import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrowserStore'; import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
import { useBucketsStore } from '@/store/modules/bucketsStore'; import { useBucketsStore } from '@/store/modules/bucketsStore';
import { useNotify } from '@/utils/hooks'; import { useNotify } from '@/utils/hooks';
@ -115,15 +115,14 @@ import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames
import IconShare from '@poc/components/icons/IconShare.vue'; import IconShare from '@poc/components/icons/IconShare.vue';
import FilePreviewItem from '@poc/components/dialogs/filePreviewComponents/FilePreviewItem.vue'; import FilePreviewItem from '@poc/components/dialogs/filePreviewComponents/FilePreviewItem.vue';
import ShareDialog from '@poc/components/dialogs/ShareDialog.vue';
const appStore = useAppStore();
const obStore = useObjectBrowserStore(); const obStore = useObjectBrowserStore();
const bucketsStore = useBucketsStore(); const bucketsStore = useBucketsStore();
const notify = useNotify(); const notify = useNotify();
const route = useRoute();
const isDownloading = ref<boolean>(false); const isDownloading = ref<boolean>(false);
const isShareDialogShown = ref<boolean>(false);
const folderType = 'folder'; const folderType = 'folder';
@ -182,6 +181,13 @@ const currentPath = computed((): string => {
return obStore.state.path; return obStore.state.path;
}); });
/**
* Returns the name of the current bucket from the store.
*/
const bucketName = computed((): string => {
return bucketsStore.state.fileComponentBucketName;
});
/** /**
* Download the current opened file. * Download the current opened file.
*/ */

View File

@ -21,7 +21,7 @@
</v-sheet> </v-sheet>
</template> </template>
<v-card-title class="font-weight-bold"> <v-card-title class="font-weight-bold">
Share {{ !filePath ? 'Bucket' : isFolder ? 'Folder' : 'File' }} Share {{ !file ? 'Bucket' : file.type == 'folder' ? 'Folder' : 'File' }}
</v-card-title> </v-card-title>
<template #append> <template #append>
<v-btn <v-btn
@ -118,18 +118,20 @@ import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useNotify } from '@/utils/hooks'; import { useNotify } from '@/utils/hooks';
import { useLinksharing } from '@/composables/useLinksharing'; import { useLinksharing } from '@/composables/useLinksharing';
import { SHARE_BUTTON_CONFIGS, ShareOptions } from '@/types/browser'; import { SHARE_BUTTON_CONFIGS, ShareOptions } from '@/types/browser';
import { BrowserObject } from '@/store/modules/objectBrowserStore';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import IconShare from '@poc/components/icons/IconShare.vue'; import IconShare from '@poc/components/icons/IconShare.vue';
const props = defineProps<{ const props = defineProps<{
modelValue: boolean, modelValue: boolean,
bucketName: string; bucketName: string,
filePath?: string; file?: BrowserObject,
isFolder?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
'update:modelValue': [value: boolean], 'update:modelValue': [value: boolean];
'contentRemoved': [];
}>(); }>();
const model = computed<boolean>({ const model = computed<boolean>({
@ -138,6 +140,7 @@ const model = computed<boolean>({
}); });
const analyticsStore = useAnalyticsStore(); const analyticsStore = useAnalyticsStore();
const bucketsStore = useBucketsStore();
const notify = useNotify(); const notify = useNotify();
const { generateBucketShareURL, generateFileOrFolderShareURL } = useLinksharing(); const { generateBucketShareURL, generateFileOrFolderShareURL } = useLinksharing();
@ -149,6 +152,8 @@ const link = ref<string>('');
const copiedTimeout = ref<ReturnType<typeof setTimeout> | null>(null); const copiedTimeout = ref<ReturnType<typeof setTimeout> | null>(null);
const justCopied = computed<boolean>(() => copiedTimeout.value !== null); const justCopied = computed<boolean>(() => copiedTimeout.value !== null);
const filePath = computed<string>(() => bucketsStore.state.fileComponentPath);
/** /**
* Saves link to clipboard. * Saves link to clipboard.
*/ */
@ -166,20 +171,23 @@ function onCopy(): void {
* Generates linksharing URL when the dialog is opened. * Generates linksharing URL when the dialog is opened.
*/ */
watch(() => innerContent.value, async (comp: Component | null): Promise<void> => { watch(() => innerContent.value, async (comp: Component | null): Promise<void> => {
if (!comp) return; if (!comp) {
emit('contentRemoved');
return;
}
isLoading.value = true; isLoading.value = true;
link.value = ''; link.value = '';
analyticsStore.eventTriggered(AnalyticsEvent.LINK_SHARED); analyticsStore.eventTriggered(AnalyticsEvent.LINK_SHARED);
try { try {
if (!props.filePath) { if (!props.file) {
link.value = await generateBucketShareURL(props.bucketName); link.value = await generateBucketShareURL(props.bucketName);
} else { } else {
link.value = await generateFileOrFolderShareURL( link.value = await generateFileOrFolderShareURL(
props.bucketName, props.bucketName,
props.filePath, `${filePath.value ? filePath.value + '/' : ''}${props.file.Key}`,
props.isFolder, props.file.type === 'folder',
); );
} }
} catch (error) { } catch (error) {