mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 17:04:27 +08:00
feat(plugin-sdk): add TypeScript SDK for Infinite Canvas plugins with automatic JSX and build support
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import type { ChangeEvent as ReactChangeEvent, DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent } from "react";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { BookOpen, Bot, Group, Home, ImageIcon, Images, List, Menu, Music2, Plus, Puzzle, Redo2, Settings2, Trash2, Undo2, Upload, Video, X } from "lucide-react";
|
||||
import { BookOpen, Bot, Group, Home, ImageIcon, Images, List, Menu, Music2, Plus, Redo2, Settings2, Trash2, Undo2, Upload, Video, X } from "lucide-react";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { requestEdit, requestGeneration, requestImageQuestion } from "@/services/api/image";
|
||||
@@ -38,6 +38,7 @@ import { CanvasNode } from "@/components/canvas/canvas-node";
|
||||
import { CanvasNodePromptPanel, type CanvasNodeGenerationMode } from "@/components/canvas/canvas-node-prompt-panel";
|
||||
import { CanvasToolbar } from "@/components/canvas/canvas-toolbar";
|
||||
import { AssetPickerModal, type InsertAssetPayload } from "@/components/canvas/asset-picker-modal";
|
||||
import { CanvasSidePanel } from "@/components/canvas/canvas-side-panel";
|
||||
import { CanvasZoomControls } from "@/components/canvas/canvas-zoom-controls";
|
||||
import { useAgentStore } from "@/stores/use-agent-store";
|
||||
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
|
||||
@@ -1049,6 +1050,21 @@ function InfiniteCanvasPage() {
|
||||
setContextMenu(null);
|
||||
}, [size.height, size.width]);
|
||||
|
||||
const focusNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
const node = nodesRef.current.find((item) => item.id === nodeId);
|
||||
if (!node) return;
|
||||
const worldX = node.position.x + node.width / 2;
|
||||
const worldY = node.position.y + node.height / 2;
|
||||
const k = Math.min(Math.max(Math.min((size.width * 0.6) / node.width, (size.height * 0.6) / node.height), 0.05), 1.5);
|
||||
setViewport({ x: size.width / 2 - worldX * k, y: size.height / 2 - worldY * k, k });
|
||||
setSelectedNodeIds(new Set([nodeId]));
|
||||
setSelectedConnectionId(null);
|
||||
setContextMenu(null);
|
||||
},
|
||||
[size.height, size.width],
|
||||
);
|
||||
|
||||
const setZoomScale = useCallback(
|
||||
(scale: number) => {
|
||||
const nextScale = Math.min(Math.max(scale, 0.05), 5);
|
||||
@@ -1651,13 +1667,13 @@ function InfiniteCanvasPage() {
|
||||
const content = node.metadata?.content?.trim();
|
||||
if (!content) return message.error("没有可保存的文本");
|
||||
addAsset({ kind: "text", title: node.metadata?.prompt?.slice(0, 24) || "画布文本", coverUrl: "", tags: [], source: "Canvas", data: { content }, metadata: { source: "canvas", nodeId: node.id } });
|
||||
message.success("已加入我的素材");
|
||||
message.success("已加入我的资产");
|
||||
return;
|
||||
}
|
||||
if (node.type === CanvasNodeType.Video) {
|
||||
if (!node.metadata?.content) return message.error("没有可保存的视频");
|
||||
addAsset({ kind: "video", title: node.metadata?.prompt?.slice(0, 24) || "画布视频", coverUrl: "", tags: [], source: "Canvas", data: { url: node.metadata.content, storageKey: node.metadata.storageKey, width: node.width, height: node.height, bytes: node.metadata.bytes || 0, mimeType: node.metadata.mimeType || "video/mp4" }, metadata: { source: "canvas", nodeId: node.id, prompt: node.metadata?.prompt } });
|
||||
message.success("已加入我的素材");
|
||||
message.success("已加入我的资产");
|
||||
return;
|
||||
}
|
||||
if (!node.metadata?.content) return message.error("没有可保存的图片");
|
||||
@@ -1678,7 +1694,7 @@ function InfiniteCanvasPage() {
|
||||
},
|
||||
metadata: { source: "canvas", nodeId: node.id, prompt: node.metadata?.prompt },
|
||||
});
|
||||
message.success("已加入我的素材");
|
||||
message.success("已加入我的资产");
|
||||
},
|
||||
[addAsset, message],
|
||||
);
|
||||
@@ -2555,6 +2571,7 @@ function InfiniteCanvasPage() {
|
||||
|
||||
return (
|
||||
<main className="flex h-full min-h-0 overflow-hidden" style={{ background: theme.canvas.background, color: theme.node.text }}>
|
||||
<CanvasSidePanel nodes={nodes} selectedNodeIds={selectedNodeIds} onFocusNode={focusNode} onInsertAsset={handleAssetInsert} />
|
||||
<section className="relative min-w-0 flex-1 overflow-hidden">
|
||||
<CanvasTopBar
|
||||
title={currentProject?.title || "未命名画布"}
|
||||
@@ -2796,9 +2813,6 @@ function InfiniteCanvasPage() {
|
||||
onDeselect={deselectCanvas}
|
||||
onBackgroundModeChange={setBackgroundMode}
|
||||
onShowImageInfoChange={setShowImageInfo}
|
||||
onOpenMyAssets={() => {
|
||||
setAssetPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{isMiniMapOpen ? <Minimap nodes={nodes} viewport={viewport} viewportSize={size} onViewportChange={setViewport} /> : null}
|
||||
@@ -2957,8 +2971,7 @@ function CanvasTopBar({
|
||||
{ key: "new", icon: <Plus className="size-4" />, label: "新建画布", onClick: onCreateProject },
|
||||
{ key: "delete", danger: true, icon: <Trash2 className="size-4" />, label: "删除当前画布", onClick: onDeleteProject },
|
||||
{ type: "divider" },
|
||||
{ key: "import", icon: <Upload className="size-4" />, label: "导入素材", onClick: onImportImage },
|
||||
{ key: "plugins", icon: <Puzzle className="size-4" />, label: "节点插件", onClick: onOpenPlugins },
|
||||
{ key: "import", icon: <Upload className="size-4" />, label: "导入资产", onClick: onImportImage },
|
||||
{ type: "divider" },
|
||||
{ key: "undo", disabled: !canUndo, icon: <Undo2 className="size-4" />, label: <MenuLabel text="撤销" shortcut="⌘ Z" />, onClick: onUndo },
|
||||
{ key: "redo", disabled: !canRedo, icon: <Redo2 className="size-4" />, label: <MenuLabel text="重做" shortcut="⌘ ⇧ Z / ⌘ Y" />, onClick: onRedo },
|
||||
@@ -3002,6 +3015,7 @@ function CanvasTopBar({
|
||||
<UserStatusActions
|
||||
variant="canvas"
|
||||
onOpenShortcuts={() => setShortcutsOpen(true)}
|
||||
onOpenPlugins={onOpenPlugins}
|
||||
/>
|
||||
<span className="h-6 w-px" style={{ background: theme.toolbar.border }} />
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user