From 25fb445fb96ecbf57bf012ce4d7566e2ebb0421a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 17:00:20 +0800 Subject: [PATCH 1/7] Storage preview pane support getting signed url of files from private buckets with custom expiry --- .../Storage/StorageExplorer/PreviewPane.tsx | 263 ++++++++++++------ .../storageExplorer/StorageExplorerStore.js | 12 +- 2 files changed, 190 insertions(+), 85 deletions(-) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 49066467eae..855bd029827 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -1,12 +1,18 @@ import { isEmpty } from 'lodash' +import { useState } from 'react' import { Button, + Dropdown, + Form, IconX, IconLoader, IconClipboard, IconDownload, IconTrash2, IconAlertCircle, + IconChevronDown, + Modal, + Input, } from 'ui' import SVG from 'react-inlinesvg' import { formatBytes } from 'lib/helpers' @@ -90,12 +96,15 @@ const PreviewFile = ({ mimeType, previewUrl }: { mimeType: string; previewUrl: s const PreviewPane = () => { const storageExplorerStore = useStorageStore() const { + selectedBucket, selectedFilePreview: file, copyFileURLToClipboard, closeFilePreview, setSelectedItemsToDelete, } = storageExplorerStore + const [showExpiryInput, setShowExpiryInput] = useState(false) + const width = 450 const isOpen = !isEmpty(file) const size = file.metadata ? formatBytes(file.metadata.size) : null @@ -104,104 +113,200 @@ const PreviewPane = () => { const updatedAt = file.updated_at ? new Date(file.updated_at).toLocaleString() : 'Unknown' return ( - -
+
- {/* Preview Header */} -
- closeFilePreview()} - /> -
- - {/* Preview Thumbnail*/} -
-
- + style={{ width }} + > + {/* Preview Header */} +
+ closeFilePreview()} + />
-
-
- {/* Preview Information */} -
-
{file.name}
- {file.isCorrupted && ( -
- + {/* Preview Thumbnail*/} +
+
+ +
+
+ +
+ {/* Preview Information */} +
+
{file.name}
+ {file.isCorrupted && ( +
+ +

+ File is corrupted, please delete and reupload this file again +

+
+ )} + {mimeType && (

- File is corrupted, please delete and reupload this file again + {mimeType} + {size && - {size}}

+ )} +
+ + {/* Preview Metadata */} +
+
+ +

{createdAt}

+
+
+ +

{updatedAt}

- )} - {mimeType && ( -

- {mimeType} - {size && - {size}} -

- )} -
- - {/* Preview Metadata */} -
-
- -

{createdAt}

-
- -

{updatedAt}

-
-
- {/* Actions */} -
- - + {/* Actions */} +
+ + + + + + {selectedBucket.public ? ( - - + ) : ( + await copyFileURLToClipboard(file, 60 * 60 * 24 * 7)} + > + Expire in 1 week + , + await copyFileURLToClipboard(file, 60 * 60 * 24 * 30)} + > + Expire in 1 month + , + await copyFileURLToClipboard(file, 60 * 60 * 24 * 365)} + > + Expire in 1 year + , + setShowExpiryInput(true)}> + Custom expiry + , + ]} + > + + + )} +
-
-
- + + setShowExpiryInput(false)} + > +
{ + setSubmitting(true) + await copyFileURLToClipboard(file, values.expiresIn) + setSubmitting(false) + setShowExpiryInput(false) + }} + validate={(values: any) => { + const errors: any = {} + if (values.expiresIn !== '' && values.expiresIn <= 0) + errors.expiresIn = 'Expiry duration cannot be less than 0' + return errors + }} + > + {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => ( + <> +
+ + seconds

} + /> +
+
+ + +
+ + +
+
+ + )} + +
+ ) } diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 5e11d3c234c..323a51d10ca 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -37,7 +37,7 @@ export const useStorageExplorerStore = () => { const CORRUPTED_THRESHOLD_MS = 15 * 60 * 1000 // 15 minutes const LIMIT = 200 const OFFSET = 0 -const DEFAULT_EXPIRY = 10 * 365 * 24 * 60 * 60 // in seconds, default to 1 year +const DEFAULT_EXPIRY = 10 * 365 * 24 * 60 * 60 // in seconds, default to 10 years const PREVIEW_SIZE_LIMIT = 10000000 // 10MB const BATCH_SIZE = 2 const EMPTY_FOLDER_PLACEHOLDER_FILE_NAME = '.emptyFolderPlaceholder' @@ -397,9 +397,9 @@ class StorageExplorerStore { this.selectedFilePreview = {} } - copyFileURLToClipboard = async (file) => { + copyFileURLToClipboard = async (file, expiresIn = 0) => { const filePreview = find(this.filePreviewCache, { id: file.id }) - if (filePreview) { + if (filePreview && expiresIn === 0) { // Already generated signed URL copyToClipboard(filePreview.url, () => { this.ui.setNotification({ @@ -410,7 +410,7 @@ class StorageExplorerStore { }) } else { // Need to generate signed URL, and might as well save it to cache as well - const signedUrl = await this.fetchFilePreview(file.name) + const signedUrl = await this.fetchFilePreview(file.name, expiresIn) try { let formattedUrl = new URL(signedUrl) @@ -840,7 +840,7 @@ class StorageExplorerStore { this.clearSelectedItemsToMove() } - fetchFilePreview = async (fileName) => { + fetchFilePreview = async (fileName, expiresIn = 0) => { const includeBucket = false const pathToFile = this.getPathAlongOpenedFolders(includeBucket) const formattedPathToFile = pathToFile.length > 0 ? `${pathToFile}/${fileName}` : fileName @@ -857,7 +857,7 @@ class StorageExplorerStore { const { data, error } = await this.supabaseClient.storage .from(this.selectedBucket.name) - .createSignedUrl(formattedPathToFile, DEFAULT_EXPIRY) + .createSignedUrl(formattedPathToFile, expiresIn || DEFAULT_EXPIRY) if (!error) { return data.signedUrl From 210f7b7e1891909d9627bfd7eaf0227c8ad852e9 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 17:04:39 +0800 Subject: [PATCH 2/7] Ensure that payment summary panel is loading when confirming payment --- .../interfaces/Billing/EnterpriseUpdate.tsx | 12 ------------ studio/components/interfaces/Billing/ProUpgrade.tsx | 1 + studio/components/interfaces/Billing/TeamUpgrade.tsx | 1 + 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 969b5d58993..2c234c62fae 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -158,18 +158,6 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { setIsSubmitting(true) - let token = captchaToken - - try { - if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null - } - } catch (error) { - setIsSubmitting(false) - return - } - const payload = { ...formSubscriptionUpdatePayload( currentSubscription, diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index b4cf2635051..81d991fe5a0 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -177,6 +177,7 @@ const ProUpgrade: FC = ({ } const onConfirmPayment = async () => { + setIsSubmitting(true) const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, diff --git a/studio/components/interfaces/Billing/TeamUpgrade.tsx b/studio/components/interfaces/Billing/TeamUpgrade.tsx index 7cc18e7847a..fcfef76cd0d 100644 --- a/studio/components/interfaces/Billing/TeamUpgrade.tsx +++ b/studio/components/interfaces/Billing/TeamUpgrade.tsx @@ -169,6 +169,7 @@ const TeamUpgrade: FC = ({ message: 'Team Plan is not enabled yet.', }) } + setIsSubmitting(true) const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, From c68d82eff3ab9b086bec65c74f191233be6db531 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 16 Jan 2023 09:20:07 -0330 Subject: [PATCH 3/7] Update studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx --- .../to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 855bd029827..a3abf1d1ec1 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -281,7 +281,7 @@ const PreviewPane = () => { disabled={isSubmitting} type="number" id="expiresIn" - label="Enter the duration for which the URL will be valid for" + label="Enter the duration for which the URL will be valid." actions={

seconds

} /> From 4d1d95be2234058197e6165aa7fc132711f36f70 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 17 Jan 2023 00:17:44 +0800 Subject: [PATCH 4/7] Refactor FileExplorerRow to tsx and reduce prop drilldown --- .../Storage/StorageExplorer/FileExplorer.js | 59 +++------ .../StorageExplorer/FileExplorerColumn.js | 31 ++--- ...FileExplorerRow.js => FileExplorerRow.tsx} | 124 +++++++++++------- .../Storage/StorageExplorer/PreviewPane.tsx | 6 +- .../StorageExplorer/StorageExplorer.js | 81 +----------- 5 files changed, 110 insertions(+), 191 deletions(-) rename studio/components/to-be-cleaned/Storage/StorageExplorer/{FileExplorerRow.js => FileExplorerRow.tsx} (78%) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js index c76f2d2a2e4..21b30091755 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js @@ -1,10 +1,11 @@ import React, { useEffect, useRef } from 'react' -import { STORAGE_VIEWS, CONTEXT_MENU_KEYS } from '../Storage.constants' +import { STORAGE_VIEWS, CONTEXT_MENU_KEYS } from '../Storage.constants' import ItemContextMenu from './ItemContextMenu' import FolderContextMenu from './FolderContextMenu' import ColumnContextMenu from './ColumnContextMenu' import FileExplorerColumn from './FileExplorerColumn' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' const FileExplorer = ({ view = STORAGE_VIEWS.COLUMNS, @@ -12,18 +13,7 @@ const FileExplorer = ({ openedFolders = [], selectedItems = [], selectedFilePreview = {}, - onCheckItem = () => {}, - onSelectItemDelete = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, - onSelectFile = () => {}, - onRenameFile = () => {}, onFilesUpload = () => {}, - onCopyFileURL = () => {}, - onDownloadFile = () => {}, - onSelectFolder = () => {}, - onRenameFolder = () => {}, - onCreateFolder = () => {}, onSelectAllItemsInColumn = () => {}, onSelectColumnEmptySpace = () => {}, onSelectCreateFolder = () => {}, @@ -33,6 +23,15 @@ const FileExplorer = ({ onColumnLoadMore = () => {}, }) => { const fileExplorerRef = useRef(null) + const storageExplorerStore = useStorageStore() + + const { + downloadFile, + setSelectedItemsToDelete, + setSelectedItemToRename, + setSelectedItemsToMove, + copyFileURLToClipboard, + } = storageExplorerStore useEffect(() => { if (fileExplorerRef) { @@ -58,16 +57,16 @@ const FileExplorer = ({ /> setSelectedItemToRename(file)} + onSelectItemMove={(file) => setSelectedItemsToMove([file])} + onSelectItemDelete={(file) => setSelectedItemsToDelete([file])} + onCopyFileURL={async (file) => await copyFileURLToClipboard(file)} + onDownloadFile={async (file) => await downloadFile(file)} /> setSelectedItemToRename(folder)} + onDeleteFolder={(folder) => setSelectedItemsToDelete([folder])} /> {view === STORAGE_VIEWS.COLUMNS ? (
@@ -80,18 +79,7 @@ const FileExplorer = ({ openedFolders={openedFolders} selectedItems={selectedItems} selectedFilePreview={selectedFilePreview} - onCheckItem={onCheckItem} - onSelectFile={onSelectFile} - onRenameFile={onRenameFile} - onCopyFileURL={onCopyFileURL} onFilesUpload={onFilesUpload} - onDownloadFile={onDownloadFile} - onSelectFolder={onSelectFolder} - onRenameFolder={onRenameFolder} - onCreateFolder={onCreateFolder} - onSelectItemDelete={onSelectItemDelete} - onSelectItemRename={onSelectItemRename} - onSelectItemMove={onSelectItemMove} onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onColumnLoadMore={onColumnLoadMore} @@ -108,18 +96,7 @@ const FileExplorer = ({ column={columns[columns.length - 1]} selectedItems={selectedItems} selectedFilePreview={selectedFilePreview} - onCheckItem={onCheckItem} - onSelectFile={onSelectFile} - onRenameFile={onRenameFile} - onCopyFileURL={onCopyFileURL} onFilesUpload={onFilesUpload} - onDownloadFile={onDownloadFile} - onSelectFolder={onSelectFolder} - onRenameFolder={onRenameFolder} - onCreateFolder={onCreateFolder} - onSelectItemDelete={onSelectItemDelete} - onSelectItemRename={onSelectItemRename} - onSelectItemMove={onSelectItemMove} onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onColumnLoadMore={onColumnLoadMore} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js index bc418c2f92a..db2a5886e1b 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js @@ -57,18 +57,7 @@ const FileExplorerColumn = ({ openedFolders = [], selectedItems = [], selectedFilePreview = {}, - onCheckItem = () => {}, - onSelectItemDelete = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, - onSelectFile = () => {}, - onRenameFile = () => {}, - onCopyFileURL = () => {}, onFilesUpload = () => {}, - onDownloadFile = () => {}, - onSelectFolder = () => {}, - onRenameFolder = () => {}, - onCreateFolder = () => {}, onSelectAllItemsInColumn = () => {}, onSelectColumnEmptySpace = () => {}, onColumnLoadMore = () => {}, @@ -212,17 +201,15 @@ const FileExplorerColumn = ({ selectedItems, openedFolders, selectedFilePreview, - onCheckItem, - onSelectFile, - onRenameFile, - onCopyFileURL, - onDownloadFile, - onSelectFolder, - onRenameFolder, - onCreateFolder, - onSelectItemDelete, - onSelectItemRename, - onSelectItemMove, + // onCheckItem, + // onRenameFile, + // onCopyFileURL, + // onDownloadFile, + // onRenameFolder, + // onCreateFolder, + // onSelectItemDelete, + // onSelectItemRename, + // onSelectItemMove, }} ItemComponent={FileExplorerRow} getItemSize={(index) => (index !== 0 && index === columnItems.length ? 85 : 37)} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx similarity index 78% rename from studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js rename to studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx index 6712cc90e09..fcb43622051 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react' +import { FC, useState, useRef, useEffect } from 'react' import { find, has, isEmpty, isEqual } from 'lodash' import { Checkbox, @@ -11,10 +11,10 @@ import { IconFile, IconAlertCircle, IconDownload, - IconTrash, - IconCopy, IconEdit, IconMove, + IconClipboard, + IconTrash2, } from 'ui' import SVG from 'react-inlinesvg' import * as Tooltip from '@radix-ui/react-tooltip' @@ -24,11 +24,11 @@ import { STORAGE_ROW_TYPES, STORAGE_ROW_STATUS, CONTEXT_MENU_KEYS, -} from '../Storage.constants.ts' +} from '../Storage.constants' import { formatBytes } from 'lib/helpers' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -const RowIcon = ({ view, status, fileType, mimeType }) => { +const RowIcon = ({ view, status, fileType, mimeType }: any) => { if (view === STORAGE_VIEWS.LIST && status === STORAGE_ROW_STATUS.LOADING) { return } @@ -43,7 +43,6 @@ const RowIcon = ({ view, status, fileType, mimeType }) => { return ( code.replace(/svg/, 'svg class="w-4 h-4 text-color-inherit opacity-75"') } @@ -66,28 +65,43 @@ const RowIcon = ({ view, status, fileType, mimeType }) => { return } -const FileExplorerRow = ({ +interface Props { + item: any + view: string + columnIndex: number + selectedItems: any[] + openedFolders: any[] + selectedFilePreview: any +} + +const FileExplorerRow: FC = ({ item = {}, view = STORAGE_VIEWS.COLUMNS, columnIndex = 0, selectedItems = [], openedFolders = [], selectedFilePreview = {}, - - onCheckItem = () => {}, - onSelectFile = () => {}, - onRenameFile = () => {}, - onCopyFileURL = () => {}, - onDownloadFile = () => {}, - onSelectFolder = () => {}, - onRenameFolder = () => {}, - onCreateFolder = () => {}, - onSelectItemDelete = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, }) => { const storageExplorerStore = useStorageStore() - const { downloadFolder } = storageExplorerStore + const { + popColumnAtIndex, + pushOpenedFolderAtIndex, + popOpenedFoldersAtIndex, + setFilePreview, + closeFilePreview, + clearSelectedItems, + addNewFolder, + renameFolder, + renameFile, + setSelectedItems, + setSelectedItemsToDelete, + setSelectedItemToRename, + setSelectedItemsToMove, + fetchFolderContents, + downloadFile, + downloadFolder, + copyFileURLToClipboard, + } = storageExplorerStore const itemWithColumnIndex = { ...item, columnIndex } const isSelected = find(selectedItems, item) !== undefined @@ -95,25 +109,47 @@ const FileExplorerRow = ({ openedFolders.length > columnIndex ? isEqual(openedFolders[columnIndex], item) : false const isPreviewed = !isEmpty(selectedFilePreview) && isEqual(selectedFilePreview.id, item.id) + const onSelectFile = async (columnIndex: number, file: any) => { + popColumnAtIndex(columnIndex) + popOpenedFoldersAtIndex(columnIndex - 1) + setFilePreview(file) + clearSelectedItems() + } + + const onSelectFolder = async (columnIndex: number, folder: any) => { + closeFilePreview() + clearSelectedItems(columnIndex + 1) + popOpenedFoldersAtIndex(columnIndex - 1) + pushOpenedFolderAtIndex(folder, columnIndex) + await fetchFolderContents(folder.id, folder.name, columnIndex) + } + + const onCheckItem = (item: any) => { + if (find(selectedItems, item) === undefined) { + setSelectedItems(selectedItems.concat([item])) + } else { + setSelectedItems(selectedItems.filter((selectedItem: any) => item.id !== selectedItem.id)) + } + closeFilePreview() + } + if (item.status === STORAGE_ROW_STATUS.EDITING) { - const inputRef = useRef(null) + const inputRef = useRef(null) const [itemName, setItemName] = useState(item.name) useEffect(() => { - if (inputRef.current) { - inputRef.current.select() - } + if (inputRef.current) inputRef.current.select() }, []) - const onSetItemName = (event) => { + const onSetItemName = async (event: any) => { event.preventDefault() event.stopPropagation() if (item.type === STORAGE_ROW_TYPES.FILE) { - onRenameFile(item, itemName, columnIndex) + await renameFile(item, itemName, columnIndex) } else if (has(item, 'id')) { - onRenameFolder(itemWithColumnIndex, itemName, columnIndex) + renameFolder(itemWithColumnIndex, itemName, columnIndex) } else { - onCreateFolder(itemName, columnIndex) + addNewFolder(itemName, columnIndex) } } @@ -146,57 +182,55 @@ const FileExplorerRow = ({ } const rowOptions = - item.type === STORAGE_ROW_TYPES.BUCKET - ? [{ name: 'Delete', onClick: () => onSelectItemDelete(itemWithColumnIndex) }] - : item.type === STORAGE_ROW_TYPES.FOLDER + item.type === STORAGE_ROW_TYPES.FOLDER ? [ { name: 'Rename', icon: , - onClick: () => onSelectItemRename(itemWithColumnIndex), + onClick: () => setSelectedItemToRename(itemWithColumnIndex), }, { name: 'Download', icon: , onClick: () => downloadFolder(itemWithColumnIndex), }, - { name: 'Separator' }, + { name: 'Separator', icon: undefined, onClick: undefined }, { name: 'Delete', - icon: , - onClick: () => onSelectItemDelete(itemWithColumnIndex), + icon: , + onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), }, ] : [ ...(!item.isCorrupted ? [ { - name: 'Copy URL', - icon: , - onClick: () => onCopyFileURL(itemWithColumnIndex), + name: 'Get URL', + icon: , + onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), }, { name: 'Rename', icon: , - onClick: () => onSelectItemRename(itemWithColumnIndex), + onClick: () => setSelectedItemToRename(itemWithColumnIndex), }, { name: 'Move', icon: , - onClick: () => onSelectItemMove(itemWithColumnIndex), + onClick: () => setSelectedItemsToMove([itemWithColumnIndex]), }, { name: 'Download', icon: , - onClick: () => onDownloadFile(itemWithColumnIndex), + onClick: async () => await downloadFile(itemWithColumnIndex), }, - { name: 'Separator' }, + { name: 'Separator', icon: undefined, onClick: undefined }, ] : []), { name: 'Delete', - icon: , - onClick: () => onSelectItemDelete(itemWithColumnIndex), + icon: , + onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), }, ] @@ -206,7 +240,7 @@ const FileExplorerRow = ({ const updatedAt = item.updated_at ? new Date(item.updated_at).toLocaleString() : '-' const { show } = useContextMenu() - const displayMenu = (event, rowType) => { + const displayMenu = (event: any, rowType: any) => { show(event, { id: rowType === STORAGE_ROW_TYPES.FILE diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 855bd029827..2b64d35abd9 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -198,7 +198,7 @@ const PreviewPane = () => { onClick={async () => await copyFileURLToClipboard(file)} disabled={file.isCorrupted} > - Copy URL + Get URL ) : ( { iconRight={} disabled={file.isCorrupted} > - Get Signed URL + Get URL )} @@ -254,7 +254,7 @@ const PreviewPane = () => { header="Custom expiry for signed URL" visible={showExpiryInput} alignFooter="right" - confirmText="Get signed URL" + confirmText="Get URL" onCancel={() => setShowExpiryInput(false)} >
{ const { columns, selectedFilePreview, - setFilePreview, closeFilePreview, selectedItems, setSelectedItems, clearSelectedItems, selectedItemsToDelete, - setSelectedItemsToDelete, clearSelectedItemsToDelete, openedFolders, - pushOpenedFolderAtIndex, popColumnAtIndex, popOpenedFoldersAtIndex, - setSelectedItemToRename, selectedItemsToMove, - setSelectedItemsToMove, clearSelectedItemsToMove, view, setView, setSortBy, setSortByOrder, currentBucketName, - copyFileURLToClipboard, openBucket, loadExplorerPreferences, addNewFolderPlaceholder, - addNewFolder, fetchFolderContents, fetchMoreFolderContents, - renameFolder, deleteFolder, uploadFiles, deleteFiles, - downloadFile, - renameFile, moveFiles, } = storageExplorerStore @@ -100,35 +90,9 @@ const StorageExplorer = observer(({ bucket }) => { openBucket(bucket) }, [bucket]) - /** Navigation methods */ - - const onSelectFile = async (columnIndex, file) => { - popColumnAtIndex(columnIndex) - popOpenedFoldersAtIndex(columnIndex - 1) - setFilePreview(file) - clearSelectedItems() - } - - const onSelectFolder = async (columnIndex, folder) => { - closeFilePreview() - clearSelectedItems(columnIndex + 1) - popOpenedFoldersAtIndex(columnIndex - 1) - pushOpenedFolderAtIndex(folder, columnIndex) - await fetchFolderContents(folder.id, folder.name, columnIndex) - } - /** Checkbox selection methods */ /** [Joshen] We'll only support checkbox selection for files ONLY */ - const onCheckItem = (item) => { - if (find(selectedItems, item) === undefined) { - setSelectedItems(selectedItems.concat([item])) - } else { - setSelectedItems(selectedItems.filter((selectedItem) => item.id !== selectedItem.id)) - } - closeFilePreview() - } - const onSelectAllItemsInColumn = (columnIndex) => { const columnFiles = columns[columnIndex].items .filter((item) => item.type === STORAGE_ROW_TYPES.FILE) @@ -155,38 +119,6 @@ const StorageExplorer = observer(({ bucket }) => { addNewFolderPlaceholder(columnIndex) } - const onCreateFolder = (folderName, columnIndex) => { - addNewFolder(folderName, columnIndex) - } - - const onRenameFolder = (folder, newName, columnIndex) => { - renameFolder(folder, newName, columnIndex) - } - - const onSelectItemDelete = (file) => { - setSelectedItemsToDelete([file]) - } - - const onSelectItemRename = (file) => { - setSelectedItemToRename(file) - } - - const onSelectItemMove = (file) => { - setSelectedItemsToMove([file]) - } - - const onCopyFileURL = async (file) => { - await copyFileURLToClipboard(file) - } - - const onDownloadFile = async (file) => { - await downloadFile(file) - } - - const onRenameFile = async (file, newName, columnIndex) => { - await renameFile(file, newName, columnIndex) - } - const onFilesUpload = async (event, columnIndex = -1) => { event.persist() const items = event.target.files || event.dataTransfer.items @@ -255,18 +187,7 @@ const StorageExplorer = observer(({ bucket }) => { openedFolders={openedFolders} selectedItems={selectedItems} selectedFilePreview={selectedFilePreview} - onCheckItem={onCheckItem} - onSelectFile={onSelectFile} - onRenameFile={onRenameFile} onFilesUpload={onFilesUpload} - onCopyFileURL={onCopyFileURL} - onDownloadFile={onDownloadFile} - onSelectFolder={onSelectFolder} - onRenameFolder={onRenameFolder} - onCreateFolder={onCreateFolder} - onSelectItemDelete={onSelectItemDelete} - onSelectItemRename={onSelectItemRename} - onSelectItemMove={onSelectItemMove} onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onSelectCreateFolder={onSelectCreateFolder} From 8cc319e8630c6463a249f3657f089fe2aafa6b17 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 17 Jan 2023 13:26:22 +0800 Subject: [PATCH 5/7] More refactors to reduce props drilldown, and implement expiry options in context menu --- .../layouts/StorageLayout/BucketRow.tsx | 73 ++++++++++++ .../layouts/StorageLayout/StorageMenu.tsx | 83 +------------- .../StorageExplorer/CustomExpiryModal.tsx | 80 +++++++++++++ .../Storage/StorageExplorer/FileExplorer.js | 20 +--- .../StorageExplorer/FileExplorerRow.tsx | 6 + .../StorageExplorer/ItemContextMenu.js | 108 +++++++++++++----- .../Storage/StorageExplorer/PreviewPane.tsx | 63 +--------- .../StorageExplorer/StorageExplorer.js | 2 + .../storageExplorer/StorageExplorerStore.js | 5 + studio/styles/contextMenu.scss | 3 + 10 files changed, 258 insertions(+), 185 deletions(-) create mode 100644 studio/components/layouts/StorageLayout/BucketRow.tsx create mode 100644 studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx diff --git a/studio/components/layouts/StorageLayout/BucketRow.tsx b/studio/components/layouts/StorageLayout/BucketRow.tsx new file mode 100644 index 00000000000..f96a5d96b4c --- /dev/null +++ b/studio/components/layouts/StorageLayout/BucketRow.tsx @@ -0,0 +1,73 @@ +import { FC } from 'react' +import { Badge, Dropdown, IconLoader, IconMoreVertical, IconTrash } from 'ui' + +import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' +import { STORAGE_ROW_STATUS } from 'components/to-be-cleaned/Storage/Storage.constants' + +interface Props { + bucket: any + projectRef?: string + isSelected: boolean + onSelectDeleteBucket: (bucket: any) => void + onSelectToggleBucketPublic: (bucket: any) => void +} + +const BucketRow: FC = ({ + bucket = {}, + projectRef = '', + isSelected = false, + onSelectDeleteBucket = () => {}, + onSelectToggleBucketPublic = () => {}, +}) => { + return ( + +

+ {bucket.name} +

+ {bucket.public && Public} +
+ } + url={`/project/${projectRef}/storage/buckets/${bucket.id}`} + isActive={isSelected} + action={ + bucket.status === STORAGE_ROW_STATUS.LOADING ? ( + + ) : bucket.status === STORAGE_ROW_STATUS.READY ? ( + onSelectToggleBucketPublic(bucket)} + > + {bucket.public ? 'Make private' : 'Make public'} + , + , + } + key="delete-bucket" + onClick={() => onSelectDeleteBucket(bucket)} + > + Delete bucket + , + ]} + > + + + ) : ( +
+ ) + } + /> + ) +} + +export default BucketRow diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index 2889292f42f..441e07c40a9 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -2,28 +2,18 @@ import { FC } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { useStore } from 'hooks' -import { - Badge, - Button, - Dropdown, - Menu, - IconLoader, - IconMoreVertical, - Alert, - IconEdit, - IconTrash, -} from 'ui' +import { useParams } from 'hooks' +import { Button, Menu, IconLoader, Alert, IconEdit } from 'ui' -import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' -import { STORAGE_ROW_STATUS } from 'components/to-be-cleaned/Storage/Storage.constants' +import BucketRow from './BucketRow' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' interface Props {} const StorageMenu: FC = () => { const router = useRouter() - const { ref, bucketId } = router.query + const { ref, bucketId } = useParams() + const page = router.pathname.split('/')[4] as | undefined | 'policies' @@ -31,9 +21,6 @@ const StorageMenu: FC = () => { | 'usage' | 'logs' - const { ui } = useStore() - const projectRef = ui.selectedProject?.ref - const storageExplorerStore = useStorageStore() const { loaded, @@ -98,7 +85,7 @@ const StorageMenu: FC = () => {
- +

Policies

@@ -110,61 +97,3 @@ const StorageMenu: FC = () => { } export default observer(StorageMenu) - -const BucketRow = ({ - bucket = {}, - projectRef = '', - isSelected = false, - onSelectDeleteBucket = () => {}, - onSelectToggleBucketPublic = () => {}, -}: any) => { - return ( - -

- {bucket.name} -

- {bucket.public && Public} -
- } - url={`/project/${projectRef}/storage/buckets/${bucket.id}`} - isActive={isSelected} - action={ - bucket.status === STORAGE_ROW_STATUS.LOADING ? ( - - ) : bucket.status === STORAGE_ROW_STATUS.READY ? ( - onSelectToggleBucketPublic(bucket)} - > - {bucket.public ? 'Make private' : 'Make public'} - , - , - } - key="delete-bucket" - onClick={() => onSelectDeleteBucket(bucket)} - > - Delete bucket - , - ]} - > - - - ) : ( -
- ) - } - /> - ) -} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx new file mode 100644 index 00000000000..53e9ed0a043 --- /dev/null +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx @@ -0,0 +1,80 @@ +import { FC } from 'react' +import { Form, Modal, Input, Button } from 'ui' +import { observer } from 'mobx-react-lite' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' + +interface Props {} + +const CustomExpiryModal: FC = () => { + const storageExplorerStore = useStorageStore() + const { selectedFileCustomExpiry, setSelectedFileCustomExpiry, copyFileURLToClipboard } = + storageExplorerStore + + const visible = selectedFileCustomExpiry !== undefined + const onClose = () => setSelectedFileCustomExpiry(undefined) + + console.log(selectedFileCustomExpiry) + + return ( + onClose()} + > +
{ + setSubmitting(true) + await copyFileURLToClipboard(selectedFileCustomExpiry, values.expiresIn) + setSubmitting(false) + onClose() + }} + validate={(values: any) => { + const errors: any = {} + if (values.expiresIn !== '' && values.expiresIn <= 0) + errors.expiresIn = 'Expiry duration cannot be less than 0' + return errors + }} + > + {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => ( + <> +
+ + seconds

} + /> +
+
+ + +
+ + +
+
+ + )} + +
+ ) +} + +export default observer(CustomExpiryModal) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js index 21b30091755..4e0f9af240b 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js @@ -1,4 +1,5 @@ import React, { useEffect, useRef } from 'react' +import { observer } from 'mobx-react-lite' import { STORAGE_VIEWS, CONTEXT_MENU_KEYS } from '../Storage.constants' import ItemContextMenu from './ItemContextMenu' @@ -25,13 +26,7 @@ const FileExplorer = ({ const fileExplorerRef = useRef(null) const storageExplorerStore = useStorageStore() - const { - downloadFile, - setSelectedItemsToDelete, - setSelectedItemToRename, - setSelectedItemsToMove, - copyFileURLToClipboard, - } = storageExplorerStore + const { setSelectedItemToRename } = storageExplorerStore useEffect(() => { if (fileExplorerRef) { @@ -55,14 +50,7 @@ const FileExplorer = ({ onSelectSort={onChangeSortBy} onSelectSortByOrder={onChangeSortByOrder} /> - setSelectedItemToRename(file)} - onSelectItemMove={(file) => setSelectedItemsToMove([file])} - onSelectItemDelete={(file) => setSelectedItemsToDelete([file])} - onCopyFileURL={async (file) => await copyFileURLToClipboard(file)} - onDownloadFile={async (file) => await downloadFile(file)} - /> + setSelectedItemToRename(folder)} @@ -110,4 +98,4 @@ const FileExplorer = ({ ) } -export default FileExplorer +export default observer(FileExplorer) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx index fcb43622051..7795203fa55 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx @@ -208,6 +208,12 @@ const FileExplorerRow: FC = ({ name: 'Get URL', icon: , onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), + children: [ + { name: 'Expire in 1 week' }, + { name: 'Expire in 1 month' }, + { name: 'Expire in 1 year' }, + { name: 'Custom expiry' }, + ], }, { name: 'Rename', diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js index 993fd661b14..1b948c8bf12 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js @@ -1,43 +1,89 @@ -import { Menu, Item } from 'react-contexify' +import { Menu, Item, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' +import { IconClipboard, IconEdit, IconMove, IconDownload, IconTrash2, IconChevronRight } from 'ui' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' +import { observer } from 'mobx-react-lite' + +const ItemContextMenu = ({ id = '' }) => { + const storageExplorerStore = useStorageStore() + const { + downloadFile, + selectedBucket, + setSelectedItemsToDelete, + setSelectedItemToRename, + setSelectedItemsToMove, + setSelectedFileCustomExpiry, + copyFileURLToClipboard, + } = storageExplorerStore + const isPublic = selectedBucket.public + + const onHandleClick = async (event, item, expiresIn) => { + if (item.isCorrupted) return + switch (event) { + case 'copy': + if (expiresIn < 0) return setSelectedFileCustomExpiry(item) + else return await copyFileURLToClipboard(item, expiresIn) + case 'rename': + return setSelectedItemToRename(item) + case 'move': + return setSelectedItemsToMove([item]) + case 'download': + return await downloadFile(item) + default: + break + } + } -const ItemContextMenu = ({ - id = '', - onCopyFileURL = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, - onDownloadFile = () => {}, - onSelectItemDelete = () => {}, -}) => { return ( - (!props.item.isCorrupted ? onCopyFileURL(props.item) : () => {})} - > - Copy URL + {isPublic ? ( + onHandleClick('copy', props.item)}> + + Copy URL + + ) : ( + + + Copy URL +
+ } + arrow={} + > + onHandleClick('copy', props.item, 60 * 60 * 24 * 7)}> + Expire in 1 week + + onHandleClick('copy', props.item, 60 * 60 * 24 * 30)}> + Expire in 1 month + + onHandleClick('copy', props.item, 60 * 60 * 24 * 365)}> + Expire in 1 year + + onHandleClick('copy', props.item, -1)}> + Custom expiry + + + )} + onHandleClick('rename', props.item)}> + + Rename - - !props.item.isCorrupted ? onSelectItemRename(props.item) : () => {} - } - > - Rename + onHandleClick('move', props.item)}> + + Move - (!props.item.isCorrupted ? onSelectItemMove(props.item) : () => {})} - > - Move + onHandleClick('download', props.item)}> + + Download - (!props.item.isCorrupted ? onDownloadFile(props.item) : () => {})} - > - Download - - onSelectItemDelete(props.item)}> - Delete + + setSelectedItemsToDelete([props.item])}> + + Delete ) } -export default ItemContextMenu +export default observer(ItemContextMenu) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 2b64d35abd9..d4b8d87b1d2 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -101,10 +101,9 @@ const PreviewPane = () => { copyFileURLToClipboard, closeFilePreview, setSelectedItemsToDelete, + setSelectedFileCustomExpiry, } = storageExplorerStore - const [showExpiryInput, setShowExpiryInput] = useState(false) - const width = 450 const isOpen = !isEmpty(file) const size = file.metadata ? formatBytes(file.metadata.size) : null @@ -220,7 +219,7 @@ const PreviewPane = () => { > Expire in 1 year , - setShowExpiryInput(true)}> + setSelectedFileCustomExpiry(file)}> Custom expiry , ]} @@ -248,64 +247,6 @@ const PreviewPane = () => {
- setShowExpiryInput(false)} - > -
{ - setSubmitting(true) - await copyFileURLToClipboard(file, values.expiresIn) - setSubmitting(false) - setShowExpiryInput(false) - }} - validate={(values: any) => { - const errors: any = {} - if (values.expiresIn !== '' && values.expiresIn <= 0) - errors.expiresIn = 'Expiry duration cannot be less than 0' - return errors - }} - > - {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => ( - <> -
- - seconds

} - /> -
-
- - -
- - -
-
- - )} - -
) } diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js index 53aa7b512e5..b19374ff0ba 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js @@ -11,6 +11,7 @@ import FileExplorerHeaderSelection from './FileExplorerHeaderSelection' import ConfirmDeleteModal from './ConfirmDeleteModal' import MoveItemsModal from './MoveItemsModal' import PreviewPane from './PreviewPane' +import CustomExpiryModal from './CustomExpiryModal' const StorageExplorer = observer(({ bucket }) => { const storageExplorerStore = useStorageStore() @@ -213,6 +214,7 @@ const StorageExplorer = observer(({ bucket }) => { onSelectCancel={clearSelectedItemsToMove} onSelectMove={onMoveSelectedFiles} /> +
) }) diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 323a51d10ca..b9a1742668b 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -58,6 +58,7 @@ class StorageExplorerStore { selectedItemsToDelete = [] selectedItemsToMove = [] selectedFilePreview = {} + selectedFileCustomExpiry = undefined DEFAULT_OPTIONS = { limit: LIMIT, @@ -300,6 +301,10 @@ class StorageExplorerStore { this.selectedItemsToMove = [] } + setSelectedFileCustomExpiry = (item) => { + this.selectedFileCustomExpiry = item + } + addNewFolderPlaceholder = (columnIndex) => { const isPrepend = true const folderName = 'Untitled folder' diff --git a/studio/styles/contextMenu.scss b/studio/styles/contextMenu.scss index 88d9680880d..418b2fcd9a0 100644 --- a/studio/styles/contextMenu.scss +++ b/studio/styles/contextMenu.scss @@ -4,6 +4,9 @@ .react-contexify__item__content { @apply dark:text-white text-sm; } + .react-contexify__submenu { + @apply dark:bg-gray-300 border; + } } .react-contexify__item:not(.react-contexify__item--disabled):hover > .react-contexify__item__content, From ddfb9beb921336546f1f32df198fa46855ff3206 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 17 Jan 2023 15:15:42 +0800 Subject: [PATCH 6/7] Implement expiry options in file explorer row menu --- .../Storage/Storage.constants.ts | 6 ++ .../StorageExplorer/CustomExpiryModal.tsx | 2 - .../StorageExplorer/FileExplorerRow.tsx | 61 +++++++++++++++++-- .../StorageExplorer/ItemContextMenu.js | 20 ++++-- .../Storage/StorageExplorer/PreviewPane.tsx | 19 +++--- 5 files changed, 86 insertions(+), 22 deletions(-) diff --git a/studio/components/to-be-cleaned/Storage/Storage.constants.ts b/studio/components/to-be-cleaned/Storage/Storage.constants.ts index 2620cf9fb2d..94fecf605f3 100644 --- a/studio/components/to-be-cleaned/Storage/Storage.constants.ts +++ b/studio/components/to-be-cleaned/Storage/Storage.constants.ts @@ -1,3 +1,9 @@ +export const URL_EXPIRY_DURATION = { + WEEK: 60 * 60 * 24 * 7, + MONTH: 60 * 60 * 24 * 30, + YEAR: 60 * 60 * 24 * 365, +} + export const STORAGE_VIEWS = { COLUMNS: 'COLUMNS', LIST: 'LIST', diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx index 53e9ed0a043..08b26f795de 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx @@ -13,8 +13,6 @@ const CustomExpiryModal: FC = () => { const visible = selectedFileCustomExpiry !== undefined const onClose = () => setSelectedFileCustomExpiry(undefined) - console.log(selectedFileCustomExpiry) - return ( = ({ setSelectedItemsToDelete, setSelectedItemToRename, setSelectedItemsToMove, + setSelectedFileCustomExpiry, fetchFolderContents, downloadFile, downloadFolder, @@ -207,12 +210,29 @@ const FileExplorerRow: FC = ({ { name: 'Get URL', icon: , - onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), children: [ - { name: 'Expire in 1 week' }, - { name: 'Expire in 1 month' }, - { name: 'Expire in 1 year' }, - { name: 'Custom expiry' }, + { + name: 'Expire in 1 week', + onClick: async () => + await copyFileURLToClipboard(itemWithColumnIndex, URL_EXPIRY_DURATION.WEEK), + }, + { + name: 'Expire in 1 month', + onClick: async () => + await copyFileURLToClipboard( + itemWithColumnIndex, + URL_EXPIRY_DURATION.MONTH + ), + }, + { + name: 'Expire in 1 year', + onClick: async () => + await copyFileURLToClipboard(itemWithColumnIndex, URL_EXPIRY_DURATION.YEAR), + }, + { + name: 'Custom expiry', + onClick: async () => setSelectedFileCustomExpiry(itemWithColumnIndex), + }, ], }, { @@ -382,7 +402,36 @@ const FileExplorerRow: FC = ({ align="end" overlay={[ rowOptions.map((option) => { - if (option.name === 'Separator') { + if ((option?.children ?? []).length > 0) { + return ( + { + return ( + + {child.name} + + ) + })} + > +
+
+ {option.icon} +

{option.name}

+
+ +
+
+ ) + } else if (option.name === 'Separator') { return } else { return ( diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js index 1b948c8bf12..eb48047e271 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js @@ -1,8 +1,10 @@ +import { observer } from 'mobx-react-lite' import { Menu, Item, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' import { IconClipboard, IconEdit, IconMove, IconDownload, IconTrash2, IconChevronRight } from 'ui' + +import { URL_EXPIRY_DURATION } from '../Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -import { observer } from 'mobx-react-lite' const ItemContextMenu = ({ id = '' }) => { const storageExplorerStore = useStorageStore() @@ -39,25 +41,31 @@ const ItemContextMenu = ({ id = '' }) => { {isPublic ? ( onHandleClick('copy', props.item)}> - Copy URL + Get URL ) : ( - Copy URL + Get URL
} arrow={} > - onHandleClick('copy', props.item, 60 * 60 * 24 * 7)}> + onHandleClick('copy', props.item, URL_EXPIRY_DURATION.WEEK)} + > Expire in 1 week - onHandleClick('copy', props.item, 60 * 60 * 24 * 30)}> + onHandleClick('copy', props.item, URL_EXPIRY_DURATION.MONTH)} + > Expire in 1 month - onHandleClick('copy', props.item, 60 * 60 * 24 * 365)}> + onHandleClick('copy', props.item, URL_EXPIRY_DURATION.YEAR)} + > Expire in 1 year onHandleClick('copy', props.item, -1)}> diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index d4b8d87b1d2..205e6fbbba6 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -1,9 +1,8 @@ +import Link from 'next/link' import { isEmpty } from 'lodash' -import { useState } from 'react' import { Button, Dropdown, - Form, IconX, IconLoader, IconClipboard, @@ -11,13 +10,11 @@ import { IconTrash2, IconAlertCircle, IconChevronDown, - Modal, - Input, } from 'ui' import SVG from 'react-inlinesvg' import { formatBytes } from 'lib/helpers' import { Transition } from '@headlessui/react' -import Link from 'next/link' +import { URL_EXPIRY_DURATION } from '../Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' const PreviewFile = ({ mimeType, previewUrl }: { mimeType: string; previewUrl: string }) => { @@ -205,17 +202,23 @@ const PreviewPane = () => { align="center" overlay={[ await copyFileURLToClipboard(file, 60 * 60 * 24 * 7)} + onClick={async () => + await copyFileURLToClipboard(file, URL_EXPIRY_DURATION.WEEK) + } > Expire in 1 week , await copyFileURLToClipboard(file, 60 * 60 * 24 * 30)} + onClick={async () => + await copyFileURLToClipboard(file, URL_EXPIRY_DURATION.MONTH) + } > Expire in 1 month , await copyFileURLToClipboard(file, 60 * 60 * 24 * 365)} + onClick={async () => + await copyFileURLToClipboard(file, URL_EXPIRY_DURATION.YEAR) + } > Expire in 1 year , From f052e13c6d2d4c46e2dcd6b0a9de983ba4e0d45d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 17 Jan 2023 15:21:27 +0800 Subject: [PATCH 7/7] Shift position of refresh button on table editor --- studio/components/grid/components/header/Header.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index 1b3e8ae89bc..4ad75e4eaef 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -40,10 +40,7 @@ const Header: FC = ({ sorts, filters, onAddColumn, onAddRow, header onAddRow={onAddRow} /> )} -
- - {headerActions} -
+
{headerActions}
) } @@ -77,6 +74,7 @@ const DefaultHeader: FC = ({ sorts, filters, onAddColumn, on return (
+