|
|
|
@ -1,8 +1,8 @@ |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react"; |
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type ReactNode } from "react"; |
|
|
|
import { useReactFlow } from "@xyflow/react"; |
|
|
|
import { Button, Dropdown, Empty, Input, Modal, Spin, message } from "antd"; |
|
|
|
import { Breadcrumb, Button, Dropdown, Empty, Input, Modal, Spin, message } from "antd"; |
|
|
|
import type { MenuProps } from "antd"; |
|
|
|
import { |
|
|
|
AudioOutlined, |
|
|
|
@ -27,6 +27,7 @@ import { |
|
|
|
import { useI18n } from "@/i18n"; |
|
|
|
import { useWorkflowStore } from "@/store/workflowStore"; |
|
|
|
import { authFetch } from "@/utils/authFetch"; |
|
|
|
import { uploadFileToGatewayMedia } from "@/utils/gatewayMediaUpload"; |
|
|
|
import { createSmartMediaNode, detectSmartMediaKind } from "@/utils/smartMediaNode"; |
|
|
|
import { |
|
|
|
getUserFileList, |
|
|
|
@ -36,9 +37,10 @@ import { |
|
|
|
type UserFileListResponse, |
|
|
|
type UserFileMutationResponse, |
|
|
|
} from "@/utils/userFileLibrary"; |
|
|
|
import { AssetLibraryUploadModal, type AssetLibraryUploadFolderOption } from "./AssetLibraryUploadModal"; |
|
|
|
import { AssetLibraryUploadModal } from "./AssetLibraryUploadModal"; |
|
|
|
|
|
|
|
type AssetLibraryFileKind = "image" | "video" | "audio" | "other"; |
|
|
|
type AssetLibraryActionKey = "move" | "cover" | "rename" | "delete"; |
|
|
|
|
|
|
|
interface AssetLibraryManagerModalProps { |
|
|
|
open: boolean; |
|
|
|
@ -158,9 +160,16 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
const [moveFolderAncestorIds, setMoveFolderAncestorIds] = useState<Record<number, Set<number>>>({}); |
|
|
|
const [actionMenuOpenId, setActionMenuOpenId] = useState<number | null>(null); |
|
|
|
const [movePanelOpenId, setMovePanelOpenId] = useState<number | null>(null); |
|
|
|
const [activeActionKey, setActiveActionKey] = useState<AssetLibraryActionKey | null>(null); |
|
|
|
const [batchMode, setBatchMode] = useState(false); |
|
|
|
const [selectedItemIds, setSelectedItemIds] = useState<Set<number>>(new Set()); |
|
|
|
const [batchDeleting, setBatchDeleting] = useState(false); |
|
|
|
const [updatingCoverId, setUpdatingCoverId] = useState<number | null>(null); |
|
|
|
const [uploadFoldersByParentId, setUploadFoldersByParentId] = useState<Record<number, UserFileItem[]>>({}); |
|
|
|
const [loadedUploadFolderIds, setLoadedUploadFolderIds] = useState<Set<number>>(new Set()); |
|
|
|
const [loadingUploadFolderIds, setLoadingUploadFolderIds] = useState<Set<number>>(new Set()); |
|
|
|
const coverInputRef = useRef<HTMLInputElement>(null); |
|
|
|
const coverTargetRef = useRef<UserFileItem | null>(null); |
|
|
|
|
|
|
|
const loadItems = useCallback(async (signal?: AbortSignal) => { |
|
|
|
setLoading(true); |
|
|
|
@ -213,9 +222,15 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
setMoveFolderAncestorIds({}); |
|
|
|
setActionMenuOpenId(null); |
|
|
|
setMovePanelOpenId(null); |
|
|
|
setActiveActionKey(null); |
|
|
|
setBatchMode(false); |
|
|
|
setSelectedItemIds(new Set()); |
|
|
|
setBatchDeleting(false); |
|
|
|
setUpdatingCoverId(null); |
|
|
|
setUploadFoldersByParentId({}); |
|
|
|
setLoadedUploadFolderIds(new Set()); |
|
|
|
setLoadingUploadFolderIds(new Set()); |
|
|
|
coverTargetRef.current = null; |
|
|
|
}, [open]); |
|
|
|
|
|
|
|
const handleEnterFolder = useCallback((folder: UserFileItem) => { |
|
|
|
@ -331,6 +346,7 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
await onChanged?.(parentId); |
|
|
|
setActionMenuOpenId(null); |
|
|
|
setMovePanelOpenId(null); |
|
|
|
setActiveActionKey(null); |
|
|
|
void message.success(t("assetLibrary.moveSuccess")); |
|
|
|
} catch (err) { |
|
|
|
const messageText = err instanceof Error ? err.message : t("assetLibrary.moveFailed"); |
|
|
|
@ -381,6 +397,37 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
} |
|
|
|
}, [loadedMoveFolderIds, loadingMoveFolderIds, t]); |
|
|
|
|
|
|
|
const loadUploadFolderChildren = useCallback(async (folderId: number) => { |
|
|
|
if (loadedUploadFolderIds.has(folderId) || loadingUploadFolderIds.has(folderId)) return; |
|
|
|
|
|
|
|
setLoadingUploadFolderIds((current) => new Set(current).add(folderId)); |
|
|
|
try { |
|
|
|
const params = new URLSearchParams({ |
|
|
|
parentId: String(folderId), |
|
|
|
keyword: "", |
|
|
|
page: "1", |
|
|
|
pageSize: String(USER_FILE_PAGE_SIZE), |
|
|
|
}); |
|
|
|
const response = await authFetch(`/api/popi/user-file/list?${params.toString()}`); |
|
|
|
const payload = (await response.json().catch(() => null)) as UserFileListResponse | null; |
|
|
|
if (!response.ok || !payload?.success) { |
|
|
|
throw new Error(payload?.error || t("assetLibrary.loadFailed")); |
|
|
|
} |
|
|
|
const folders = getUserFileList(payload.data).filter((item) => item.nodeType === "folder"); |
|
|
|
setUploadFoldersByParentId((current) => ({ ...current, [folderId]: folders })); |
|
|
|
setLoadedUploadFolderIds((current) => new Set(current).add(folderId)); |
|
|
|
} catch (err) { |
|
|
|
const messageText = err instanceof Error ? err.message : t("assetLibrary.loadFailed"); |
|
|
|
void message.error(messageText); |
|
|
|
} finally { |
|
|
|
setLoadingUploadFolderIds((current) => { |
|
|
|
const next = new Set(current); |
|
|
|
next.delete(folderId); |
|
|
|
return next; |
|
|
|
}); |
|
|
|
} |
|
|
|
}, [loadedUploadFolderIds, loadingUploadFolderIds, t]); |
|
|
|
|
|
|
|
const handleDeleteItem = useCallback((item: UserFileItem) => { |
|
|
|
const isFolder = item.nodeType === "folder"; |
|
|
|
Modal.confirm({ |
|
|
|
@ -468,6 +515,49 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
}); |
|
|
|
}, [batchDeleting, loadItems, onChanged, parentId, selectedItemIds, t]); |
|
|
|
|
|
|
|
const handleStartChangeCover = useCallback((item: UserFileItem) => { |
|
|
|
if (item.nodeType !== "folder" || updatingCoverId !== null) return; |
|
|
|
coverTargetRef.current = item; |
|
|
|
setActionMenuOpenId(null); |
|
|
|
setMovePanelOpenId(null); |
|
|
|
coverInputRef.current?.click(); |
|
|
|
}, [updatingCoverId]); |
|
|
|
|
|
|
|
const handleCoverFileChange = useCallback(async (event: ChangeEvent<HTMLInputElement>) => { |
|
|
|
const file = event.target.files?.[0]; |
|
|
|
event.target.value = ""; |
|
|
|
const target = coverTargetRef.current; |
|
|
|
coverTargetRef.current = null; |
|
|
|
|
|
|
|
if (!file || !target || updatingCoverId !== null) return; |
|
|
|
if (!file.type.startsWith("image/")) { |
|
|
|
void message.error(t("assetLibrary.selectImageFile")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setUpdatingCoverId(target.id); |
|
|
|
try { |
|
|
|
const uploaded = await uploadFileToGatewayMedia(file, "image"); |
|
|
|
const response = await authFetch("/api/popi/user-file/update-cover", { |
|
|
|
method: "POST", |
|
|
|
headers: { "Content-Type": "application/json" }, |
|
|
|
body: JSON.stringify({ id: target.id, cover: uploaded.url }), |
|
|
|
}); |
|
|
|
const payload = (await response.json().catch(() => null)) as UserFileMutationResponse | null; |
|
|
|
if (!response.ok || !payload?.success) { |
|
|
|
throw new Error(payload?.error || t("assetLibrary.updateCoverFailed")); |
|
|
|
} |
|
|
|
await loadItems(); |
|
|
|
await onChanged?.(parentId); |
|
|
|
void message.success(t("assetLibrary.updateCoverSuccess")); |
|
|
|
} catch (err) { |
|
|
|
const messageText = err instanceof Error ? err.message : t("assetLibrary.updateCoverFailed"); |
|
|
|
void message.error(messageText); |
|
|
|
} finally { |
|
|
|
setUpdatingCoverId(null); |
|
|
|
} |
|
|
|
}, [loadItems, onChanged, parentId, t, updatingCoverId]); |
|
|
|
|
|
|
|
const handleApplyToCanvas = useCallback(async (file: UserFileItem) => { |
|
|
|
if (!file.url || applyingId !== null) return; |
|
|
|
const kind = getFileKind(file); |
|
|
|
@ -509,50 +599,25 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
} |
|
|
|
}, [addNode, applyingId, screenToFlowPosition, selectSingleNode, t, updateNodeData]); |
|
|
|
|
|
|
|
const uploadFolderOptions = useMemo<AssetLibraryUploadFolderOption[]>(() => { |
|
|
|
const currentPathLabel = breadcrumbs.map((crumb) => crumb.name).join(" / "); |
|
|
|
const options: AssetLibraryUploadFolderOption[] = [ |
|
|
|
...breadcrumbs.map((item, index) => ({ |
|
|
|
id: item.id, |
|
|
|
label: breadcrumbs.slice(0, index + 1).map((crumb) => crumb.name).join(" / "), |
|
|
|
})), |
|
|
|
...items |
|
|
|
.filter((item) => item.nodeType === "folder") |
|
|
|
.map((item) => ({ |
|
|
|
id: item.id, |
|
|
|
label: parentId === ROOT_PARENT_ID |
|
|
|
? item.name |
|
|
|
: `${currentPathLabel} / ${item.name}`, |
|
|
|
})), |
|
|
|
]; |
|
|
|
return options; |
|
|
|
}, [breadcrumbs, items, parentId]); |
|
|
|
|
|
|
|
const newMenuItems = useMemo<MenuProps["items"]>(() => { |
|
|
|
if (parentId === ROOT_PARENT_ID) { |
|
|
|
return [{ |
|
|
|
return [ |
|
|
|
{ |
|
|
|
key: "folder", |
|
|
|
icon: <FolderAddOutlined />, |
|
|
|
label: t("assetLibrary.newFolder"), |
|
|
|
onClick: () => setCreateFolderOpen(true), |
|
|
|
}]; |
|
|
|
} |
|
|
|
|
|
|
|
return [ |
|
|
|
{ |
|
|
|
key: "folder", |
|
|
|
icon: <FolderAddOutlined />, |
|
|
|
label: t("assetLibrary.newFolder"), |
|
|
|
onClick: () => setCreateFolderOpen(true), |
|
|
|
}, |
|
|
|
{ |
|
|
|
key: "upload", |
|
|
|
icon: <UploadOutlined />, |
|
|
|
label: t("assetLibrary.uploadAsset"), |
|
|
|
onClick: () => setUploadOpen(true), |
|
|
|
key: "upload", |
|
|
|
icon: <UploadOutlined />, |
|
|
|
label: t("assetLibrary.uploadAsset"), |
|
|
|
onClick: () => { |
|
|
|
setUploadOpen(true); |
|
|
|
void loadUploadFolderChildren(ROOT_PARENT_ID); |
|
|
|
}, |
|
|
|
}, |
|
|
|
]; |
|
|
|
}, [parentId, t]); |
|
|
|
}, [loadUploadFolderChildren, t]); |
|
|
|
|
|
|
|
const handleToggleMoveFolder = useCallback((folderId: number) => { |
|
|
|
setExpandedMoveFolderIds((current) => { |
|
|
|
@ -669,10 +734,16 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
}, [handleMoveItem, loadingMoveFolderIds, moveFoldersByParentId, renderMoveTreeRows, t]); |
|
|
|
|
|
|
|
const handleOpenMovePanel = useCallback((item: UserFileItem) => { |
|
|
|
setActiveActionKey("move"); |
|
|
|
setMovePanelOpenId(item.id); |
|
|
|
void loadMoveFolderChildren(ROOT_PARENT_ID); |
|
|
|
}, [loadMoveFolderChildren]); |
|
|
|
|
|
|
|
const handleActivateAction = useCallback((key: AssetLibraryActionKey) => { |
|
|
|
setActiveActionKey(key); |
|
|
|
if (key !== "move") setMovePanelOpenId(null); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const renderItemActions = useCallback((item: UserFileItem) => { |
|
|
|
const isFolder = item.nodeType === "folder"; |
|
|
|
const movePanelOpen = movePanelOpenId === item.id; |
|
|
|
@ -687,7 +758,7 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
type="button" |
|
|
|
className={[ |
|
|
|
"flex h-8 w-full items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]", |
|
|
|
movePanelOpen ? "bg-[var(--surface-hover)] text-[var(--text-primary)]" : "", |
|
|
|
activeActionKey === "move" ? "bg-[var(--surface-hover)] text-[var(--text-primary)]" : "", |
|
|
|
].join(" ")} |
|
|
|
onMouseEnter={() => handleOpenMovePanel(item)} |
|
|
|
onClick={(event) => { |
|
|
|
@ -702,10 +773,15 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
{isFolder && ( |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="flex h-8 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]" |
|
|
|
className={[ |
|
|
|
"flex h-8 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]", |
|
|
|
activeActionKey === "cover" ? "bg-[var(--surface-hover)] text-[var(--text-primary)]" : "", |
|
|
|
].join(" ")} |
|
|
|
onMouseEnter={() => handleActivateAction("cover")} |
|
|
|
onClick={(event) => { |
|
|
|
event.stopPropagation(); |
|
|
|
void message.info(t("assetLibrary.changeCoverUnsupported")); |
|
|
|
handleActivateAction("cover"); |
|
|
|
handleStartChangeCover(item); |
|
|
|
}} |
|
|
|
> |
|
|
|
<PictureOutlined className="text-[14px]" /> |
|
|
|
@ -714,11 +790,17 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
)} |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="flex h-8 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]" |
|
|
|
className={[ |
|
|
|
"flex h-8 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]", |
|
|
|
activeActionKey === "rename" ? "bg-[var(--surface-hover)] text-[var(--text-primary)]" : "", |
|
|
|
].join(" ")} |
|
|
|
onMouseEnter={() => handleActivateAction("rename")} |
|
|
|
onClick={(event) => { |
|
|
|
event.stopPropagation(); |
|
|
|
handleActivateAction("rename"); |
|
|
|
setActionMenuOpenId(null); |
|
|
|
setMovePanelOpenId(null); |
|
|
|
setActiveActionKey(null); |
|
|
|
openRenameModal(item); |
|
|
|
}} |
|
|
|
> |
|
|
|
@ -727,11 +809,17 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
</button> |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="flex h-8 items-center gap-2 rounded-md px-3 text-left text-red-400 transition-colors hover:bg-red-500/10 hover:text-red-300" |
|
|
|
className={[ |
|
|
|
"flex h-8 items-center gap-2 rounded-md px-3 text-left text-red-400 transition-colors hover:bg-red-500/10 hover:text-red-300", |
|
|
|
activeActionKey === "delete" ? "bg-red-500/10 text-red-300" : "", |
|
|
|
].join(" ")} |
|
|
|
onMouseEnter={() => handleActivateAction("delete")} |
|
|
|
onClick={(event) => { |
|
|
|
event.stopPropagation(); |
|
|
|
handleActivateAction("delete"); |
|
|
|
setActionMenuOpenId(null); |
|
|
|
setMovePanelOpenId(null); |
|
|
|
setActiveActionKey(null); |
|
|
|
handleDeleteItem(item); |
|
|
|
}} |
|
|
|
> |
|
|
|
@ -746,7 +834,7 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
)} |
|
|
|
</div> |
|
|
|
); |
|
|
|
}, [handleDeleteItem, handleOpenMovePanel, movePanelOpenId, openRenameModal, renderMovePanel, t]); |
|
|
|
}, [activeActionKey, handleActivateAction, handleDeleteItem, handleOpenMovePanel, handleStartChangeCover, movePanelOpenId, openRenameModal, renderMovePanel, t]); |
|
|
|
|
|
|
|
const renderItem = (item: UserFileItem) => { |
|
|
|
const isFolder = item.nodeType === "folder"; |
|
|
|
@ -755,6 +843,7 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
const canApply = !isFolder && kind !== "other" && Boolean(item.url); |
|
|
|
const canSelect = batchMode && !isFolder; |
|
|
|
const selected = selectedItemIds.has(item.id); |
|
|
|
const folderCoverUrl = isFolder ? item.coverUrl : null; |
|
|
|
|
|
|
|
return ( |
|
|
|
<div |
|
|
|
@ -784,7 +873,9 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
}} |
|
|
|
> |
|
|
|
<div className="relative flex aspect-[4/3] w-full items-center justify-center overflow-hidden rounded-md bg-[var(--surface-1)] transition-opacity group-hover:opacity-90"> |
|
|
|
{isFolder ? ( |
|
|
|
{isFolder && folderCoverUrl ? ( |
|
|
|
<img src={folderCoverUrl} alt="" className="absolute inset-0 h-full w-full object-cover" /> |
|
|
|
) : isFolder ? ( |
|
|
|
<FolderOutlined className="text-[34px] text-[var(--text-muted)]" /> |
|
|
|
) : previewUrl ? ( |
|
|
|
kind === "video" ? ( |
|
|
|
@ -836,7 +927,10 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
open={actionMenuOpenId === item.id} |
|
|
|
onOpenChange={(nextOpen) => { |
|
|
|
setActionMenuOpenId(nextOpen ? item.id : null); |
|
|
|
if (!nextOpen) setMovePanelOpenId(null); |
|
|
|
if (!nextOpen) { |
|
|
|
setMovePanelOpenId(null); |
|
|
|
setActiveActionKey(null); |
|
|
|
} |
|
|
|
}} |
|
|
|
trigger={["click"]} |
|
|
|
placement="bottomRight" |
|
|
|
@ -873,16 +967,16 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
rootClassName="[&_.ant-modal-container]:!p-0" |
|
|
|
onCancel={onClose} |
|
|
|
className={[ |
|
|
|
"nodrag nopan nowheel", |
|
|
|
"nodrag nopan nowheel !overflow-hidden !rounded-xl", |
|
|
|
"[&_.ant-modal-content]:!overflow-hidden [&_.ant-modal-content]:!rounded-xl [&_.ant-modal-content]:!border-0 [&_.ant-modal-content]:!bg-[var(--surface-2)] [&_.ant-modal-content]:!p-0 [&_.ant-modal-content]:!shadow-[var(--shadow-panel)]", |
|
|
|
"[&_.ant-modal-body]:!p-0", |
|
|
|
].join(" ")} |
|
|
|
> |
|
|
|
<div className="flex min-h-0 flex-col bg-[var(--surface-2)] text-[var(--text-primary)]"> |
|
|
|
<div className="flex min-h-0 flex-col overflow-hidden rounded-xl bg-[var(--surface-2)] text-[var(--text-primary)]"> |
|
|
|
<AssetLibraryModalHeader title={t("assetLibrary.manageTitle")} onClose={onClose} /> |
|
|
|
|
|
|
|
<div className="px-4 pt-4"> |
|
|
|
<div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-[var(--text-primary)]"> |
|
|
|
<div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-[var(--text-primary)] [&_.ant-breadcrumb-link]:!text-[var(--text-secondary)] [&_.ant-breadcrumb-link:hover]:!text-[var(--text-primary)] [&_.ant-breadcrumb-separator]:!text-[var(--text-muted)]"> |
|
|
|
{breadcrumbs.length > 0 && ( |
|
|
|
<Button |
|
|
|
type="text" |
|
|
|
@ -892,29 +986,38 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
className="!h-7 !w-7 !rounded-md !text-[var(--text-secondary)] hover:!bg-[var(--surface-hover)] hover:!text-[var(--text-primary)]" |
|
|
|
/> |
|
|
|
)} |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="shrink-0 text-[var(--text-secondary)] transition-colors hover:text-[var(--text-primary)]" |
|
|
|
onClick={() => handleBreadcrumbClick(-1)} |
|
|
|
> |
|
|
|
{t("assetLibrary.title")} |
|
|
|
</button> |
|
|
|
{breadcrumbs.map((item, index) => ( |
|
|
|
<span key={item.id} className="min-w-0"> |
|
|
|
<span className="mx-1 text-[var(--text-muted)]">/</span> |
|
|
|
{index === breadcrumbs.length - 1 ? ( |
|
|
|
<span className="text-[var(--text-primary)]">{item.name}</span> |
|
|
|
) : ( |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="text-[var(--text-secondary)] transition-colors hover:text-[var(--text-primary)]" |
|
|
|
onClick={() => handleBreadcrumbClick(index)} |
|
|
|
> |
|
|
|
{item.name} |
|
|
|
</button> |
|
|
|
)} |
|
|
|
</span> |
|
|
|
))} |
|
|
|
<Breadcrumb |
|
|
|
separator="/" |
|
|
|
className="min-w-0" |
|
|
|
items={[ |
|
|
|
{ |
|
|
|
title: breadcrumbs.length === 0 ? ( |
|
|
|
<span className="text-[var(--text-primary)]">{t("assetLibrary.title")}</span> |
|
|
|
) : ( |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="text-[var(--text-secondary)] transition-colors hover:text-[var(--text-primary)]" |
|
|
|
onClick={() => handleBreadcrumbClick(-1)} |
|
|
|
> |
|
|
|
{t("assetLibrary.title")} |
|
|
|
</button> |
|
|
|
), |
|
|
|
}, |
|
|
|
...breadcrumbs.map((item, index) => ({ |
|
|
|
title: index === breadcrumbs.length - 1 ? ( |
|
|
|
<span className="text-[var(--text-primary)]">{item.name}</span> |
|
|
|
) : ( |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="max-w-[160px] truncate text-[var(--text-secondary)] transition-colors hover:text-[var(--text-primary)]" |
|
|
|
onClick={() => handleBreadcrumbClick(index)} |
|
|
|
> |
|
|
|
{item.name} |
|
|
|
</button> |
|
|
|
), |
|
|
|
})), |
|
|
|
]} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
@ -1138,8 +1241,11 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
|
|
|
|
<AssetLibraryUploadModal |
|
|
|
open={uploadOpen} |
|
|
|
folderOptions={uploadFolderOptions} |
|
|
|
foldersByParentId={uploadFoldersByParentId} |
|
|
|
loadedFolderIds={loadedUploadFolderIds} |
|
|
|
loadingFolderIds={loadingUploadFolderIds} |
|
|
|
initialFolderId={parentId} |
|
|
|
onLoadFolderChildren={loadUploadFolderChildren} |
|
|
|
onClose={() => setUploadOpen(false)} |
|
|
|
onError={setError} |
|
|
|
onUploaded={async (folderId) => { |
|
|
|
@ -1149,6 +1255,13 @@ export function AssetLibraryManagerModal({ open, onClose, onChanged }: AssetLibr |
|
|
|
await onChanged?.(folderId); |
|
|
|
}} |
|
|
|
/> |
|
|
|
<input |
|
|
|
ref={coverInputRef} |
|
|
|
type="file" |
|
|
|
accept="image/*" |
|
|
|
className="hidden" |
|
|
|
onChange={handleCoverFileChange} |
|
|
|
/> |
|
|
|
</> |
|
|
|
); |
|
|
|
} |
|
|
|
|