Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions components/object/info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie
),
])
const data = { ...head, SignedUrl: signed }
setShowVersions(false)
onPreview({ key, data })
} catch (err) {
message.error((err as Error)?.message ?? t("Failed to fetch versions"))
Expand Down Expand Up @@ -326,16 +327,24 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie
}
}

const submitTagForm = async (e?: React.FormEvent | React.MouseEvent<HTMLButtonElement>) => {
e?.preventDefault()
if (!canEditObjectTags || !object?.Key) return
if (!tagFormValue.Key || !tagFormValue.Value) {
const submitTagForm = async () => {
if (!canEditObjectTags) return
if (!resolvedObjectKey) {
message.error(t("Failed to fetch object info"))
return
}

const nextTag = {
Key: tagFormValue.Key.trim(),
Value: tagFormValue.Value.trim(),
}
if (!nextTag.Key || !nextTag.Value) {
message.error(t("Please fill in the correct format"))
return
}
try {
const nextTags = [...tags, { ...tagFormValue }]
await objectApi.putObjectTags(object.Key as string, nextTags)
const nextTags = [...tags, nextTag]
await objectApi.putObjectTags(resolvedObjectKey, nextTags)
setTags(nextTags)
setTagFormValue({ Key: "", Value: "" })
message.success(t("Create Success"))
Expand All @@ -355,10 +364,10 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie
}

const removeTag = async (tagKey: string) => {
if (!canEditObjectTags || !object?.Key) return
if (!canEditObjectTags || !resolvedObjectKey) return
try {
const nextTags = tags.filter((tag) => tag.Key !== tagKey)
await objectApi.putObjectTags(object.Key as string, nextTags)
await objectApi.putObjectTags(resolvedObjectKey, nextTags)
setTags(nextTags)
message.success(t("Delete Success"))
} catch (err) {
Expand Down Expand Up @@ -426,8 +435,8 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie

return (
<>
<Drawer open={open} onOpenChange={(nextOpen) => !showRetentionView && onOpenChange(nextOpen)} direction="right">
<DrawerContent className="max-h-[95vh] overflow-y-auto overflow-x-hidden data-[vaul-drawer-direction=right]:w-[92vw] data-[vaul-drawer-direction=right]:sm:max-w-2xl">
<Drawer open={open} onOpenChange={onOpenChange} direction="right">
<DrawerContent className="overflow-y-auto overflow-x-hidden data-[vaul-drawer-direction=right]:w-[92vw] data-[vaul-drawer-direction=right]:sm:max-w-2xl">
<DrawerHeader>
<DrawerTitle>{t("Object Details")}</DrawerTitle>
<DrawerDescription />
Expand Down Expand Up @@ -635,7 +644,7 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie
</DrawerContent>
</Drawer>

<Dialog open={showTagView} onOpenChange={setShowTagView}>
<Dialog open={showTagView} onOpenChange={setShowTagView} disablePointerDismissal>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>{t("Set Tags")}</DialogTitle>
Expand All @@ -657,7 +666,13 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie
</Badge>
))}
</div>
<form className="space-y-4" onSubmit={submitTagForm}>
<form
className="space-y-4"
onSubmit={(e) => {
e.preventDefault()
void submitTagForm()
}}
>
<div className="grid gap-4 sm:grid-cols-2">
<Field>
<FieldLabel>{t("Tag Key")}</FieldLabel>
Expand Down Expand Up @@ -688,7 +703,12 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie
</Field>
</div>
<div className="flex justify-end">
<Button type="button" variant="default" onClick={submitTagForm} disabled={!canEditObjectTags}>
<Button
type="button"
variant="default"
onClick={() => void submitTagForm()}
disabled={!canEditObjectTags || !resolvedObjectKey}
>
{t("Add")}
</Button>
</div>
Expand All @@ -697,7 +717,7 @@ export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPrevie
</DialogContent>
</Dialog>

<Dialog open={showRetentionView} onOpenChange={setShowRetentionView}>
<Dialog open={showRetentionView} onOpenChange={setShowRetentionView} disablePointerDismissal>
<DialogContent className="sm:max-w-lg">
<div ref={retentionDialogContentRef} className="contents">
<DialogHeader>
Expand Down
2 changes: 1 addition & 1 deletion components/object/versions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export function ObjectVersions({
})

return (
<Dialog open={visible} onOpenChange={(open) => !open && onClose()}>
<Dialog open={visible} onOpenChange={(open) => !open && onClose()} disablePointerDismissal>
<DialogContent className="sm:max-w-4xl max-h-[80vh] overflow-auto">
<DialogHeader>
<DialogTitle>{t("Object Versions")}</DialogTitle>
Expand Down
2 changes: 1 addition & 1 deletion components/tasks/stats-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function TaskStatsButton() {

return (
<Drawer open={isTaskPanelOpen} onOpenChange={setTaskPanelOpen} direction="right">
<DrawerTrigger>
<DrawerTrigger asChild>
<Button variant="outline">
{processing.length > 0 ? (
<div className="flex items-center gap-2">
Expand Down
4 changes: 2 additions & 2 deletions components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props)
<DialogPrimitive.Backdrop
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
"fixed inset-0 isolate z-50 pointer-events-auto bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className,
)}
{...props}
Expand All @@ -50,7 +50,7 @@ function DialogContent({
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
"fixed top-1/2 start-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 rtl:translate-x-1/2 -translate-y-1/2 gap-4 rounded-none bg-popover p-4 text-xs/relaxed text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
"fixed top-1/2 start-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 rtl:translate-x-1/2 -translate-y-1/2 gap-4 rounded-none bg-popover p-4 text-xs/relaxed text-popover-foreground ring-1 ring-foreground/10 pointer-events-auto duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className,
)}
{...props}
Expand Down
2 changes: 1 addition & 1 deletion components/ui/drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function DrawerContent({ className, children, ...props }: React.ComponentProps<t
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"group/drawer-content fixed z-50 flex h-auto flex-col bg-popover text-xs/relaxed text-popover-foreground data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-none data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:start-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-none data-[vaul-drawer-direction=left]:border-e data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:end-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-none data-[vaul-drawer-direction=right]:border-s data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-none data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
"group/drawer-content fixed z-50 flex h-auto flex-col bg-popover text-xs/relaxed text-popover-foreground data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-none data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:start-0 data-[vaul-drawer-direction=left]:h-dvh data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-none data-[vaul-drawer-direction=left]:border-e data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:end-0 data-[vaul-drawer-direction=right]:h-dvh data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-none data-[vaul-drawer-direction=right]:border-s data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-none data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
className,
)}
{...props}
Expand Down
9 changes: 9 additions & 0 deletions tests/lib/dialog-source.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import test from "node:test"
import assert from "node:assert/strict"
import fs from "node:fs"

test("dialog overlay and content remain clickable above modal parents", () => {
const source = fs.readFileSync("components/ui/dialog.tsx", "utf8")

assert.equal(source.includes("pointer-events-auto"), true)
})
10 changes: 10 additions & 0 deletions tests/lib/drawer-source.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import test from "node:test"
import assert from "node:assert/strict"
import fs from "node:fs"

test("right and left drawers fill the viewport height", () => {
const source = fs.readFileSync("components/ui/drawer.tsx", "utf8")

assert.equal(source.includes("data-[vaul-drawer-direction=right]:h-dvh"), true)
assert.equal(source.includes("data-[vaul-drawer-direction=left]:h-dvh"), true)
})
27 changes: 26 additions & 1 deletion tests/lib/object-info-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,32 @@ test("object info retention confirm uses an explicit click handler", () => {
assert.equal(source.includes("legalHoldEnabled: lockStatus"), true)
assert.equal(source.includes("<DateTimePicker"), true)
assert.equal(source.includes("portalContainer={retentionDialogContentRef}"), true)
assert.equal(source.includes("!showRetentionView && onOpenChange(nextOpen)"), true)
assert.match(source, /onClick=\{\(\) => void submitRetention\(\)\}/)
assert.match(source, /objectApi\.putObjectRetention\(resolvedObjectKey/)
})

test("object info tag add uses the resolved object key and an explicit click handler", () => {
const source = fs.readFileSync("components/object/info.tsx", "utf8")

assert.match(source, /objectApi\.putObjectTags\(resolvedObjectKey/)
assert.match(source, /onSubmit=\{\(e\) => \{\s*e\.preventDefault\(\)\s*void submitTagForm\(\)\s*\}\}/)
assert.match(source, /onClick=\{\(\) => void submitTagForm\(\)\}/)
})

test("object info drawer stays open while the tag dialog is open", () => {
const source = fs.readFileSync("components/object/info.tsx", "utf8")

assert.equal(source.includes("modal={false}"), false)
assert.equal(source.includes("modal={!showTagView && !showRetentionView}"), false)
assert.match(source, /<Dialog open=\{showTagView\} onOpenChange=\{setShowTagView\} disablePointerDismissal>/)
assert.match(
source,
/<Dialog open=\{showRetentionView\} onOpenChange=\{setShowRetentionView\} disablePointerDismissal>/,
)
})

test("object info closes the versions dialog before opening a version preview", () => {
const source = fs.readFileSync("components/object/info.tsx", "utf8")

assert.match(source, /setShowVersions\(false\)\s+onPreview\(\{ key, data \}\)/)
})
9 changes: 9 additions & 0 deletions tests/lib/task-stats-button-source.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import test from "node:test"
import assert from "node:assert/strict"
import fs from "node:fs"

test("task stats drawer trigger renders the button as its child", () => {
const source = fs.readFileSync("components/tasks/stats-button.tsx", "utf8")

assert.match(source, /<DrawerTrigger\s+asChild>/)
})
Loading