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
62 changes: 20 additions & 42 deletions app/(dashboard)/browser/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import { useMessage } from "@/lib/feedback/message"
import { buildBucketPath } from "@/lib/bucket-path"
import { useTasks } from "@/contexts/task-context"
import { ObjectPreviewModal } from "@/components/object/preview-modal"
import { useObject } from "@/hooks/use-object"

interface BrowserContentProps {
bucketName: string
Expand All @@ -22,7 +24,7 @@
previewKey?: string
}

export function BrowserContent({ bucketName, keyPath = "", preview = false, previewKey = "" }: BrowserContentProps) {

Check warning on line 27 in app/(dashboard)/browser/content.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

'previewKey' is assigned a value but never used

Check warning on line 27 in app/(dashboard)/browser/content.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

'preview' is assigned a value but never used

Check warning on line 27 in app/(dashboard)/browser/content.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

'previewKey' is assigned a value but never used

Check warning on line 27 in app/(dashboard)/browser/content.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

'preview' is assigned a value but never used

Check warning on line 27 in app/(dashboard)/browser/content.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality

'previewKey' is assigned a value but never used

Check warning on line 27 in app/(dashboard)/browser/content.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality

'preview' is assigned a value but never used
const { t } = useTranslation()
const router = useRouter()
const searchParams = useSearchParams()
Expand All @@ -34,18 +36,11 @@

const [infoOpen, setInfoOpen] = React.useState(false)
const [infoKey, setInfoKey] = React.useState<string | null>(null)
const [autoPreview, setAutoPreview] = React.useState(false)
const [uploadPickerOpen, setUploadPickerOpen] = React.useState(false)
const [refreshTrigger, setRefreshTrigger] = React.useState(0)

// Handle initial preview params - set infoOpen and trigger auto-preview
React.useEffect(() => {
if (preview && previewKey) {
setInfoKey(previewKey)
setInfoOpen(true)
setAutoPreview(true)
}
}, [preview, previewKey])
const [showPreview, setShowPreview] = React.useState(false)
const [previewObject, setPreviewObject] = React.useState<Record<string, unknown> | null>(null)
const objectApi = useObject(bucketName)

React.useEffect(() => {
if (!bucketName) return
Expand Down Expand Up @@ -82,40 +77,24 @@
setInfoOpen(true)
}

const updatePreviewParams = (showPreview: boolean, key?: string) => {
const currentParams = new URLSearchParams(searchParams.toString())
const params = new URLSearchParams(searchParams.toString())
if (showPreview && key) {
params.set("preview", "true")
params.set("previewKey", key)
} else {
params.delete("preview")
params.delete("previewKey")
}
if (params.toString() === currentParams.toString()) return
router.replace(`/browser?${params.toString()}`, { scroll: false })
}

const handleInfoOpenChange = (open: boolean) => {
setInfoOpen(open)
if (!open) {
setAutoPreview(false)
updatePreviewParams(false)
}
}

const handlePreviewChange = (showPreview: boolean) => {
if (showPreview && infoKey) {
updatePreviewParams(true, infoKey)
} else {
updatePreviewParams(false)
}
}

const handleRefresh = () => {
setRefreshTrigger((n) => n + 1)
}

const handleOpenPreview = React.useCallback(
async ({ key, data }: { key?: string; data?: Record<string, unknown> }) => {
if (!key) return
const info = data ?? (await objectApi.getObjectInfo(key))
setPreviewObject(info as Record<string, unknown>)
setShowPreview(true)
},
[objectApi],
)

const tasks = useTasks()
const debounceTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
const prevCompletedIdsRef = React.useRef(new Set<string>())
Expand All @@ -126,10 +105,7 @@
if (!currentIds.has(id)) prevCompletedIdsRef.current.delete(id)
}
const completedForBucket = tasks.filter(
(t) =>
(t.kind === "upload" || t.kind === "delete") &&
t.bucketName === bucketName &&
t.status === "completed",
(t) => (t.kind === "upload" || t.kind === "delete") && t.bucketName === bucketName && t.status === "completed",
)
const newCompletions = completedForBucket.filter((t) => !prevCompletedIdsRef.current.has(t.id))
if (newCompletions.length > 0) {
Expand Down Expand Up @@ -178,6 +154,7 @@
onUploadClick={() => setUploadPickerOpen(true)}
onRefresh={handleRefresh}
refreshTrigger={refreshTrigger}
onPreview={handleOpenPreview}
/>
) : (
<ObjectView bucketName={bucketName} objectKey={keyPath} />
Expand All @@ -190,8 +167,7 @@
open={infoOpen}
onOpenChange={handleInfoOpenChange}
onRefresh={handleRefresh}
autoPreview={autoPreview}
onPreviewChange={handlePreviewChange}
onPreview={handleOpenPreview}
/>

<ObjectUploadPicker
Expand All @@ -200,6 +176,8 @@
bucketName={bucketName}
prefix={prefix}
/>

<ObjectPreviewModal show={showPreview} onShowChange={(show) => setShowPreview(show)} object={previewObject} />
</Page>
)
}
75 changes: 5 additions & 70 deletions components/object/info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import { exportFile } from "@/lib/export-file"
import { getContentType } from "@/lib/mime-types"
import { ObjectVersions } from "@/components/object/versions"
import { ObjectPreviewModal } from "@/components/object/preview-modal"
import { GetObjectCommand, HeadObjectCommand } from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
import { useS3 } from "@/contexts/s3-context"
Expand All @@ -35,18 +34,10 @@
open: boolean
onOpenChange: (open: boolean) => void
onRefresh?: () => void
autoPreview?: boolean
onPreviewChange?: (showPreview: boolean) => void
onPreview: (params: { key?: string; data?: Record<string, unknown> }) => void
}

