mirror of
https://github.com/rustfs/console.git
synced 2026-09-01 15:17:45 +08:00
Merge branch 'main' of github.com:rustfs/console
This commit is contained in:
@@ -13,6 +13,7 @@ import { ObjectUploadPicker } from "@/components/object/upload-picker"
|
||||
import { useBucket } from "@/hooks/use-bucket"
|
||||
import { useMessage } from "@/lib/feedback/message"
|
||||
import { buildBucketPath } from "@/lib/bucket-path"
|
||||
import { useTasks } from "@/contexts/task-context"
|
||||
|
||||
interface BrowserContentProps {
|
||||
bucketName: string
|
||||
@@ -100,6 +101,37 @@ export function BrowserContent({ bucketName, keyPath = "", preview = false, prev
|
||||
setRefreshTrigger((n) => n + 1)
|
||||
}
|
||||
|
||||
const tasks = useTasks()
|
||||
const debounceTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const prevCompletedIdsRef = React.useRef(new Set<string>())
|
||||
|
||||
React.useEffect(() => {
|
||||
const currentIds = new Set(tasks.map((t) => t.id))
|
||||
for (const id of prevCompletedIdsRef.current) {
|
||||
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",
|
||||
)
|
||||
const newCompletions = completedForBucket.filter((t) => !prevCompletedIdsRef.current.has(t.id))
|
||||
if (newCompletions.length > 0) {
|
||||
newCompletions.forEach((t) => prevCompletedIdsRef.current.add(t.id))
|
||||
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current)
|
||||
debounceTimerRef.current = setTimeout(() => {
|
||||
setRefreshTrigger((n) => n + 1)
|
||||
}, 1500)
|
||||
}
|
||||
}, [tasks, bucketName, setRefreshTrigger])
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader>
|
||||
@@ -152,7 +184,6 @@ export function BrowserContent({ bucketName, keyPath = "", preview = false, prev
|
||||
onShowChange={setUploadPickerOpen}
|
||||
bucketName={bucketName}
|
||||
prefix={prefix}
|
||||
onSuccess={handleRefresh}
|
||||
/>
|
||||
</Page>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { RiFileCopyLine } from "@remixicon/react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { copyToClipboard } from "@/lib/clipboard"
|
||||
import { useMessage } from "@/lib/feedback/message"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
@@ -29,8 +30,7 @@ export function CopyInput({
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
if (!value) throw new Error("No value to copy")
|
||||
await navigator.clipboard.writeText(value)
|
||||
await copyToClipboard(value)
|
||||
message.success(t("Copy Success"))
|
||||
} catch {
|
||||
message.error(t("Copy Failed"))
|
||||
|
||||
@@ -438,7 +438,6 @@ export function ObjectList({
|
||||
message.success(t("Delete task created"))
|
||||
}
|
||||
table.resetRowSelection()
|
||||
;(onRefresh ?? fetchObjects)()
|
||||
} catch (err) {
|
||||
message.error((err as Error)?.message ?? t("Delete Failed"))
|
||||
}
|
||||
@@ -454,7 +453,6 @@ export function ObjectList({
|
||||
message.success(t("Delete task created"))
|
||||
}
|
||||
table.resetRowSelection()
|
||||
;(onRefresh ?? fetchObjects)()
|
||||
} catch (err) {
|
||||
message.error((err as Error)?.message ?? t("Delete Failed"))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { copyToClipboard } from "@/lib/clipboard"
|
||||
import { RiFileCopyLine, RiCheckLine } from "@remixicon/react"
|
||||
|
||||
interface Segment {
|
||||
@@ -21,7 +22,7 @@ function useClipboard(value: string, copiedDuring = 3000) {
|
||||
|
||||
const copy = React.useCallback(async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value)
|
||||
await copyToClipboard(value)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), copiedDuring)
|
||||
} catch {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DataTable } from "@/components/data-table/data-table"
|
||||
import { useDataTable } from "@/hooks/use-data-table"
|
||||
import { useObject } from "@/hooks/use-object"
|
||||
import { useMessage } from "@/lib/feedback/message"
|
||||
import { copyToClipboard } from "@/lib/clipboard"
|
||||
import { exportFile } from "@/lib/export-file"
|
||||
import { getContentType } from "@/lib/mime-types"
|
||||
import { formatBytes } from "@/lib/functions"
|
||||
@@ -72,7 +73,7 @@ export function ObjectVersions({
|
||||
async (versionId: string) => {
|
||||
if (!versionId) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(versionId)
|
||||
await copyToClipboard(versionId)
|
||||
message.success(t("Copy Success"))
|
||||
} catch {
|
||||
message.error(t("Copy Failed"))
|
||||
|
||||
@@ -27,6 +27,7 @@ export type AnyTask = {
|
||||
displayName: string
|
||||
subInfo: string
|
||||
actionLabel: string
|
||||
bucketName?: string
|
||||
}
|
||||
|
||||
const emptyTasks: AnyTask[] = []
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
export async function copyToClipboard(value: string): Promise<void> {
|
||||
if (!value) throw new Error("No value to copy")
|
||||
|
||||
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value)
|
||||
return
|
||||
} catch (error) {
|
||||
// Fallback to legacy copy only when Clipboard API is denied or unsupported in current context
|
||||
const fallback = legacyCopyToClipboard(value)
|
||||
if (fallback) return
|
||||
if (error instanceof Error) throw error
|
||||
throw new Error("Failed to copy text")
|
||||
}
|
||||
}
|
||||
|
||||
const fallback = legacyCopyToClipboard(value)
|
||||
if (fallback) return
|
||||
|
||||
throw new Error("Failed to copy text")
|
||||
}
|
||||
|
||||
function legacyCopyToClipboard(value: string): boolean {
|
||||
if (typeof document === "undefined" || !document?.execCommand) return false
|
||||
|
||||
const textarea = document.createElement("textarea")
|
||||
textarea.value = value
|
||||
textarea.setAttribute("readonly", "")
|
||||
textarea.style.position = "fixed"
|
||||
textarea.style.top = "0"
|
||||
textarea.style.left = "0"
|
||||
textarea.style.opacity = "0"
|
||||
textarea.style.pointerEvents = "none"
|
||||
textarea.style.zIndex = "-1"
|
||||
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
|
||||
let copied = false
|
||||
try {
|
||||
copied = document.execCommand("copy")
|
||||
} catch {
|
||||
copied = false
|
||||
} finally {
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
|
||||
return copied
|
||||
}
|
||||
Reference in New Issue
Block a user