web/satellite/vuetify-poc: object browser improvements
- Object preview can be opened through the object row actions menu. - Previewed objects no longer shift vertically when transitioning. - Previewed objects no longer display the next object's preview when transitioning. - The download notification text has been changed from "Success" to "Download Started". - Clicking object browser breadcrumbs no longer redirects to the all projects dashboard. - Upload progress snackbar: - The expanded and collapsed widths are now the same. - Clicking an item opens the object preview for it. - The "Uploading" tooltip position has been moved to the left so that it doesn't block the cancel button. Resolves #6379 Change-Id: Ic1f5cc7948ffa62dc0bce488b61f6d5e121c77b9
This commit is contained in:
parent
99ba88ae2f
commit
b6b9cccb72
@ -118,7 +118,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, h, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
@ -425,13 +425,12 @@ function openDropdown(): void {
|
||||
async function download(): Promise<void> {
|
||||
try {
|
||||
await obStore.download(props.file);
|
||||
const message = `
|
||||
<p class="message-title">Downloading...</p>
|
||||
<p class="message-info">
|
||||
Keep this download link private.<br>If you want to share, use the Share option.
|
||||
</p>
|
||||
`;
|
||||
notify.success('', message);
|
||||
notify.success(() => [
|
||||
h('p', { class: 'message-title' }, 'Downloading...'),
|
||||
h('p', { class: 'message-info' }, [
|
||||
'Keep this download link private.', h('br'), 'If you want to share, use the Share option.',
|
||||
]),
|
||||
]);
|
||||
} catch (error) {
|
||||
notify.error('Can not download your file', AnalyticsErrorEventSource.FILE_BROWSER_ENTRY);
|
||||
}
|
||||
|
@ -112,7 +112,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeMount, onMounted, ref, Teleport, watch } from 'vue';
|
||||
import { Component, computed, h, onBeforeMount, onMounted, ref, Teleport, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
@ -324,13 +324,12 @@ async function onDelete(): Promise<void> {
|
||||
async function download(): Promise<void> {
|
||||
try {
|
||||
await obStore.download(file.value);
|
||||
const message = `
|
||||
<p class="message-title">Downloading...</p>
|
||||
<p class="message-info">
|
||||
Keep this download link private.<br>If you want to share, use the Share option.
|
||||
</p>
|
||||
`;
|
||||
notify.success('', message);
|
||||
notify.success(() => [
|
||||
h('p', { class: 'message-title' }, 'Downloading...'),
|
||||
h('p', { class: 'message-info' }, [
|
||||
'Keep this download link private.', h('br'), 'If you want to share, use the Share option.',
|
||||
]),
|
||||
]);
|
||||
} catch (error) {
|
||||
notify.error('Can not download your file', AnalyticsErrorEventSource.OBJECT_DETAILS_MODAL);
|
||||
}
|
||||
|
@ -114,7 +114,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
import { computed, h, onBeforeMount, ref, watch } from 'vue';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||
@ -273,9 +273,15 @@ async function fetchPreviewAndMapUrl(): Promise<void> {
|
||||
* Download the current opened file.
|
||||
*/
|
||||
async function download(): Promise<void> {
|
||||
if (!file.value) return;
|
||||
try {
|
||||
await obStore.download(file.value);
|
||||
notify.warning('Do not share download link with other people. If you want to share this data better use "Share" option.');
|
||||
notify.success(() => [
|
||||
h('p', { class: 'message-title' }, 'Downloading...'),
|
||||
h('p', { class: 'message-info' }, [
|
||||
'Keep this download link private.', h('br'), 'If you want to share, use the Share option.',
|
||||
]),
|
||||
]);
|
||||
} catch (error) {
|
||||
notify.error('Can not download your file', AnalyticsErrorEventSource.OBJECT_DETAILS_MODAL);
|
||||
}
|
||||
|
@ -105,7 +105,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref } from 'vue';
|
||||
import { computed, h, onBeforeMount, ref } from 'vue';
|
||||
|
||||
import ArrowDownIcon from '../../../static/images/common/dropIcon.svg';
|
||||
|
||||
@ -298,10 +298,10 @@ async function sendRequest(): Promise<void> {
|
||||
limit = limit * Number(Memory.TB);
|
||||
}
|
||||
await projectsStore.requestLimitIncrease(activeLimit.value, limit);
|
||||
notify.success('', `
|
||||
<span class="message-title">Your request for limits increase has been submitted.</span>
|
||||
<span class="message-info">Limit increases may take up to 3 business days to be reflected in your limits.</span>
|
||||
`);
|
||||
notify.success(() => [
|
||||
h('span', { class: 'message-title' }, 'Your request for limits increase has been submitted.\xa0'),
|
||||
h('span', { class: 'message-info' }, 'Limit increases may take up to 3 business days to be reflected in your limits.'),
|
||||
]);
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
notify.error(error.message, AnalyticsErrorEventSource.REQUEST_PROJECT_LIMIT_MODAL);
|
||||
|
@ -2,15 +2,21 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div :style="notification.style" class="notification-wrap" :class="{ active: isClassActive }" @mouseover="onMouseOver" @mouseleave="onMouseLeave">
|
||||
<div
|
||||
:style="{ backgroundColor: notification.backgroundColor }"
|
||||
class="notification-wrap"
|
||||
:class="{ active: isClassActive }"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<div class="notification-wrap__content-area">
|
||||
<div class="notification-wrap__content-area__image">
|
||||
<component :is="notification.icon" />
|
||||
</div>
|
||||
<div class="notification-wrap__content-area__message-area">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-if="notification.messageNode" v-html="notification.messageNode" />
|
||||
<p v-else class="notification-wrap__content-area__message">{{ notification.message }}</p>
|
||||
<p ref="messageArea" class="notification-wrap__content-area__message">
|
||||
<component :is="notification.messageNode" />
|
||||
</p>
|
||||
|
||||
<p v-if="isTimeoutMentioned && notOnSettingsPage" class="notification-wrap__content-area__account-msg">
|
||||
To change this go to your
|
||||
@ -41,7 +47,7 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { DelayedNotification } from '@/types/DelayedNotification';
|
||||
import { DelayedNotification, NotificationType } from '@/types/DelayedNotification';
|
||||
import { useNotificationsStore } from '@/store/modules/notificationsStore';
|
||||
import { useConfigStore } from '@/store/modules/configStore';
|
||||
import { RouteConfig } from '@/types/router';
|
||||
@ -55,10 +61,13 @@ const route = useRoute();
|
||||
const props = withDefaults(defineProps<{
|
||||
notification: DelayedNotification;
|
||||
}>(), {
|
||||
notification: () => new DelayedNotification(() => { return; }, '', ''),
|
||||
notification: () => new DelayedNotification(() => {}, NotificationType.Info, ''),
|
||||
});
|
||||
|
||||
const isClassActive = ref<boolean>(false);
|
||||
const isTimeoutMentioned = ref<boolean>(false);
|
||||
const isSupportLinkMentioned = ref<boolean>(false);
|
||||
const messageArea = ref<HTMLParagraphElement | null>(null);
|
||||
|
||||
/**
|
||||
* Returns the correct settings route based on if we're on all projects dashboard.
|
||||
@ -89,22 +98,6 @@ const notOnSettingsPage = computed((): boolean => {
|
||||
&& route.name !== RouteConfig.Settings2.name;
|
||||
});
|
||||
|
||||
/**
|
||||
* Indicates if session timeout is mentioned in message.
|
||||
* Temporal solution, can be changed later.
|
||||
*/
|
||||
const isTimeoutMentioned = computed((): boolean => {
|
||||
return props.notification.message.toLowerCase().includes('session timeout');
|
||||
});
|
||||
|
||||
/**
|
||||
* Indicates if support word is mentioned in message.
|
||||
* Temporal solution, can be changed later.
|
||||
*/
|
||||
const isSupportLinkMentioned = computed((): boolean => {
|
||||
return props.notification.message.toLowerCase().includes('support');
|
||||
});
|
||||
|
||||
/**
|
||||
* Forces notification deletion.
|
||||
*/
|
||||
@ -130,6 +123,10 @@ function onMouseLeave(): void {
|
||||
* Uses for class change for animation.
|
||||
*/
|
||||
onMounted((): void => {
|
||||
const msg = messageArea.value?.innerText.toLowerCase() || '';
|
||||
isSupportLinkMentioned.value = msg.includes('support');
|
||||
isTimeoutMentioned.value = msg.includes('session timeout');
|
||||
|
||||
setTimeout(() => {
|
||||
isClassActive.value = true;
|
||||
}, 100);
|
||||
|
@ -1,10 +1,10 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { reactive } from 'vue';
|
||||
import { VNode, reactive } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { DelayedNotification, NOTIFICATION_TYPES } from '@/types/DelayedNotification';
|
||||
import { DelayedNotification, NotificationMessage, NotificationType } from '@/types/DelayedNotification';
|
||||
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
|
||||
@ -13,11 +13,6 @@ export class NotificationsState {
|
||||
public analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
|
||||
}
|
||||
|
||||
interface ErrorPayload {
|
||||
message: string,
|
||||
source: AnalyticsErrorEventSource | null,
|
||||
}
|
||||
|
||||
export const useNotificationsStore = defineStore('notifications', () => {
|
||||
const state = reactive<NotificationsState>(new NotificationsState());
|
||||
|
||||
@ -51,47 +46,46 @@ export const useNotificationsStore = defineStore('notifications', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function notifySuccess(message: string, messageNode?: string): void {
|
||||
function notifySuccess(message: NotificationMessage, title?: string): void {
|
||||
const notification = new DelayedNotification(
|
||||
() => deleteNotification(notification.id),
|
||||
NOTIFICATION_TYPES.SUCCESS,
|
||||
NotificationType.Success,
|
||||
message,
|
||||
messageNode,
|
||||
title,
|
||||
);
|
||||
|
||||
addNotification(notification);
|
||||
}
|
||||
|
||||
function notifyInfo(message: string): void {
|
||||
function notifyInfo(message: NotificationMessage): void {
|
||||
const notification = new DelayedNotification(
|
||||
() => deleteNotification(notification.id),
|
||||
NOTIFICATION_TYPES.NOTIFICATION,
|
||||
NotificationType.Info,
|
||||
message,
|
||||
);
|
||||
|
||||
addNotification(notification);
|
||||
}
|
||||
|
||||
function notifyWarning(message: string): void {
|
||||
function notifyWarning(message: NotificationMessage): void {
|
||||
const notification = new DelayedNotification(
|
||||
() => deleteNotification(notification.id),
|
||||
NOTIFICATION_TYPES.WARNING,
|
||||
NotificationType.Warning,
|
||||
message,
|
||||
);
|
||||
|
||||
addNotification(notification);
|
||||
}
|
||||
|
||||
function notifyError(payload: ErrorPayload, messageNode?: string): void {
|
||||
if (payload.source) {
|
||||
state.analytics.errorEventTriggered(payload.source);
|
||||
function notifyError(message: NotificationMessage, source?: AnalyticsErrorEventSource): void {
|
||||
if (source) {
|
||||
state.analytics.errorEventTriggered(source);
|
||||
}
|
||||
|
||||
const notification = new DelayedNotification(
|
||||
() => deleteNotification(notification.id),
|
||||
NOTIFICATION_TYPES.ERROR,
|
||||
payload.message,
|
||||
messageNode,
|
||||
NotificationType.Error,
|
||||
message,
|
||||
);
|
||||
|
||||
addNotification(notification);
|
||||
|
@ -537,7 +537,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
|
||||
|
||||
if (config.state.config.newUploadModalEnabled) {
|
||||
if (state.uploading.some(f => f.Key === key && f.status === UploadingStatus.InProgress)) {
|
||||
notifyError({ message: `${key} is already uploading`, source: AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR });
|
||||
notifyError(`${key} is already uploading`, AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -558,6 +558,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
|
||||
Body: body,
|
||||
status: UploadingStatus.Failed,
|
||||
failedMessage: FailedUploadMessage.TooBig,
|
||||
type: 'file',
|
||||
});
|
||||
|
||||
return;
|
||||
@ -585,10 +586,10 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
|
||||
const item = state.uploading.find(f => f.Key === key);
|
||||
if (!item) {
|
||||
upload.off('httpUploadProgress', progressListener);
|
||||
notifyError({
|
||||
message: `Error updating progress. No file found with key '${key}'`,
|
||||
source: AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR,
|
||||
});
|
||||
notifyError(
|
||||
`Error updating progress. No file found with key '${key}'`,
|
||||
AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -607,6 +608,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
|
||||
Size: 0,
|
||||
LastModified: new Date(),
|
||||
status: UploadingStatus.InProgress,
|
||||
type: 'file',
|
||||
});
|
||||
|
||||
state.uploadChain = state.uploadChain.then(async () => {
|
||||
@ -664,9 +666,9 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
|
||||
|
||||
const limitExceededError = 'storage limit exceeded';
|
||||
if (error.message.includes(limitExceededError)) {
|
||||
notifyError({ message: `Error: ${limitExceededError}`, source: AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR });
|
||||
notifyError(`Error: ${limitExceededError}`, AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR);
|
||||
} else {
|
||||
notifyError({ message: error.message, source: AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR });
|
||||
notifyError(error.message, AnalyticsErrorEventSource.OBJECT_UPLOAD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { VNode, createTextVNode } from 'vue';
|
||||
|
||||
import { getId } from '@/utils/idGenerator';
|
||||
|
||||
import SuccessIcon from '@/../static/images/notifications/success.svg';
|
||||
@ -8,18 +10,36 @@ import NotificationIcon from '@/../static/images/notifications/notification.svg'
|
||||
import ErrorIcon from '@/../static/images/notifications/error.svg';
|
||||
import WarningIcon from '@/../static/images/notifications/warning.svg';
|
||||
|
||||
export const NOTIFICATION_TYPES = {
|
||||
SUCCESS: 'SUCCESS',
|
||||
NOTIFICATION: 'NOTIFICATION',
|
||||
ERROR: 'ERROR',
|
||||
WARNING: 'WARNING',
|
||||
export enum NotificationType {
|
||||
Success = 'Success',
|
||||
Info = 'Info',
|
||||
Error = 'Error',
|
||||
Warning = 'Warning',
|
||||
}
|
||||
|
||||
type RenderFunction = () => (string | VNode | (string | VNode)[]);
|
||||
export type NotificationMessage = string | RenderFunction;
|
||||
|
||||
const StyleInfo: Record<NotificationType, { icon: string; backgroundColor: string }> = {
|
||||
[NotificationType.Success]: {
|
||||
backgroundColor: '#DBF1D3',
|
||||
icon: SuccessIcon,
|
||||
},
|
||||
[NotificationType.Error]: {
|
||||
backgroundColor: '#FFD4D2',
|
||||
icon: ErrorIcon,
|
||||
},
|
||||
[NotificationType.Warning]: {
|
||||
backgroundColor: '#FCF8E3',
|
||||
icon: WarningIcon,
|
||||
},
|
||||
[NotificationType.Info]: {
|
||||
backgroundColor: '#D0E3FE',
|
||||
icon: NotificationIcon,
|
||||
},
|
||||
};
|
||||
|
||||
export class DelayedNotification {
|
||||
private readonly successColor: string = '#DBF1D3';
|
||||
private readonly errorColor: string = '#FFD4D2';
|
||||
private readonly infoColor: string = '#D0E3FE';
|
||||
private readonly warningColor: string = '#FCF8E3';
|
||||
public readonly id: string;
|
||||
|
||||
private readonly callback: () => void;
|
||||
@ -27,43 +47,23 @@ export class DelayedNotification {
|
||||
private startTime: number;
|
||||
private remainingTime: number;
|
||||
|
||||
public readonly type: string;
|
||||
public readonly message: string;
|
||||
public readonly messageNode: string | undefined;
|
||||
public readonly style: { backgroundColor: string };
|
||||
public readonly type: NotificationType;
|
||||
public readonly title: string | undefined;
|
||||
public readonly messageNode: RenderFunction;
|
||||
public readonly backgroundColor: string;
|
||||
public readonly icon: string;
|
||||
|
||||
constructor(callback: () => void, type: string, message: string, messageNode?: string) {
|
||||
constructor(callback: () => void, type: NotificationType, message: NotificationMessage, title?: string) {
|
||||
this.callback = callback;
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
this.messageNode = messageNode;
|
||||
this.title = title;
|
||||
this.messageNode = typeof message === 'string' ? () => createTextVNode(message) : message;
|
||||
this.id = getId();
|
||||
this.remainingTime = 3000;
|
||||
this.start();
|
||||
|
||||
// Switch for choosing notification style depends on notification type
|
||||
switch (this.type) {
|
||||
case NOTIFICATION_TYPES.SUCCESS:
|
||||
this.style = { backgroundColor: this.successColor };
|
||||
this.icon = SuccessIcon;
|
||||
break;
|
||||
|
||||
case NOTIFICATION_TYPES.ERROR:
|
||||
this.style = { backgroundColor: this.errorColor };
|
||||
this.icon = ErrorIcon;
|
||||
break;
|
||||
|
||||
case NOTIFICATION_TYPES.WARNING:
|
||||
this.style = { backgroundColor: this.warningColor };
|
||||
this.icon = WarningIcon;
|
||||
break;
|
||||
|
||||
default:
|
||||
this.style = { backgroundColor: this.infoColor };
|
||||
this.icon = NotificationIcon;
|
||||
break;
|
||||
}
|
||||
this.backgroundColor = StyleInfo[type].backgroundColor;
|
||||
this.icon = StyleInfo[type].icon;
|
||||
}
|
||||
|
||||
public pause(): void {
|
||||
|
@ -1,9 +1,12 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { VNode, h } from 'vue';
|
||||
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { useNotificationsStore } from '@/store/modules/notificationsStore';
|
||||
import { APIError } from '@/utils/error';
|
||||
import { NotificationMessage } from '@/types/DelayedNotification';
|
||||
|
||||
/**
|
||||
* Exposes UI notifications functionality.
|
||||
@ -11,41 +14,36 @@ import { APIError } from '@/utils/error';
|
||||
export class Notificator {
|
||||
public constructor() {}
|
||||
|
||||
public success(message: string, messageNode?: string): void {
|
||||
public success(message: NotificationMessage, title?: string): void {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
notificationsStore.notifySuccess(message, messageNode);
|
||||
notificationsStore.notifySuccess(message, title);
|
||||
}
|
||||
|
||||
public notifyError(error: Error, source: AnalyticsErrorEventSource | null): void {
|
||||
public notifyError(error: Error, source?: AnalyticsErrorEventSource): void {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
|
||||
if (error instanceof APIError) {
|
||||
let template = `
|
||||
<p class="message-title">${error.message}</p>
|
||||
`;
|
||||
if (error.requestID) {
|
||||
template = `
|
||||
${template}
|
||||
<p class="message-footer text-caption">Request ID: ${error.requestID}</p>
|
||||
`;
|
||||
}
|
||||
notificationsStore.notifyError({ message: '', source }, template);
|
||||
return;
|
||||
let msg: NotificationMessage = error.message;
|
||||
if (error instanceof APIError && error.requestID) {
|
||||
msg = () => [
|
||||
h('p', { class: 'message-title' }, error.message),
|
||||
h('p', { class: 'message-footer' }, `Request ID: ${error.requestID}`),
|
||||
];
|
||||
}
|
||||
notificationsStore.notifyError({ message: error.message, source });
|
||||
|
||||
notificationsStore.notifyError(msg, source);
|
||||
}
|
||||
|
||||
public error(message: string, source: AnalyticsErrorEventSource | null): void {
|
||||
public error(message: NotificationMessage, source?: AnalyticsErrorEventSource): void {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
notificationsStore.notifyError({ message, source });
|
||||
notificationsStore.notifyError(message, source);
|
||||
}
|
||||
|
||||
public notify(message: string): void {
|
||||
public notify(message: NotificationMessage): void {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
notificationsStore.notifyInfo(message);
|
||||
}
|
||||
|
||||
public warning(message: string): void {
|
||||
public warning(message: NotificationMessage): void {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
notificationsStore.notifyWarning(message);
|
||||
}
|
||||
|
@ -19,11 +19,6 @@ import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
const projectsStore = useProjectsStore();
|
||||
const bucketsStore = useBucketsStore();
|
||||
|
||||
/**
|
||||
* Returns ID of selected project from store.
|
||||
*/
|
||||
const projectId = computed<string>(() => projectsStore.state.selectedProject.id);
|
||||
|
||||
/**
|
||||
* Returns the name of the selected bucket.
|
||||
*/
|
||||
@ -43,7 +38,7 @@ type BreadcrumbItem = {
|
||||
* Returns breadcrumb items corresponding to parts in the file browser path.
|
||||
*/
|
||||
const items = computed<BreadcrumbItem[]>(() => {
|
||||
const bucketsURL = `/projects/${projectId.value}/buckets`;
|
||||
const bucketsURL = `/projects/${projectsStore.state.selectedProject.urlId}/buckets`;
|
||||
|
||||
const pathParts = [bucketName.value];
|
||||
if (filePath.value) pathParts.push(...filePath.value.split('/'));
|
||||
|
@ -30,7 +30,7 @@
|
||||
<v-menu activator="parent">
|
||||
<v-list class="pa-2">
|
||||
<template v-if="file.type !== 'folder'">
|
||||
<v-list-item density="comfortable" link rounded="lg">
|
||||
<v-list-item density="comfortable" link rounded="lg" @click="emit('previewClick')">
|
||||
<template #prepend>
|
||||
<icon-preview />
|
||||
</template>
|
||||
@ -59,7 +59,7 @@
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<v-list-item density="comfortable" link rounded="lg" @click="() => emit('shareClick')">
|
||||
<v-list-item density="comfortable" link rounded="lg" @click="emit('shareClick')">
|
||||
<template #prepend>
|
||||
<icon-share bold />
|
||||
</template>
|
||||
@ -85,7 +85,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, h } from 'vue';
|
||||
import {
|
||||
VMenu,
|
||||
VList,
|
||||
@ -119,14 +119,13 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
previewClick: [];
|
||||
deleteFileClick: [];
|
||||
shareClick: [];
|
||||
}>();
|
||||
|
||||
const isDownloading = ref<boolean>(false);
|
||||
|
||||
const filePath = computed<string>(() => bucketsStore.state.fileComponentPath);
|
||||
|
||||
async function onDownloadClick(): Promise<void> {
|
||||
if (isDownloading.value) {
|
||||
return;
|
||||
@ -135,7 +134,10 @@ async function onDownloadClick(): Promise<void> {
|
||||
isDownloading.value = true;
|
||||
try {
|
||||
await obStore.download(props.file);
|
||||
notify.success('', `<p>Keep this download link private.<br>If you want to share, use the Share option.</p>`);
|
||||
notify.success(
|
||||
() => ['Keep this download link private.', h('br'), 'If you want to share, use the Share option.'],
|
||||
'Download Started',
|
||||
);
|
||||
} catch (error) {
|
||||
error.message = `Error downloading file. ${error.message}`;
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.FILE_BROWSER_ENTRY);
|
||||
|
@ -9,11 +9,12 @@
|
||||
elevation="24"
|
||||
rounded="lg"
|
||||
class="upload-snackbar"
|
||||
:max-width="xs ? '400px' : ''"
|
||||
width="100%"
|
||||
max-width="400px"
|
||||
>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-expansion-panels theme="dark">
|
||||
<v-expansion-panels theme="dark" @update:model-value="v => isExpanded = v != undefined">
|
||||
<v-expansion-panel
|
||||
color="default"
|
||||
rounded="lg"
|
||||
@ -49,14 +50,16 @@
|
||||
</v-row>
|
||||
</v-expansion-panel-text>
|
||||
<v-divider />
|
||||
<v-expansion-panel-text class="uploading-content">
|
||||
<UploadItem
|
||||
v-for="item in uploading"
|
||||
:key="item.Key"
|
||||
:item="item"
|
||||
/>
|
||||
<v-expansion-panel-text />
|
||||
</v-expansion-panel-text>
|
||||
<v-expand-transition>
|
||||
<div v-show="isExpanded" class="uploading-content">
|
||||
<UploadItem
|
||||
v-for="item in uploading"
|
||||
:key="item.Key"
|
||||
:item="item"
|
||||
@click="item.status === UploadingStatus.Finished && emit('fileClick', item)"
|
||||
/>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-col>
|
||||
@ -78,10 +81,10 @@ import {
|
||||
VTooltip,
|
||||
VIcon,
|
||||
VDivider,
|
||||
VExpandTransition,
|
||||
} from 'vuetify/components';
|
||||
import { useDisplay } from 'vuetify';
|
||||
|
||||
import { UploadingBrowserObject, UploadingStatus, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
|
||||
import { BrowserObject, UploadingBrowserObject, UploadingStatus, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { Duration } from '@/utils/time';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
@ -93,8 +96,11 @@ const remainingTimeString = ref<string>('');
|
||||
const interval = ref<NodeJS.Timer>();
|
||||
const notify = useNotify();
|
||||
const startDate = ref<number>(Date.now());
|
||||
const isExpanded = ref<boolean>(false);
|
||||
|
||||
const { xs } = useDisplay();
|
||||
const emit = defineEmits<{
|
||||
'fileClick': [file: BrowserObject],
|
||||
}>();
|
||||
|
||||
/**
|
||||
* Returns header's status label.
|
||||
|
@ -81,8 +81,9 @@
|
||||
<template #item.actions="{ item }: ItemSlotProps">
|
||||
<browser-row-actions
|
||||
:file="item.raw.browserObject"
|
||||
@delete-file-click="() => onDeleteFileClick(item.raw.browserObject)"
|
||||
@share-click="() => onShareClick(item.raw.browserObject)"
|
||||
@preview-click="onFileClick(item.raw.browserObject)"
|
||||
@delete-file-click="onDeleteFileClick(item.raw.browserObject)"
|
||||
@share-click="onShareClick(item.raw.browserObject)"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table-row>
|
||||
@ -104,6 +105,7 @@
|
||||
:file="fileToShare || undefined"
|
||||
@content-removed="fileToShare = null"
|
||||
/>
|
||||
<browser-snackbar-component v-model="isObjectsUploadModal" @file-click="onFileClick" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -127,11 +129,13 @@ import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
import { useConfigStore } from '@/store/modules/configStore';
|
||||
import { tableSizeOptions } from '@/types/common';
|
||||
import { LocalData } from '@/utils/localData';
|
||||
import { useAppStore } from '@/store/modules/appStore';
|
||||
|
||||
import BrowserRowActions from '@poc/components/BrowserRowActions.vue';
|
||||
import FilePreviewDialog from '@poc/components/dialogs/FilePreviewDialog.vue';
|
||||
import DeleteFileDialog from '@poc/components/dialogs/DeleteFileDialog.vue';
|
||||
import ShareDialog from '@poc/components/dialogs/ShareDialog.vue';
|
||||
import BrowserSnackbarComponent from '@poc/components/BrowserSnackbarComponent.vue';
|
||||
|
||||
import folderIcon from '@poc/assets/icon-folder-tonal.svg';
|
||||
import pdfIcon from '@poc/assets/icon-pdf-tonal.svg';
|
||||
@ -180,6 +184,7 @@ const config = useConfigStore();
|
||||
const obStore = useObjectBrowserStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const bucketsStore = useBucketsStore();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const notify = useNotify();
|
||||
const router = useRouter();
|
||||
@ -246,6 +251,11 @@ const totalObjectCount = computed<number>(() => obStore.state.totalObjectCount);
|
||||
*/
|
||||
const cursor = computed<ObjectBrowserCursor>(() => obStore.state.cursor);
|
||||
|
||||
/**
|
||||
* Indicates whether objects upload modal should be shown.
|
||||
*/
|
||||
const isObjectsUploadModal = computed<boolean>(() => appStore.state.isUploadingModal);
|
||||
|
||||
/**
|
||||
* Returns every file under the current path.
|
||||
*/
|
||||
@ -403,7 +413,7 @@ function onFileClick(file: BrowserObject): void {
|
||||
return;
|
||||
}
|
||||
|
||||
obStore.setObjectPathForModal(obStore.state.path + file.Key);
|
||||
obStore.setObjectPathForModal(file.path + file.Key);
|
||||
previewDialog.value = true;
|
||||
isFileGuideShown.value = false;
|
||||
LocalData.setFileGuideHidden();
|
||||
|
@ -2,12 +2,9 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<v-row class="pt-2" justify="space-between">
|
||||
<v-col cols="9">
|
||||
<p class="text-truncate">{{ item.Key }}</p>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-tooltip :text="uploadStatus">
|
||||
<v-list-item :title="item.Key" class="px-6" height="54" :link="props.item.status === UploadingStatus.Finished">
|
||||
<template #append>
|
||||
<v-tooltip :text="uploadStatus" location="left">
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-progress-circular
|
||||
v-if="props.item.status === UploadingStatus.InProgress"
|
||||
@ -36,13 +33,13 @@
|
||||
/>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { VCol, VIcon, VProgressCircular, VRow, VTooltip } from 'vuetify/components';
|
||||
import { VListItem, VIcon, VProgressCircular, VTooltip } from 'vuetify/components';
|
||||
|
||||
import {
|
||||
UploadingBrowserObject,
|
||||
|
@ -4,92 +4,94 @@
|
||||
<template>
|
||||
<v-dialog v-model="model" transition="fade-transition" class="preview-dialog" fullscreen theme="dark" persistent no-click-animation>
|
||||
<v-card class="preview-card">
|
||||
<v-carousel v-model="constCarouselIndex" hide-delimiters show-arrows="hover" height="100vh">
|
||||
<template #prev>
|
||||
<v-btn
|
||||
v-if="files.length > 1"
|
||||
color="default"
|
||||
class="rounded-circle"
|
||||
icon
|
||||
@click="onPrevious"
|
||||
>
|
||||
<v-icon icon="mdi-chevron-left" size="x-large" />
|
||||
<v-toolbar
|
||||
color="rgba(0, 0, 0, 0.3)"
|
||||
theme="dark"
|
||||
>
|
||||
<v-toolbar-title>
|
||||
{{ fileName }}
|
||||
</v-toolbar-title>
|
||||
<template #append>
|
||||
<v-btn :loading="isDownloading" icon size="small" color="white" @click="download">
|
||||
<img src="@poc/assets/icon-download.svg" width="22" alt="Download">
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Download
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white" @click="isShareDialogShown = true">
|
||||
<icon-share size="22" />
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Share
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white" @click="isGeographicDistributionDialogShown = true">
|
||||
<icon-distribution size="22" />
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Geographic Distribution
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white">
|
||||
<img src="@poc/assets/icon-more.svg" width="22" alt="More">
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
More
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white" @click="model = false">
|
||||
<img src="@poc/assets/icon-close.svg" width="18" alt="Close">
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Close
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template #next>
|
||||
<v-btn
|
||||
v-if="files.length > 1"
|
||||
color="default"
|
||||
class="rounded-circle"
|
||||
icon
|
||||
@click="onNext"
|
||||
>
|
||||
<v-icon icon="mdi-chevron-right" size="x-large" />
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-toolbar
|
||||
color="rgba(0, 0, 0, 0.3)"
|
||||
theme="dark"
|
||||
>
|
||||
<v-toolbar-title>
|
||||
{{ fileName }}
|
||||
</v-toolbar-title>
|
||||
<template #append>
|
||||
<v-btn :loading="isDownloading" icon size="small" color="white" @click="download">
|
||||
<img src="@poc/assets/icon-download.svg" width="22" alt="Download">
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Download
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white" @click="isShareDialogShown = true">
|
||||
<icon-share size="22" />
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Share
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white" @click="isGeographicDistributionDialogShown = true">
|
||||
<icon-distribution size="22" />
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Geographic Distribution
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white">
|
||||
<img src="@poc/assets/icon-more.svg" width="22" alt="More">
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
More
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" color="white" @click="model = false">
|
||||
<img src="@poc/assets/icon-close.svg" width="18" alt="Close">
|
||||
<v-tooltip
|
||||
activator="parent"
|
||||
location="bottom"
|
||||
>
|
||||
Close
|
||||
</v-tooltip>
|
||||
</v-toolbar>
|
||||
<div class="flex-grow-1">
|
||||
<v-carousel v-model="constCarouselIndex" hide-delimiters show-arrows="hover" class="h-100">
|
||||
<template #prev>
|
||||
<v-btn
|
||||
v-if="files.length > 1"
|
||||
color="default"
|
||||
class="rounded-circle"
|
||||
icon
|
||||
@click="onPrevious"
|
||||
>
|
||||
<v-icon icon="mdi-chevron-left" size="x-large" />
|
||||
</v-btn>
|
||||
</template>
|
||||
<template #next>
|
||||
<v-btn
|
||||
v-if="files.length > 1"
|
||||
color="default"
|
||||
class="rounded-circle"
|
||||
icon
|
||||
@click="onNext"
|
||||
>
|
||||
<v-icon icon="mdi-chevron-right" size="x-large" />
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-toolbar>
|
||||
|
||||
<v-carousel-item v-for="(file, i) in files" :key="file.Key">
|
||||
<!-- v-carousel will mount all items at the same time -->
|
||||
<!-- so :active will tell file-preview-item if it is the current. -->
|
||||
<!-- If it is, it'll load the preview. -->
|
||||
<file-preview-item :active="i === fileIndex" :file="file" @download="download" />
|
||||
</v-carousel-item>
|
||||
</v-carousel>
|
||||
<v-carousel-item v-for="(file, i) in files" :key="file.Key">
|
||||
<!-- v-carousel will mount all items at the same time -->
|
||||
<!-- so :active will tell file-preview-item if it is the current. -->
|
||||
<!-- If it is, it'll load the preview. -->
|
||||
<file-preview-item :active="i === fileIndex" :file="file" @download="download" />
|
||||
</v-carousel-item>
|
||||
</v-carousel>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
@ -98,7 +100,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, h, ref, watch } from 'vue';
|
||||
import {
|
||||
VBtn,
|
||||
VCard,
|
||||
@ -205,7 +207,10 @@ async function download(): Promise<void> {
|
||||
isDownloading.value = true;
|
||||
try {
|
||||
await obStore.download(currentFile.value);
|
||||
notify.success('', `<p>Keep this download link private.<br>If you want to share, use the Share option.</p>`);
|
||||
notify.success(
|
||||
() => ['Keep this download link private.', h('br'), 'If you want to share, use the Share option.'],
|
||||
'Download Started',
|
||||
);
|
||||
} catch (error) {
|
||||
error.message = `Error downloading file. ${error.message}`;
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.OBJECT_DETAILS_MODAL);
|
||||
|
@ -101,25 +101,18 @@ const bucket = computed((): string => {
|
||||
return bucketsStore.state.fileComponentBucketName;
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieve the current filepath.
|
||||
*/
|
||||
const filePath = computed((): string => {
|
||||
return obStore.state.objectPathForModal;
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieve the encoded filepath.
|
||||
*/
|
||||
const encodedFilePath = computed((): string => {
|
||||
return encodeURIComponent(`${bucket.value}/${filePath.value.trim()}`);
|
||||
return encodeURIComponent(`${bucket.value}/${props.file.path}${props.file.Key}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the extension of the current file.
|
||||
*/
|
||||
const extension = computed((): string | undefined => {
|
||||
return filePath.value.split('.').pop();
|
||||
return props.file.Key.split('.').pop();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -175,7 +168,7 @@ async function fetchPreviewAndMapUrl(): Promise<void> {
|
||||
|
||||
let url = '';
|
||||
try {
|
||||
url = await generateObjectPreviewAndMapURL(bucketsStore.state.fileComponentBucketName, filePath.value);
|
||||
url = await generateObjectPreviewAndMapURL(bucketsStore.state.fileComponentBucketName, props.file.path + props.file.Key);
|
||||
} catch (error) {
|
||||
error.message = `Unable to get file preview and map URL. ${error.message}`;
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.GALLERY_VIEW);
|
||||
|
@ -14,9 +14,8 @@
|
||||
:key="item.id"
|
||||
closable
|
||||
variant="elevated"
|
||||
:title="title(item.type)"
|
||||
:text="item.messageNode ? '' : item.message"
|
||||
:type="getType(item.type)"
|
||||
:title="item.title || item.type"
|
||||
:type="item.type.toLowerCase() as 'error' | 'success' | 'warning' | 'info'"
|
||||
rounded="lg"
|
||||
class="my-2"
|
||||
border
|
||||
@ -25,8 +24,7 @@
|
||||
@click:close="() => onCloseClick(item.id)"
|
||||
>
|
||||
<template #default>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-if="item.messageNode" v-html="item.messageNode" />
|
||||
<component :is="item.messageNode" />
|
||||
</template>
|
||||
</v-alert>
|
||||
</v-snackbar>
|
||||
@ -37,7 +35,7 @@ import { computed } from 'vue';
|
||||
import { VAlert, VSnackbar } from 'vuetify/components';
|
||||
|
||||
import { useNotificationsStore } from '@/store/modules/notificationsStore';
|
||||
import { DelayedNotification, NOTIFICATION_TYPES } from '@/types/DelayedNotification';
|
||||
import { DelayedNotification } from '@/types/DelayedNotification';
|
||||
|
||||
const notificationsStore = useNotificationsStore();
|
||||
|
||||
@ -55,34 +53,6 @@ const notifications = computed((): DelayedNotification[] => {
|
||||
return notificationsStore.state.notificationQueue as DelayedNotification[];
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns notification title based on type.
|
||||
* @param itemType
|
||||
*/
|
||||
function title(itemType: string): string {
|
||||
const type = getType(itemType);
|
||||
const [firstLetter, ...rest] = type;
|
||||
|
||||
return `${firstLetter.toUpperCase()}${rest.join('')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns notification type.
|
||||
* @param itemType
|
||||
*/
|
||||
function getType(itemType: string): string {
|
||||
switch (itemType) {
|
||||
case NOTIFICATION_TYPES.SUCCESS:
|
||||
return 'success';
|
||||
case NOTIFICATION_TYPES.ERROR:
|
||||
return 'error';
|
||||
case NOTIFICATION_TYPES.WARNING:
|
||||
return 'warning';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces notification to stay on page on mouse over it.
|
||||
*/
|
||||
|
@ -287,6 +287,7 @@ import { useAppStore } from '@poc/store/appStore';
|
||||
import { useUsersStore } from '@/store/modules/usersStore';
|
||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
import { RouteName } from '@poc/router';
|
||||
|
||||
import IconProject from '@poc/components/icons/IconProject.vue';
|
||||
import IconSettings from '@poc/components/icons/IconSettings.vue';
|
||||
@ -377,13 +378,21 @@ function compareProjects(a: Project, b: Project): number {
|
||||
*/
|
||||
async function onProjectSelected(project: Project): Promise<void> {
|
||||
analyticsStore.eventTriggered(AnalyticsEvent.NAVIGATE_PROJECTS);
|
||||
await router.push({
|
||||
name: route.name || undefined,
|
||||
params: {
|
||||
...route.params,
|
||||
id: project.urlId,
|
||||
},
|
||||
});
|
||||
if (route.name === RouteName.Bucket) {
|
||||
await router.push({
|
||||
name: RouteName.Buckets,
|
||||
params: { id: project.urlId },
|
||||
});
|
||||
} else {
|
||||
await router.push({
|
||||
name: route.name || undefined,
|
||||
params: {
|
||||
...route.params,
|
||||
id: project.urlId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
bucketsStore.clearS3Data();
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@
|
||||
:disabled="!isInitialized"
|
||||
v-bind="props"
|
||||
>
|
||||
<browser-snackbar-component v-model="isObjectsUploadModal" />
|
||||
<IconUpload class="mr-2" />
|
||||
Upload
|
||||
</v-btn>
|
||||
@ -111,7 +110,6 @@ import { useAppStore } from '@/store/modules/appStore';
|
||||
|
||||
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
|
||||
import BrowserBreadcrumbsComponent from '@poc/components/BrowserBreadcrumbsComponent.vue';
|
||||
import BrowserSnackbarComponent from '@poc/components/BrowserSnackbarComponent.vue';
|
||||
import BrowserTableComponent from '@poc/components/BrowserTableComponent.vue';
|
||||
import BrowserNewFolderDialog from '@poc/components/dialogs/BrowserNewFolderDialog.vue';
|
||||
import IconUpload from '@poc/components/icons/IconUpload.vue';
|
||||
@ -159,13 +157,6 @@ const projectId = computed<string>(() => projectsStore.state.selectedProject.id)
|
||||
*/
|
||||
const isPromptForPassphrase = computed<boolean>(() => bucketsStore.state.promptForPassphrase);
|
||||
|
||||
/**
|
||||
* Indicates whether objects upload modal should be shown.
|
||||
*/
|
||||
const isObjectsUploadModal = computed((): boolean => {
|
||||
return appStore.state.isUploadingModal;
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the operating system's file system for file upload.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user