export function ObjectInfo({
bucketName,
objectKey,
open,
onOpenChange,
autoPreview = false,
onPreviewChange,
}: ObjectInfoProps) {
export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPreview }: ObjectInfoProps) {
const { t } = useTranslation()
const message = useMessage()
const dialog = useDialog()
Expand All @@ -62,9 +53,7 @@
const [signedUrl, setSignedUrl] = React.useState("")
const [showTagView, setShowTagView] = React.useState(false)
const [showRetentionView, setShowRetentionView] = React.useState(false)
const [showPreview, setShowPreview] = React.useState(false)
const [showVersions, setShowVersions] = React.useState(false)
const [previewObject, setPreviewObject] = React.useState<Record<string, unknown> | null>(null)
const [tagFormValue, setTagFormValue] = React.useState({
Key: "",
Value: "",
Expand Down Expand Up @@ -198,7 +187,6 @@

React.useEffect(() => {
if (open && objectKey) {
setPreviewObject(null)
const key = objectKey
loadObjectInfoRef
.current(key)
Expand All @@ -209,7 +197,6 @@
})
} else {
setObject(null)
setPreviewObject(null)
}
}, [open, objectKey, message, t])

Expand All @@ -230,50 +217,14 @@
}
}

// Auto-open preview once when autoPreview is true and object data is first loaded
const hasAutoPreviewedRef = React.useRef(false)
React.useEffect(() => {
if (!open) {
hasAutoPreviewedRef.current = false
return
}
if (autoPreview && object && !hasAutoPreviewedRef.current) {
hasAutoPreviewedRef.current = true
setShowPreview(true)
}
}, [autoPreview, object, open])

React.useEffect(() => {
return () => {
if (previewParamSyncTimerRef.current) {
clearTimeout(previewParamSyncTimerRef.current)

Check warning on line 223 in components/object/info.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

The ref value 'previewParamSyncTimerRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'previewParamSyncTimerRef.current' to a variable inside the effect, and use that variable in the cleanup function

Check warning on line 223 in components/object/info.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

The ref value 'previewParamSyncTimerRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'previewParamSyncTimerRef.current' to a variable inside the effect, and use that variable in the cleanup function

Check warning on line 223 in components/object/info.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality

The ref value 'previewParamSyncTimerRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'previewParamSyncTimerRef.current' to a variable inside the effect, and use that variable in the cleanup function
}
}
}, [])

const schedulePreviewParamSync = React.useCallback(
(show: boolean) => {
if (previewParamSyncTimerRef.current) {
clearTimeout(previewParamSyncTimerRef.current)
previewParamSyncTimerRef.current = null
}

previewParamSyncTimerRef.current = setTimeout(() => {
onPreviewChange?.(show)
previewParamSyncTimerRef.current = null
}, 140)
},
[onPreviewChange],
)

