mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-07-27 18:14:31 +08:00
Merge branch 'fork/chen3737/main'
This commit is contained in:
@@ -1847,13 +1847,31 @@ function InfiniteCanvasPage() {
|
||||
|
||||
const handleImageInputChange = useCallback(
|
||||
async (event: ReactChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
const target = uploadTargetRef.current;
|
||||
if (!file || (!file.type.startsWith("image/") && !file.type.startsWith("video/") && !isAudioFile(file))) return;
|
||||
const files = Array.from(event.target.files || []).filter(
|
||||
(f) => f.type.startsWith("image/") || f.type.startsWith("video/") || isAudioFile(f),
|
||||
);
|
||||
if (!files.length) {
|
||||
uploadTargetRef.current = null;
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const target = uploadTargetRef.current;
|
||||
const basePosition =
|
||||
target?.position ||
|
||||
screenToCanvas(
|
||||
(containerRef.current?.getBoundingClientRect().left || 0) + size.width / 2,
|
||||
(containerRef.current?.getBoundingClientRect().top || 0) + size.height / 2,
|
||||
);
|
||||
const STAGGER = 40; // 多文件时的偏移间距
|
||||
|
||||
// 如果有替换目标节点,第一个文件替换它,其余在附近新建
|
||||
if (target?.nodeId) {
|
||||
if (isAudioFile(file)) {
|
||||
const audio = await uploadMediaFile(file, "audio");
|
||||
const [first, ...rest] = files;
|
||||
|
||||
// 第一个文件:替换目标节点
|
||||
if (isAudioFile(first)) {
|
||||
const audio = await uploadMediaFile(first, "audio");
|
||||
const spec = NODE_DEFAULT_SIZE[CanvasNodeType.Audio];
|
||||
setNodes((prev) =>
|
||||
prev.map((node) =>
|
||||
@@ -1861,7 +1879,7 @@ function InfiniteCanvasPage() {
|
||||
? {
|
||||
...node,
|
||||
type: CanvasNodeType.Audio,
|
||||
title: file.name,
|
||||
title: first.name,
|
||||
position: { x: node.position.x + node.width / 2 - spec.width / 2, y: node.position.y + node.height / 2 - spec.height / 2 },
|
||||
width: spec.width,
|
||||
height: spec.height,
|
||||
@@ -1872,12 +1890,8 @@ function InfiniteCanvasPage() {
|
||||
);
|
||||
setSelectedNodeIds(new Set([target.nodeId]));
|
||||
setSelectedConnectionId(null);
|
||||
uploadTargetRef.current = null;
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
if (file.type.startsWith("video/")) {
|
||||
const video = await uploadMediaFile(file, "video");
|
||||
} else if (first.type.startsWith("video/")) {
|
||||
const video = await uploadMediaFile(first, "video");
|
||||
const nextSize = fitNodeSize(video.width || 1280, video.height || 720, VIDEO_NODE_MAX_WIDTH, VIDEO_NODE_MAX_HEIGHT);
|
||||
setNodes((prev) =>
|
||||
prev.map((node) =>
|
||||
@@ -1885,7 +1899,7 @@ function InfiniteCanvasPage() {
|
||||
? {
|
||||
...node,
|
||||
type: CanvasNodeType.Video,
|
||||
title: file.name,
|
||||
title: first.name,
|
||||
position: { x: node.position.x + node.width / 2 - nextSize.width / 2, y: node.position.y + node.height / 2 - nextSize.height / 2 },
|
||||
width: nextSize.width,
|
||||
height: nextSize.height,
|
||||
@@ -1896,50 +1910,69 @@ function InfiniteCanvasPage() {
|
||||
);
|
||||
setSelectedNodeIds(new Set([target.nodeId]));
|
||||
setSelectedConnectionId(null);
|
||||
setDialogNodeId(target.nodeId);
|
||||
uploadTargetRef.current = null;
|
||||
event.target.value = "";
|
||||
return;
|
||||
} else {
|
||||
const image = await uploadImage(first);
|
||||
const s = fitNodeSize(image.width, image.height);
|
||||
setNodes((prev) =>
|
||||
prev.map((node) =>
|
||||
node.id === target.nodeId
|
||||
? {
|
||||
...node,
|
||||
type: CanvasNodeType.Image,
|
||||
title: first.name,
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
metadata: {
|
||||
...node.metadata,
|
||||
...imageMetadata(image),
|
||||
errorDetails: undefined,
|
||||
freeResize: false,
|
||||
isBatchRoot: undefined,
|
||||
batchRootId: undefined,
|
||||
batchChildIds: undefined,
|
||||
batchUsesReferenceImages: undefined,
|
||||
generationType: undefined,
|
||||
model: undefined,
|
||||
size: undefined,
|
||||
quality: undefined,
|
||||
count: undefined,
|
||||
references: undefined,
|
||||
primaryImageId: undefined,
|
||||
imageBatchExpanded: undefined,
|
||||
},
|
||||
}
|
||||
: node,
|
||||
),
|
||||
);
|
||||
setSelectedNodeIds(new Set([target.nodeId]));
|
||||
setSelectedConnectionId(null);
|
||||
}
|
||||
|
||||
// 剩余文件:在目标节点附近新建
|
||||
for (let i = 0; i < rest.length; i++) {
|
||||
const offsetPos = { x: basePosition.x + (i + 1) * STAGGER, y: basePosition.y + (i + 1) * STAGGER };
|
||||
const f = rest[i];
|
||||
if (isAudioFile(f)) {
|
||||
void createAudioFileNode(f, offsetPos);
|
||||
} else if (f.type.startsWith("video/")) {
|
||||
void createVideoFileNode(f, offsetPos);
|
||||
} else {
|
||||
void createImageFileNode(f, offsetPos);
|
||||
}
|
||||
}
|
||||
const image = await uploadImage(file);
|
||||
const size = fitNodeSize(image.width, image.height);
|
||||
setNodes((prev) =>
|
||||
prev.map((node) =>
|
||||
node.id === target.nodeId
|
||||
? {
|
||||
...node,
|
||||
type: CanvasNodeType.Image,
|
||||
title: file.name,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
metadata: {
|
||||
...node.metadata,
|
||||
...imageMetadata(image),
|
||||
errorDetails: undefined,
|
||||
freeResize: false,
|
||||
isBatchRoot: undefined,
|
||||
batchRootId: undefined,
|
||||
batchChildIds: undefined,
|
||||
batchUsesReferenceImages: undefined,
|
||||
generationType: undefined,
|
||||
model: undefined,
|
||||
size: undefined,
|
||||
quality: undefined,
|
||||
count: undefined,
|
||||
references: undefined,
|
||||
primaryImageId: undefined,
|
||||
imageBatchExpanded: undefined,
|
||||
},
|
||||
}
|
||||
: node,
|
||||
),
|
||||
);
|
||||
setSelectedNodeIds(new Set([target.nodeId]));
|
||||
setSelectedConnectionId(null);
|
||||
setDialogNodeId(target.nodeId);
|
||||
} else {
|
||||
const position = target?.position || screenToCanvas((containerRef.current?.getBoundingClientRect().left || 0) + size.width / 2, (containerRef.current?.getBoundingClientRect().top || 0) + size.height / 2);
|
||||
void (isAudioFile(file) ? createAudioFileNode(file, position) : file.type.startsWith("video/") ? createVideoFileNode(file, position) : createImageFileNode(file, position));
|
||||
// 无替换目标:所有文件在画布中心附近新建
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const offsetPos = { x: basePosition.x + i * STAGGER, y: basePosition.y + i * STAGGER };
|
||||
const f = files[i];
|
||||
if (isAudioFile(f)) {
|
||||
void createAudioFileNode(f, offsetPos);
|
||||
} else if (f.type.startsWith("video/")) {
|
||||
void createVideoFileNode(f, offsetPos);
|
||||
} else {
|
||||
void createImageFileNode(f, offsetPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadTargetRef.current = null;
|
||||
@@ -1951,11 +1984,24 @@ function InfiniteCanvasPage() {
|
||||
const handleDrop = useCallback(
|
||||
(event: ReactDragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
const file = Array.from(event.dataTransfer.files).find((item) => item.type.startsWith("image/") || item.type.startsWith("video/") || isAudioFile(item));
|
||||
if (!file) return;
|
||||
const files = Array.from(event.dataTransfer.files).filter(
|
||||
(item) => item.type.startsWith("image/") || item.type.startsWith("video/") || isAudioFile(item),
|
||||
);
|
||||
if (!files.length) return;
|
||||
|
||||
const pos = screenToCanvas(event.clientX, event.clientY);
|
||||
void (isAudioFile(file) ? createAudioFileNode(file, pos) : file.type.startsWith("video/") ? createVideoFileNode(file, pos) : createImageFileNode(file, pos));
|
||||
const basePos = screenToCanvas(event.clientX, event.clientY);
|
||||
const STAGGER = 40;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const pos = { x: basePos.x + i * STAGGER, y: basePos.y + i * STAGGER };
|
||||
const f = files[i];
|
||||
if (isAudioFile(f)) {
|
||||
void createAudioFileNode(f, pos);
|
||||
} else if (f.type.startsWith("video/")) {
|
||||
void createVideoFileNode(f, pos);
|
||||
} else {
|
||||
void createImageFileNode(f, pos);
|
||||
}
|
||||
}
|
||||
},
|
||||
[createAudioFileNode, createImageFileNode, createVideoFileNode, screenToCanvas],
|
||||
);
|
||||
@@ -2928,7 +2974,7 @@ function InfiniteCanvasPage() {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<input ref={imageInputRef} type="file" accept="image/*,video/*,audio/mpeg,audio/wav,audio/x-wav,.mp3,.wav" className="hidden" onChange={handleImageInputChange} />
|
||||
<input ref={imageInputRef} type="file" multiple accept="image/*,video/*,audio/mpeg,audio/wav,audio/x-wav,.mp3,.wav" className="hidden" onChange={handleImageInputChange} />
|
||||
|
||||
<CanvasNodeInfoModal node={infoNode} open={Boolean(infoNode)} onClose={() => setInfoNodeId(null)} />
|
||||
<CanvasPluginManagerModal open={pluginManagerOpen} onClose={() => setPluginManagerOpen(false)} />
|
||||
|
||||
Reference in New Issue
Block a user