大家好,我是前端西瓜哥。
最近有人想学习我开源的 suika 图形编辑器,问我下面的项目结构哪些文件对应什么功能,文件太多看不太懂。
那我就在这里简单展开讲讲,内容很多,建议跳着看,或者不看直接收藏了,收藏就是会了。
suika 是我维护的一个开源图形编辑器项目,目前 star 数 700+,欢迎 star。
github 地址:
线上体验:
总览
suika 项目使用了 monorepo,基于 pnpm 实现,将代码拆分到不同包下。
注意目录结构为文章发布时的状态,之后可能会变更。
monorepo 的包在 packages 和 apps 目录下。
首先看 packages 目录。
packages
├── common
├── components
├── core
├── geo
└── icons
-
common:
@suika/common
包放一些公共方法,通常都是些工具方法,其他所有包都可以引用它。 -
geo:
@suika/geo
包都是一些几何算法。 -
core:
@suika/core
包为编辑器内核,编辑器的业务逻辑都在这里。 -
icon:
@suika/icon
包为一些图标 React 组件,定位类似 antd 的@ant-design/icon
。 -
component:
@suika/component
包为一些公共组件。
然后是 apps 目录。
apps
├── docs
├── suika
└── suika-multiplayer
apps 目录放的是业务的包,就是最终的构建产物,一个 application,通常不会被其他包引用。
-
suika:
@suika/suika
包,将编辑器内核和 UI 组合一起,得到一个单机图形编辑器。 -
suika-multiplayer:
@suika/multiplayer
包,其实就是 @suika/suika 拷贝过来,然后加了一点多人协同功能,后端代码在另一个项目。 -
docs:
@suika/docs
包,suika 的文档,使用了 vitepress,目前还没有内容。
common
@suika/common
放一些公共的方法,可以被其他包引用的。
基本都是些工具方法。
packages/common/src
├── array_util.ts # 一些数组方法
├── color.ts
├── common.ts
├── event_emitter.ts # 发布订阅类
├── index.ts
├── lodash.ts
└── vite-env.d.ts
没有什么特别的,简单说一些。
其中 event_emitter.ts 文件下的 EventEmitter 类用来做发布订阅模式,很多地方都会用到。另外,它是类型安全,这样不用害怕写错事件名了。
lodash.ts 就是把少量用到的 lodash 方法导入然后重新导出。
geo
@suika/geo
下是一些平面几何算法的方法,根据不同的图形,划分到不同的文件下。
packages/geo/src/geo
├── constant.ts # 一些常量
├── geo_angle.ts # 角
├── geo_bezier.ts # 三阶贝塞尔
├── geo_bezier_class.ts # 三阶贝塞尔(类形式)
├── geo_box.ts # 盒(通常用于包围盒计算)
├── geo_circle.ts # 圆
├── geo_ellipse.ts # 椭圆
├── geo_line.ts # 线
├── geo_matrix.ts # 矩阵
├── geo_matrix_class.ts # 矩阵(类形式)
├── geo_path.ts # 路径
├── geo_path_class.ts # 路径(类形式)
├── geo_point.ts # 点
├── geo_polygon.ts # 多边形
├── geo_rect.ts # 矩形
├── geo_resize_line.ts # 缩放线
├── geo_resize_rect
│ ├── geo_resize_rect.ts # 缩放矩形(用于缩放图形)
│ └── index.ts
├── geo_star.ts # 星形
├── geo_text.ts # 文本
└── index.ts
举个例子,geo_rect 是矩形相关的几何方法,像是两个矩形是否碰撞,是否为包含关系。
geo_beziser 是和贝赛尔曲线相关的几何算法,如点到曲线的最近点(投影)、求贝赛尔的包围盒等等。
大部分都是纯函数,即没有使用类似 GeoRect 这种的类的形式。
这样是为了更好的原子化,其他模块通常只是简单地调用一个方法,没有必要专门实例化,同时我们可以随意组合这些方法。
但还是有些是需要用类的形式的。
比如矩阵类 Matrix,这样可以进行链式操作组合,参与各种计算,可读性更好。当然我也提供纯函数的形式。顺带一提这个 Matrix 是从 pixi.js 里面拷贝过来,进行了一些修改。
还有一个是贝赛尔类 GeoBezier,因为需要用到缓存,比如图形拾取时可能需要频繁求最近点时,就需要一个查找表 lut 提前算好曲线的一些点的位置。
当做新需求用到一些新的几何方法,如果是 比较底层的通用算法,建议放到 geo 包下。如果觉得通用性不强,可不放到 geo 下,即放到用到这个方法的文件附近。后来如果有更多地方用到这个局部几何方法,可以再放到 geo包下。
core
@suika/core
是图形编辑器内核,是整个图形编辑器项目的核心,管理着编辑器的所有业务逻辑,其下有大量的模块。这些模块经过合理的设计,能良好地组织工作在一起。
@suika/suika
不允许有任何 UI 相关的逻辑。该包会提供必要的事件给 UI 层,UI 层逻辑只能在 @suika/suika
里实现。
packages/core/src/
├── Img_manager.ts
├── canvas_dragger.ts
├── clipboard.ts
├── commands # 历史记录命令相关
│ ├── add_graphs.ts
│ ├── command_manager.ts
│ ├── index.ts
│ ├── macro.ts
│ ├── reparent.ts
│ ├── set_elements_attrs.ts
│ ├── type.ts
│ └── update_graphics_attrs_cmd.ts
├── constant.ts
├── control_handle_manager # 控制点管理
│ ├── control_handle.ts
│ ├── control_handle_manager.ts
│ ├── index.ts
│ ├── type.ts
│ └── util.ts
├── cursor_manager # 光标管理
│ ├── cursor-icons
│ │ ├── suika-cursor-default.png
│ │ ├── ...
│ ├── cursor.css
│ ├── cursor_manager.ts
│ ├── index.ts
│ └── util.ts
├── editor.ts # 编辑器类(根类)
├── export_manager.ts
├── graphics # 图形类
│ ├── rect.ts
│ ├── ...
│ └── utils.ts
├── grid.ts # 网格
├── host_event_manager # 原生事件的封装和注册
│ ├── command_key_binding.ts
│ ├── host_event_manager.ts
│ ├── index.ts
│ ├── mouse_event_manager.ts
│ └── move_graphs_key_binding.ts
├── index.ts
├── key_binding_manager.ts # 键盘事件封装
├── paint.ts
├── path_editor # 路径编辑器(切换为钢笔工具时触发)
│ ├── index.ts
│ ├── path_editor.ts
│ ├── selected_control.ts
│ └── type.ts
├── perf_monitor.ts
├── ref_line.ts # 图形参考线吸附和绘制
├── ruler.ts # 标尺
├── scene
│ └── scene_graph.ts # 场景树渲染
├── selected_box.ts # 选中框
├── selected_elements.ts # 选中图形管理
├── service # 操作图形并保存到历史记录的服务
│ ├── align_and_record.ts
│ ├── ...
├── setting.ts # 全局设置(类似环境变量)
├── snap.ts # 快捷吸附方法(目前没有内容)
├── text # 文本编辑器
│ ├── range_manager.ts
│ └── text_editor.ts
├── to_svg.ts
├── tools # 工具和工具管理类
│ ├── index.ts
│ ├── tool_drag_canvas.ts
│ ├── tool_draw_ellipse.ts
│ ├── ...
├── transaction.ts # “事务”:历史记录操作的高级封装(常用)
├── type.ts
├── utils # 杂七杂八的工具方法
│ ├── canvas.ts
│ ├── common.ts
│ ├── frame.ts
│ ├── geo.ts
│ ├── group.ts
│ ├── index.ts
│ └── raf_throttle.ts
├── viewport_manager.ts # 画布视口
├── vite-env.d.ts
└── zoom_manager.ts # 画布缩放
可以看到,模块很多,这里有 122 个文件。随着功能越来越多,文件只会又来越多。
下面简单介绍一下这些模块。
基础模块
-
editor.ts
:SuikaEditor 类,基本所有模块都会放在 SuikaEditor 的下,同时也会注入到其他模块下,模块可以通过 editor 这一桥梁,访问另一个模块。虽然不符合最小知识原则,但胜在灵活。一些 常用的方法可以放到或代理到 editor 下,比如场景坐标系到视口坐标系的坐标转换。 -
setting.ts
:Setting 类存放编辑器的所有的设置,类似环境变量。设置项是拍平的,这样方便读写。设置项需要通过setting.get('snapToObject')
的方式来获取,这样你才知道你在使用设置,写也同样道理。使用了 TypeScript 确保类型安全。
事件相关
-
key_binding_manager.ts
:KeyBindingManager 类对浏览器的键盘事件进行了封装。之后编辑器会完全使用这个类,不再直接使用原生事件,方便统一管理,比如高优先级的快捷键注册会阻断低优先级的注册。 -
command_key_binding.ts
:CommandKeyBinding 类只是绑定一些快捷键对应的命令。在编辑器初始化的时候,调用前面的 KeyBindManager 类下的方法绑定一些命令快捷键,比如 delete 会注册删除图形的逻辑。 -
mouse_event_manager.ts
:MouseEventManager 类是对鼠标事件做了一层封装,提供了额外的高级功能,如拖拽事件(drag)、连击事件(comboClick)。对原声的事件对象也做了封装,可以直接拿到光标所在的世界坐标、是否在画布区域外等重要的信息。 -
move_graphs_key_binding.ts
:MoveGraphsKeyBinding 类处理通过快捷键移动图形的逻辑。按住方向键会触发多次的键盘事件,每个都生成一个历史记录是不必要的,对此我们通过防抖来处理。 -
host_event_manager.ts
:HostEventManager 类负责封装一些泛用的原生事件,包括监听 Shift、Alt、Space、Command 等按键的按下释放事件,滚轮事件(wheel),右键菜单事件(contextmenu,提供给 UI 层显示右键菜单)
图形相关
graphics 目录下,全都是图形类。
packages/core/src/graphics
├── canvas.ts # 画布类(对应 figme 的 page)
├── document.ts # 文档类,根节点
├── ellipse.ts # 椭圆类
├── frame
│ ├── frame.ts # 画板或组(如果 attrs.resizeToFit 是 true)
│ └── index.ts
├── graphics
│ ├── graphics.ts # 图形基类
│ ├── graphics_attrs.ts # 图形属性 interface
│ └── index.ts
├── graphics_manger.ts # 图形存储管理类(id 到图形的映射,方便查找)
├── index.ts
├── line.ts # 线类
├── path
│ ├── index.ts
│ ├── path.ts # 路径类
│ └── type.ts
├── rect.ts # 矩形类
├── regular_polygon.ts # 正多边形
├── star.ts # 星形
├── text.ts # 文本
├── type.ts
└── utils.ts
具体哪个文件保存的是什么图形类,直接看上面的注释就好了。
它们会组合成一棵图形树,然后渲染在画布上。
需要注意的是,图形类和图形库的图形类是不一样的,叫实体类(entity)会更好。它们是 业务上的图形,不完全对应渲染上的图形对象,因为一个实体可能需要多个渲染图形的组合,具体看需求。
图形类的 attrs 属性为图形要进行持久化的属性。图形类通常会有非常多的方法,随着新需求还会越来越多,比如如果加了个新需求,需要支持导出为 png,就要再补上一个 toPng 之类的方法。
这些图形类其实更好再封装成一个 momorepo 包,防止污染,比如把 editor 往图形类里面塞,这个是不对的,这样是依赖具体的类了。
工具相关
tools 目录下都是工具类。
packages/core/src/tools
├── index.ts
├── tool_drag_canvas.ts # 拖拽画布工具
├── tool_draw_ellipse.ts # 绘制椭圆工具
├── tool_draw_frame.ts # 绘制画板工具
├── tool_draw_graphics.ts # 绘制图形工具基类(抽象类)
├── tool_draw_img.ts # 添加图片工具
├── tool_draw_line.ts # 绘制线条工具
├── tool_draw_path.ts # 绘制路径工具(钢笔工具)
├── tool_draw_rect.ts # 绘制矩形工具
├── tool_draw_regular_polygon.ts # 绘制正多边形工具
├── tool_draw_star.ts # 绘制星形工具
├── tool_draw_text.ts # 绘制文本工具
├── tool_manager.ts # 工具管理类
├── tool_path_select
│ ├── index.ts
│ ├── tool_path_select.ts # 路径选中工具(选中锚点、控制点等)
│ ├── tool_path_select_move.ts # 路径移动工具(移动锚点、控制点等)
│ └── tool_path_select_selection.ts # 路径选区工具
├── tool_pencil.ts # 铅笔工具
├── tool_select
│ ├── index.ts
│ ├── tool_select.ts # 选择工具
│ ├── tool_select_move.ts # 选择工具下的移动图形子工具
│ ├── tool_select_resize.ts # 缩放图形子工具
│ ├── tool_select_rotation.ts # 旋转图形子工具
│ ├── tool_select_selection.ts # 绘制选区子工具
│ └── utils.ts
└── type.ts
编辑器永远会有一个工具会被激活,这个工具会监听各种事件,尤其是鼠标事件,执行对应的逻辑。
-
tool_manager.ts
:ToolManager 类用于管理工具,将监听的事件传递给实现了工具接口的对象。工具类需要实现工具接口,然后注册到 ToolManager 下。 -
其他的工具类见上面注释。对于复杂的工具类,需要用策略模式进行进一步的拆分为子工具类,以隔离复杂度,避免大量的分支语句。
历史记录相关
commands 目录都是历史记录相关的文件,使用了命令模式。
packages/core/src/commands
├── add_graphs.ts # 新增图形命令
├── command_manager.ts # 命令管理类
├── index.ts
├── macro.ts # 宏命令
├── reparent.ts # 更改父节点命令
├── set_elements_attrs.ts # 设置
├── type.ts
└── update_graphics_attrs_cmd.ts # 更新图形命令(万能命令,支持增删改)
command_manager.ts
:CommandManager 类通过撤销栈和重做栈维护历史记录。
后面组的出现,导致大量父子元素会发生联动更新,每个命令都要专门处理太麻烦,以及协同的数据同步。
所以现在基本要用更高层级的 Transaction 来替代原来的各种有具体语义的命令了。
transaction.ts
(不在 commands 目录下):Transaction 类是在 UpdateGraphicsAttrsCmd 类做了更高级的封装,并被广泛使用。比如移动图形时,会先记录好图形修改前的属性(recordOld 方法),然后更新图形,记录更新后的属性(update 方法),处理组导致的父子联动属性更改(updateParentSize 方法),缩放完成后提交更改(commit 方法)。
路径编辑器
路径编辑器,其实就是钢笔工具,会临时接管图形编辑器的部分事件,关闭图形编辑器的一些功能,让图形编辑器变成 “路径编辑器”。
packages/core/src/path_editor
├── index.ts
├── path_editor.ts # 路径编辑器类
├── selected_control.ts # 控制点选中类,维护路径中被选中的锚点、控制点等
└── type.ts
路径编辑器的核心逻辑其实在 tool_draw_path.ts
和 tool_path_select
下。
service
service 目录放的是一些 "公共服务" 的逻辑,大多是更新图形属性相关的业务。
packages/core/src/service
├── align_and_record.ts # 图形对齐,并记录(记录是指保存到历史记录)
├── arrange_and_record.ts # 图形排列,并记录
├── export_service.ts # 导出服务
├── flip_and_record.ts # 翻转图形,并记录
├── group_and_record.ts # 对图形编组,并记录
├── import_service.ts # 导入服务
├── index.ts
├── mutate_graphs_and_record.ts # 批量修改图形的属性(通常是属性面板会用到)
├── remove_service.ts # 删除图形
└── ungroup_and_record.ts # 对组进行解组,并记录
文本相关
文本编辑器,类似路径编辑器,也会临时接管图形编辑器的事件,以及关闭图形编辑器的一些功能。
packages/core/src/text
├── range_manager.ts # 文本选区管理
└── text_editor.ts # 文本编辑器
工具方法
utils 目录下都是些工具方法。
packages/core/src/utils
├── canvas.ts # canvas 2d 的一些方法,比如绘制 x。
├── common.ts # 普通工具方法
├── frame.ts # 画板图形用到的方法
├── geo.ts # 一些不太泛用的几何方法
├── group.ts # 编组相关
├── index.ts
└── raf_throttle.ts # 动画帧回调函数
其他
最后是一些比较单一的模块。
-
scene_graph.ts
:SceneGraph 是场景树类,其中 render 方法负责触发图形场景树的渲染,渲染图形、控制点、选中框、网格等在画布上的所有物体。 -
cursor_manager.ts
:CursorManger 类负责维护编辑器的光标,支持设置自定义光标(比如带旋转角度的 resize 光标),设置当前光标。 -
control_handle_manager.ts
:ControlHandleManager 控制点管理类,管理选中图形时显示的控制点。 -
control_handle.ts
:ControlHandle 控制点类,维护控制点如何渲染、如何被 hitTest、是否可见等逻辑。 -
canvas_dragger.ts
:CanvasDragger 画布拖拽类。因为触发画布拖拽的方式太多(抓手工具、按住空格、按住滚轮),所以专门抽一个类出来。 -
clipboard.ts
:ClipboardManager 剪贴板管理类,用于管理图形的复制粘贴逻辑。复制图形会将数据保存在操作系统剪贴板中,粘贴的时候会读取。这样可以实现跨图纸粘贴。 -
grid.ts
:Grid 类负责绘制网格。 -
Img_manager.ts
:ImgManager 图片管理类。 -
paint.ts
:图形的填充和描边用到的 paint(纯色、图形) 的 interface 定义,和一些默认值等。 -
perf_monitor
:性能监控用的,基本没开过,开始后会有一个方形的性能监控。 -
ref_line.ts
:RefLine 参考线类,负责计算图形吸附的偏移值,以及吸附后参考线渲染。算法和渲染感觉可以分离一下。 -
ruler.ts
:Ruler 类负责计算和渲染标尺。 -
selected_box.ts
: SelectedBox 类主要负责渲染选中框、宽高提示。 -
selected_elements.ts
:SelectedElements 类维护选中的图形,以及 hover 的图形。hover 的图形感觉可以单独抽到另一个类里,比较好? -
snap.ts
:一些封装了一些业务的吸附方法,目前只有网格吸附; -
to_svg.ts
:“复制为 SVG” 功能用到的方法; -
viewport_manager.ts
:ViewportManager 视口管理类负责维护编辑器的视口,缩放浏览器窗口时要通知它进行更新。 -
zoom_manager.ts
:ZoomManager 类管理画布缩放值(zoom),支持适应画布,适应选中图形等。ViewportManager 和 ZoomManager 类职责可以考虑合并为一个 Camera 类。
icons
@suika/icon
包放了一堆 React 图标组件。
图标来自:www.figma.com/community/f…
packages/icons/src
├── icons
│ ├── add-outlined.tsx
│ ├── align
│ │ ├── align-bottom.tsx
│ │ ├── align-h-center.tsx
│ │ ├── align-left.tsx
│ │ ├── align-right.tsx
│ │ ├── align-top.tsx
│ │ ├── align-v-center.tsx
│ │ └── index.tsx
│ ├── arrow-down-outlined.tsx
│ ├── check-outlined.tsx
│ ├── close-outlined.tsx
│ ├── hide-outlined.tsx
│ ├── i18n-outlined.tsx
│ ├── index.ts
│ ├── line-width-outlined.tsx
│ ├── lock-filled.tsx
│ ├── pen-outlined.tsx
│ ├── pencil-outlined.tsx
│ ├── point-solid.tsx
│ ├── remove-outlined.tsx
│ ├── right-outlined.tsx
│ ├── show-outlined.tsx
│ ├── small-caret-down-solid.tsx
│ ├── social
│ │ ├── github-outlined.tsx
│ │ └── index.ts
│ ├── tool
│ │ ├── ellipse-outlined.tsx
│ │ ├── frame-outline.tsx
│ │ ├── hand-outlined.tsx
│ │ ├── image-outlined.tsx
│ │ ├── index.ts
│ │ ├── line-outlined.tsx
│ │ ├── menu-outlined.tsx
│ │ ├── polygon-outlined.tsx
│ │ ├── rect-outlined.tsx
│ │ ├── select-outlined.tsx
│ │ ├── star-outlined.tsx
│ │ └── text-filled.tsx
│ └── unlock-filled.tsx
├── index.ts
└── vite-env.d.ts
components
@suika/components
是一个 React 组件库。
目前组件非常少,组件支持的属性也很少。
组件库要做好还是很花费精力的,目前个人并没有太多精力搞这个,影响我开发进度了。
最好还是用成熟的第三方组件库,比如 AntD。
packages/components/src
├── components
│ ├── button # 按钮组件
│ │ ├── button.scss
│ │ ├── button.tsx
│ │ └── index.ts
│ ├── dropdown # 下拉选择框
│ │ ├── dropdown-item
│ │ │ ├── dropdown-item.scss
│ │ │ ├── dropdown-item.tsx
│ │ │ └── index.ts
│ │ ├── dropdown.scss
│ │ ├── dropdown.stories.tsx
│ │ ├── dropdown.tsx
│ │ ├── index.ts
│ │ └── type.ts
│ ├── icon-button # 图标按钮
│ │ ├── icon-button.scss
│ │ ├── icon-button.tsx
│ │ └── index.ts
│ ├── popover # popover
│ │ ├── index.ts
│ │ ├── popover.scss
│ │ ├── popover.stories.tsx
│ │ └── popover.tsx
│ └── select # 选择器
│ ├── index.ts
│ ├── select.scss
│ ├── select.stories.tsx
│ └── select.tsx
├── index.ts
└── vite-env.d.ts
suika
@suika/suika
是图形编辑器的最终产品包,其下构建的文件可直接进行部署。
@suika/suika
包使用了 React 组件,主要都是一些 UI 层的逻辑,如属性面板、右键菜单、工具栏。
@suika/core
下的图形编辑器类 SuikaEditor 会实例化挂载到一个 Canvas DOM 元素上,然后会把 SuikeEditor 通过 context 的方式透传给所有需要的组件。
apps/suika/src
├── App.css
├── App.tsx
├── components
│ ├── Cards
│ │ ├── AlignCard # 对齐操作面板卡片组件
│ │ │ ├── AlignCard.scss
│ │ │ ├── AlignCard.tsx
│ │ │ └── index.ts
│ │ ├── BaseCard # 卡片基类组件
│ │ │ ├── index.tsx
│ │ │ └── style.scss
│ │ ├── ElementsInfoCard # 图形属性卡片组件
│ │ │ ├── ElementsInfoCard.tsx
│ │ │ ├── index.tsx
│ │ │ └── style.scss
│ │ ├── FillCard # 图形填充卡片组件
│ │ │ ├── FillCard.tsx
│ │ │ └── index.ts
│ │ ├── LayerInfoCard # 图形信息卡片(透明度)
│ │ │ ├── LayerInfoCard.scss
│ │ │ ├── LayerInfoCard.tsx
│ │ │ └── index.ts
│ │ ├── PaintCard # 颜色卡片基类组件
│ │ │ ├── PaintCard.scss
│ │ │ ├── PaintCard.tsx
│ │ │ └── index.tsx
│ │ └── StrokeCard # 描边卡片组件
│ │ ├── StrokeCard.tsx
│ │ └── index.ts
│ ├── ColorPicker # 颜色相关
│ │ ├── ImagePicker # 图片选择器
│ │ │ ├── ImagePicker.scss
│ │ │ ├── ImagePicker.tsx
│ │ │ └── index.ts
│ │ ├── PaintPicker # paint 选择器(图片 + 纯色)
│ │ │ ├── PaintPicker.scss
│ │ │ ├── PaintPicker.tsx
│ │ │ └── index.ts
│ │ └── SolidPicker # 纯色选择器
│ │ ├── SolidPicker.tsx
│ │ └── index.ts
│ ├── ContextMenu # 右键菜单组件
│ │ ├── ContextMenu.scss
│ │ ├── ContextMenu.tsx
│ │ ├── components
│ │ │ ├── ContextMenuItem.scss
│ │ │ ├── ContextMenuItem.tsx
│ │ │ ├── ContextMenuSep.scss
│ │ │ └── ContextMenuSep.tsx
│ │ └── index.tsx
│ ├── DebugPanel # debug 面板
│ │ ├── DebugPanel.tsx
│ │ └── index.ts
│ ├── Editor.scss
│ ├── Editor.tsx # 编辑器组件,初始化SuikaEditor的地方
│ ├── Header
│ │ ├── Header.scss
│ │ ├── Header.tsx # 顶部栏
│ │ ├── components
│ │ │ ├── Title # 标题
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.scss
│ │ │ └── Toolbar
│ │ │ ├── Toolbar.scss
│ │ │ ├── Toolbar.tsx # 工具栏
│ │ │ ├── components
│ │ │ │ └── ToolBtn # 工具栏按钮
│ │ │ │ ├── ToolBtn.scss
│ │ │ │ ├── ToolBtn.tsx
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ └── menu # 左上角的下拉菜单
│ │ │ ├── Menu.tsx
│ │ │ ├── index.ts
│ │ │ └── menu.scss
│ │ └── index.ts
│ ├── InfoPanel # 右侧整个信息面板
│ │ ├── InfoPanel.tsx
│ │ ├── index.tsx
│ │ └── style.scss
│ ├── LayerPanel # 左侧图层树组件
│ │ ├── LayerPanel.scss
│ │ ├── LayerPanel.tsx
│ │ ├── LayerTree
│ │ │ ├── LayerIcon.tsx
│ │ │ ├── LayerItem.scss
│ │ │ ├── LayerItem.tsx
│ │ │ ├── LayerTree.tsx
│ │ │ ├── index.ts
│ │ │ └── type.ts
│ │ └── index.ts
│ ├── LocaleSelector # 语言选择器(国际化)
│ │ ├── LocaleSelector.scss
│ │ ├── LocaleSelector.tsx
│ │ └── index.ts
│ ├── ZoomActions # 右上角的 zoom 相关下拉框
│ │ ├── ZoomActions.scss
│ │ ├── ZoomActions.tsx
│ │ ├── components
│ │ │ ├── ActionItem
│ │ │ │ ├── ActionItem.scss
│ │ │ │ ├── ActionItem.tsx
│ │ │ │ └── index.ts
│ │ │ └── ZoomInput
│ │ │ ├── ZoomInput.tsx
│ │ │ ├── index.ts
│ │ │ └── style.scss
│ │ └── index.ts
│ └── input # 输入框相关
│ ├── ColorHexInput.tsx # 颜色值输入框
│ ├── CustomRuleInput # 自定义规则输入框(基类)
│ │ ├── index.tsx
│ │ └── style.scss
│ ├── NumberInput.tsx # 数字输入框
│ └── PercentInput.tsx # 百分比输入框
├── constant.ts
├── context.ts
├── events.ts # 事件(目前只有国际化语言更改事件)
├── global.d.ts
├── index.css
├── index.tsx
├── locale # 国际化文案
│ ├── en.json
│ ├── index.ts
│ └── zh.json
├── store # 自动保存数据到本地方法
│ └── auto-save-graphs.ts
└── vite-env.d.ts
suika-multiplayer
@suika/suika-multiplayer
包是 @suika/suika
复制过来,加上多人协同的逻辑。
这里只介绍不一样的地方,见下方注释。
apps/suika-multiplayer/src
├── ...
├── api-service # 放了一些后端接口
│ ├── api-config.ts
│ ├── api-service.ts
│ └── index.ts
├── components
│ ├── MultiCursorsView # 多光标处理
│ │ ├── MultiCursorsView.scss
│ │ ├── MultiCursorsView.tsx
│ │ ├── index.ts
│ │ └── utils.ts
│ └── ...
├── store
│ ├── join-room.ts # 连接文档 id 对应的房间
│ └── y-suika.ts # 协同数据同步逻辑(使用了 y.js)
└── ...
其他
看一下项目根目录下的一些文件。
.
├── Dockerfile
├── LICENSE
├── README.md
├── README_zh.md
├── apps
├── jest.config.js
├── nginx
├── node_modules
├── package.json
├── packages
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── screenshot.png
├── scripts
├── tsconfig.json
└── tsconfig.node.json
工程化相关。
-
.eslintrc.json
:ESLint 配置。 -
packages.json
:除了基础的信息,还有 prettier 的配置、lint-staged 配置、commitLint 配置。 -
.husky
:使用了 husky(基于 git hook) 进行 commit message 的检验、以及格式化。 -
nginx
目录和 Dockerfile 文件没啥用,请忽略,它们和另一个后端项目有关,方便 Docker 快速启动用的。
结尾
基本简单过了一遍,希望对你了解 suika 图形编辑器项目有所帮助。
我是前端西瓜哥,关注我,学习更多图形编辑器知识。
相关阅读,