Skip to content

Commit 96ae12e

Browse files
authored
feat: add safe object copy and move actions (#221)
1 parent a63eda8 commit 96ae12e

19 files changed

Lines changed: 1643 additions & 18 deletions

components/object/list.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
RiFileLine,
1313
RiEyeLine,
1414
RiEdit2Line,
15+
RiFileCopyLine,
16+
RiFolderTransferLine,
1517
RiArrowUpSLine,
1618
RiArrowDownSLine,
1719
} from "@remixicon/react"
@@ -77,6 +79,7 @@ import {
7779
type ObjectRenameValidation,
7880
} from "@/lib/object-rename"
7981
import { TaskStatsButton } from "@/components/tasks/stats-button"
82+
import { ObjectTransferDialog } from "@/components/object/transfer-dialog"
8083
import { useAddDeleteKeys, useAddDeleteFolder, useTasks } from "@/contexts/task-context"
8184
import type { ColumnDef } from "@tanstack/react-table"
8285

@@ -151,6 +154,12 @@ export function ObjectList({
151154
const [renameSourceKey, setRenameSourceKey] = React.useState("")
152155
const [renameName, setRenameName] = React.useState("")
153156
const [renameSubmitting, setRenameSubmitting] = React.useState(false)
157+
const [transfer, setTransfer] = React.useState<{
158+
mode: "copy" | "move"
159+
key: string
160+
trigger: HTMLElement
161+
} | null>(null)
162+
const refreshButtonRef = React.useRef<HTMLButtonElement>(null)
154163

155164
const prefix = decodeURIComponent(path)
156165
const resolvedPageSize = resolveObjectListPageSize(pageSize)
@@ -522,7 +531,7 @@ export function ObjectList({
522531
header: () => t("Actions"),
523532
enableSorting: false,
524533
cell: ({ row }) => (
525-
<div className="flex items-center gap-2">
534+
<div className="flex flex-wrap items-center gap-2 [&_button]:min-h-11 sm:[&_button]:min-h-0">
526535
{row.original.type === "object" ? (
527536
<>
528537
{canCapability("objects.preview", { bucket, objectKey: row.original.Key }) ? (
@@ -549,6 +558,30 @@ export function ObjectList({
549558
<span>{t("Rename")}</span>
550559
</Button>
551560
) : null}
561+
{canCapability("objects.copy", { bucket, objectKey: row.original.Key }) ? (
562+
<Button
563+
variant="outline"
564+
size="sm"
565+
onClick={(event) =>
566+
setTransfer({ mode: "copy", key: row.original.Key, trigger: event.currentTarget })
567+
}
568+
>
569+
<RiFileCopyLine data-icon="inline-start" aria-hidden />
570+
<span>{t("Copy")}</span>
571+
</Button>
572+
) : null}
573+
{canCapability("objects.move", { bucket, objectKey: row.original.Key }) ? (
574+
<Button
575+
variant="outline"
576+
size="sm"
577+
onClick={(event) =>
578+
setTransfer({ mode: "move", key: row.original.Key, trigger: event.currentTarget })
579+
}
580+
>
581+
<RiFolderTransferLine data-icon="inline-start" aria-hidden />
582+
<span>{t("Move")}</span>
583+
</Button>
584+
) : null}
552585
</>
553586
) : null}
554587
{canCapability("objects.delete", { bucket, objectKey: row.original.Key }) ? (
@@ -834,7 +867,11 @@ export function ObjectList({
834867
</span>
835868
</Button>
836869
) : null}
837-
<Button variant="outline" onClick={() => (onRefresh ? onRefresh() : resetAndFetchObjects())}>
870+
<Button
871+
ref={refreshButtonRef}
872+
variant="outline"
873+
onClick={() => (onRefresh ? onRefresh() : resetAndFetchObjects())}
874+
>
838875
<RiRefreshLine className="size-4" aria-hidden />
839876
<span>{t("Refresh")}</span>
840877
</Button>
@@ -988,6 +1025,17 @@ export function ObjectList({
9881025
</AlertDialogContent>
9891026
</AlertDialog>
9901027

1028+
{transfer ? (
1029+
<ObjectTransferDialog
1030+
mode={transfer.mode}
1031+
bucket={bucket}
1032+
objectKey={transfer.key}
1033+
returnFocus={() => (transfer.trigger.isConnected ? transfer.trigger : refreshButtonRef.current)}
1034+
onClose={() => setTransfer(null)}
1035+
onRefresh={resetAndFetchObjects}
1036+
/>
1037+
) : null}
1038+
9911039
<Dialog open={renameDialogOpen} onOpenChange={handleRenameOpenChange} disablePointerDismissal>
9921040
<DialogContent className="sm:max-w-md">
9931041
<DialogHeader>

0 commit comments

Comments
 (0)