diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesContext.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesContext.tsx index d89baa5c377..1fad42f73e3 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesContext.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesContext.tsx @@ -1,5 +1,13 @@ import { parseAsBoolean, useQueryState } from 'nuqs' -import { createContext, useCallback, useContext, useState, type ReactNode } from 'react' +import { + createContext, + useCallback, + useContext, + useState, + type Dispatch, + type ReactNode, + type SetStateAction, +} from 'react' import { type DeletedObjectVersion, @@ -24,6 +32,8 @@ interface DeletedFilesContextValue { lastToggledId: string | null setLastToggledId: (id: string | null) => void clearDeletedSelection: () => void + expandedVersionIds: Set + setExpandedVersionIds: Dispatch>> } const DeletedFilesContext = createContext({ @@ -39,6 +49,8 @@ const DeletedFilesContext = createContext({ lastToggledId: null, setLastToggledId: () => {}, clearDeletedSelection: () => {}, + expandedVersionIds: new Set(), + setExpandedVersionIds: () => {}, }) export const useDeletedFilesContext = () => useContext(DeletedFilesContext) @@ -59,6 +71,7 @@ export const DeletedFilesProvider = ({ enabled, children }: DeletedFilesProvider >() const [selectedDeletedIds, setSelectedDeletedIds] = useState([]) const [lastToggledId, setLastToggledId] = useState(null) + const [expandedVersionIds, setExpandedVersionIds] = useState>(new Set()) const clearDeletedSelection = useCallback(() => { setSelectedDeletedIds([]) @@ -80,6 +93,8 @@ export const DeletedFilesProvider = ({ enabled, children }: DeletedFilesProvider lastToggledId, setLastToggledId, clearDeletedSelection, + expandedVersionIds, + setExpandedVersionIds, }} > {children} diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesList.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesList.tsx index c2232454fd7..a24b6c84459 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesList.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesList.tsx @@ -59,6 +59,8 @@ export const DeletedFilesList = ({ bucketId, searchString }: DeletedFilesListPro setSelectedDeletedIds, lastToggledId, setLastToggledId, + expandedVersionIds, + setExpandedVersionIds, } = useDeletedFilesContext() const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') @@ -67,8 +69,6 @@ export const DeletedFilesList = ({ bucketId, searchString }: DeletedFilesListPro parentObject: TrashObject version: DeletedObjectVersion }>() - const [expandedIds, setExpandedIds] = useState>(new Set()) - const { data: objects, isPending, @@ -173,7 +173,7 @@ export const DeletedFilesList = ({ bucketId, searchString }: DeletedFilesListPro const orderedIds = filtered.flatMap((o) => { const ids = [o.id] - if (expandedIds.has(o.id) && o.noncurrentVersions) { + if (expandedVersionIds.has(o.id) && o.noncurrentVersions) { for (const v of o.noncurrentVersions) { ids.push(versionKey(o.id, v.versionId)) } @@ -223,15 +223,8 @@ export const DeletedFilesList = ({ bucketId, searchString }: DeletedFilesListPro setVersionToDelete({ parentObject: parent, version }) } - // ── Expand / collapse all ─────────────────────────────────────────── - - const expandableIds = filtered - .filter((o) => o.noncurrentVersions && o.noncurrentVersions.length > 0) - .map((o) => o.id) - const isAllExpanded = expandableIds.length > 0 && expandableIds.every((id) => expandedIds.has(id)) - const toggleExpanded = (id: string) => { - setExpandedIds((prev) => { + setExpandedVersionIds((prev) => { const next = new Set(prev) if (next.has(id)) { next.delete(id) @@ -242,9 +235,6 @@ export const DeletedFilesList = ({ bucketId, searchString }: DeletedFilesListPro }) } - const expandAll = () => setExpandedIds(new Set(expandableIds)) - const collapseAll = () => setExpandedIds(new Set()) - const totalVersionCount = (object: TrashObject) => { return 1 + (object.noncurrentVersions?.length ?? 0) } @@ -278,25 +268,7 @@ export const DeletedFilesList = ({ bucketId, searchString }: DeletedFilesListPro Original location Deleted Size - - {expandableIds.length > 0 && ( -
- - · - -
- )} -
+ @@ -306,7 +278,7 @@ export const DeletedFilesList = ({ bucketId, searchString }: DeletedFilesListPro selectedDeletedFile?.id === object.id && selectedDeletedVersion === undefined const hasVersions = object.noncurrentVersions !== undefined && object.noncurrentVersions.length > 0 - const isExpanded = expandedIds.has(object.id) + const isExpanded = expandedVersionIds.has(object.id) return ( @@ -540,14 +512,14 @@ const NoncurrentVersionRow = ({ {/* Tree connector: vertical line */}
{/* Tree connector: horizontal branch */} -
- {/* Content aligned with parent row text */} -
+
+ {/* Content aligned after horizontal branch with gap */} +
{shortId} ({version.action})
diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx index 3e9ef868771..71456953417 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx @@ -3,6 +3,8 @@ import { compact, isEqual, noop } from 'lodash' import { ArrowLeft, Check, + ChevronsDownUp, + ChevronsUpDown, Columns, Edit2, FolderPlus, @@ -50,6 +52,8 @@ import { useTrack } from '@/lib/telemetry/track' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer' +import { useBucketTrashQuery } from '@/data/storage/protection/bucket-trash-query' + import { useSelectedBucket } from '../FilesBuckets/useSelectedBucket' import { STORAGE_SORT_BY, STORAGE_SORT_BY_ORDER, STORAGE_VIEWS } from '../Storage.constants' import { hasVersioningHistory } from '../StorageProtection.constants' @@ -293,7 +297,19 @@ export const FileExplorerHeader = ({ const { data: bucket } = useSelectedBucket() const isVersioned = hasVersioningHistory(bucket?.id) - const { isShowingDeleted, setIsShowingDeleted } = useDeletedFilesContext() + const { isShowingDeleted, setIsShowingDeleted, expandedVersionIds, setExpandedVersionIds } = + useDeletedFilesContext() + + const { data: trashObjects } = useBucketTrashQuery({ + projectRef: projectRef ?? undefined, + bucketId: bucket?.id, + }) + const expandableIds = (trashObjects ?? []) + .filter((o) => o.noncurrentVersions && o.noncurrentVersions.length > 0) + .map((o) => o.id) + const hasExpandableVersions = expandableIds.length > 0 + const isAllExpanded = + hasExpandableVersions && expandableIds.every((id) => expandedVersionIds.has(id)) const refreshData = async () => { await refreshAll() @@ -454,6 +470,29 @@ export const FileExplorerHeader = ({ )}
+ {isShowingDeleted && hasExpandableVersions && ( +
+ + +
+ )} + {!isShowingDeleted && (
diff --git a/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx b/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx index e437a58e47e..5dcbd9caa62 100644 --- a/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx +++ b/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx @@ -231,14 +231,14 @@ const NoncurrentVersionRow = ({ {/* Tree connector: vertical line */}
{/* Tree connector: horizontal branch */} -
+
{/* Content aligned with parent row text */} -
+
{shortId} ({version.action})