import { Check, ImageIcon, Music, Search, Video } from 'lucide-react'; import { type ReactNode, useEffect, useLayoutEffect, useMemo, useState, } from 'react'; import { PlatformActionButton } from '../../../packages/shared/src/components/PlatformActionButton'; import { PlatformResourceFilterBar } from '../../../packages/shared/src/components/PlatformResourceFilterBar'; import { PlatformStatusMessage } from '../../../packages/shared/src/components/PlatformStatusMessage'; import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton'; import { UnifiedModal } from '../common/UnifiedModal'; import type { EditorAsset } from './ImageCanvasEditorTypes'; import { PROJECT_ASSET_PICKER_CATEGORY_OPTIONS, projectAssetMatchesPickerFilter, type ProjectAssetPickerCategory, projectAssetPickerCategory, type ProjectAssetPickerMediaCategory, projectAssetPickerUsesThumbnail, } from './projectAssetReferencePickerModel'; type ImageCanvasProjectAssetPickerDialogProps = { open: boolean; /** 项目已登记素材;弹窗只负责选择,不改素材本身。 */ assets: readonly EditorAsset[]; /** 打开时的初始选择,取消时原选择保持不变。 */ selectedAssetIds: readonly string[]; onCancel: () => void; onConfirm: (assetIds: string[]) => void; /** * 单选模式:点击即整组替换当前选择,不再渲染「已选」chip 行。 * 默认 `false`,网页端美术画布的参考图多选行为逐字不变。 */ singleSelect?: boolean; /** * 被禁用的素材 id → 用户可见的禁用原因。默认空,即所有素材都可选。 * * 禁用项仍然渲染(不隐藏):隐藏会让用户以为"素材不存在",而真实原因是它不可替换。 */ assetBlockedReasons?: Readonly>; /** * 可选素材 id → 非阻断提示(例如"格式与源素材不同")。默认空,即不显示任何提示。 * * 与 `assetBlockedReasons` 的区别:提示不改变可点性,只把差异说清楚。 */ assetHints?: Readonly>; /** * 素材缩略图渲染器。默认 `undefined` → 沿用 ``。 * * 宿主(如 AGC 资源工作台)没有同步 `src` 时必须传它:AGC 的预览读取走带 scope 的 * 原生调度器 + Blob URL,弹窗内取不到,直接给空 `src` 会挂破图。 */ renderAssetMedia?: (asset: EditorAsset) => ReactNode; /** * 选择对象的中文名词,用于拼弹窗标题与可访问名称。默认「参考图」, * 即网页端美术画布的现有文案逐字不变。 */ selectionNoun?: string; /** * 宿主的失败原因(例如后端拒绝了这次替换)。默认 `undefined` → 不渲染。 * * 用于在弹窗内说明"为什么这次操作没成功",而不是静默关闭弹窗让用户以为成功了。 */ errorMessage?: string | null; /** * 非模态浮层:不铺全屏遮罩、不做焦点陷阱,宿主画布保持可点。 * * AGC 的「替换素材」用它:面板开着的时候直接在资源画布上点目标素材,点中的候选落进面板的 * 当前选择,写入仍然只由面板的「确认」发起。默认 `false` → 网页端美术画布的弹窗行为逐字不变。 * * 打开期间 Esc 仍等于「取消」(document 阶段截断,宿主画布的全局 Esc 不随之触发); * 点外部不关闭——非模态面板与画布是同一屏的两半,点画布是要选目标,不是要关面板。 */ nonModal?: boolean; /** * 「初值换了」的信号:宿主在面板**开着**的时候又给了新的 `selectedAssetIds`(AGC 里是在 * 画布上点选目标素材),序号一变就按新初值重同步当前选择。 * * 不能用 `selectedAssetIds` 的引用当信号:调用方每次渲染都会重建那个数组,把它放进依赖会 * 让「父级任何一次重渲染」都清掉用户的选择(组件里原本就是这么写的)。所以同步点交给这个 * 显式序号:只有宿主真的换了目标才变化。缺省 `0`(网页端美术画布不传,行为逐字不变)。 */ initialSelectionRevision?: number; }; function assetIcon(category: ProjectAssetPickerCategory) { if (category === 'video') { return