Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

248 lines
8.6 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.
# maic-renderer 设计稿(v1:只读画布)
> 配套计划:`docs/superpowers/plans/2026-05-28-maic-renderer-package.md`
> 原始 spec`docs/superpowers/specs/2026-05-28-maic-renderer-package-design.md`
## 1. 目标
把 OpenMAIC 主仓 `components/maic-renderer/` 中的只读画布部分抽成独立 workspace 包,让任意 React + Tailwind 4 项目都能"装包 → 传 Slide → 直接渲染"。
**v1 范围限定为只读画布**(对应主仓 `Editor/ScreenCanvas.tsx` 子树)。编辑能力(`Editor/Canvas/*``Operate/*`、ProseMirror live editor)留给 v2。
理由:
1. 闭环最小可用,跟 `pptxtojson-pro` 形成「产 Slide → 渲 Slide」对子。
2. 编辑器接口面(选中态、命令、撤销、剪贴板、协作)定型后难改,第一版强行定下来大概率返工。
3. 只读包本身有独立价值(嵌课件、报告、回放、PDF 导出预览等)。
## 2. 设计决策
| # | 决策点 | 选择 | 理由 |
|---|--------|------|------|
| D1 | 范围 | 分两步:v1 只读 + v2 编辑 | 闭环最小,风险可控 |
| D2 | 数据 API | **纯 Props 主**;同时导出可选 Provider | 上手最快,进阶用户也照顾到 |
| D3 | 样式 | **要求消费者使用 Tailwind 4** | 工作量最小,跟主仓工程实践一致 |
| D4 | 特效层 | **包含**,4 个全做成可选 props,默认关 | 还原"OpenMAIC 同款画布"必备,但默认零负担 |
| D5 | 业务耦合元素 | **暴露 renderImage / renderVideo 插槽**,包内默认渲原生标签 | 包不感知业务,消费者按需注入 |
| D6 | i18n | 包不带文案 | UI 文案下放给消费者 |
| D7 | ProseMirror | v1 不依赖 | 只读用 dangerouslySetInnerHTML 就够,体积大幅缩小 |
| D8 | 包发布 | workspace 包,未来可发 npm;当前名 `maic-renderer`(同 `pptxtojson-pro` 风格) | 一致性 |
## 3. 对外 API
### 3.1 主入口
```ts
import { SlideCanvas, type SlideCanvasProps } from 'maic-renderer';
interface SlideCanvasProps {
/** 单页幻灯片数据(PPTist 风格) */
slide: Slide;
/** 画布缩放,默认 1 */
scale?: number;
/** 可选:覆盖 slide.background */
background?: SlideBackground;
/** 可选:4 个播放特效,默认全关 */
effects?: {
laser?: { elementId: string; color?: string; duration?: number };
spotlight?: { elementId: string };
highlight?: { elementId: string };
zoom?: { elementId: string; scale: number };
};
/** 可选:图片渲染插槽,消费者可注入 placeholder/retry 逻辑 */
renderImage?: (el: PPTImageElement, resolvedSrc: string) => React.ReactNode;
/** 可选:视频渲染插槽 */
renderVideo?: (el: PPTVideoElement) => React.ReactNode;
/** 可选:元素点击 */
onElementClick?: (el: PPTElement, event: React.MouseEvent) => void;
/** 透传外层 */
className?: string;
style?: React.CSSProperties;
}
```
### 3.2 Provider 高阶模式(可选)
```tsx
import { SlideRendererProvider, useSlideContext } from 'maic-renderer';
<SlideRendererProvider slide={slide} scale={0.9}>
<SlideCanvas />
<MyCustomOverlay /> {/* 子组件 useSlideContext() 拿数据 */}
</SlideRendererProvider>
```
### 3.3 元素子组件(细粒度复用)
```ts
import {
BaseTextElement, BaseShapeElement, BaseImageElement,
BaseLineElement, BaseChartElement, BaseLatexElement,
BaseTableElement, BaseVideoElement, BaseCodeElement,
} from 'maic-renderer/elements';
```
### 3.4 类型
```ts
import type {
Slide, PPTElement, SlideBackground, SlideTheme,
PPTTextElement, PPTShapeElement, PPTImageElement,
PPTLineElement, PPTChartElement, PPTLatexElement,
PPTTableElement, PPTVideoElement, PPTCodeElement,
ImageElementClip, ImageElementFilters,
Gradient, GradientType, PPTElementOutline, PPTElementShadow,
} from 'maic-renderer/types';
```
## 4. 包结构
```
packages/maic-renderer/
├── package.json
├── README.md
├── DESIGN.md
├── tsconfig.json
├── rollup.config.js
└── src/
├── index.ts # 主入口
├── SlideCanvas.tsx # 替代 ScreenCanvasprops 驱动)
├── SlideElement.tsx # 替代 ScreenElement
├── context.tsx # Provider + useSlideContext
├── effects/
│ ├── index.ts
│ ├── HighlightOverlay.tsx
│ ├── SpotlightOverlay.tsx
│ ├── LaserOverlay.tsx
│ └── ZoomWrapper.tsx
├── elements/
│ ├── index.ts
│ ├── text/BaseTextElement.tsx
│ ├── shape/
│ │ ├── BaseShapeElement.tsx
│ │ ├── GradientDefs.tsx
│ │ └── PatternDefs.tsx
│ ├── image/
│ │ ├── BaseImageElement.tsx
│ │ ├── ImageOutline.tsx
│ │ ├── useClipImage.ts
│ │ └── useFilter.ts
│ ├── line/BaseLineElement.tsx
│ ├── chart/
│ │ ├── BaseChartElement.tsx
│ │ └── Chart.tsx
│ ├── latex/BaseLatexElement.tsx
│ ├── table/BaseTableElement.tsx
│ ├── video/BaseVideoElement.tsx
│ ├── code/BaseCodeElement.tsx
│ └── shared/
│ ├── ElementOutline.tsx
│ ├── useElementFill.ts
│ ├── useElementOutline.ts
│ ├── useElementShadow.ts
│ └── useElementFlip.ts
├── hooks/
│ ├── useSlideBackgroundStyle.ts
│ └── useViewportSize.ts
├── utils/
│ ├── cn.ts
│ ├── geometry.ts
│ └── element.ts
└── types/
├── slides.ts # 从 lib/types/slides 拷贝
├── effects.ts # 新增
└── index.ts
```
## 5. 依赖
**peerDependencies**
```json
{
"react": ">=18",
"react-dom": ">=18",
"motion": ">=11",
"tailwindcss": ">=4"
}
```
**dependencies**(运行时必备):
- `katex` — latex 渲染
- `tinycolor2` — 颜色工具
- `lucide-react` — 图标
- `clsx``tailwind-merge``cn` helper
**chart 库**:复用主仓现用方案,T10 实现阶段确认。开发阶段先 inline 进 dependencies,若体积过大改 peerDep。
**ProseMirror 系列**v1 完全不依赖。
## 6. 构建
`pptxtojson-pro` 完全一致:
```bash
pnpm build # rollup -c && tsc --emitDeclarationOnly
```
产物:
```
dist/
├── index.{js,cjs,d.ts}
├── elements/index.{js,cjs,d.ts}
└── types/index.{js,cjs,d.ts}
```
## 7. 关键解耦点
### 7.1 ScreenCanvas → SlideCanvas
| 原 | 新 |
|----|----|
| `useCanvasStore.use.canvasScale()` | `props.scale ?? 1` |
| `useSceneSelector(c => c.canvas.elements)` | `props.slide.elements` |
| `useSceneSelector(c => c.canvas.background)` | `props.background ?? props.slide.background` |
| `useCanvasStore.use.laserElementId/Options()` | `props.effects?.laser` |
| `useCanvasStore.use.zoomTarget()` | `props.effects?.zoom` |
| `findElementGeometry(...)` | 拷贝进 `utils/geometry.ts` |
### 7.2 ScreenElement → SlideElement
`theme.fontColor` / `theme.fontName` 改成从 `props.slide.theme` 读取;`useSceneSelector` 移除。
### 7.3 BaseImageElement
包内退化成纯 `<img>` 版本(含 clip/filter/outline/shadow),所有 placeholder / retry / i18n 逻辑剥离。消费者通过 `renderImage` 注入业务版本。
### 7.4 BaseVideoElement
同上,纯 `<video>`
### 7.5 其余 Base*Element
只有类型导入路径要改(`@/lib/types/slides``../../types/slides`),无业务耦合。
## 8. 验收标准
1. `pnpm --filter maic-renderer build` 通过,产出干净 dist
2. 主仓 `/maic-renderer-demo` 路由用包渲染一份手写 Slide,目视与原 `ScreenCanvas` 一致
3. v1 **不替换主仓既有调用**
## 9. v2 预告
- `Editor/Canvas/*` 抽到包内 `editing/`
- ProseMirror live editor
- 选中/拖拽/resize/旋转/裁剪/对齐线/标尺
- 撤销/重做/剪贴板
- API`<SlideEditor slide editable onChange={...} />`
## 10. 风险
| 风险 | 等级 | 缓解 |
|------|------|------|
| Chart 库依赖不明,包体积可能膨胀 | 中 | T10 阶段确认;若 >200KB gz 改 peerDep |
| Tailwind 4 强依赖筛掉很多消费者 | 中 | README 显著说明;v3 再考虑编译 CSS |
| 主仓 `Slide` 类型未来演进,包内拷贝版会漂移 | 低 | 后续可通过 `pptxtojson-pro` 已有的 `openmaic/types/slides.ts` 三方共享 |
| BaseImageElement 砍业务后主仓现有页面会丢功能 | **无影响** | 本次任务不替换主仓调用,主仓继续用自己的 `BaseImageElement` |