// Open preview dialog
const openPreview = () => {
if (!object) return
setPreviewObject(object)
setShowPreview(true)
schedulePreviewParamSync(true)
}

const handlePreviewVersion = async (versionId: string) => {
if (!object?.Key) return
try {
Expand All @@ -297,27 +248,12 @@
{ expiresIn: 3600 },
),
])
setPreviewObject({
...head,
Key: key,
SignedUrl: signed,
VersionId: version,
})
setShowPreview(true)
schedulePreviewParamSync(true)
const data = { ...head, SignedUrl: signed }
onPreview({ key, data })
} catch (err) {
message.error((err as Error)?.message ?? t("Failed to fetch versions"))
}
}

const handlePreviewShowChange = React.useCallback(
(show: boolean) => {
setShowPreview(show)
schedulePreviewParamSync(show)
},
[schedulePreviewParamSync],
)

const toggleLegalHold = async () => {
if (!object?.Key) return
try {
Expand Down Expand Up @@ -432,7 +368,7 @@
<RiDownloadLine className="size-4" />
{t("Download")}
</Button>
<Button variant="outline" size="sm" onClick={openPreview}>
<Button variant="outline" size="sm" onClick={() => onPreview({ key: String(object?.Key ?? "") })}>
<RiEyeLine className="size-4" />
{t("Preview")}
</Button>
Expand Down Expand Up @@ -723,7 +659,6 @@
</DialogContent>
</Dialog>

<ObjectPreviewModal show={showPreview} onShowChange={handlePreviewShowChange} object={previewObject ?? object} />
<ObjectVersions
bucketName={bucketName}
objectKey={(object?.Key as string) ?? ""}
Expand Down
18 changes: 11 additions & 7 deletions components/object/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
pageSize?: number
onRefresh?: () => void
refreshTrigger?: number
onPreview: (params: { key?: string; data?: Record<string, unknown> }) => void
}

export function ObjectList({
Expand All @@ -70,6 +71,7 @@
pageSize = 25,
onRefresh,
refreshTrigger = 0,
onPreview,
}: ObjectListProps) {
const { t } = useTranslation()
const message = useMessage()
Expand Down Expand Up @@ -286,13 +288,15 @@
<div className="flex items-center gap-2">
{row.original.type === "object" ? (
<>
<Button variant="outline" size="sm" asChild>
<Link
href={`/browser?bucket=${encodeURIComponent(bucket)}&key=${encodeURIComponent(prefix)}&preview=true&previewKey=${encodeURIComponent(row.original.Key)}`}
>
<RiEyeLine className="size-4" />
<span>{t("Preview")}</span>
</Link>
<Button
variant="outline"
size="sm"
onClick={() => {
onPreview({ key: row.original.Key })
}}
>
<RiEyeLine className="size-4" />
<span>{t("Preview")}</span>
</Button>
<Button variant="outline" size="sm" onClick={() => downloadFile(row.original.Key)}>
<RiDownloadCloud2Line className="size-4" />
Expand All @@ -308,7 +312,7 @@
),
},
],
[t, displayKey, bucketPath, onOpenInfo, bucket, prefix, downloadFile],

Check warning on line 315 in components/object/list.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

React Hook React.useMemo has a missing dependency: 'onPreview'. Either include it or remove the dependency array. If 'onPreview' changes too often, find the parent component that defines it and wrap that definition in useCallback

Check warning on line 315 in components/object/list.tsx

View workflow job for this annotation

GitHub Actions / 💅 Code Formatting

React Hook React.useMemo has a missing dependency: 'onPreview'. Either include it or remove the dependency array. If 'onPreview' changes too often, find the parent component that defines it and wrap that definition in useCallback

Check warning on line 315 in components/object/list.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality

React Hook React.useMemo has a missing dependency: 'onPreview'. Either include it or remove the dependency array. If 'onPreview' changes too often, find the parent component that defines it and wrap that definition in useCallback
)

const { table, selectedRowIds } = useDataTable<ObjectRow>({
Expand Down
Loading