diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/Components/ImageAssetSelector.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/Components/ImageAssetSelector.tsx new file mode 100644 index 000000000..0e44b2406 --- /dev/null +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/Components/ImageAssetSelector.tsx @@ -0,0 +1,161 @@ +import { Check, ImageIcon, X } from 'lucide-react'; +import { useState } from 'react'; + +import { ThemedModal } from '../../../../../components/modal/ThemedModal'; +import type { SpriteAsset } from '../../../../../features/ui-editor/types/SpriteAsset'; +import { ComponentField } from './ComponentField'; + +export function ImageAssetSelector({ + value, + sprites, + previewUrls, + readOnly, + onChange, +}: { + value: string | null; + sprites: Record; + previewUrls: Record; + readOnly: boolean; + onChange: (value: string | null) => void; +}) { + const [open, setOpen] = useState(false); + const [failedPreviewIds, setFailedPreviewIds] = useState>( + () => new Set(), + ); + const selectedSprite = value ? sprites[value] : undefined; + const selectedLabel = selectedSprite + ? selectedSprite.metadata.name || value || '未绑定' + : value + ? `素材不存在(${value})` + : '未绑定'; + const buttonLabel = value ? '更换素材' : '选择素材'; + + function close() { + setOpen(false); + } + + function select(nextValue: string | null) { + onChange(nextValue); + close(); + } + + function markPreviewFailed(id: string) { + setFailedPreviewIds((current) => { + if (current.has(id)) return current; + const next = new Set(current); + next.add(id); + return next; + }); + } + + return ( + <> + +
+
+ {selectedLabel} +
+ +
+
+ + +
+

选择图片素材

+ +
+ +
+
+ + + {value && !selectedSprite ? ( +
+
+ ) : null} + + {Object.entries(sprites).map(([id, sprite]) => { + const previewUrl = previewUrls[id]; + const hasPreview = + Boolean(previewUrl) && !failedPreviewIds.has(id); + const selected = value === id; + return ( + + ); + })} +
+ {Object.keys(sprites).length === 0 ? ( +

+ 暂无可用素材 +

+ ) : null} +
+
+ + ); +} diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/Components/ImagePanel.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/Components/ImagePanel.tsx index 176021783..06b3e8b53 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/Components/ImagePanel.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/Components/ImagePanel.tsx @@ -4,10 +4,12 @@ import type { ImageType } from '../../../../../features/ui-editor/types/ImageTyp import type { UiEditorOperationResult } from '../../../../../features/ui-editor/useUiEditorState'; import type { ImageEditorProps } from './componentEditorTypes'; import { ComponentNumberInput, ComponentSelect } from './ComponentField'; +import { ImageAssetSelector } from './ImageAssetSelector'; export function ImagePanel({ component, sprites, + previewUrls, readOnly, onChange, }: ImageEditorProps) { @@ -17,24 +19,13 @@ export function ImagePanel({ return (
- - update({ - ...component, - target_graphic: event.target.value || null, - }) - } - > - - {Object.entries(sprites).map(([id, sprite]) => ( - - ))} - + update({ ...component, target_graphic })} + />