接入桌面图片拖入创作槽位

CreativeImageInputPanel 订阅 file.imageDropped 桌面拖入事件

按拖入坐标命中主图卡片后转换为现有 File 回调

避免窗口级拖入被多个创作面板同时消费

更新组件测试和原生壳架构文档
This commit is contained in:
2026-06-18 03:27:55 +08:00
parent d5cab219a1
commit d261cc8d0b
5 changed files with 244 additions and 3 deletions
@@ -5,6 +5,7 @@ import {
canUseNativeHostCapability,
type HostFileImportImageResult,
importHostImageFile,
subscribeHostImageDrop,
} from '../../services/host-bridge/hostBridge';
import { puzzleReferenceImageDataUrlToFile } from '../../services/puzzleReferenceImage';
import { ResolvedAssetImage } from '../ResolvedAssetImage';
@@ -144,6 +145,7 @@ export function CreativeImageInputPanel({
onHistoryClick,
onSubmit,
}: CreativeImageInputPanelProps) {
const mainImageCardRef = useRef<HTMLDivElement | null>(null);
const mainImageInputRef = useRef<HTMLInputElement | null>(null);
const promptReferenceInputRef = useRef<HTMLInputElement | null>(null);
const [previewReferenceImage, setPreviewReferenceImage] =
@@ -166,6 +168,8 @@ export function CreativeImageInputPanel({
const shouldShowMainImageUploadButton =
isMainImageUploadEnabled && shouldPreviewMainImage;
const canImportHostImage = canUseNativeHostCapability('file.importImage');
const canReceiveHostImageDrop =
canUseNativeHostCapability('file.imageDropped');
const promptReferenceInputId = `${mainImageInputId}-prompt-reference`;
useEffect(() => {
@@ -187,6 +191,50 @@ export function CreativeImageInputPanel({
}
}, [previewReferenceImage, promptReferenceImages]);
useEffect(() => {
if (!canReceiveHostImageDrop || disabled || !isMainImageUploadEnabled) {
return undefined;
}
return subscribeHostImageDrop((payload) => {
const card = mainImageCardRef.current;
const position = payload.position;
if (!card || !position) {
return;
}
const bounds = card.getBoundingClientRect();
const isInsideCard =
position.x >= bounds.left &&
position.x <= bounds.right &&
position.y >= bounds.top &&
position.y <= bounds.bottom;
if (!isInsideCard) {
return;
}
const topElement =
typeof document.elementFromPoint === 'function'
? document.elementFromPoint(position.x, position.y)
: null;
// 中文注释:桌面拖入是窗口级事件;只让坐标命中的最上层主图槽位消费,避免多个创作面板同时接收同一张图。
if (topElement && !card.contains(topElement)) {
return;
}
try {
onMainImageFileSelect(hostImageImportResultToFile(payload));
} catch {
// 中文注释:宿主已校验图片类型和体积;这里仅兜住浏览器 File 构造异常,保持当前表单状态。
}
});
}, [
canReceiveHostImageDrop,
disabled,
isMainImageUploadEnabled,
onMainImageFileSelect,
]);
const bodyClassName = fillHeight
? 'creative-image-input-panel__body puzzle-creation-form-body flex min-h-0 flex-1 flex-col overflow-hidden pr-0 lg:overflow-y-auto lg:pr-1'
: 'creative-image-input-panel__body puzzle-creation-form-body flex flex-none flex-col overflow-visible pr-0 lg:pr-1';
@@ -282,7 +330,7 @@ export function CreativeImageInputPanel({
{labels.imageField}
</PlatformFieldLabel>
<div className={imageFrameClassName}>
<div className={imageCardClassName}>
<div ref={mainImageCardRef} className={imageCardClassName}>
{isMainImageUploadEnabled ? (
<input
ref={mainImageInputRef}