Skip to content

Commit

Permalink
feat: renterd move files
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Feb 13, 2024
1 parent f23b1d6 commit e253b3e
Show file tree
Hide file tree
Showing 17 changed files with 979 additions and 296 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-bags-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renterd': minor
---

Files can now be moved by dragging into or out of directories.
5 changes: 5 additions & 0 deletions .changeset/sour-snakes-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/design-system': minor
---

The Table now supports drag and drop on rows.
12 changes: 12 additions & 0 deletions apps/renterd/components/Files/FilesExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export function FilesExplorer() {
sortDirection,
sortableColumns,
toggleSort,
onDragEnd,
onDragOver,
onDragStart,
onDragCancel,
onDragMove,
draggingObject,
} = useFiles()
const canUpload = useCanUpload()
return (
Expand All @@ -34,6 +40,12 @@ export function FilesExplorer() {
sortDirection={sortDirection}
toggleSort={toggleSort}
rowSize="dense"
onDragStart={onDragStart}
onDragOver={onDragOver}
onDragEnd={onDragEnd}
onDragCancel={onDragCancel}
onDragMove={onDragMove}
draggingDatum={draggingObject}
/>
</Dropzone>
</div>
Expand Down
13 changes: 12 additions & 1 deletion apps/renterd/contexts/files/dataset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useRouter } from 'next/router'
import { useMemo } from 'react'

type Props = {
setActiveDirectory: (func: (directory: string[]) => string[]) => void
activeDirectoryPath: string
uploadsList: ObjectData[]
sortDirection: 'asc' | 'desc'
Expand All @@ -34,6 +35,7 @@ type Props = {
const defaultLimit = 50

export function useDataset({
setActiveDirectory,
activeDirectoryPath,
uploadsList,
sortDirection,
Expand Down Expand Up @@ -107,19 +109,28 @@ export function useDataset({
size: 0,
health: 0,
name,
onClick: () => {
setActiveDirectory((p) => p.concat(name))
},
type: 'bucket',
}
})
} else if (response.data) {
response.data.entries?.forEach(({ name: key, size, health }) => {
const path = bucketAndResponseKeyToFilePath(activeBucketName, key)
const name = getFilename(key)
dataMap[path] = {
id: path,
path,
bucket: activeBucket,
size,
health,
name: getFilename(key),
name,
onClick: isDirectory(key)
? () => {
setActiveDirectory((p) => p.concat(name.slice(0, -1)))
}
: undefined,
type: isDirectory(key) ? 'directory' : 'file',
}
})
Expand Down
60 changes: 57 additions & 3 deletions apps/renterd/contexts/files/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import {
import { useRouter } from 'next/router'
import { createContext, useCallback, useContext, useMemo } from 'react'
import { columns } from './columns'
import { defaultSortField, columnsDefaultVisible, sortOptions } from './types'
import {
defaultSortField,
columnsDefaultVisible,
sortOptions,
ObjectData,
} from './types'
import {
FullPath,
FullPathSegments,
Expand All @@ -17,6 +22,7 @@ import {
import { useUploads } from './uploads'
import { useDownloads } from './downloads'
import { useDataset } from './dataset'
import { useMove } from './move'

function useFilesMain() {
const {
Expand Down Expand Up @@ -77,13 +83,29 @@ function useFilesMain() {

const { limit, offset, response, dataset } = useDataset({
activeDirectoryPath,
setActiveDirectory,
uploadsList,
sortField,
sortDirection,
filters,
})

const datasetPage = useMemo(() => {
const {
onDragEnd,
onDragOver,
onDragCancel,
onDragMove,
onDragStart,
draggingObject,
} = useMove({
dataset,
activeDirectory,
setActiveDirectory,
mutate: response.mutate,
})

// Add parent directory to the dataset
const _datasetPage = useMemo(() => {
if (!dataset) {
return null
}
Expand All @@ -94,7 +116,10 @@ function useFilesMain() {
name: '..',
path: '..',
type: 'directory',
},
onClick: () => {
setActiveDirectory((p) => p.slice(0, -1))
},
} as ObjectData,
...dataset,
]
}
Expand All @@ -104,6 +129,29 @@ function useFilesMain() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataset])

// Add drag and drop properties to the dataset
const datasetPage = useMemo(() => {
if (!_datasetPage) {
return null
}
return _datasetPage.map((d) => {
if (
draggingObject &&
draggingObject.id !== d.id &&
d.type === 'directory'
) {
return {
...d,
isDroppable: true,
}
}
return {
...d,
isDraggable: d.type !== 'bucket' && !d.isUploading,
}
})
}, [_datasetPage, draggingObject])

const filteredTableColumns = useMemo(
() =>
columns.filter(
Expand Down Expand Up @@ -177,6 +225,12 @@ function useFilesMain() {
sortDirection,
resetDefaultColumnVisibility,
getFileUrl,
onDragStart,
onDragEnd,
onDragMove,
onDragCancel,
onDragOver,
draggingObject,
}
}

Expand Down
143 changes: 143 additions & 0 deletions apps/renterd/contexts/files/move.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { ObjectData } from './types'
import { useCallback, useState } from 'react'
import {
DragStartEvent,
DragEndEvent,
DragOverEvent,
DragMoveEvent,
DragCancelEvent,
} from '@dnd-kit/core'
import { FullPathSegments, getDirectorySegmentsFromPath } from './paths'
import { useObjectRename } from '@siafoundation/react-renterd'
import { triggerErrorToast } from '@siafoundation/design-system'
import { getRenameParams } from './rename'

type Props = {
activeDirectory: FullPathSegments
setActiveDirectory: (
func: (directory: FullPathSegments) => FullPathSegments
) => void
dataset?: ObjectData[]
mutate: () => void
}

export function useMove({
dataset,
activeDirectory,
setActiveDirectory,
mutate,
}: Props) {
const [draggingObject, setDraggingObject] = useState<ObjectData | null>(null)
const [, setNavTimeout] = useState<NodeJS.Timeout>()
const rename = useObjectRename()

const moveFiles = useCallback(
async (e: DragEndEvent) => {
const { bucket, from, to, mode } = getRenameParams(e, activeDirectory)
if (from === to) {
return
}
const response = await rename.post({
payload: {
force: false,
bucket,
from,
to,
mode,
},
})
mutate()
if (response.error) {
triggerErrorToast(response.error)
}
},
[mutate, rename, activeDirectory]
)

const delayedNavigation = useCallback(
(directory?: FullPathSegments) => {
if (!directory) {
setNavTimeout((t) => {
if (t) {
clearTimeout(t)
}
return null
})
return
}
const newTimeout = setTimeout(() => {
setActiveDirectory(() => directory)
}, 1000)
setNavTimeout((t) => {
if (t) {
clearTimeout(t)
}
return newTimeout
})
},
[setNavTimeout, setActiveDirectory]
)

const scheduleNavigation = useCallback(
(e: { collisions: { id: string | number }[] }) => {
if (!e.collisions.length) {
delayedNavigation(undefined)
} else {
const path = e.collisions?.[0].id as string
if (path === '..') {
delayedNavigation(activeDirectory.slice(0, -1))
} else {
delayedNavigation(getDirectorySegmentsFromPath(path))
}
}
},
[delayedNavigation, activeDirectory]
)

const onDragStart = useCallback(
(e: DragStartEvent) => {
setDraggingObject(dataset.find((d) => d.id === e.active.id) || null)
},
[dataset, setDraggingObject]
)

const onDragOver = useCallback(
(e: DragOverEvent) => {
scheduleNavigation(e)
},
[scheduleNavigation]
)

const onDragMove = useCallback(
(e: DragMoveEvent) => {
scheduleNavigation(e)
},
[scheduleNavigation]
)

const onDragEnd = useCallback(
async (e: DragEndEvent) => {
delayedNavigation(undefined)
setDraggingObject(undefined)
moveFiles(e)
},
[setDraggingObject, delayedNavigation, moveFiles]
)

const onDragCancel = useCallback(
async (e: DragCancelEvent) => {
delayedNavigation(undefined)
setDraggingObject(undefined)
},
[setDraggingObject, delayedNavigation]
)

return {
onDragEnd,
onDragOver,
onDragCancel,
onDragMove,
onDragStart,
draggingObject,
}
}
8 changes: 7 additions & 1 deletion apps/renterd/contexts/files/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ export type FullPathSegments = string[]
export type FullPath = string
export type KeyPath = string

export function join(a: string, b: string): FullPath {
const _a = a.endsWith('/') ? a.slice(0, -1) : a
const _b = b.startsWith('/') ? b.slice(1) : b
return `${_a}/${_b}`
}

export function getFilePath(dirPath: FullPath, name: string): FullPath {
const n = name.startsWith('/') ? name.slice(1) : name
return dirPath + n
Expand All @@ -24,7 +30,7 @@ export function getBucketFromPath(path: FullPath): string {
return path.split('/')[0]
}

function getKeyFromPath(path: FullPath): KeyPath {
export function getKeyFromPath(path: FullPath): KeyPath {
const segsWithoutBucket = path.split('/').slice(1).join('/')
return `/${segsWithoutBucket}`
}
Expand Down
Loading

0 comments on commit e253b3e

Please sign in to comment.