mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 05:39:11 +03:00
fix merge conflicts
This commit is contained in:
6 files changed
+235
-277
No files matched your search
@@ -441,7 +441,6 @@
|
||||
"components/interfaces/Storage/StorageExplorer/FileExplorerColumn.tsx": 6,
|
||||
"components/interfaces/Storage/StorageExplorer/FileExplorerRow.tsx": 1,
|
||||
"components/interfaces/Storage/StorageExplorer/FileExplorerRowEditing.tsx": 2,
|
||||
"components/interfaces/Storage/StorageExplorer/ItemContextMenu.tsx": 1,
|
||||
"components/interfaces/Storage/StorageExplorer/MoveItemsModal.tsx": 1,
|
||||
"components/interfaces/Storage/StorageExplorer/StorageExplorer.tsx": 1,
|
||||
"components/interfaces/Storage/StorageExplorer/StorageExplorer.utils.tsx": 2,
|
||||
|
||||
@@ -41,12 +41,6 @@ export enum STORAGE_ROW_STATUS {
|
||||
EDITING = 'EDITING',
|
||||
}
|
||||
|
||||
export const CONTEXT_MENU_KEYS = {
|
||||
STORAGE_COLUMN: 'STORAGE_COLUMN',
|
||||
STORAGE_ITEM: 'STORAGE_ITEM',
|
||||
STORAGE_FOLDER: 'STORAGE_FOLDER',
|
||||
}
|
||||
|
||||
export const STORAGE_CLIENT_LIBRARY_MAPPINGS = {
|
||||
upload: ['INSERT'],
|
||||
download: ['SELECT'],
|
||||
|
||||
@@ -2,12 +2,9 @@ import { noop } from 'lodash'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
import { CONTEXT_MENU_KEYS, STORAGE_ROW_STATUS, STORAGE_VIEWS } from '../Storage.constants'
|
||||
import { STORAGE_ROW_STATUS, STORAGE_VIEWS } from '../Storage.constants'
|
||||
import type { StorageColumn, StorageItemWithColumn } from '../Storage.types'
|
||||
import { ColumnContextMenu } from './ColumnContextMenu'
|
||||
import { FileExplorerColumn } from './FileExplorerColumn'
|
||||
import { FolderContextMenu } from './FolderContextMenu'
|
||||
import { ItemContextMenu } from './ItemContextMenu'
|
||||
import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer'
|
||||
|
||||
export interface FileExplorerProps {
|
||||
@@ -52,10 +49,6 @@ export const FileExplorer = ({
|
||||
snap.view === STORAGE_VIEWS.COLUMNS && 'h-full'
|
||||
)}
|
||||
>
|
||||
<ColumnContextMenu id={CONTEXT_MENU_KEYS.STORAGE_COLUMN} />
|
||||
<ItemContextMenu id={CONTEXT_MENU_KEYS.STORAGE_ITEM} />
|
||||
<FolderContextMenu id={CONTEXT_MENU_KEYS.STORAGE_FOLDER} />
|
||||
|
||||
{isLoading ? (
|
||||
<FileExplorerColumn
|
||||
column={{ id: '', name: '', path: '', items: [], status: STORAGE_ROW_STATUS.LOADING }}
|
||||
|
||||
+231
-127
@@ -1,21 +1,33 @@
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { get, noop, sum } from 'lodash'
|
||||
import { Upload } from 'lucide-react'
|
||||
import { get, noop, sum, uniqBy } from 'lodash'
|
||||
import { ChevronsDown, ChevronsUp, Copy, Eye, FolderPlus, Upload } from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useContextMenu } from 'react-contexify'
|
||||
import { toast } from 'sonner'
|
||||
import { Checkbox, cn } from 'ui'
|
||||
import {
|
||||
Checkbox,
|
||||
cn,
|
||||
ContextMenu_Shadcn_,
|
||||
ContextMenuContent_Shadcn_,
|
||||
ContextMenuItem_Shadcn_,
|
||||
ContextMenuSeparator_Shadcn_,
|
||||
ContextMenuSub_Shadcn_,
|
||||
ContextMenuSubContent_Shadcn_,
|
||||
ContextMenuSubTrigger_Shadcn_,
|
||||
ContextMenuTrigger_Shadcn_,
|
||||
} from 'ui'
|
||||
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
|
||||
import {
|
||||
CONTEXT_MENU_KEYS,
|
||||
STORAGE_ROW_STATUS,
|
||||
STORAGE_ROW_TYPES,
|
||||
STORAGE_SORT_BY,
|
||||
STORAGE_SORT_BY_ORDER,
|
||||
STORAGE_VIEWS,
|
||||
} from '../Storage.constants'
|
||||
import type { StorageColumn, StorageItemWithColumn } from '../Storage.types'
|
||||
import { FileExplorerRow } from './FileExplorerRow'
|
||||
import { FileExplorerRowContextMenuProvider } from './FileExplorerRowContextMenu'
|
||||
import { isPickerItemSelectable, useStorageExplorerPicker } from './StorageExplorerPickerContext'
|
||||
import { InfiniteListDefault, LoaderForIconMenuItems } from '@/components/ui/InfiniteList'
|
||||
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
||||
@@ -115,12 +127,24 @@ export const FileExplorerColumn = ({
|
||||
const isEmpty =
|
||||
visibleColumnItems.filter((item) => item.status !== STORAGE_ROW_STATUS.LOADING).length === 0
|
||||
|
||||
const { show } = useContextMenu()
|
||||
const displayMenu = (event: any) => {
|
||||
show(event, {
|
||||
id: CONTEXT_MENU_KEYS.STORAGE_COLUMN,
|
||||
props: { index },
|
||||
})
|
||||
const onSelectCreateFolder = () => {
|
||||
snap.addNewFolderPlaceholder(index)
|
||||
}
|
||||
|
||||
const onSelectAllItems = () => {
|
||||
const colFiles = visibleColumnItems
|
||||
.filter((item) => item.type === STORAGE_ROW_TYPES.FILE)
|
||||
.map((item) => ({ ...item, columnIndex: index }))
|
||||
const colFilesId = colFiles.map((item) => item.id).filter(Boolean)
|
||||
const selectedFromCol = selectedItems.filter((item) => item.id && colFilesId.includes(item.id))
|
||||
|
||||
if (selectedFromCol.length === colFiles.length) {
|
||||
snap.setSelectedItems(
|
||||
selectedItems.filter((item) => item.id && !colFilesId.includes(item.id))
|
||||
)
|
||||
} else {
|
||||
snap.setSelectedItems(uniqBy(selectedItems.concat(colFiles), 'id'))
|
||||
}
|
||||
}
|
||||
|
||||
const onDragOver = (event: any) => {
|
||||
@@ -171,133 +195,213 @@ export const FileExplorerColumn = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={fileExplorerColumnRef}
|
||||
className={cn(
|
||||
'hide-scrollbar relative flex flex-col',
|
||||
fullWidth ? 'w-full' : 'w-64 border-r border-overlay',
|
||||
snap.view === STORAGE_VIEWS.LIST && 'min-h-0 flex-1 overflow-hidden',
|
||||
snap.view === STORAGE_VIEWS.COLUMNS && 'h-full flex-shrink-0 overflow-auto'
|
||||
)}
|
||||
onContextMenu={displayMenu}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDrop}
|
||||
onClick={(event) => {
|
||||
const eventTarget = get(event.target, ['className'], '')
|
||||
if (typeof eventTarget === 'string' && eventTarget.includes('react-contexify')) return
|
||||
onSelectColumnEmptySpace(index)
|
||||
}}
|
||||
>
|
||||
{/* Checkbox selection for select all */}
|
||||
{snap.view === STORAGE_VIEWS.COLUMNS && (
|
||||
<ContextMenu_Shadcn_ modal={false}>
|
||||
<ContextMenuTrigger_Shadcn_ asChild>
|
||||
<div
|
||||
ref={fileExplorerColumnRef}
|
||||
className={cn(
|
||||
'sticky top-0 z-10 mb-0 flex items-center bg-table-header-light px-2.5 [[data-theme*=dark]_&]:bg-table-header-dark',
|
||||
haveSelectedItems ? 'h-10 py-3 opacity-100' : 'h-0 py-0 opacity-0',
|
||||
'transition-all duration-200'
|
||||
'hide-scrollbar relative flex flex-col',
|
||||
fullWidth ? 'w-full' : 'w-64 border-r border-overlay',
|
||||
snap.view === STORAGE_VIEWS.LIST && 'min-h-0 flex-1 overflow-hidden',
|
||||
snap.view === STORAGE_VIEWS.COLUMNS && 'h-full flex-shrink-0 overflow-auto'
|
||||
)}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDrop}
|
||||
onClick={() => {
|
||||
onSelectColumnEmptySpace(index)
|
||||
}}
|
||||
>
|
||||
{columnFiles.length > 0 ? (
|
||||
<>
|
||||
<SelectAllCheckbox />
|
||||
<p className="text-sm text-foreground-light">Select all {columnFiles.length} files</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-foreground-light">No files available for selection</p>
|
||||
{/* Checkbox selection for select all */}
|
||||
{snap.view === STORAGE_VIEWS.COLUMNS && (
|
||||
<div
|
||||
className={cn(
|
||||
'sticky top-0 z-10 mb-0 flex items-center bg-table-header-light px-2.5 [[data-theme*=dark]_&]:bg-table-header-dark',
|
||||
haveSelectedItems ? 'h-10 py-3 opacity-100' : 'h-0 py-0 opacity-0',
|
||||
'transition-all duration-200'
|
||||
)}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
{columnFiles.length > 0 ? (
|
||||
<>
|
||||
<SelectAllCheckbox />
|
||||
<p className="text-sm text-foreground-light">
|
||||
Select all {columnFiles.length} files
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-foreground-light">No files available for selection</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* List Interface Header */}
|
||||
{snap.view === STORAGE_VIEWS.LIST && (
|
||||
<div className="sticky top-0 py-2 z-10 flex min-w-min items-center border-b border-overlay bg-surface-100 px-2.5">
|
||||
<div className="flex w-[40%] min-w-[250px] items-center">
|
||||
<SelectAllCheckbox />
|
||||
<p className="text-sm">Name</p>
|
||||
</div>
|
||||
<p className="w-[11%] min-w-[100px] text-sm">Size</p>
|
||||
<p className="w-[14%] min-w-[100px] text-sm">Type</p>
|
||||
<p className="w-[15%] min-w-[160px] text-sm">Created at</p>
|
||||
<p className="w-[15%] min-w-[160px] text-sm">Last modified at</p>
|
||||
</div>
|
||||
)}
|
||||
{/* List Interface Header */}
|
||||
{snap.view === STORAGE_VIEWS.LIST && (
|
||||
<div className="sticky top-0 py-2 z-10 flex min-w-min items-center border-b border-overlay bg-surface-100 px-2.5">
|
||||
<div className="flex w-[40%] min-w-[250px] items-center">
|
||||
<SelectAllCheckbox />
|
||||
<p className="text-sm">Name</p>
|
||||
</div>
|
||||
<p className="w-[11%] min-w-[100px] text-sm">Size</p>
|
||||
<p className="w-[14%] min-w-[100px] text-sm">Type</p>
|
||||
<p className="w-[15%] min-w-[160px] text-sm">Created at</p>
|
||||
<p className="w-[15%] min-w-[160px] text-sm">Last modified at</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Shimmering loaders while fetching contents */}
|
||||
{column.status === STORAGE_ROW_STATUS.LOADING && (
|
||||
<div
|
||||
className={`
|
||||
{/* Shimmering loaders while fetching contents */}
|
||||
{column.status === STORAGE_ROW_STATUS.LOADING && (
|
||||
<div
|
||||
className={`
|
||||
${fullWidth ? 'w-full' : 'w-64 border-r border-default'}
|
||||
px-2 py-1 my-1 flex flex-shrink-0 flex-col space-y-2 overflow-auto
|
||||
`}
|
||||
>
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader />
|
||||
>
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Column Interface */}
|
||||
{columnItems.length > 0 && (
|
||||
<FileExplorerRowContextMenuProvider>
|
||||
<InfiniteListDefault
|
||||
className={snap.view === STORAGE_VIEWS.LIST ? 'min-h-0 flex-1' : 'h-full'}
|
||||
items={columnItems}
|
||||
itemProps={itemProps}
|
||||
getItemKey={getItemKey}
|
||||
getItemSize={(index: number) =>
|
||||
index !== 0 && index === columnItems.length ? 85 : 37
|
||||
}
|
||||
ItemComponent={FileExplorerRow}
|
||||
LoaderComponent={LoaderForIconMenuItems}
|
||||
hasNextPage={column.status !== STORAGE_ROW_STATUS.LOADING && column.hasMoreItems}
|
||||
isLoadingNextPage={column.isLoadingMoreItems}
|
||||
onLoadNextPage={() => onColumnLoadMore(index, column)}
|
||||
/>
|
||||
</FileExplorerRowContextMenuProvider>
|
||||
)}
|
||||
|
||||
{/* Drag drop upload CTA for when column is empty */}
|
||||
{!(snap.isSearching && itemSearchString.length > 0) &&
|
||||
visibleColumnItems.length === 0 &&
|
||||
column.status !== STORAGE_ROW_STATUS.LOADING && (
|
||||
<div className="h-full w-full flex flex-col items-center justify-center">
|
||||
<img
|
||||
alt="storage-placeholder"
|
||||
src={`${BASE_PATH}/img/storage-placeholder.svg`}
|
||||
className="opacity-75 pointer-events-none"
|
||||
/>
|
||||
<p className="text-sm my-3 opacity-75">Drop your files here</p>
|
||||
<p className="w-40 text-center text-xs text-foreground-light">
|
||||
Or upload them via the "Upload file" button above
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{snap.isSearching &&
|
||||
itemSearchString.length > 0 &&
|
||||
isEmpty &&
|
||||
column.status !== STORAGE_ROW_STATUS.LOADING && (
|
||||
<div className="h-full w-full flex flex-col items-center justify-center">
|
||||
<p className="text-sm my-3 text-foreground">No results found in this folder</p>
|
||||
<p className="w-40 text-center text-sm text-foreground-light">
|
||||
Your search for "{itemSearchString}" did not return any results
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Drag drop upload CTA for when column has files */}
|
||||
<DragOverOverlay
|
||||
isOpen={isDraggedOver}
|
||||
folderIsEmpty={isEmpty}
|
||||
onDragLeave={() => setIsDraggedOver(false)}
|
||||
onDrop={() => setIsDraggedOver(false)}
|
||||
/>
|
||||
|
||||
{/* List interface footer */}
|
||||
{snap.view === STORAGE_VIEWS.LIST && (
|
||||
<div className="shrink-0 rounded-b-md z-10 flex min-w-min items-center bg-panel-footer-light px-2.5 py-2 [[data-theme*=dark]_&]:bg-panel-footer-dark w-full">
|
||||
<p className="text-sm">
|
||||
{formatBytes(columnItemsSize)} for {columnItems.length} items
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Column Interface */}
|
||||
{columnItems.length > 0 && (
|
||||
<InfiniteListDefault
|
||||
className={snap.view === STORAGE_VIEWS.LIST ? 'min-h-0 flex-1' : 'h-full'}
|
||||
items={columnItems}
|
||||
itemProps={itemProps}
|
||||
getItemKey={getItemKey}
|
||||
getItemSize={(index: number) => (index !== 0 && index === columnItems.length ? 85 : 37)}
|
||||
ItemComponent={FileExplorerRow}
|
||||
LoaderComponent={LoaderForIconMenuItems}
|
||||
hasNextPage={column.status !== STORAGE_ROW_STATUS.LOADING && column.hasMoreItems}
|
||||
isLoadingNextPage={column.isLoadingMoreItems}
|
||||
onLoadNextPage={() => onColumnLoadMore(index, column)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Drag drop upload CTA for when column is empty */}
|
||||
{!(snap.isSearching && itemSearchString.length > 0) &&
|
||||
visibleColumnItems.length === 0 &&
|
||||
column.status !== STORAGE_ROW_STATUS.LOADING && (
|
||||
<div className="h-full w-full flex flex-col items-center justify-center">
|
||||
<img
|
||||
alt="storage-placeholder"
|
||||
src={`${BASE_PATH}/img/storage-placeholder.svg`}
|
||||
className="opacity-75 pointer-events-none"
|
||||
/>
|
||||
<p className="text-sm my-3 opacity-75">Drop your files here</p>
|
||||
<p className="w-40 text-center text-xs text-foreground-light">
|
||||
Or upload them via the "Upload file" button above
|
||||
</p>
|
||||
</div>
|
||||
</ContextMenuTrigger_Shadcn_>
|
||||
<ContextMenuContent_Shadcn_>
|
||||
{canUpdateStorage && (
|
||||
<>
|
||||
<ContextMenuItem_Shadcn_ className="gap-x-2" onSelect={onSelectCreateFolder}>
|
||||
<FolderPlus size={14} />
|
||||
<span className="text-xs">New folder</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
<ContextMenuSeparator_Shadcn_ />
|
||||
</>
|
||||
)}
|
||||
|
||||
{snap.isSearching &&
|
||||
itemSearchString.length > 0 &&
|
||||
isEmpty &&
|
||||
column.status !== STORAGE_ROW_STATUS.LOADING && (
|
||||
<div className="h-full w-full flex flex-col items-center justify-center">
|
||||
<p className="text-sm my-3 text-foreground">No results found in this folder</p>
|
||||
<p className="w-40 text-center text-sm text-foreground-light">
|
||||
Your search for "{itemSearchString}" did not return any results
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Drag drop upload CTA for when column has files */}
|
||||
<DragOverOverlay
|
||||
isOpen={isDraggedOver}
|
||||
folderIsEmpty={isEmpty}
|
||||
onDragLeave={() => setIsDraggedOver(false)}
|
||||
onDrop={() => setIsDraggedOver(false)}
|
||||
/>
|
||||
|
||||
{/* List interface footer */}
|
||||
{snap.view === STORAGE_VIEWS.LIST && (
|
||||
<div className="shrink-0 rounded-b-md z-10 flex min-w-min items-center bg-panel-footer-light px-2.5 py-2 [[data-theme*=dark]_&]:bg-panel-footer-dark w-full">
|
||||
<p className="text-sm">
|
||||
{formatBytes(columnItemsSize)} for {columnItems.length} items
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ContextMenuItem_Shadcn_ className="gap-x-2" onSelect={onSelectAllItems}>
|
||||
<Copy size={14} />
|
||||
<span className="text-xs">Select all items</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
<ContextMenuSub_Shadcn_>
|
||||
<ContextMenuSubTrigger_Shadcn_ className="gap-x-2">
|
||||
<Eye size={14} />
|
||||
<span className="text-xs">View</span>
|
||||
</ContextMenuSubTrigger_Shadcn_>
|
||||
<ContextMenuSubContent_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_ onSelect={() => snap.setView(STORAGE_VIEWS.COLUMNS)}>
|
||||
<span className="text-xs">As columns</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_ onSelect={() => snap.setView(STORAGE_VIEWS.LIST)}>
|
||||
<span className="text-xs">As list</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
</ContextMenuSubContent_Shadcn_>
|
||||
</ContextMenuSub_Shadcn_>
|
||||
<ContextMenuSub_Shadcn_>
|
||||
<ContextMenuSubTrigger_Shadcn_ className="gap-x-2">
|
||||
<ChevronsDown size={14} />
|
||||
<span className="text-xs">Sort by</span>
|
||||
</ContextMenuSubTrigger_Shadcn_>
|
||||
<ContextMenuSubContent_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_ onSelect={() => void snap.setSortBy(STORAGE_SORT_BY.NAME)}>
|
||||
<span className="text-xs">Name</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_
|
||||
onSelect={() => void snap.setSortBy(STORAGE_SORT_BY.CREATED_AT)}
|
||||
>
|
||||
<span className="text-xs">Last created</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_
|
||||
onSelect={() => void snap.setSortBy(STORAGE_SORT_BY.UPDATED_AT)}
|
||||
>
|
||||
<span className="text-xs">Last modified</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_
|
||||
onSelect={() => void snap.setSortBy(STORAGE_SORT_BY.LAST_ACCESSED_AT)}
|
||||
>
|
||||
<span className="text-xs">Last accessed</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
</ContextMenuSubContent_Shadcn_>
|
||||
</ContextMenuSub_Shadcn_>
|
||||
<ContextMenuSub_Shadcn_>
|
||||
<ContextMenuSubTrigger_Shadcn_ className="gap-x-2">
|
||||
<ChevronsUp size={14} />
|
||||
<span className="text-xs">Sort by order</span>
|
||||
</ContextMenuSubTrigger_Shadcn_>
|
||||
<ContextMenuSubContent_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_
|
||||
onSelect={() => void snap.setSortByOrder(STORAGE_SORT_BY_ORDER.ASC)}
|
||||
>
|
||||
<span className="text-xs">Ascending</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
<ContextMenuItem_Shadcn_
|
||||
onSelect={() => void snap.setSortByOrder(STORAGE_SORT_BY_ORDER.DESC)}
|
||||
>
|
||||
<span className="text-xs">Descending</span>
|
||||
</ContextMenuItem_Shadcn_>
|
||||
</ContextMenuSubContent_Shadcn_>
|
||||
</ContextMenuSub_Shadcn_>
|
||||
</ContextMenuContent_Shadcn_>
|
||||
</ContextMenu_Shadcn_>
|
||||
)
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
Trash2,
|
||||
} from 'lucide-react'
|
||||
import type { CSSProperties } from 'react'
|
||||
import { useContextMenu } from 'react-contexify'
|
||||
import {
|
||||
Checkbox,
|
||||
cn,
|
||||
@@ -36,13 +35,13 @@ import {
|
||||
} from 'ui'
|
||||
|
||||
import {
|
||||
CONTEXT_MENU_KEYS,
|
||||
STORAGE_ROW_STATUS,
|
||||
STORAGE_ROW_TYPES,
|
||||
STORAGE_VIEWS,
|
||||
URL_EXPIRY_DURATION,
|
||||
} from '../Storage.constants'
|
||||
import { StorageItemWithColumn, type StorageItem } from '../Storage.types'
|
||||
import { useFileExplorerContextMenu } from './FileExplorerRowContextMenu'
|
||||
import { FileExplorerRowEditing } from './FileExplorerRowEditing'
|
||||
import { copyPathToFolder } from './StorageExplorer.utils'
|
||||
import { isPickerItemSelectable, useStorageExplorerPicker } from './StorageExplorerPickerContext'
|
||||
@@ -129,7 +128,7 @@ export const FileExplorerRow = ({
|
||||
selectRangeItems,
|
||||
} = useStorageExplorerStateSnapshot()
|
||||
const picker = useStorageExplorerPicker()
|
||||
const { show } = useContextMenu()
|
||||
const ctx = useFileExplorerContextMenu()
|
||||
const { onCopyUrl } = useCopyUrl()
|
||||
|
||||
const isPublic = selectedBucket.public
|
||||
@@ -275,18 +274,6 @@ export const FileExplorerRow = ({
|
||||
const createdAt = item.created_at ? new Date(item.created_at).toLocaleString() : '-'
|
||||
const updatedAt = item.updated_at ? new Date(item.updated_at).toLocaleString() : '-'
|
||||
|
||||
const displayMenu = (event: any, rowType: STORAGE_ROW_TYPES) => {
|
||||
show(event, {
|
||||
id:
|
||||
rowType === STORAGE_ROW_TYPES.FILE
|
||||
? CONTEXT_MENU_KEYS.STORAGE_ITEM
|
||||
: CONTEXT_MENU_KEYS.STORAGE_FOLDER,
|
||||
props: {
|
||||
item: itemWithColumnIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const nameWidth =
|
||||
view === STORAGE_VIEWS.LIST && item.isCorrupted
|
||||
? `calc(100% - 60px)`
|
||||
@@ -307,9 +294,7 @@ export const FileExplorerRow = ({
|
||||
onContextMenu={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
item.type === STORAGE_ROW_TYPES.FILE
|
||||
? displayMenu(event, STORAGE_ROW_TYPES.FILE)
|
||||
: displayMenu(event, STORAGE_ROW_TYPES.FOLDER)
|
||||
ctx?.onRowContextMenu(event, rowOptions)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { ChevronRight, Copy, Download, Edit, Move, Trash2 } from 'lucide-react'
|
||||
import { Item, Menu, Separator, Submenu } from 'react-contexify'
|
||||
|
||||
import 'react-contexify/dist/ReactContexify.css'
|
||||
|
||||
import { URL_EXPIRY_DURATION } from '../Storage.constants'
|
||||
import { StorageItemWithColumn } from '../Storage.types'
|
||||
import { useCopyUrl } from './useCopyUrl'
|
||||
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
||||
import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer'
|
||||
|
||||
interface ItemContextMenuProps {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const ItemContextMenu = ({ id = '' }: ItemContextMenuProps) => {
|
||||
const {
|
||||
selectedBucket,
|
||||
setSelectedItemsToDelete,
|
||||
setSelectedItemToRename,
|
||||
setSelectedItemsToMove,
|
||||
setSelectedFileCustomExpiry,
|
||||
downloadFile,
|
||||
} = useStorageExplorerStateSnapshot()
|
||||
const { onCopyUrl } = useCopyUrl()
|
||||
const isPublic = selectedBucket.public
|
||||
const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
|
||||
const onHandleClick = async (
|
||||
event: 'copy' | 'rename' | 'move' | 'download',
|
||||
item: StorageItemWithColumn,
|
||||
expiresIn?: number
|
||||
) => {
|
||||
if (item.isCorrupted) return
|
||||
if (event === 'copy') {
|
||||
if (expiresIn !== undefined && expiresIn < 0) {
|
||||
setSelectedFileCustomExpiry(item)
|
||||
return
|
||||
}
|
||||
if (item.path) {
|
||||
onCopyUrl(item.path, expiresIn)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event === 'rename') {
|
||||
setSelectedItemToRename(item)
|
||||
return
|
||||
}
|
||||
if (event === 'move') {
|
||||
setSelectedItemsToMove([item])
|
||||
return
|
||||
}
|
||||
if (event === 'download') {
|
||||
await downloadFile(item)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu id={id} animation="fade">
|
||||
{isPublic ? (
|
||||
<Item onClick={({ props }) => onHandleClick('copy', props.item)}>
|
||||
<Copy size={14} />
|
||||
<span className="ml-2 text-xs">Get URL</span>
|
||||
</Item>
|
||||
) : (
|
||||
<Submenu
|
||||
label={
|
||||
<div className="flex items-center space-x-2">
|
||||
<Copy size={14} />
|
||||
<span className="text-xs">Get URL</span>
|
||||
</div>
|
||||
}
|
||||
arrow={<ChevronRight size={14} />}
|
||||
>
|
||||
<Item
|
||||
onClick={({ props }) => onHandleClick('copy', props.item, URL_EXPIRY_DURATION.WEEK)}
|
||||
>
|
||||
<span className="ml-2 text-xs">Expire in 1 week</span>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={({ props }) => onHandleClick('copy', props.item, URL_EXPIRY_DURATION.MONTH)}
|
||||
>
|
||||
<span className="ml-2 text-xs">Expire in 1 month</span>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={({ props }) => onHandleClick('copy', props.item, URL_EXPIRY_DURATION.YEAR)}
|
||||
>
|
||||
<span className="ml-2 text-xs">Expire in 1 year</span>
|
||||
</Item>
|
||||
<Item onClick={({ props }) => onHandleClick('copy', props.item, -1)}>
|
||||
<span className="ml-2 text-xs">Custom expiry</span>
|
||||
</Item>
|
||||
</Submenu>
|
||||
)}
|
||||
<Item onClick={({ props }) => onHandleClick('download', props.item)}>
|
||||
<Download size={14} strokeWidth={1} />
|
||||
<span className="ml-2 text-xs">Download</span>
|
||||
</Item>
|
||||
{canUpdateFiles && [
|
||||
<Item key="rename-file" onClick={({ props }) => onHandleClick('rename', props.item)}>
|
||||
<Edit size={14} strokeWidth={1} />
|
||||
<span className="ml-2 text-xs">Rename</span>
|
||||
</Item>,
|
||||
<Item key="move-file" onClick={({ props }) => onHandleClick('move', props.item)}>
|
||||
<Move size={14} strokeWidth={1} />
|
||||
<span className="ml-2 text-xs">Move</span>
|
||||
</Item>,
|
||||
<Separator key="file-separator" />,
|
||||
<Item key="delete-file" onClick={({ props }) => setSelectedItemsToDelete([props.item])}>
|
||||
<Trash2 size={14} strokeWidth={1} stroke="red" />
|
||||
<span className="ml-2 text-xs">Delete</span>
|
||||
</Item>,
|
||||
]}
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user