web/satellite/vuetify-poc: add bucket sharing option to buckets table

This change allows buckets to be shared from the Buckets page through
a dropdown menu in the buckets table.

Resolves #6116

Change-Id: I92055a3ea174d786f2ef960124aafcbd3b2139c4
This commit is contained in:
Jeremy Wharton 2023-08-16 03:34:46 -05:00 committed by Storj Robot
parent ae2cba1d23
commit ffa50f0758
29 changed files with 514 additions and 155 deletions

View File

@ -269,7 +269,8 @@ async function fetchPreviewAndMapUrl(): Promise<void> {
let url = '';
try {
url = await generateObjectPreviewAndMapURL(filePath.value);
url = await generateObjectPreviewAndMapURL(
bucketsStore.state.fileComponentBucketName, filePath.value);
} catch (error) {
error.message = `Unable to get file preview and map URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.GALLERY_VIEW);

View File

@ -4,29 +4,39 @@
<template>
<a
class="share-button"
:href="item.link"
:href="config.getLink(link)"
target="_blank"
rel="noopener noreferrer"
:aria-label="item.label"
:aria-label="props.option"
:style="style"
>
<component :is="item.image" />
<span>{{ item.label }}</span>
<component :is="config.icon" width="12" height="12" />
<span>{{ props.option }}</span>
</a>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { ShareButtonConfig } from '@/types/browser';
import { ShareOptions, SHARE_BUTTON_CONFIGS, ShareButtonConfig } from '@/types/browser';
const props = defineProps<{ item: ShareButtonConfig }>();
const props = defineProps<{
option: ShareOptions;
link: string;
}>();
/**
* Returns share button background color.
*/
const style = computed((): Record<string, string> => {
return { 'background-color': props.item.color };
return { 'background-color': config.value.color };
});
/**
* Returns the configuration for this button's share option.
*/
const config = computed((): ShareButtonConfig => {
return SHARE_BUTTON_CONFIGS[props.option];
});
</script>
@ -45,8 +55,6 @@ const style = computed((): Record<string, string> => {
font-family: 'font_regular', sans-serif;
svg {
width: 12px;
height: 12px;
margin-right: 5px;
}
}

View File

@ -4,97 +4,20 @@
<template>
<div class="share-container">
<ShareButton
v-for="button of shareButtons"
:key="button.label"
:item="button"
v-for="opt of ShareOptions"
:key="opt"
:option="opt"
:link="link"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { ShareButtonConfig, ShareOptions } from '@/types/browser';
import { ShareOptions } from '@/types/browser';
import ShareButton from '@/components/common/share/ShareButton.vue';
import RedditIcon from '@/../static/images/objects/reddit.svg';
import FacebookIcon from '@/../static/images/objects/facebook.svg';
import TwitterIcon from '@/../static/images/objects/twitter.svg';
import HackerNewsIcon from '@/../static/images/objects/hackerNews.svg';
import LinkedInIcon from '@/../static/images/objects/linkedIn.svg';
import TelegramIcon from '@/../static/images/objects/telegram.svg';
import WhatsAppIcon from '@/../static/images/objects/whatsApp.svg';
import EmailIcon from '@/../static/images/objects/email.svg';
const props = defineProps<{ link: string; }>();
const images: Record<string, string> = {
[ShareOptions.Reddit]: RedditIcon,
[ShareOptions.Facebook]: FacebookIcon,
[ShareOptions.Twitter]: TwitterIcon,
[ShareOptions.HackerNews]: HackerNewsIcon,
[ShareOptions.LinkedIn]: LinkedInIcon,
[ShareOptions.Telegram]: TelegramIcon,
[ShareOptions.WhatsApp]: WhatsAppIcon,
[ShareOptions.Email]: EmailIcon,
};
/**
* Returns share buttons list.
*/
const shareButtons = computed((): ShareButtonConfig[] => {
return [
new ShareButtonConfig(
ShareOptions.Reddit,
'#5f99cf',
`https://reddit.com/submit/?url=${props.link}&resubmit=true&title=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage`,
images[ShareOptions.Reddit],
),
new ShareButtonConfig(
ShareOptions.Facebook,
'#3b5998',
`https://facebook.com/sharer/sharer.php?u=${props.link}`,
images[ShareOptions.Facebook],
),
new ShareButtonConfig(
ShareOptions.Twitter,
'#55acee',
`https://twitter.com/intent/tweet/?text=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&url=${props.link}`,
images[ShareOptions.Twitter],
),
new ShareButtonConfig(
ShareOptions.HackerNews,
'#f60',
`https://news.ycombinator.com/submitlink?u=${props.link}&t=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage`,
images[ShareOptions.HackerNews],
),
new ShareButtonConfig(
ShareOptions.LinkedIn,
'#0077b5',
`https://www.linkedin.com/shareArticle?mini=true&url=${props.link}&title=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&summary=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&source=${props.link}`,
images[ShareOptions.LinkedIn],
),
new ShareButtonConfig(
ShareOptions.Telegram,
'#54a9eb',
`https://telegram.me/share/url?text=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&url=${props.link}`,
images[ShareOptions.Telegram],
),
new ShareButtonConfig(
ShareOptions.WhatsApp,
'#25d366',
`whatsapp://send?text=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage%20${props.link}`,
images[ShareOptions.WhatsApp],
),
new ShareButtonConfig(
ShareOptions.Email,
'#777',
`mailto:?subject=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&body=${props.link}`,
images[ShareOptions.Email],
),
];
});
</script>
<style scoped lang="scss">

View File

