web/satellite: options dropdown and details modal for gallery view

Added options dropdown and object details modal.
Also added closing gallery on ESC click.

Issue:
https://github.com/storj/storj/issues/5824

Change-Id: I721a2d0272049cc38c1219098d17c170ce6c862e
This commit is contained in:
Vitalii 2023-06-02 16:00:39 +03:00
parent 32f683fe9d
commit accf695aa3
5 changed files with 289 additions and 3 deletions

View File

@ -3,7 +3,7 @@
<template>
<Teleport to="#app">
<div class="gallery">
<div ref="viewContainer" class="gallery" tabindex="0" @keydown.esc="closeModal">
<div class="gallery__header">
<LogoIcon class="gallery__header__logo" />
<SmallLogoIcon class="gallery__header__small-logo" />
@ -14,11 +14,23 @@
<p class="gallery__header__name__label" :title="file.Key">{{ file.Key }}</p>
</div>
<div class="gallery__header__functional">
<ButtonIcon :icon="DotsIcon" :on-press="() => {}" />
<ButtonIcon
v-click-outside="closeDropdown"
:icon="DotsIcon"
:on-press="toggleDropdown"
:is-active="isOptionsDropdown === true"
/>
<ButtonIcon :icon="MapIcon" :on-press="() => {}" />
<ButtonIcon class="gallery__header__functional__item" :icon="DownloadIcon" :on-press="download" />
<ButtonIcon class="gallery__header__functional__item" :icon="ShareIcon" :on-press="() => {}" />
<ButtonIcon :icon="CloseIcon" :on-press="closeModal" />
<OptionsDropdown
v-if="isOptionsDropdown"
:on-view-details="() => setActiveModal(DetailsModal)"
:on-download="download"
:on-share="() => {}"
:on-delete="() => {}"
/>
</div>
</div>
<div class="gallery__main">
@ -67,11 +79,18 @@
<ArrowIcon class="gallery__main__right-arrow" @click="onNext" />
</div>
</div>
<div v-if="activeModal">
<component
:is="activeModal"
:on-close="() => setActiveModal(undefined)"
:object="file"
/>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, ref, Teleport, watch } from 'vue';
import { Component, computed, onBeforeMount, onMounted, ref, Teleport, watch } from 'vue';
import { useRoute } from 'vue-router';
import prettyBytes from 'pretty-bytes';
@ -82,6 +101,8 @@ import { useNotify } from '@/utils/hooks';
import { RouteConfig } from '@/router';
import ButtonIcon from '@/components/browser/galleryView/ButtonIcon.vue';
import OptionsDropdown from '@/components/browser/galleryView/OptionsDropdown.vue';
import DetailsModal from '@/components/browser/galleryView/modals/Details.vue';
import VLoader from '@/components/common/VLoader.vue';
import VButton from '@/components/common/VButton.vue';
@ -103,8 +124,11 @@ const notify = useNotify();
const route = useRoute();
const viewContainer = ref<HTMLElement>();
const isLoading = ref<boolean>(false);
const previewAndMapFailed = ref<boolean>(false);
const isOptionsDropdown = ref<boolean>(false);
const activeModal = ref<Component>();
const objectMapUrl = ref<string>('');
const objectPreviewUrl = ref<string>('');
@ -231,6 +255,31 @@ function closeModal(): void {
appStore.setGalleryView(false);
}
/**
* Toggles options dropdown.
*/
function toggleDropdown(): void {
isOptionsDropdown.value = !isOptionsDropdown.value;
}
/**
* Closes options dropdown.
*/
function closeDropdown(): void {
isOptionsDropdown.value = false;
}
/**
* Sets active modal.
*/
function setActiveModal(value: Component | undefined): void {
activeModal.value = value;
if (!value) {
viewContainer.value?.focus();
}
}
/**
* Handles on previous click logic.
*/
@ -281,6 +330,10 @@ onBeforeMount((): void => {
fetchPreviewAndMapUrl();
});
onMounted((): void => {
viewContainer.value?.focus();
});
/**
* Watch for changes on the filepath and call `fetchObjectMapUrl` the moment it updates.
*/
@ -364,6 +417,7 @@ watch(filePath, () => {
&__functional {
column-gap: 16px;
position: relative;
@media screen and (width <= 1100px) {
column-gap: 8px;

View File

@ -0,0 +1,82 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="options">
<div class="options__item" @click="onViewDetails">
<DetailsIcon />
<p class="options__item__label">View details</p>
</div>
<div class="options__item" @click="onDownload">
<SmallDownloadIcon />
<p class="options__item__label">Download</p>
</div>
<div class="options__item" @click="onShare">
<SmallShareIcon />
<p class="options__item__label">Share</p>
</div>
<div class="options__item" @click="onDelete">
<DeleteIcon />
<p class="options__item__label">Delete</p>
</div>
</div>
</template>
<script setup lang="ts">
import DetailsIcon from '@/../static/images/browser/galleryView/details.svg';
import SmallDownloadIcon from '@/../static/images/browser/galleryView/downloadSmall.svg';
import SmallShareIcon from '@/../static/images/browser/galleryView/shareSmall.svg';
import DeleteIcon from '@/../static/images/browser/galleryView/delete.svg';
const props = defineProps<{
onViewDetails: () => void
onDownload: () => void
onShare: () => void
onDelete: () => void
}>();
</script>
<style scoped lang="scss">
.options {
position: absolute;
left: 0;
top: calc(100% + 10px);
width: 240px;
border: 1px solid var(--c-grey-2);
filter: drop-shadow(0 7px 20px rgb(0 0 0 / 15%));
border-radius: 8px;
background-color: var(--c-white);
font-family: 'font_regular', sans-serif;
z-index: 1;
@media screen and (width < 1100px) {
width: 155px;
}
&__item {
display: flex;
align-items: center;
cursor: pointer;
padding: 16px;
&__label {
margin-left: 16px;
font-size: 14px;
line-height: 20px;
color: var(--c-grey-6);
}
&:first-of-type {
border-radius: 8px 8px 0 0;
}
&:last-of-type {
border-radius: 0 0 8px 8px;
}
&:hover {
background-color: var(--c-grey-0);
}
}
}
</style>

View File

@ -0,0 +1,111 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="onClose">
<template #content>
<div class="modal">
<ModalHeader
:icon="DetailsIcon"
title="View Details"
/>
<div class="modal__item">
<p class="modal__item__label">Name</p>
<p class="modal__item__label right" :title="object.Key">{{ object.Key }}</p>
</div>
<div class="modal__item">
<p class="modal__item__label">Size</p>
<p class="modal__item__label right">{{ prettyBytes(object.Size) }}</p>
</div>
<div class="modal__item">
<p class="modal__item__label">Last Edited</p>
<p class="modal__item__label right" :title="object.LastModified.toLocaleString()">
{{ object.LastModified.toLocaleString() }}
</p>
</div>
<div class="modal__item last">
<p class="modal__item__label">Saved in</p>
<p class="modal__item__label right" :title="bucket">{{ bucket }}</p>
</div>
<VButton
label="Close"
height="52px"
width="100%"
border-radius="10px"
font-size="14px"
:on-press="onClose"
/>
</div>
</template>
</VModal>
</template>
<script setup lang="ts">
import prettyBytes from 'pretty-bytes';
import { computed } from 'vue';
import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
import VModal from '@/components/common/VModal.vue';
import VButton from '@/components/common/VButton.vue';
import ModalHeader from '@/components/browser/galleryView/modals/ModalHeader.vue';
import DetailsIcon from '@/../static/images/browser/galleryView/modals/details.svg';
const obStore = useObjectBrowserStore();
const props = defineProps<{
object: BrowserObject
onClose: () => void
}>();
/**
* Returns active bucket name from store.
*/
const bucket = computed((): string => {
return obStore.state.bucket;
});
</script>
<style scoped lang="scss">
.modal {
padding: 32px;
font-family: 'font_regular', sans-serif;
background-color: var(--c-white);
box-shadow: 0 20px 30px rgb(10 27 44 / 20%);
border-radius: 20px;
width: 410px;
box-sizing: border-box;
@media screen and (width <= 520px) {
width: 320px;
}
&__item {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 100%;
padding-bottom: 16px;
&__label {
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: var(--c-black);
}
}
}
.right {
margin-left: 16px;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.last {
border-bottom: 1px solid var(--c-grey-2);
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,35 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="header">
<component :is="icon" />
<h1 class="header__title">{{ title }}</h1>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
icon: string
title: string
}>();
</script>
<style scoped lang="scss">
.header {
display: flex;
align-items: center;
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 31px;
letter-spacing: -0.02em;
color: var(--c-black);
margin-left: 16px;
}
}
</style>

View File

@ -0,0 +1,4 @@
<svg width="40" height="41" viewBox="0 0 40 41" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4423 0.980896H23.3463C28.8835 0.980896 31.0805 1.59462 33.2353 2.74706C35.3902 3.89951 37.0814 5.59067 38.2338 7.74555L38.3214 7.91145C39.4029 9.98761 39.9846 12.1809 40 17.4232V24.3272C40 29.8644 39.3863 32.0614 38.2338 34.2162C37.0814 36.3711 35.3902 38.0623 33.2353 39.2147L33.0694 39.3023C30.9933 40.3838 28.8 40.9655 23.5577 40.9809H16.6537C11.1165 40.9809 8.91954 40.3672 6.76466 39.2147C4.60977 38.0623 2.91861 36.3711 1.76617 34.2162L1.67858 34.0503C0.597074 31.9742 0.0154219 29.7809 0 24.5386V17.6346C0 12.0974 0.613723 9.90043 1.76617 7.74555C2.91861 5.59067 4.60977 3.89951 6.76466 2.74706L6.93055 2.65948C9.00672 1.57797 11.2 0.996318 16.4423 0.980896Z" fill="#EBEEF1"/>
<path d="M11.8999 22.2749L11.9005 22.0499C11.9122 20.1774 12.1115 19.2084 12.6338 18.2231C12.8856 17.7478 13.1999 17.3225 13.5721 16.9535C13.8578 16.6703 14.319 16.6723 14.6022 16.958C14.8854 17.2436 14.8834 17.7048 14.5977 17.988C14.3307 18.2527 14.1044 18.559 13.9209 18.9053C13.5138 19.6732 13.3614 20.4337 13.3567 22.1486L13.3566 22.2727L13.3576 22.4354C13.3712 23.9799 13.516 24.7231 13.8719 25.4277L13.9209 25.5222C14.277 26.1943 14.795 26.7169 15.4601 27.0758L15.5335 27.1146C16.2682 27.4949 17.0241 27.6386 18.6655 27.6432L21.2833 27.6433L21.5226 27.6415C22.9718 27.6239 23.6929 27.482 24.3665 27.1434L24.4385 27.1064L24.496 27.0758C25.161 26.7169 25.679 26.1943 26.0352 25.5222C26.4423 24.7543 26.5947 23.9938 26.5993 22.2789L26.5995 22.1548L26.5985 21.9921C26.5849 20.4476 26.4401 19.7043 26.0842 18.9998L26.0352 18.9053C25.8776 18.6078 25.6883 18.3398 25.4689 18.1024C25.1959 17.807 25.214 17.3462 25.5094 17.0732C25.8048 16.8001 26.2656 16.8183 26.5386 17.1137C26.8223 17.4205 27.0685 17.7625 27.2767 18.139L27.3241 18.2265L27.3582 18.2915C27.8372 19.2195 28.0316 20.159 28.0541 21.8834L28.0562 22.1526L28.0556 22.3776C28.0438 24.2501 27.8446 25.2191 27.3223 26.2044C26.8444 27.1061 26.1445 27.8234 25.2567 28.3198L25.1843 28.3596L25.1198 28.394L25.026 28.4423C24.1181 28.8995 23.1737 29.0823 21.465 29.0989L21.2854 29.1H18.7483L18.5658 29.0994C16.7092 29.0874 15.7466 28.8857 14.7683 28.3578C13.8737 27.875 13.1629 27.1688 12.6713 26.274L12.632 26.201L12.5979 26.1359C12.0945 25.1608 11.9054 24.1729 11.8999 22.2749ZM16.6617 15.9437L16.682 15.9226L19.4912 13.1134C19.7687 12.8359 20.2145 12.8291 20.5002 13.0931L20.5213 13.1134L23.3305 15.9226C23.615 16.207 23.615 16.6682 23.3305 16.9526C23.053 17.2301 22.6073 17.2369 22.3216 16.9729L22.3005 16.9526L20.7103 15.3624V23.5915C20.7103 23.9938 20.3842 24.3199 19.9819 24.3199C19.5895 24.3199 19.2695 24.0095 19.2542 23.6208L19.2536 23.5915V15.4109L17.7121 16.9526C17.4346 17.2301 16.9888 17.2369 16.7031 16.9729L16.682 16.9526C16.4045 16.6751 16.3977 16.2294 16.6617 15.9437Z" fill="#56606D"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB