import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
import { useParams } from 'common'
import {
ArrowLeft,
Check,
ChevronRight,
Columns,
List,
RefreshCw,
Search,
Upload,
X,
} from 'lucide-react'
import { useRef, useState, type ChangeEvent } from 'react'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { STORAGE_SORT_BY, STORAGE_SORT_BY_ORDER, STORAGE_VIEWS } from '../Storage.constants'
import { useStoragePreference } from '../StorageExplorer/useStoragePreference'
import { uploadFilesToBucket } from './BucketFilePickerDialog.utils'
import { useBucketFilePickerStateSnapshot } from './BucketFilePickerState'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { storageKeys } from '@/data/storage/keys'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
const VIEW_OPTIONS = [
{ key: STORAGE_VIEWS.COLUMNS, name: 'As columns' },
{ key: STORAGE_VIEWS.LIST, name: 'As list' },
]
const SORT_BY_OPTIONS = [
{ key: STORAGE_SORT_BY.NAME, name: 'Name' },
{ key: STORAGE_SORT_BY.CREATED_AT, name: 'Time created' },
{ key: STORAGE_SORT_BY.UPDATED_AT, name: 'Time modified' },
{ key: STORAGE_SORT_BY.LAST_ACCESSED_AT, name: 'Time last accessed' },
]
const SORT_ORDER_OPTIONS = [
{ key: STORAGE_SORT_BY_ORDER.ASC, name: 'Ascending' },
{ key: STORAGE_SORT_BY_ORDER.DESC, name: 'Descending' },
]
const HeaderBreadcrumbs = ({
breadcrumbs,
selectBreadcrumb,
}: {
breadcrumbs: string[]
selectBreadcrumb: (i: number) => void
}) => {
// Max 5 crumbs, otherwise replace middle segment with ellipsis and only
// have the first 2 and last 2 crumbs visible
const ellipsis = '...'
const breadcrumbsWithIndexes = breadcrumbs.map((name: string, index: number) => {
return { name, index }
})
const formattedBreadcrumbs =
breadcrumbsWithIndexes.length <= 5
? breadcrumbsWithIndexes
: breadcrumbsWithIndexes
.slice(0, 2)
.concat([{ name: ellipsis, index: -1 }])
.concat(
breadcrumbsWithIndexes.slice(
breadcrumbsWithIndexes.length - 2,
breadcrumbsWithIndexes.length
)
)
return (
{formattedBreadcrumbs.map((crumb, idx: number) => {
const isEllipsis = crumb.name === ellipsis
const isActive = crumb.index === breadcrumbs.length - 1
return (
{idx !== 0 && (
)}
{isEllipsis ? (
{crumb.name}
) : isActive ? (
{crumb.name}
) : (
)}
)
})}
)
}
export const BucketFilePickerHeader = () => {
const { ref: projectRef } = useParams()
const queryClient = useQueryClient()
const [isSearching, setIsSearching] = useState(false)
const [isRefreshing, setIsRefreshing] = useState(false)
const [isUploading, setIsUploading] = useState(false)
const uploadButtonRef = useRef(null)
const { hostEndpoint } = useProjectApiUrl({ projectRef: projectRef! })
const { view, sortBy, sortByOrder, setSortBy, setSortByOrder, setView } = useStoragePreference(
projectRef!
)
const {
bucket,
columns,
itemSearchString,
setItemSearchString,
popColumn,
popColumnAtIndex,
setSelectedFilePreview,
} = useBucketFilePickerStateSnapshot()
const { can: canUpdateStorage } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
const breadcrumbs = columns
const backDisabled = columns.length < 1
const onSelectBack = () => {
popColumn()
setSelectedFilePreview(undefined)
}
const onSelectUpload = () => {
if (uploadButtonRef.current) {
uploadButtonRef.current.click()
}
}
const handleFilesUpload = async (event: ChangeEvent) => {
if (!hostEndpoint) {
console.error('Host endpoint not available')
return
}
const files = Array.from(event.target.files || [])
try {
setIsUploading(true)
await uploadFilesToBucket({
files,
projectRef: projectRef!,
hostEndpoint,
bucketName: bucket.name,
bucketId: bucket.id,
currentPath: columns.join('/'),
queryClient,
})
queryClient.invalidateQueries({
queryKey: storageKeys.objects(projectRef!, bucket.id, columns.join('/')),
})
} catch (error) {
console.error('Failed to upload files:', error)
// Consider showing a toast notification to the user
} finally {
event.target.value = ''
setIsUploading(false)
}
}
/** Methods for searching */
const toggleSearch = () => {
setIsSearching(true)
}
const onCancelSearch = () => {
setIsSearching(false)
setItemSearchString('')
}
/** Methods for breadcrumbs */
const selectBreadcrumb = (columnIndex: number) => {
popColumnAtIndex(columnIndex)
}
const refreshData = async () => {
setIsRefreshing(true)
const queryKey = storageKeys.objects(projectRef!, bucket.id, '').filter(Boolean)
try {
await queryClient.refetchQueries({ queryKey: queryKey, type: 'active' })
} finally {
setIsRefreshing(false)
}
}
return (
{/* Navigation */}
{breadcrumbs.length > 0 && (
<>
}
size="tiny"
type="text"
className="shrink-0 px-1"
disabled={backDisabled}
onClick={() => {
onSelectBack()
}}
/>
>
)}
{breadcrumbs.length > 0 ? (
) : null}
{/* Actions */}
}
type="text"
loading={isRefreshing}
onClick={refreshData}
>
Reload
) : (
)
}
>
View
{VIEW_OPTIONS.map((option) => (
setView(option.key)}>
{option.name}
{view === option.key && (
)}
))}
Sort by
{SORT_BY_OPTIONS.map((option) => (
setSortBy(option.key)}>
{option.name}
{sortBy === option.key && (
)}
))}
Sort order
{SORT_ORDER_OPTIONS.map((option) => (
setSortByOrder(option.key)}
>
{option.name}
{sortByOrder === option.key && (
)}
))}
{isSearching ? (
}
actions={[
}
onClick={onCancelSearch}
className="p-0 h-5 w-5"
/>,
]}
placeholder="Search for a file or folder"
type="text"
value={itemSearchString}
onChange={(event) => setItemSearchString(event.target.value)}
/>
) : (
}
size="tiny"
type="text"
className="px-1"
onClick={toggleSearch}
/>
)}
)
}