Files

550 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多功能工具箱架构设计文档
> Feature: feat/08-multifunctional-toolbox
> Created: 2025-12-08
> Status: 设计阶段
## 📋 概述
多功能工具箱是一个允许用户在画布上嵌入第三方工具网页的功能。工具以 iframe 形式"钉在"画布上,成为画布的原生元素,支持拖拽、缩放、旋转等完整交互能力。
### 核心特性
- ✅ 左侧工具箱抽屉,展示可用工具列表
- ✅ 工具作为画布元素PlaitTool完全集成到 Plait 坐标系统
- ✅ 使用 SVG foreignObject 嵌入 iframe
- ✅ 自动继承 Plait 的拖拽、缩放、旋转、选中等能力
- ✅ 支持内置工具和自定义工具
- ✅ 工具状态持久化(随画板数据保存)
---
## 🏗️ 整体架构
```
┌─────────────────────────────────────────────────────────────────┐
│ Drawnix 应用层 │
│ ┌──────────────────┐ ┌───────────────────────────┐ │
│ │ ToolboxDrawer │ │ Plait Canvas (SVG) │ │
│ │ (左侧抽屉) │ │ │ │
│ │ │ │ ┌─────────────────────┐ │ │
│ │ 🍌 香蕉提示词 │ click │ │ PlaitTool Element │ │ │
│ │ 📝 小红薯工具 │ ─────> │ │ (foreignObject) │ │ │
│ │ ⚙️ 批处理工具 │ │ │ └─ iframe │ │ │
│ │ │ │ └─────────────────────┘ │ │
│ └──────────────────┘ └───────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Plait 插件层 (withTool) │ │
│ │ - 注册 ToolComponent │ │
│ │ - 提供 ToolTransforms API │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 📐 核心组件设计
### 1. 数据模型层
#### PlaitTool 元素(画布元素)
```typescript
/**
* 工具元素 - 画布上的工具实例
* 继承 PlaitElement成为画布的原生元素
*/
export interface PlaitTool extends PlaitElement {
type: 'tool';
// 位置和尺寸(画布坐标)
points: [Point, Point]; // [左上角, 右下角]
// 旋转角度
angle: number;
// 工具标识
toolId: string; // 工具定义ID
url: string; // iframe URL
// 可选元数据
metadata?: {
name?: string;
category?: string;
permissions?: string[];
};
}
```
#### ToolDefinition工具配置
```typescript
/**
* 工具定义 - 工具箱中的工具配置
*/
export interface ToolDefinition {
id: string; // 唯一标识
name: string; // 工具名称
description?: string; // 工具描述
icon?: string; // 图标emoji 或 icon name
category?: string; // 分类
url: string; // iframe URL
defaultWidth?: number; // 默认宽度(画布单位)
defaultHeight?: number; // 默认高度(画布单位)
permissions?: string[]; // iframe sandbox 权限
}
```
---
### 2. Plait 插件层
#### withTool 插件
```typescript
/**
* 工具插件 - 注册 ToolComponent 到 Plait
*/
export const withTool: PlaitPlugin = (board: PlaitBoard) => {
// 1. 注册元素类型
board.drawElement = (element: PlaitDrawElement) => {
if (element.type === 'tool') {
return ToolComponent;
}
return board.drawElement(element);
};
return board;
};
```
#### ToolTransforms API
```typescript
/**
* 工具元素操作 API
*/
export const ToolTransforms = {
// 插入工具到画布
insertTool(
board: PlaitBoard,
toolId: string,
url: string,
position: Point,
size: { width: number; height: number }
): void;
// 更新工具尺寸
resizeTool(
board: PlaitBoard,
element: PlaitTool,
newSize: { width: number; height: number }
): void;
// 删除工具
removeTool(board: PlaitBoard, elementId: string): void;
};
```
---
### 3. 渲染层
#### ToolComponent画布组件
```typescript
/**
* 工具元素渲染组件
* 继承 CommonElementFlavour集成到 Plait 渲染流程
*/
export class ToolComponent
extends CommonElementFlavour<PlaitTool, PlaitBoard>
implements OnContextChanged<PlaitTool, PlaitBoard>
{
toolGenerator: ToolGenerator;
// 初始化生成器
initializeGenerator(): void;
// 组件初始化
initialize(): void;
// 响应元素变化
onContextChanged(value, previous): void;
// 清理资源
destroy(): void;
}
```
#### ToolGenerator渲染生成器
```typescript
/**
* 工具元素渲染生成器
* 负责实际的 SVG/HTML 渲染
*/
export class ToolGenerator extends Generator<PlaitTool> {
// 绘制工具元素
draw(element: PlaitTool): SVGGElement;
// 更新工具元素
updateImage(nodeG: SVGGElement, previous: PlaitTool, current: PlaitTool): void;
// 创建 foreignObject + iframe
private createForeignObject(element: PlaitTool): SVGForeignObjectElement;
// 创建 iframe
private createIframe(element: PlaitTool): HTMLIFrameElement;
// 清理资源
destroy(): void;
}
```
---
### 4. UI 组件层
#### ToolboxDrawer工具箱抽屉
```typescript
/**
* 工具箱侧边栏
* 展示可用工具列表,点击后插入到画布
*/
export const ToolboxDrawer: React.FC<{
isOpen: boolean;
onOpenChange: (open: boolean) => void;
}> = ({ isOpen, onOpenChange }) => {
// 工具列表
const tools = useToolboxService().getAvailableTools();
// 点击工具 -> 插入到画布
const handleToolClick = (tool: ToolDefinition) => {
ToolTransforms.insertTool(board, tool.id, tool.url, ...);
};
return (
<div className="toolbox-drawer">
<ToolList tools={tools} onToolClick={handleToolClick} />
</div>
);
};
```
---
### 5. 服务层
#### ToolboxService工具管理
```typescript
/**
* 工具箱管理服务
* 管理内置工具和自定义工具
*/
export class ToolboxService {
private builtInTools: ToolDefinition[];
private customTools: ToolDefinition[];
// 获取所有工具
getAvailableTools(): ToolDefinition[];
// 根据 ID 获取工具
getToolById(id: string): ToolDefinition | null;
// 添加自定义工具
addCustomTool(tool: ToolDefinition): void;
// 移除自定义工具
removeCustomTool(id: string): void;
}
```
---
## 🔄 数据流
### 插入工具流程
```
用户点击工具箱中的工具
ToolboxDrawer.handleToolClick()
ToolTransforms.insertTool(board, toolId, url, position, size)
创建 PlaitTool 元素对象
DrawTransforms.insertElement(board, toolElement)
Plait 触发重新渲染
withTool 插件识别 type='tool'
实例化 ToolComponent
ToolGenerator.draw() 渲染 foreignObject + iframe
工具显示在画布上
```
### 拖拽/缩放流程
```
用户拖拽工具元素
Plait 原生拖拽系统处理
更新 PlaitTool.points
触发 ToolComponent.onContextChanged()
ToolGenerator.updateImage() 更新位置
foreignObject transform 更新
工具随画布移动/缩放
```
---
## 🎨 技术实现要点
### 1. SVG foreignObject 嵌入 HTML
```xml
<svg>
<g data-element-id="tool_123">
<foreignObject x="100" y="100" width="800" height="600">
<div xmlns="http://www.w3.org/1999/xhtml" class="tool-container">
<iframe src="https://tool.com" sandbox="allow-scripts allow-same-origin" />
</div>
</foreignObject>
</g>
</svg>
```
### 2. 画布坐标转换
```typescript
// 屏幕坐标 → 画布坐标
const boardX = (screenX - viewport.offsetX) / viewport.zoom;
const boardY = (screenY - viewport.offsetY) / viewport.zoom;
// 画布坐标 → 屏幕坐标
const screenX = boardX * viewport.zoom + viewport.offsetX;
const screenY = boardY * viewport.zoom + viewport.offsetY;
```
### 3. iframe 安全配置
```typescript
// sandbox 权限
const permissions = [
'allow-scripts', // 允许执行脚本
'allow-same-origin', // 允许同源访问
'allow-forms', // 允许表单提交
];
// allow 属性Feature Policy
iframe.setAttribute('allow', 'clipboard-read; clipboard-write');
```
### 4. 数据持久化
工具元素作为 `PlaitElement` 的一部分,自动保存到画板数据中:
```typescript
// 画板数据结构
{
children: [
{ type: 'geometry', ... },
{ type: 'image', ... },
{
type: 'tool',
id: 'tool_123',
toolId: 'banana-prompt',
url: 'https://banana-prompt.com',
points: [[100, 100], [900, 700]],
angle: 0
}
],
viewport: { ... },
theme: { ... }
}
```
---
## 📂 文件结构
```
packages/drawnix/src/
├── types/
│ └── toolbox.types.ts # 类型定义
├── plugins/
│ └── with-tool.ts # Plait 插件
├── components/
│ ├── tool-element/ # 画布元素渲染
│ │ ├── tool.component.ts # ToolComponent
│ │ ├── tool.generator.ts # ToolGenerator
│ │ └── tool.component.scss # 样式
│ │
│ └── toolbox-drawer/ # UI 组件
│ ├── ToolboxDrawer.tsx # 主抽屉
│ ├── ToolboxDrawer.scss
│ ├── ToolList.tsx # 工具列表
│ └── ToolItem.tsx # 工具项
├── services/
│ └── toolbox-service.ts # 工具管理服务
├── constants/
│ └── built-in-tools.ts # 内置工具配置
└── utils/
└── tool-helpers.ts # 辅助函数
```
---
## 🚀 实施计划
### Phase 1: 基础架构(核心功能)
**目标**: 建立基础框架,实现最小可用版本
1. **类型定义** (30分钟)
- `toolbox.types.ts` - PlaitTool, ToolDefinition 接口
2. **Plait 插件** (1小时)
- `with-tool.ts` - withTool 插件
- `ToolTransforms` API
3. **渲染组件** (2小时)
- `tool.component.ts` - ToolComponent
- `tool.generator.ts` - ToolGeneratorforeignObject + iframe
4. **测试集成** (30分钟)
- 在 drawnix.tsx 中集成 withTool
- 手动测试插入工具元素
**验收标准**: 可以通过代码手动插入工具元素到画布,并正常显示 iframe
---
### Phase 2: UI 组件(用户交互)
**目标**: 实现用户可见的工具箱界面
1. **工具箱抽屉** (2小时)
- `ToolboxDrawer.tsx` - 主组件
- `ToolList.tsx` - 工具列表
- `ToolItem.tsx` - 工具项
2. **工具管理服务** (1小时)
- `toolbox-service.ts` - ToolboxService
- `built-in-tools.ts` - 内置工具配置
3. **集成到 UnifiedToolbar** (30分钟)
- 添加工具箱按钮
- 管理抽屉状态
4. **测试交互** (30分钟)
- 点击工具插入到画布
- 拖拽、缩放测试
**验收标准**: 用户可以从工具箱点击工具,工具正常插入到画布并可交互
---
### Phase 3: 优化与完善(可选)
**目标**: 提升用户体验和功能完整性
1. **样式优化** (1小时)
- 工具箱样式美化
- 工具元素选中态样式
- 响应式适配
2. **数据通信** (1-2小时)
- postMessage 通信协议
- 复制文本到画布功能
3. **自定义工具** (1小时)
- 支持用户添加自定义工具
- 工具配置持久化
4. **错误处理** (30分钟)
- iframe 加载失败处理
- 权限错误提示
**验收标准**: 完整的用户体验,支持高级功能
---
## 🎯 关键优势
### 1. 完全集成 Plait 生态
- ✅ 自动支持拖拽、缩放、旋转
- ✅ 自动支持撤销/重做
- ✅ 自动支持复制/粘贴
- ✅ 自动序列化和持久化
### 2. 架构清晰
- ✅ 类似 ImageComponent 的实现模式
- ✅ 复用 Plait 的成熟能力
- ✅ 易于维护和扩展
### 3. 性能优良
- ✅ SVG 原生渲染,性能优秀
- ✅ iframe 隔离,不影响主应用
- ✅ 懒加载工具内容
---
## 📝 注意事项
### 1. iframe 跨域限制
- ⚠️ 第三方网页可能设置了 `X-Frame-Options`,导致无法嵌入
- 🔧 解决方案:提供代理服务或使用支持嵌入的工具
### 2. iframe 通信
- ⚠️ postMessage 需要工具网页配合实现
- 🔧 解决方案:提供标准协议文档,工具开发者按协议实现
### 3. 性能考虑
- ⚠️ 大量工具可能影响性能
- 🔧 解决方案:限制同时显示的工具数量,超出部分隐藏
### 4. 安全考虑
- ⚠️ iframe 可能执行恶意脚本
- 🔧 解决方案:严格的 sandbox 权限控制,白名单机制
---
## 🔗 相关文档
- [Plait 官方文档](https://github.com/worktile/plait)
- [SVG foreignObject 规范](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject)
- [iframe sandbox 文档](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
- [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
---
## 📅 更新日志
- 2025-12-08: 初始架构设计