Skip to content

Commit

Permalink
feat(renterd): directory mode files multiselect and batch delete
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Oct 18, 2024
1 parent 70ee5a4 commit 474ab7c
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 32 deletions.
2 changes: 1 addition & 1 deletion apps/renterd/components/Files/BucketContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function BucketContextMenu({ name }: Props) {
return (
<DropdownMenu
trigger={
<Button variant="ghost" icon="hover">
<Button size="none" variant="ghost" icon="hover">
<BucketIcon size={16} />
</Button>
}
Expand Down
1 change: 1 addition & 0 deletions apps/renterd/components/Files/DirectoryContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function DirectoryContextMenu({ path, size }: Props) {
trigger={
<Button
aria-label="Directory context menu"
size="none"
variant="ghost"
icon="hover"
>
Expand Down
7 changes: 6 additions & 1 deletion apps/renterd/components/Files/FileContextMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ export function FileContextMenu({ trigger, path, contentProps }: Props) {
<DropdownMenu
trigger={
trigger || (
<Button aria-label="File context menu" variant="ghost" icon="hover">
<Button
size="none"
aria-label="File context menu"
variant="ghost"
icon="hover"
>
<Document16 />
</Button>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
Button,
Paragraph,
triggerSuccessToast,
triggerErrorToast,
} from '@siafoundation/design-system'
import { Delete16 } from '@siafoundation/react-icons'
import { useCallback, useMemo } from 'react'
import { useDialog } from '../../../contexts/dialog'
import { useFilesDirectory } from '../../../contexts/filesDirectory'
import { useObjectsRemove } from '@siafoundation/renterd-react'

export function FilesDirectoryBatchDelete() {
const { multiSelect } = useFilesDirectory()

const filesToDelete = useMemo(
() =>
Object.entries(multiSelect.selectionMap).map(([_, item]) => ({
bucket: item.bucket.name,
prefix: item.key,
})),
[multiSelect.selectionMap]
)
const { openConfirmDialog } = useDialog()
const objectsRemove = useObjectsRemove()
const deleteFiles = useCallback(async () => {
const totalCount = filesToDelete.length
let errorCount = 0
for (const { bucket, prefix } of filesToDelete) {
const response = await objectsRemove.post({
payload: {
bucket,
prefix,
},
})
if (response.error) {
errorCount++
}
}
if (errorCount > 0) {
triggerErrorToast({
title: `${totalCount - errorCount} files deleted`,
body: `Error deleting ${errorCount}/${totalCount} total files.`,
})
} else {
triggerSuccessToast({ title: `${totalCount} files deleted` })
}
multiSelect.deselectAll()
}, [multiSelect, filesToDelete, objectsRemove])

return (
<Button
aria-label="delete selected files"
tip="Delete selected files"
onClick={() => {
openConfirmDialog({
title: `Delete files`,
action: 'Delete',
variant: 'red',
body: (
<div className="flex flex-col gap-1">
<Paragraph size="14">
Are you sure you would like to delete the{' '}
{multiSelect.selectionCount.toLocaleString()} selected files?
</Paragraph>
</div>
),
onConfirm: async () => {
deleteFiles()
},
})
}}
>
<Delete16 />
</Button>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MultiSelectionMenu } from '@siafoundation/design-system'
import { FilesDirectoryBatchDelete } from './FilesDirectoryBatchDelete'
import { useFilesDirectory } from '../../../contexts/filesDirectory'

export function FilesDirectoryBatchMenu() {
const { multiSelect } = useFilesDirectory()

return (
<MultiSelectionMenu multiSelect={multiSelect} entityWord="file">
<FilesDirectoryBatchDelete />
</MultiSelectionMenu>
)
}
2 changes: 2 additions & 0 deletions apps/renterd/components/FilesDirectory/FilesExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function FilesExplorer() {
datasetPage,
pageCount,
dataState,
cellContext,
onDragEnd,
onDragOver,
onDragStart,
Expand All @@ -40,6 +41,7 @@ export function FilesExplorer() {
emptyState={<EmptyState />}
pageSize={10}
data={datasetPage}
context={cellContext}
columns={columns}
sortableColumns={sortableColumns}
sortField={sortField}
Expand Down
2 changes: 2 additions & 0 deletions apps/renterd/components/FilesDirectory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RenterdAuthedLayout } from '../RenterdAuthedLayout'
import { FilesActionsMenu } from './FilesActionsMenu'
import { FilesStatsMenu } from './FilesStatsMenu'
import { FilesExplorer } from './FilesExplorer'
import { FilesDirectoryBatchMenu } from './FilesDirectoryBatchMenu'

export function FilesDirectory() {
const { openDialog } = useDialog()
Expand All @@ -21,6 +22,7 @@ export function FilesDirectory() {
actions={<FilesActionsMenu />}
openSettings={() => openDialog('settings')}
>
<FilesDirectoryBatchMenu />
<div className="p-6 min-w-fit">
<FilesExplorer />
</div>
Expand Down
4 changes: 3 additions & 1 deletion apps/renterd/components/FilesFlat/FilesExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import { columns } from '../../contexts/filesFlat/columns'

export function FilesExplorer() {
const { sortableColumns, toggleSort } = useFilesManager()
const { datasetPage, dataState, sortField, sortDirection } = useFilesFlat()
const { datasetPage, dataState, cellContext, sortField, sortDirection } =
useFilesFlat()
return (
<div className="relative">
<Table
isLoading={dataState === 'loading'}
emptyState={<EmptyState />}
pageSize={10}
data={datasetPage}
context={cellContext}
columns={columns}
sortableColumns={sortableColumns}
sortField={sortField}
Expand Down
26 changes: 23 additions & 3 deletions apps/renterd/contexts/filesDirectory/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Button, Text, Tooltip, ValueNum } from '@siafoundation/design-system'
import {
Button,
Checkbox,
ControlGroup,
Text,
Tooltip,
ValueNum,
} from '@siafoundation/design-system'
import {
Document16,
Earth16,
Expand All @@ -20,21 +27,31 @@ export const columns: FilesTableColumn[] = [
id: 'type',
label: '',
fixed: true,
cellClassName: 'w-[50px] !pl-2 !pr-2 [&+*]:!pl-0',
contentClassName: '!pl-3 !pr-4',
cellClassName: 'w-[20px] !pl-0 !pr-0',
heading: ({ context: { multiSelect } }) => (
<ControlGroup className="flex h-4">
<Checkbox
onClick={multiSelect.onSelectPage}
checked={multiSelect.isPageAllSelected}
/>
</ControlGroup>
),
render: function TypeColumn({
data: { isUploading, type, name, path, size },
}) {
const { setActiveDirectory } = useFilesManager()
if (isUploading) {
return (
<Button variant="ghost" state="waiting">
<Button size="none" variant="ghost" state="waiting">
<Document16 />
</Button>
)
}
if (name === '..') {
return (
<Button
size="none"
variant="ghost"
icon="hover"
onClick={(e) => {
Expand Down Expand Up @@ -70,6 +87,7 @@ export const columns: FilesTableColumn[] = [
color="accent"
weight="semibold"
className="cursor-pointer"
underline="hover"
onClick={(e) => {
e.stopPropagation()
setActiveDirectoryAndFileNamePrefix([name], '')
Expand All @@ -87,6 +105,7 @@ export const columns: FilesTableColumn[] = [
color="accent"
weight="semibold"
className="cursor-pointer"
underline="hover"
onClick={(e) => {
e.stopPropagation()
setActiveDirectory((p) => p.slice(0, -1))
Expand All @@ -102,6 +121,7 @@ export const columns: FilesTableColumn[] = [
color="accent"
weight="semibold"
className="cursor-pointer"
underline="hover"
onClick={(e) => {
e.stopPropagation()
setActiveDirectory((p) => p.concat(name.slice(0, -1)))
Expand Down
75 changes: 60 additions & 15 deletions apps/renterd/contexts/filesDirectory/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { useDatasetEmptyState } from '@siafoundation/design-system'
import { createContext, useContext, useMemo } from 'react'
import { ObjectData } from '../filesManager/types'
import {
useDatasetEmptyState,
useMultiSelect,
} from '@siafoundation/design-system'
import {
createContext,
MouseEvent,
useContext,
useEffect,
useMemo,
} from 'react'
import { CellContext, ObjectData } from '../filesManager/types'
import { useDataset } from './dataset'
import { useMove } from './move'
import { useFilesManager } from '../filesManager'
Expand All @@ -9,8 +18,7 @@ import { columns } from './columns'
function useFilesDirectoryMain() {
const {
activeDirectory,
activeBucketName: activeBucket,
activeDirectoryPath,
activeBucket,
setActiveDirectory,
filters,
enabledColumns,
Expand All @@ -32,17 +40,20 @@ function useFilesDirectoryMain() {
refresh,
})

// Add parent directory to the dataset
// Add parent directory to the dataset.
const _datasetPage = useMemo(() => {
if (!dataset) {
return null
return undefined
}
if (activeDirectory.length > 0 && dataset.length > 0) {
return [
{
bucket: activeBucket,
id: '..',
name: '..',
path: '..',
key: '..',
size: 0,
type: 'directory',
onClick: () => {
setActiveDirectory((p) => p.slice(0, -1))
Expand All @@ -57,12 +68,40 @@ function useFilesDirectoryMain() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataset])

// Add drag and drop properties to the dataset
const datasetPage = useMemo(() => {
const multiSelect = useMultiSelect(_datasetPage)

// If the active bucket changes, clear the multi-select.
useEffect(() => {
if (activeBucket) {
multiSelect.deselectAll()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeBucket])

const datasetPageWithOnClick = useMemo(() => {
if (!_datasetPage) {
return undefined
}
return _datasetPage.map((d) => {
return _datasetPage.map((datum) => {
if (datum.type === 'bucket') {
return datum
}
return {
...datum,
isSelected: !!multiSelect.selectionMap[datum.id],
onClick: (e: MouseEvent<HTMLTableRowElement>) =>
multiSelect.onSelect(datum.id, e),
}
return datum
})
}, [_datasetPage, multiSelect])

// Add drag and drop properties to the dataset.
const datasetPage = useMemo(() => {
if (!datasetPageWithOnClick) {
return undefined
}
return datasetPageWithOnClick.map((d) => {
if (
draggingObject &&
draggingObject.id !== d.id &&
Expand All @@ -78,7 +117,7 @@ function useFilesDirectoryMain() {
isDraggable: d.type !== 'bucket' && !d.isUploading,
}
})
}, [_datasetPage, draggingObject])
}, [datasetPageWithOnClick, draggingObject])

const dataState = useDatasetEmptyState(
dataset,
Expand All @@ -95,13 +134,19 @@ function useFilesDirectoryMain() {
[enabledColumns]
)

const cellContext = useMemo(
() =>
({
multiSelect,
} as CellContext),
[multiSelect]
)

return {
activeBucket,
activeDirectory,
setActiveDirectory,
activeDirectoryPath,
dataState,
columns: filteredTableColumns,
multiSelect,
cellContext,
refresh,
limit,
marker,
Expand Down
Loading

0 comments on commit 474ab7c

Please sign in to comment.