@ -123,6 +123,7 @@ import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrow
import { useAppStore } from '@/store/modules/appStore';
import { useLinksharing } from '@/composables/useLinksharing';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import VModal from '@/components/common/VModal.vue';
import VButton from '@/components/common/VButton.vue';
@ -134,6 +135,8 @@ import PlaceholderImage from '@/../static/images/browser/placeholder.svg';
const analyticsStore = useAnalyticsStore();
const appStore = useAppStore();
const obStore = useObjectBrowserStore();
const bucketsStore = useBucketsStore();
const notify = useNotify();
const { generateFileOrFolderShareURL, generateObjectPreviewAndMapURL } = useLinksharing();
@ -239,7 +242,7 @@ async function fetchPreviewAndMapUrl(): Promise<void> {
let url = '';
try {
url = await generateObjectPreviewAndMapURL(filePath.value);
url = await generateObjectPreviewAndMapURL(bucketsStore.state.fileComponentBucketName, filePath.value);
} catch (error) {
error.message = `Unable to get file preview and map URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
@ -304,7 +307,8 @@ async function copy(): Promise<void> {
async function getSharedLink(): Promise<void> {
analyticsStore.eventTriggered(AnalyticsEvent.LINK_SHARED);
try {
objectLink.value = await generateFileOrFolderShareURL(filePath.value);
objectLink.value = await generateFileOrFolderShareURL(
bucketsStore.state.fileComponentBucketName, filePath.value);
} catch (error) {
error.message = `Unable to get sharing URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OBJECT_DETAILS_MODAL);

View File

@ -57,6 +57,7 @@ import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { ShareType } from '@/types/browser';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import VModal from '@/components/common/VModal.vue';
import VLoader from '@/components/common/VLoader.vue';
@ -74,6 +75,8 @@ enum ButtonStates {
const analyticsStore = useAnalyticsStore();
const appStore = useAppStore();
const obStore = useObjectBrowserStore();
const bucketsStore = useBucketsStore();
const { generateFileOrFolderShareURL, generateBucketShareURL } = useLinksharing();
const notify = useNotify();
@ -120,9 +123,13 @@ onMounted(async (): Promise<void> => {
analyticsStore.eventTriggered(AnalyticsEvent.LINK_SHARED);
try {
if (shareType.value === ShareType.Bucket) {
link.value = await generateBucketShareURL();
link.value = await generateBucketShareURL(bucketsStore.state.fileComponentBucketName);
} else {
link.value = await generateFileOrFolderShareURL(filePath.value, shareType.value === ShareType.Folder);
link.value = await generateFileOrFolderShareURL(
bucketsStore.state.fileComponentBucketName,
filePath.value,
shareType.value === ShareType.Folder,
);
}
} catch (error) {
error.message = `Unable to get sharing URL. ${error.message}`;

View File

@ -19,14 +19,14 @@ export function useLinksharing() {
const worker = computed((): Worker | null => agStore.state.accessGrantsWebWorker);
async function generateFileOrFolderShareURL(path: string, isFolder = false): Promise<string> {
const fullPath = `${bucketsStore.state.fileComponentBucketName}/${path}`;
async function generateFileOrFolderShareURL(bucketName: string, path: string, isFolder = false): Promise<string> {
const fullPath = `${bucketName}/${path}`;
const type = isFolder ? 'folder' : 'object';
return generateShareURL(fullPath, type);
}
async function generateBucketShareURL(): Promise<string> {
return generateShareURL(bucketsStore.state.fileComponentBucketName, 'bucket');
async function generateBucketShareURL(bucketName: string): Promise<string> {
return generateShareURL(bucketName, 'bucket');
}
async function generateShareURL(path: string, type: string): Promise<string> {
@ -39,10 +39,10 @@ export function useLinksharing() {
return `${configStore.state.config.publicLinksharingURL}/${credentials.accessKeyId}/${encodeURIComponent(path.trim())}`;
}
async function generateObjectPreviewAndMapURL(path: string): Promise<string> {
async function generateObjectPreviewAndMapURL(bucketName: string, path: string): Promise<string> {
if (!worker.value) throw new Error(WORKER_ERR_MSG);
path = bucketsStore.state.fileComponentBucketName + '/' + path;
path = bucketName + '/' + path;
const now = new Date();
const inOneDay = new Date(now.setDate(now.getDate() + 1));
const creds: EdgeCredentials = await generateCredentials(bucketsStore.state.apiKey, path, inOneDay);

View File

@ -1,30 +1,79 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
import EmailIcon from '../../static/images/objects/email.svg';
import { Component } from 'vue';
import RedditIcon from '@poc/components/icons/share/IconReddit.vue';
import FacebookIcon from '@poc/components/icons/share/IconFacebook.vue';
import TwitterIcon from '@poc/components/icons/share/IconTwitter.vue';
import HackerNewsIcon from '@poc/components/icons/share/IconHackerNews.vue';
import LinkedInIcon from '@poc/components/icons/share/IconLinkedIn.vue';
import TelegramIcon from '@poc/components/icons/share/IconTelegram.vue';
import WhatsAppIcon from '@poc/components/icons/share/IconWhatsApp.vue';
import EmailIcon from '@poc/components/icons/share/IconEmail.vue';
export enum ShareOptions {
Reddit = 'Reddit',
Facebook = 'Facebook',
Twitter = 'Twitter',
HackerNews = 'Hacker News',
LinkedIn = 'LinkedIn',
Telegram = 'Telegram',
WhatsApp = 'WhatsApp',
Email = 'E-Mail',
Reddit = 'Reddit',
Facebook = 'Facebook',
Twitter = 'Twitter',
HackerNews = 'Hacker News',
LinkedIn = 'LinkedIn',
Telegram = 'Telegram',
WhatsApp = 'WhatsApp',
Email = 'E-Mail',
}
export class ShareButtonConfig {
constructor(
public label: ShareOptions = ShareOptions.Email,
public color: string = 'white',
public link: string = '',
public image: string = EmailIcon,
) {}
export interface ShareButtonConfig {
color: string;
getLink: (linksharingURL: string) => string;
icon: Component;
}
export const SHARE_BUTTON_CONFIGS: Record<ShareOptions, ShareButtonConfig> = {
[ShareOptions.Reddit]: {
color: '#5f99cf',
getLink: url => `https://reddit.com/submit/?url=${url}&resubmit=true&title=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage`,
icon: RedditIcon,
},
[ShareOptions.Facebook]: {
color: '#3b5998',
getLink: url => `https://facebook.com/sharer/sharer.php?u=${url}`,
icon: FacebookIcon,
},
[ShareOptions.Twitter]: {
color: '#55acee',
getLink: url => `https://twitter.com/intent/tweet/?text=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&url=${url}`,
icon: TwitterIcon,
},
[ShareOptions.HackerNews]: {
color: '#f60',
getLink: url => `https://news.ycombinator.com/submitlink?u=${url}&t=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage`,
icon: HackerNewsIcon,
},
[ShareOptions.LinkedIn]: {
color: '#0077b5',
getLink: url => `https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&summary=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&source=${url}`,
icon: LinkedInIcon,
},
[ShareOptions.Telegram]: {
color: '#54a9eb',
getLink: url => `https://telegram.me/share/url?text=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&url=${url}`,
icon: TelegramIcon,
},
[ShareOptions.WhatsApp]: {
color: '#25d366',
getLink: url => `whatsapp://send?text=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage%20${url}`,
icon: WhatsAppIcon,
},
[ShareOptions.Email]: {
color: '#777',
getLink: url => `mailto:?subject=Shared%20using%20Storj%20Decentralized%20Cloud%20Storage&body=${url}`,
icon: EmailIcon,
},
};
export enum ShareType {
File = 'File',
Folder = 'Folder',
Bucket = 'Bucket',
File = 'File',
Folder = 'Folder',
Bucket = 'Bucket',
}

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M22 4H2C.9 4 0 4.9 0 6v12c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM7.25 14.43l-3.5 2c-.08.05-.17.07-.25.07-.17 0-.34-.1-.43-.25-.14-.24-.06-.55.18-.68l3.5-2c.24-.14.55-.06.68.18.14.24.06.55-.18.68zm4.75.07c-.1 0-.2-.03-.27-.08l-8.5-5.5c-.23-.15-.3-.46-.15-.7.15-.22.46-.3.7-.14L12 13.4l8.23-5.32c.23-.15.54-.08.7.15.14.23.07.54-.16.7l-8.5 5.5c-.08.04-.17.07-.27.07zm8.93 1.75c-.1.16-.26.25-.43.25-.08 0-.17-.02-.25-.07l-3.5-2c-.24-.13-.32-.44-.18-.68s.44-.32.68-.18l3.5 2c.24.13.32.44.18.68z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 597 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M18.77 7.46H14.5v-1.9c0-.9.6-1.1 1-1.1h3V.5h-4.33C10.24.5 9.5 3.44 9.5 5.32v2.15h-3v4h3v12h5v-12h3.85l.42-4z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 205 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 140">
<path fill-rule="evenodd" d="M60.94 82.314L17 0h20.08l25.85 52.093c.397.927.86 1.888 1.39 2.883.53.994.995 2.02 1.393 3.08.265.4.463.764.596 1.095.13.334.262.63.395.898.662 1.325 1.26 2.618 1.79 3.877.53 1.26.993 2.42 1.39 3.48 1.06-2.254 2.22-4.673 3.48-7.258 1.26-2.585 2.552-5.27 3.877-8.052L103.49 0h18.69L77.84 83.308v53.087h-16.9v-54.08z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 433 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M6.5 21.5h-5v-13h5v13zM4 6.5C2.5 6.5 1.5 5.3 1.5 4s1-2.4 2.5-2.4c1.6 0 2.5 1 2.6 2.5 0 1.4-1 2.5-2.6 2.5zm11.5 6c-1 0-2 1-2 2v7h-5v-13h5V10s1.6-1.5 4-1.5c3 0 5 2.2 5 6.3v6.7h-5v-7c0-1-1-2-2-2z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M24 11.5c0-1.65-1.35-3-3-3-.96 0-1.86.48-2.42 1.24-1.64-1-3.75-1.64-6.07-1.72.08-1.1.4-3.05 1.52-3.7.72-.4 1.73-.24 3 .5C17.2 6.3 18.46 7.5 20 7.5c1.65 0 3-1.35 3-3s-1.35-3-3-3c-1.38 0-2.54.94-2.88 2.22-1.43-.72-2.64-.8-3.6-.25-1.64.94-1.95 3.47-2 4.55-2.33.08-4.45.7-6.1 1.72C4.86 8.98 3.96 8.5 3 8.5c-1.65 0-3 1.35-3 3 0 1.32.84 2.44 2.05 2.84-.03.22-.05.44-.05.66 0 3.86 4.5 7 10 7s10-3.14 10-7c0-.22-.02-.44-.05-.66 1.2-.4 2.05-1.54 2.05-2.84zM2.3 13.37C1.5 13.07 1 12.35 1 11.5c0-1.1.9-2 2-2 .64 0 1.22.32 1.6.82-1.1.85-1.92 1.9-2.3 3.05zm3.7.13c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2zm9.8 4.8c-1.08.63-2.42.96-3.8.96-1.4 0-2.74-.34-3.8-.95-.24-.13-.32-.44-.2-.68.15-.24.46-.32.7-.18 1.83 1.06 4.76 1.06 6.6 0 .23-.13.53-.05.67.2.14.23.06.54-.18.67zm.2-2.8c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm5.7-2.13c-.38-1.16-1.2-2.2-2.3-3.05.38-.5.97-.82 1.6-.82 1.1 0 2 .9 2 2 0 .84-.53 1.57-1.3 1.87z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1001 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M.707 8.475C.275 8.64 0 9.508 0 9.508s.284.867.718 1.03l5.09 1.897 1.986 6.38a1.102 1.102 0 0 0 1.75.527l2.96-2.41a.405.405 0 0 1 .494-.013l5.34 3.87a1.1 1.1 0 0 0 1.046.135 1.1 1.1 0 0 0 .682-.803l3.91-18.795A1.102 1.102 0 0 0 22.5.075L.706 8.475z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 345 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M23.44 4.83c-.8.37-1.5.38-2.22.02.93-.56.98-.96 1.32-2.02-.88.52-1.86.9-2.9 1.1-.82-.88-2-1.43-3.3-1.43-2.5 0-4.55 2.04-4.55 4.54 0 .36.03.7.1 1.04-3.77-.2-7.12-2-9.36-4.75-.4.67-.6 1.45-.6 2.3 0 1.56.8 2.95 2 3.77-.74-.03-1.44-.23-2.05-.57v.06c0 2.2 1.56 4.03 3.64 4.44-.67.2-1.37.2-2.06.08.58 1.8 2.26 3.12 4.25 3.16C5.78 18.1 3.37 18.74 1 18.46c2 1.3 4.4 2.04 6.97 2.04 8.35 0 12.92-6.92 12.92-12.93 0-.2 0-.4-.02-.6.9-.63 1.96-1.22 2.56-2.14z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 543 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M20.1 3.9C17.9 1.7 15 .5 12 .5 5.8.5.7 5.6.7 11.9c0 2 .5 3.9 1.5 5.6L.6 23.4l6-1.6c1.6.9 3.5 1.3 5.4 1.3 6.3 0 11.4-5.1 11.4-11.4-.1-2.8-1.2-5.7-3.3-7.8zM12 21.4c-1.7 0-3.3-.5-4.8-1.3l-.4-.2-3.5 1 1-3.4L4 17c-1-1.5-1.4-3.2-1.4-5.1 0-5.2 4.2-9.4 9.4-9.4 2.5 0 4.9 1 6.7 2.8 1.8 1.8 2.8 4.2 2.8 6.7-.1 5.2-4.3 9.4-9.5 9.4zm5.1-7.1c-.3-.1-1.7-.9-1.9-1-.3-.1-.5-.1-.7.1-.2.3-.8 1-.9 1.1-.2.2-.3.2-.6.1s-1.2-.5-2.3-1.4c-.9-.8-1.4-1.7-1.6-2-.2-.3 0-.5.1-.6s.3-.3.4-.5c.2-.1.3-.3.4-.5.1-.2 0-.4 0-.5C10 9 9.3 7.6 9 7c-.1-.4-.4-.3-.5-.3h-.6s-.4.1-.7.3c-.3.3-1 1-1 2.4s1 2.8 1.1 3c.1.2 2 3.1 4.9 4.3.7.3 1.2.5 1.6.6.7.2 1.3.2 1.8.1.6-.1 1.7-.7 1.9-1.3.2-.7.2-1.2.2-1.3-.1-.3-.3-.4-.6-.5z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 775 B

View File

@ -48,6 +48,7 @@ export default defineConfig(({ mode }) => {
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@poc': resolve(__dirname, './vuetify-poc/src'),
'stream': 'stream-browserify',
'util': 'util/',
},
@ -60,9 +61,6 @@ export default defineConfig(({ mode }) => {
output: {
experimentalMinChunkSize: 50*1024,
},
external: [
/vuetify-poc/,
],
},
chunkSizeWarningLimit: 3000,
},

View File

@ -1,3 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.20117 14.1995L1.20192 13.8995C1.21761 11.4028 1.48327 10.1109 2.17965 8.79708C2.51549 8.16345 2.9345 7.59632 3.4308 7.10432C3.8117 6.72672 4.42659 6.72939 4.80419 7.11029C5.18179 7.49119 5.17912 8.10608 4.79822 8.48368C4.44227 8.83655 4.14045 9.24506 3.89577 9.70668C3.35305 10.7306 3.14982 11.7445 3.1436 14.0311L3.14345 14.1966L3.14471 14.4135C3.16287 16.4729 3.35598 17.4639 3.83047 18.4033L3.89577 18.5293C4.3707 19.4253 5.06134 20.1222 5.94808 20.6007L6.04599 20.6525C7.02552 21.1596 8.03348 21.3511 10.2219 21.3573L13.7123 21.3574L14.0314 21.3551C15.9637 21.3315 16.9252 21.1423 17.8232 20.6909L17.9193 20.6416L17.996 20.6007C18.8827 20.1222 19.5733 19.4253 20.0483 18.5293C20.591 17.5054 20.7942 16.4915 20.8004 14.2049L20.8006 14.0394L20.7993 13.8225C20.7812 11.7631 20.5881 10.7721 20.1136 9.83268L20.0483 9.70668C19.8381 9.31013 19.5857 8.95278 19.2932 8.63627C18.9291 8.24241 18.9533 7.62799 19.3471 7.26393C19.741 6.89987 20.3554 6.92403 20.7195 7.31789C21.0976 7.72702 21.426 8.18297 21.7036 8.68499L21.7668 8.80165L21.8122 8.8884C22.4509 10.1257 22.7101 11.3783 22.7401 13.6775L22.7429 14.0365L22.7421 14.3365C22.7264 16.8332 22.4608 18.1251 21.7644 19.4389C21.1272 20.6411 20.194 21.5975 19.0103 22.2595L18.9137 22.3125L18.8277 22.3583L18.7026 22.4227C17.4921 23.0324 16.2328 23.2761 13.9546 23.2982L13.7152 23.2997H10.3324L10.089 23.2989C7.61354 23.2828 6.33012 23.0139 5.02571 22.31C3.83296 21.6664 2.88514 20.7248 2.22974 19.5316L2.17724 19.4343L2.13187 19.3476C1.46062 18.0474 1.20855 16.7302 1.20117 14.1995ZM7.55025 5.75795L7.57732 5.72979L11.323 1.98415C11.693 1.61414 12.2873 1.60512 12.6682 1.95707L12.6964 1.98415L16.442 5.72979C16.8213 6.10904 16.8213 6.72394 16.442 7.10319C16.072 7.4732 15.4777 7.48222 15.0968 7.13027L15.0686 7.10319L12.9484 4.98288V15.955C12.9484 16.4914 12.5136 16.9262 11.9772 16.9262C11.454 16.9262 11.0273 16.5123 11.0068 15.9941L11.0061 15.955V5.0475L8.95072 7.10319C8.58072 7.4732 7.98643 7.48222 7.60548 7.13027L7.57732 7.10319C7.20732 6.73319 7.19829 6.1389 7.55025 5.75795Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -98,7 +98,7 @@
</v-tooltip>
</v-btn>
<v-btn icon size="small" color="white">
<img src="@poc/assets/icon-share.svg" width="22" alt="Share">
<icon-share size="22" />
<v-tooltip
activator="parent"
location="bottom"
@ -172,6 +172,8 @@ import zipIcon from '@poc/assets/icon-zip-tonal.svg';
import spreadsheetIcon from '@poc/assets/icon-spreadsheet-tonal.svg';
import fileIcon from '@poc/assets/icon-file-tonal.svg';
import IconShare from '@poc/components/icons/IconShare.vue';
const search = ref<string>('');
const selected = ref([]);
const previewDialog = ref<boolean>(false);

View File

@ -77,6 +77,15 @@
/>
</template>
<v-list class="pa-0">
<v-list-item link @click="() => showShareBucketDialog(item.raw.name)">
<template #prepend>
<icon-share size="18" />
</template>
<v-list-item-title class="text-body-2 ml-3">
Share bucket
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item link @click="() => showDeleteBucketDialog(item.raw.name)">
<template #prepend>
<icon-trash />
@ -91,13 +100,14 @@
</v-data-table-server>
</v-card>
<delete-bucket-dialog v-model="isDeleteBucketDialogShown" :bucket-name="bucketToDelete" />
<enter-bucket-passphrase-dialog v-model="isBucketPassphraseDialogOpen" @passphraseEntered="() => openBucket(selectedBucketName)" />
<enter-bucket-passphrase-dialog v-model="isBucketPassphraseDialogOpen" @passphraseEntered="passphraseDialogCallback" />
<share-dialog v-model="isShareBucketDialogShown" :bucket-name="shareBucketName" />
</template>
<script setup lang="ts">
import { ref, watch, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import { VCard, VTextField, VBtn, VMenu, VList, VListItem, VListItemTitle } from 'vuetify/components';
import { VCard, VTextField, VBtn, VMenu, VList, VListItem, VListItemTitle, VDivider } from 'vuetify/components';
import { VDataTableServer } from 'vuetify/labs/components';
import { BucketPage, BucketCursor } from '@/types/buckets';
@ -112,8 +122,10 @@ import { EdgeCredentials } from '@/types/accessGrants';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import IconTrash from '@poc/components/icons/IconTrash.vue';
import IconShare from '@poc/components/icons/IconShare.vue';
import DeleteBucketDialog from '@poc/components/dialogs/DeleteBucketDialog.vue';
import EnterBucketPassphraseDialog from '@poc/components/dialogs/EnterBucketPassphraseDialog.vue';
import ShareDialog from '@poc/components/dialogs/ShareDialog.vue';
const analyticsStore = useAnalyticsStore();
const bucketsStore = useBucketsStore();
@ -126,9 +138,14 @@ const areBucketsFetching = ref<boolean>(true);
const search = ref<string>('');
const searchTimer = ref<NodeJS.Timeout>();
const selected = ref([]);
const shareBucketName = ref<string>('');
const isDeleteBucketDialogShown = ref<boolean>(false);
const bucketToDelete = ref<string>('');
const isBucketPassphraseDialogOpen = ref(false);
const isShareBucketDialogShown = ref<boolean>(false);
let passphraseDialogCallback: () => void = () => {};
const headers = [
{
@ -228,6 +245,7 @@ async function openBucket(bucketName: string): Promise<void> {
router.push(`/projects/${projectsStore.state.selectedProject.id}/buckets/${bucketsStore.state.fileComponentBucketName}`);
return;
}
passphraseDialogCallback = () => openBucket(selectedBucketName.value);
isBucketPassphraseDialogOpen.value = true;
}
@ -235,7 +253,7 @@ async function openBucket(bucketName: string): Promise<void> {
* Displays the Delete Bucket dialog.
*/
function showDeleteBucketDialog(bucketName: string): void {
bucketToDelete.value = bucketName;
shareBucketName.value = bucketName;
isDeleteBucketDialogShown.value = true;
}
@ -251,6 +269,20 @@ watch(() => search.value, () => {
}, 500); // 500ms delay for every new call.
});
/**
* Displays the Share Bucket dialog.
*/
function showShareBucketDialog(bucketName: string): void {
shareBucketName.value = bucketName;
if (promptForPassphrase.value) {
bucketsStore.setFileComponentBucketName(bucketName);
isBucketPassphraseDialogOpen.value = true;
passphraseDialogCallback = () => isShareBucketDialogShown.value = true;
return;
}
isShareBucketDialogShown.value = true;
}
onMounted(() => {
fetchBuckets();
});

View File

@ -0,0 +1,217 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<v-dialog
v-model="model"
width="410px"
transition="fade-transition"
:persistent="isLoading"
>
<v-card ref="innerContent" rounded="xlg">
<v-card-item class="pl-7 py-4 share-dialog__header">
<template #prepend>
<v-sheet
class="bg-on-surface-variant d-flex justify-center align-center"
width="40"
height="40"
rounded="lg"
>
<icon-share size="18" />
</v-sheet>
</template>
<v-card-title class="font-weight-bold">
Share {{ !filePath ? 'Bucket' : isFolder ? 'Folder' : 'File' }}
</v-card-title>
<template #append>
<v-btn
icon="$close"
variant="text"
size="small"
color="default"
:disabled="isLoading"
@click="model = false"
/>
</template>
</v-card-item>
<v-divider />
<div class="pa-7 share-dialog__content" :class="{ 'share-dialog__content--loading': isLoading }">
<v-row>
<v-col cols="12">
<p class="text-subtitle-2 font-weight-bold mb-4">Share via</p>
<div class="ma-n2">
<v-chip
v-for="opt in ShareOptions"
:key="opt"
:color="SHARE_BUTTON_CONFIGS[opt].color"
:href="SHARE_BUTTON_CONFIGS[opt].getLink(link)"
link
class="ma-2"
>
<component
:is="SHARE_BUTTON_CONFIGS[opt].icon"
class="share-dialog__content__icon"
size="21"
/>
{{ opt }}
</v-chip>
</div>
</v-col>
<v-divider class="my-2" />
<v-col cols="12">
<p class="text-subtitle-2 font-weight-bold mb-2">Copy link</p>
<v-textarea :model-value="link" variant="solo-filled" rows="1" auto-grow no-resize flat readonly />
</v-col>
</v-row>
</div>
<v-divider />
<v-card-actions class="pa-7">
<v-row>
<v-col>
<v-btn variant="outlined" color="default" block :disabled="isLoading" @click="model = false">
Cancel
</v-btn>
</v-col>
<v-col>
<v-btn
:color="justCopied ? 'success' : 'primary'"
variant="flat"
:prepend-icon="justCopied ? 'mdi-check' : 'mdi-content-copy'"
:disabled="isLoading"
block
@click="onCopy"
>
{{ justCopied ? 'Copied' : 'Copy' }}
</v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch, Component } from 'vue';
import {
VDialog,
VCard,
VCardItem,
VSheet,
VCardTitle,
VDivider,
VCardActions,
VRow,
VCol,
VBtn,
VChip,
VTextarea,
} from 'vuetify/components';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useNotify } from '@/utils/hooks';
import { useLinksharing } from '@/composables/useLinksharing';
import { SHARE_BUTTON_CONFIGS, ShareOptions } from '@/types/browser';
import IconShare from '@poc/components/icons/IconShare.vue';
const props = defineProps<{
modelValue: boolean,
bucketName: string;
filePath?: string;
isFolder?: boolean;
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean],
}>();
const model = computed<boolean>({
get: () => props.modelValue,
set: value => emit('update:modelValue', value),
});
const analyticsStore = useAnalyticsStore();
const notify = useNotify();
const { generateBucketShareURL, generateFileOrFolderShareURL } = useLinksharing();
const innerContent = ref<Component | null>(null);
const isLoading = ref<boolean>(true);
const link = ref<string>('');
const copiedTimeout = ref<ReturnType<typeof setTimeout> | null>(null);
const justCopied = computed<boolean>(() => copiedTimeout.value !== null);
/**
* Saves link to clipboard.
*/
function onCopy(): void {
navigator.clipboard.writeText(link.value);
analyticsStore.eventTriggered(AnalyticsEvent.COPY_TO_CLIPBOARD_CLICKED);
if (copiedTimeout.value) clearTimeout(copiedTimeout.value);
copiedTimeout.value = setTimeout(() => {
copiedTimeout.value = null;
}, 750);
}
/**
* Generates linksharing URL when the dialog is opened.
*/
watch(() => innerContent.value, async (comp: Component | null): Promise<void> => {
if (!comp) return;
isLoading.value = true;
link.value = '';
analyticsStore.eventTriggered(AnalyticsEvent.LINK_SHARED);
try {
if (!props.filePath) {
link.value = await generateBucketShareURL(props.bucketName);
} else {
link.value = await generateFileOrFolderShareURL(
props.bucketName,
props.filePath,
props.isFolder,
);
}
} catch (error) {
error.message = `Unable to get sharing URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.SHARE_MODAL);
model.value = false;
return;
}
isLoading.value = false;
});
</script>
<style scoped lang="scss">
.share-dialog {
&__header {
position: relative;
}
&__content {
transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
&--loading {
opacity: 0.3;
transition: opacity 0s;
pointer-events: none;
}
&__icon {
margin-right: 5.5px;
}
}
}
</style>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg :width="size" :height="size" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 11.3478L1.0006 11.1089L1.00255 10.9104C1.02806 9.02016 1.24887 7.94766 1.8301 6.85107C2.11856 6.30683 2.47921 5.81884 2.9065 5.39525C3.09805 5.20536 3.40727 5.2067 3.59716 5.39825C3.78705 5.5898 3.7857 5.89902 3.59416 6.08891C3.23741 6.44256 2.93571 6.8508 2.69311 7.30849C2.19298 8.25209 2.00189 9.18053 1.97909 10.9293L1.97733 11.1132L1.97674 11.3476L1.97788 11.535C1.9943 13.2869 2.17173 14.231 2.63211 15.143L2.6566 15.191L2.68949 15.2539L2.72923 15.3276C3.20185 16.188 3.88274 16.8645 4.74262 17.3286C5.69396 17.8419 6.6299 18.0329 8.42017 18.0507L8.51354 18.0515L8.70427 18.0521L11.3745 18.0521L11.562 18.0509C13.243 18.0346 14.1739 17.8665 15.0477 17.4368L15.0953 17.4131L15.1881 17.3654L15.2505 17.3323L15.3236 17.2921C16.1764 16.8152 16.8471 16.1283 17.3069 15.2608C17.807 14.3172 17.9981 13.3887 18.0209 11.6399L18.0227 11.456L18.0233 11.2241L18.0211 10.9429L18.0181 10.7617C17.9859 9.18703 17.8059 8.29438 17.3684 7.42735L17.3434 7.37829L17.3108 7.31592L17.263 7.22755C17.0626 6.86518 16.8262 6.53681 16.554 6.24235C16.3709 6.04428 16.3831 5.7353 16.5812 5.55222C16.7792 5.36914 17.0882 5.38129 17.2713 5.57936C17.5739 5.90671 17.839 6.26837 18.0677 6.66622L18.1198 6.75867L18.173 6.85705L18.2101 6.92791C18.7529 7.97927 18.966 9.03409 18.9963 10.8355L18.9978 10.9327L19 11.2214L18.9994 11.4603L18.9975 11.6588C18.9719 13.5491 18.7511 14.6216 18.1699 15.7182C17.6362 16.7251 16.8586 17.5334 15.8752 18.102L15.7971 18.1464L15.7152 18.1914L15.6412 18.2307L15.5385 18.2835C14.5129 18.8001 13.4582 19.0024 11.6681 19.0265L11.5698 19.0276L11.3776 19.0288H8.70271L8.5088 19.0282L8.3118 19.0262C6.43571 19.0002 5.36874 18.7763 4.27877 18.1881C3.27953 17.6489 2.47826 16.8644 1.91534 15.8733L1.8714 15.7947L1.82695 15.7122L1.78989 15.6413L1.73422 15.5311C1.21659 14.4834 1.01922 13.4053 1.00124 11.5464L1 11.3478ZM6.16539 5.11581L6.18383 5.09635L9.63714 1.64304C9.8215 1.45868 10.1166 1.45253 10.3083 1.6246L10.3278 1.64304L13.7811 5.09635C13.9718 5.28708 13.9718 5.59629 13.7811 5.78702C13.5968 5.97138 13.3017 5.97753 13.1099 5.80545L13.0905 5.78702L10.4707 3.1673L10.4708 12.6975C10.4708 12.9672 10.2522 13.1859 9.98247 13.1859C9.72174 13.1859 9.50873 12.9816 9.49482 12.7243L9.4941 12.6975L9.494 3.1673L6.87449 5.78702C6.69013 5.97138 6.39503 5.97753 6.20329 5.80545L6.18383 5.78702C5.99946 5.60265 5.99332 5.30756 6.16539 5.11581Z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 18,
});
</script>

View File

@ -0,0 +1,17 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 24 24">
<path d="M22 4H2C.9 4 0 4.9 0 6v12c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM7.25 14.43l-3.5 2c-.08.05-.17.07-.25.07-.17 0-.34-.1-.43-.25-.14-.24-.06-.55.18-.68l3.5-2c.24-.14.55-.06.68.18.14.24.06.55-.18.68zm4.75.07c-.1 0-.2-.03-.27-.08l-8.5-5.5c-.23-.15-.3-.46-.15-.7.15-.22.46-.3.7-.14L12 13.4l8.23-5.32c.23-.15.54-.08.7.15.14.23.07.54-.16.7l-8.5 5.5c-.08.04-.17.07-.27.07zm8.93 1.75c-.1.16-.26.25-.43.25-.08 0-.17-.02-.25-.07l-3.5-2c-.24-.13-.32-.44-.18-.68s.44-.32.68-.18l3.5 2c.24.13.32.44.18.68z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 24 24">
<path d="M18.77 7.46H14.5v-1.9c0-.9.6-1.1 1-1.1h3V.5h-4.33C10.24.5 9.5 3.44 9.5 5.32v2.15h-3v4h3v12h5v-12h3.85l.42-4z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 140 140">
<path fill-rule="evenodd" d="M60.94 82.314L17 0h20.08l25.85 52.093c.397.927.86 1.888 1.39 2.883.53.994.995 2.02 1.393 3.08.265.4.463.764.596 1.095.13.334.262.63.395.898.662 1.325 1.26 2.618 1.79 3.877.53 1.26.993 2.42 1.39 3.48 1.06-2.254 2.22-4.673 3.48-7.258 1.26-2.585 2.552-5.27 3.877-8.052L103.49 0h18.69L77.84 83.308v53.087h-16.9v-54.08z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 24 24">
<path d="M6.5 21.5h-5v-13h5v13zM4 6.5C2.5 6.5 1.5 5.3 1.5 4s1-2.4 2.5-2.4c1.6 0 2.5 1 2.6 2.5 0 1.4-1 2.5-2.6 2.5zm11.5 6c-1 0-2 1-2 2v7h-5v-13h5V10s1.6-1.5 4-1.5c3 0 5 2.2 5 6.3v6.7h-5v-7c0-1-1-2-2-2z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 24 24">
<path d="M24 11.5c0-1.65-1.35-3-3-3-.96 0-1.86.48-2.42 1.24-1.64-1-3.75-1.64-6.07-1.72.08-1.1.4-3.05 1.52-3.7.72-.4 1.73-.24 3 .5C17.2 6.3 18.46 7.5 20 7.5c1.65 0 3-1.35 3-3s-1.35-3-3-3c-1.38 0-2.54.94-2.88 2.22-1.43-.72-2.64-.8-3.6-.25-1.64.94-1.95 3.47-2 4.55-2.33.08-4.45.7-6.1 1.72C4.86 8.98 3.96 8.5 3 8.5c-1.65 0-3 1.35-3 3 0 1.32.84 2.44 2.05 2.84-.03.22-.05.44-.05.66 0 3.86 4.5 7 10 7s10-3.14 10-7c0-.22-.02-.44-.05-.66 1.2-.4 2.05-1.54 2.05-2.84zM2.3 13.37C1.5 13.07 1 12.35 1 11.5c0-1.1.9-2 2-2 .64 0 1.22.32 1.6.82-1.1.85-1.92 1.9-2.3 3.05zm3.7.13c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2zm9.8 4.8c-1.08.63-2.42.96-3.8.96-1.4 0-2.74-.34-3.8-.95-.24-.13-.32-.44-.2-.68.15-.24.46-.32.7-.18 1.83 1.06 4.76 1.06 6.6 0 .23-.13.53-.05.67.2.14.23.06.54-.18.67zm.2-2.8c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm5.7-2.13c-.38-1.16-1.2-2.2-2.3-3.05.38-.5.97-.82 1.6-.82 1.1 0 2 .9 2 2 0 .84-.53 1.57-1.3 1.87z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 24 24">
<path d="M.707 8.475C.275 8.64 0 9.508 0 9.508s.284.867.718 1.03l5.09 1.897 1.986 6.38a1.102 1.102 0 0 0 1.75.527l2.96-2.41a.405.405 0 0 1 .494-.013l5.34 3.87a1.1 1.1 0 0 0 1.046.135 1.1 1.1 0 0 0 .682-.803l3.91-18.795A1.102 1.102 0 0 0 22.5.075L.706 8.475z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 24 24">
<path d="M23.44 4.83c-.8.37-1.5.38-2.22.02.93-.56.98-.96 1.32-2.02-.88.52-1.86.9-2.9 1.1-.82-.88-2-1.43-3.3-1.43-2.5 0-4.55 2.04-4.55 4.54 0 .36.03.7.1 1.04-3.77-.2-7.12-2-9.36-4.75-.4.67-.6 1.45-.6 2.3 0 1.56.8 2.95 2 3.77-.74-.03-1.44-.23-2.05-.57v.06c0 2.2 1.56 4.03 3.64 4.44-.67.2-1.37.2-2.06.08.58 1.8 2.26 3.12 4.25 3.16C5.78 18.1 3.37 18.74 1 18.46c2 1.3 4.4 2.04 6.97 2.04 8.35 0 12.92-6.92 12.92-12.93 0-.2 0-.4-.02-.6.9-.63 1.96-1.22 2.56-2.14z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>

View File

@ -0,0 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size" viewBox="0 0 24 24">
<path d="M20.1 3.9C17.9 1.7 15 .5 12 .5 5.8.5.7 5.6.7 11.9c0 2 .5 3.9 1.5 5.6L.6 23.4l6-1.6c1.6.9 3.5 1.3 5.4 1.3 6.3 0 11.4-5.1 11.4-11.4-.1-2.8-1.2-5.7-3.3-7.8zM12 21.4c-1.7 0-3.3-.5-4.8-1.3l-.4-.2-3.5 1 1-3.4L4 17c-1-1.5-1.4-3.2-1.4-5.1 0-5.2 4.2-9.4 9.4-9.4 2.5 0 4.9 1 6.7 2.8 1.8 1.8 2.8 4.2 2.8 6.7-.1 5.2-4.3 9.4-9.5 9.4zm5.1-7.1c-.3-.1-1.7-.9-1.9-1-.3-.1-.5-.1-.7.1-.2.3-.8 1-.9 1.1-.2.2-.3.2-.6.1s-1.2-.5-2.3-1.4c-.9-.8-1.4-1.7-1.6-2-.2-.3 0-.5.1-.6s.3-.3.4-.5c.2-.1.3-.3.4-.5.1-.2 0-.4 0-.5C10 9 9.3 7.6 9 7c-.1-.4-.4-.3-.5-.3h-.6s-.4.1-.7.3c-.3.3-1 1-1 2.4s1 2.8 1.1 3c.1.2 2 3.1 4.9 4.3.7.3 1.2.5 1.6.6.7.2 1.3.2 1.8.1.6-.1 1.7-.7 1.9-1.3.2-.7.2-1.2.2-1.3-.1-.3-.3-.4-.6-.5z" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
size: number | string;
}>(), {
size: 24,
});
</script>