plaitChange
-plaitBoardInitialized
+change
+initialized
diff --git a/src/assets/content/docs/guides/getting-started.html b/src/assets/content/docs/guides/getting-started.html index 12c884a53..02c30a58e 100644 --- a/src/assets/content/docs/guides/getting-started.html +++ b/src/assets/content/docs/guides/getting-started.html @@ -1,44 +1,54 @@基本使用(集成 @plait/mind 插件)
HTML 模版:
<plait-board [plaitPlugins]="plugins" [plaitValue]="value"
- (plaitBoardInitialized)="plaitBoardInitialized($event)" (plaitChange)="change($event)">
-</plait-board>TS 文件:
+ (initialized)="initialized($event)" (change)="change($event)"> +</plait-board>TS 文件:
// .ts
-@Component({
+@Component({
selector: 'board-basic',
templateUrl: './board-basic.component.html',
- host: {
+ host: {
class: 'board-basic-container',
- },
-})
-export class BasicBoardComponent {
+ },
+})
+export class BasicBoardComponent {
plugins = [withMind];
value: PlaitElement[] = demoData;
board!: PlaitBoard;
- change(event: PlaitBoardChangeEvent) {
+ change(event: PlaitBoardChangeEvent) {
// console.log(event.children);
- }
+ }
- plaitBoardInitialized(value: PlaitBoard) {
+ initialized(value: PlaitBoard) {
this.board = value;
- }
-}
+ }
+}
const demoData = [
- {
+ {
type: 'mindmap',
id: '2',
rightNodeCount: 3,
- data: { topic: { children: [{ text: '思维导图' }] } },
+ data: { topic: { children: [{ text: '思维导图' }] } },
children: [],
width: 72,
height: 25,
isRoot: true,
points: [[560, 700]],
- },
-] as PlaitElement[];效果图:
-更详细的示例说明参考: https://github.com/pubuzhixing8/plait-basic
+ }, +] as PlaitElement[];效果图:
++ 更详细的示例说明参考: + https://github.com/pubuzhixing8/plait-basic +
diff --git a/src/assets/content/docs/guides/intro.html b/src/assets/content/docs/guides/intro.html index 02624fd79..fd6344d13 100644 --- a/src/assets/content/docs/guides/intro.html +++ b/src/assets/content/docs/guides/intro.html @@ -1,127 +1,147 @@ -Plait 被定位为一个绘图框架,提供插件机制,允许开发者通过插件的方式扩展功能。它底层只提供一个基础的绘图白板,仅仅包含放大、缩小、移动端画布等基础功能,而不包含任何业务功能,所有业务功能均需要通过插件的方式扩展,实现自由组合,可以方便的实现独立的或者一体化的绘图工具。Plait 也会提供一些基础的功能插件,目前已经实现了思维导图插件和状态流转两大功能插件,后续要回逐步实现流程图插件。Plait 架构以富文本编辑器框架 Slate 为灵感,适用于交互式绘图场景,当前还在 beta 状态。
++ Plait + 被定位为一个绘图框架,提供插件机制,允许开发者通过插件的方式扩展功能。它底层只提供一个基础的绘图白板,仅仅包含放大、缩小、移动端画布等基础功能,而不包含任何业务功能,所有业务功能均需要通过插件的方式扩展,实现自由组合,可以方便的实现独立的或者一体化的绘图工具。Plait + 也会提供一些基础的功能插件,目前已经实现了思维导图插件和状态流转两大功能插件,后续要回逐步实现流程图插件。Plait 架构以富文本编辑器框架 + Slate 为灵感,适用于交互式绘图场景,当前还在 beta 状态。 +
-| Package Name | -Description | -Currently Version | -
|---|---|---|
| @plait/core | -框架核心:1.插件机制设计 2.提供数据模型、数据变换函数 3.提供基础的 board 组件,包含放大、缩小、滚动方案实现 | -- |
| @plait/richtext | -一个轻量的富文本编辑器,用于在画板中接入文本数据显示和编辑 | -- |
| @plait/mind | -思维导图插件实现,基于独立的自动布局算法,目前支持:逻辑布局、标准布局、缩进布局 | -- |
| @plait/layouts | -思维导图支持库,包含自动布局算法 | -- |
| @plait/flow | -状态流转插件,可以用于实现可视化的状态流转配置、工作流转配置等功能 | -- |
| Package Name | +Description | +Currently Version | +
|---|---|---|
| @plait/core | +框架核心:1.插件机制设计 2.提供数据模型、数据变换函数 3.提供基础的 board 组件,包含放大、缩小、滚动方案实现 | ++ |
| @plait/richtext | +一个轻量的富文本编辑器,用于在画板中接入文本数据显示和编辑 | ++ |
| @plait/mind | +思维导图插件实现,基于独立的自动布局算法,目前支持:逻辑布局、标准布局、缩进布局 | ++ |
| @plait/layouts | +思维导图支持库,包含自动布局算法 | ++ |
| @plait/flow | +状态流转插件,可以用于实现可视化的状态流转配置、工作流转配置等功能 | ++ |
npm i
+
+
+ 开发
+
+npm i
npm run build
npm run start
-
-
- 使用
-
- 基本使用(集成 @plait/mind 插件)
+
+
+ 使用
+
+基本使用(集成 @plait/mind 插件)
HTML 模版:
<plait-board [plaitPlugins]="plugins" [plaitValue]="value"
- (plaitBoardInitialized)="plaitBoardInitialized($event)" (plaitChange)="change($event)">
-</plait-board>
TS 文件:
+ (initialized)="initialized($event)" (change)="change($event)">
+</plait-board>
+TS 文件:
// .ts
-@Component({
+@Component({
selector: 'board-basic',
templateUrl: './board-basic.component.html',
- host: {
+ host: {
class: 'board-basic-container',
- },
-})
-export class BasicBoardComponent {
+ },
+})
+export class BasicBoardComponent {
plugins = [withMind];
value: PlaitElement[] = demoData;
board!: PlaitBoard;
- change(event: PlaitBoardChangeEvent) {
+ change(event: PlaitBoardChangeEvent) {
// console.log(event.children);
- }
+ }
- plaitBoardInitialized(value: PlaitBoard) {
+ initialized(value: PlaitBoard) {
this.board = value;
- }
-}
+ }
+}
const demoData = [
- {
+ {
type: 'mindmap',
id: '2',
rightNodeCount: 3,
- data: { topic: { children: [{ text: '思维导图' }] } },
+ data: { topic: { children: [{ text: '思维导图' }] } },
children: [],
width: 72,
height: 25,
isRoot: true,
points: [[560, 700]],
- },
-] as PlaitElement[];效果图:
-更详细的示例说明参考: https://github.com/pubuzhixing8/plait-basic
+ }, +] as PlaitElement[]; +效果图:
++ 更详细的示例说明参考: + https://github.com/pubuzhixing8/plait-basic +
-roughjs
+roughjs
-当前 plait/core 框架还有 plait/mind 等插件都在高速的迭代中,大家有任何意见或者想法欢迎给我们反馈,也欢迎社区内对画图工具感性趣的同学给我们 PR。
++ 当前 plait/core 框架还有 plait/mind + 等插件都在高速的迭代中,大家有任何意见或者想法欢迎给我们反馈,也欢迎社区内对画图工具感性趣的同学给我们 PR。 +
-@plait/mind 库包含思维导图的核心逻辑的实现,基于 Plait 框架,是最早也是目前唯一一个落地的业务插件。
@plait/mind 核心仅仅包含数据渲染及核心交互实现,不包含工具栏、属性设置等基于界面的交互实现,因为这部分功能依赖于特定的界面风格(插件层不希望引入组件库),我们在设计上倾向于把这部分功能交由使用方自定义实现,插件层只提供事件支持及个性化配置支持。
++ @plait/mind + 核心仅仅包含数据渲染及核心交互实现,不包含工具栏、属性设置等基于界面的交互实现,因为这部分功能依赖于特定的界面风格(插件层不希望引入组件库),我们在设计上倾向于把这部分功能交由使用方自定义实现,插件层只提供事件支持及个性化配置支持。 +
提供 Mind 插件特有的可重写函数,用于使用方自定义 Mind 插件配置(控制渲染样式、交互风格等):
-export interface MindOptions {
+export interface MindOptions {
emojiPadding: number;
spaceBetweenEmojis: number;
-}
目前仅支持 emoji 扩展相关的自定义配置,后续会把节点之间的间隙、文本与节点之间的间隙等等做成自定义配置,目前这些配置是按照我们自己的需求固定在代码中的。
+}
++ 目前仅支持 emoji + 扩展相关的自定义配置,后续会把节点之间的间隙、文本与节点之间的间隙等等做成自定义配置,目前这些配置是按照我们自己的需求固定在代码中的。 +
Mind 插件支持 Emoji 功能的时候仅仅提供了一个调用入口,需要使用方提供具体的一个 Emoji 渲染组件,用于具体的实现 Emoji 的渲染及交互,插件层不关注 Emoji 的交互细节、也不管理 Emoji 资源,仅仅控制 emoji 的渲染位置及空间占位。
++ Mind 插件支持 Emoji 功能的时候仅仅提供了一个调用入口,需要使用方提供具体的一个 Emoji 渲染组件,用于具体的实现 Emoji + 的渲染及交互,插件层不关注 Emoji 的交互细节、也不管理 Emoji 资源,仅仅控制 emoji 的渲染位置及空间占位。 +
提供可重写函数签名:
-drawEmoji: (emoji: EmojiItem, element: MindElement) => ComponentType<MindEmojiBaseComponent>;提供 Emoji 渲染组件基类:
-@Directive({
- host: {
+drawEmoji: (emoji: EmojiItem, element: MindElement) => ComponentType<MindEmojiBaseComponent>;
+提供 Emoji 渲染组件基类:
+@Directive({
+ host: {
class: 'mind-node-emoji'
- }
-})
-export class MindEmojiBaseComponent implements OnInit {
+ }
+})
+export class MindEmojiBaseComponent implements OnInit {
@Input()
fontSize: number = 14;
@@ -49,31 +60,35 @@
@Input()
element!: MindElement<EmojiData>;
- get nativeElement() {
+ get nativeElement() {
return this.elementRef.nativeElement;
- }
+ }
- constructor(protected elementRef: ElementRef<HTMLElement>) {}
+ constructor(protected elementRef: ElementRef<HTMLElement>) {}
- ngOnInit(): void {
- this.elementRef.nativeElement.style.fontSize = `${this.fontSize}px`;
- }
-}
不同提供默认实现
-newBoard.drawEmoji = (emoji: EmojiItem, element: MindElement) => {
+ ngOnInit(): void {
+ this.elementRef.nativeElement.style.fontSize = `${this.fontSize}px`;
+ }
+}
+不同提供默认实现
+newBoard.drawEmoji = (emoji: EmojiItem, element: MindElement) => {
throw new Error('Not implement drawEmoji method error.');
-};
-- 概要调整
+};
+因为业务方需要在概要拖拽调整范围时做一定的业务处理,所以插件层增加了一个可重写方法用于抛出概要调整时的状态
-提供可选的可重写函数签名 onAbstractResize:
-export interface PlaitAbstractBoard extends PlaitBoard {
+提供可选的可重写函数签名 onAbstractResize:
+export interface PlaitAbstractBoard extends PlaitBoard {
onAbstractResize?: (state: AbstractResizeState) => void;
-}
AbstractResizeState 状态定义:
-export enum AbstractResizeState {
+}
+AbstractResizeState 状态定义:
+export enum AbstractResizeState {
start = 'start',
resizing = 'resizing',
end = 'end'
-}
依赖
+}
+@plait/core
@plait/layouts
@plait/richtext
diff --git a/src/assets/content/docs/guides/solutions/island.html b/src/assets/content/docs/guides/solutions/island.html index 545d453a9..66d0cc7eb 100644 --- a/src/assets/content/docs/guides/solutions/island.html +++ b/src/assets/content/docs/guides/solutions/island.html @@ -1,64 +1,85 @@ -以前在 @plait/core 中实现了一个 toolbar 组件,这个组件意义不大,外层通常还会自定义工具栏,现在把这个组件从 core 中移除,并且把工具栏中可能会用到的一些操作方法进行整理统一放到 BoardTransforms 中,并且在 core 中提供一个组件基类 PlaitIslandBaseComponent 用来约束类似于工具栏这类组件的基础行为。
++ 以前在 @plait/core 中实现了一个 toolbar 组件,这个组件意义不大,外层通常还会自定义工具栏,现在把这个组件从 core + 中移除,并且把工具栏中可能会用到的一些操作方法进行整理统一放到 BoardTransforms 中,并且在 core 中提供一个组件基类 + PlaitIslandBaseComponent 用来约束类似于工具栏这类组件的基础行为。 +
-首先 PlaitIslandBaseComponent 不提供具体的功能,主要意义是连接 board 和外部组件、同步 board 的更改到外部组件(自定义的工具栏或者属性设置面板)。
-外部组件理论上是不受底层控制的,然后一个基本的需求是:当 board 数据更改或者内部的状态更改(比如指针)底层需要把这些更改同步给外层组件,以驱动这些组件的界面刷新,所以抽取了这个基类对这类组件组件进行统一的管控。
++ 首先 PlaitIslandBaseComponent 不提供具体的功能,主要意义是连接 board 和外部组件、同步 board + 的更改到外部组件(自定义的工具栏或者属性设置面板)。 +
++ 外部组件理论上是不受底层控制的,然后一个基本的需求是:当 board + 数据更改或者内部的状态更改(比如指针)底层需要把这些更改同步给外层组件,以驱动这些组件的界面刷新,所以抽取了这个基类对这类组件组件进行统一的管控。 +
-@Component({
+@Component({
selector: 'app-toolbar',
templateUrl: './toolbar.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
- providers: [{ provide: PlaitIslandBaseComponent, useExisting: forwardRef(() => AppToolbarBaseComponent) }]
-})
-export class AppToolbarBaseComponent extends PlaitIslandBaseComponent {
- constructor(protected cdr: ChangeDetectorRef) {
+ providers: [{ provide: PlaitIslandBaseComponent, useExisting: forwardRef(() => AppToolbarBaseComponent) }]
+})
+export class AppToolbarBaseComponent extends PlaitIslandBaseComponent {
+ constructor(protected cdr: ChangeDetectorRef) {
super(cdr);
- }
-}
-- 在使用
是需要将它放到 组件内部
+ }
+}
+<plait-board>
<app-toolbar></app-toolbar>
- </plait-board>如此就可以将外部自定义的组件与 board 关联,当 board 数据变换或者 board 的指针类型发生变化外部组件均可收到通知进行变换检测。
+ </plait-board> +如此就可以将外部自定义的组件与 board 关联,当 board 数据变换或者 board 的指针类型发生变化外部组件均可收到通知进行变换检测。
当外部组件需要在数据变化时进行二次数据处理,则可以实现 OnBoardChange ** ** 接口,在 onBoardChange 中进行数据处理
-export class AppToolbarBaseComponent extends PlaitIslandBaseComponent implements OnBoardChange {
- constructor(protected cdr: ChangeDetectorRef) {
+当外部组件需要在数据变化时进行二次数据处理,则可以实现 OnBoardChange ** ** 接口,在 onBoardChange 中进行数据处理
+export class AppToolbarBaseComponent extends PlaitIslandBaseComponent implements OnBoardChange {
+ constructor(protected cdr: ChangeDetectorRef) {
super(cdr);
- }
+ }
- onBoardChange() {
- //
- }
-}
每次数据更新或者指针变更底层都会自动调用组件的 onBoardChange 方法,方法执行完成后再进行变化检测刷新界面。
+ onBoardChange() {
+ //
+ }
+}
+每次数据更新或者指针变更底层都会自动调用组件的 onBoardChange 方法,方法执行完成后再进行变化检测刷新界面。
-这里将这个基类抽象为 Island(意为:岛屿),避免与工具栏强绑定。
+这里将这个基类抽象为 Island(意为:岛屿),避免与工具栏强绑定。
在 board 组件中通过 ContentChildren 获取所有嵌入到 board 组件内的所有 island 组件
-@ContentChildren(PlaitIslandBaseComponent, { descendants: true }) islands?: QueryList<PlaitIslandBaseComponent>;因为底层自动识别嵌入到组件中的所有集成自 PlaitIslandBaseComponent 的组件,所有需要显示的在组件定义的地方配置一个 provider 将 PlaitIslandBaseComponent 指定为当前组件。
+@ContentChildren(PlaitIslandBaseComponent, { descendants: true }) islands?: QueryList<PlaitIslandBaseComponent>;
++ 因为底层自动识别嵌入到组件中的所有集成自 PlaitIslandBaseComponent 的组件,所有需要显示的在组件定义的地方配置一个 provider 将 + PlaitIslandBaseComponent 指定为当前组件。 +
-这种方式还是有些隐晦,使用起来也有些繁琐,未来可以会采用其他方式将 island 传递给 board 组件
+这种方式还是有些隐晦,使用起来也有些繁琐,未来可以会采用其他方式将 island 传递给 board 组件
底层分别在相应入口处调用 Island 组件的 markForCheck 以及 onBoardChange 函数。
-diff --git a/src/assets/content/docs/guides/solutions/plugin-options.html b/src/assets/content/docs/guides/solutions/plugin-options.html index f63c301b6..39922eaf9 100644 --- a/src/assets/content/docs/guides/solutions/plugin-options.html +++ b/src/assets/content/docs/guides/solutions/plugin-options.html @@ -1,61 +1,80 @@目前 onBoardChange 没有任何的参数,无法直接确定是数据更新还是 pointerType 更新 但是数据更新的同步是在 onChange 的执行周期内进行的,所有可以访问 board.operations 进行操作类型的断言,如果是指针更新那么获取到的 operations 为空,暂时可以以此进行逻辑的处理。
++ 目前 onBoardChange 没有任何的参数,无法直接确定是数据更新还是 pointerType 更新 但是数据更新的同步是在 onChange + 的执行周期内进行的,所有可以访问 board.operations 进行操作类型的断言,如果是指针更新那么获取到的 operations + 为空,暂时可以以此进行逻辑的处理。 +
实现一个自定义插件有时候免不了要支持一些自定义配置。
-比如 @plait/core 中的 with-selection 插件支持支持了点选/框选的功能,但是有些场景下业务方可能只需要点选,不希望有框选的功能(不允许多个节点同时选中),这时候插件就需要自定义的一些配置。
-在 @plait/mind 中支持 emoji 表情时也遇到了类似的需求,在确定 emoji 组件渲染大小和位置时希望可以动态配置间距,以解决不同场景下间距不一致的问题。
-为此,@plait/core 中支持了一个 with-options 的插件,用于为每一个插件动态配置自定义选项,它内部使用 Map 存储每个插件的自定义配置,所有如果插件需要自定义配置,则需要额外给插件定义个唯一 Key。
++ 比如 @plait/core 中的 with-selection + 插件支持支持了点选/框选的功能,但是有些场景下业务方可能只需要点选,不希望有框选的功能(不允许多个节点同时选中),这时候插件就需要自定义的一些配置。 +
++ 在 @plait/mind 中支持 emoji 表情时也遇到了类似的需求,在确定 emoji + 组件渲染大小和位置时希望可以动态配置间距,以解决不同场景下间距不一致的问题。 +
++ 为此,@plait/core 中支持了一个 with-options 的插件,用于为每一个插件动态配置自定义选项,它内部使用 Map + 存储每个插件的自定义配置,所有如果插件需要自定义配置,则需要额外给插件定义个唯一 Key。 +
-export interface WithPluginOptions extends PlaitPluginOptions {
+
+
+ 如何使用
+
+1.插件默认配置(withSelection):
+export interface WithPluginOptions extends PlaitPluginOptions {
isMultipleSelection: boolean;
-}
-export function withSelection(board: PlaitBoard) {
+}
+export function withSelection(board: PlaitBoard) {
// ...
- (board as PlaitOptionsBoard).setPluginOptions<WithPluginOptions>(PlaitPluginKey.withSelection, { isMultipleSelection: true });
-}
2.上层重写配置(withFlow):
-export const withFlow: PlaitPlugin = (board: PlaitBoard) => {
+ (board as PlaitOptionsBoard).setPluginOptions<WithPluginOptions>(PlaitPluginKey.withSelection, { isMultipleSelection: true });
+}
+2.上层重写配置(withFlow):
+export const withFlow: PlaitPlugin = (board: PlaitBoard) => {
// ...
- (board as PlaitOptionsBoard).setPluginOptions<WithPluginOptions>(PlaitPluginKey.withSelection, { isMultipleSelection: false });
-};
-
-
- 代码实现
-
- 该方案逻辑非常简单,就是在 board 对象上挂载了两个函数:
+ (board as PlaitOptionsBoard).setPluginOptions<WithPluginOptions>(PlaitPluginKey.withSelection, { isMultipleSelection: false });
+};
+该方案逻辑非常简单,就是在 board 对象上挂载了两个函数:
getPluginOptions - 获取插件配置
setPluginOptions - 重写插件配置
-export const withOptions = (board: PlaitBoard) => {
+export const withOptions = (board: PlaitBoard) => {
const pluginOptions = new Map<string, any>();
const newBoard = board as PlaitOptionsBoard;
- newBoard.getPluginOptions = key => {
+ newBoard.getPluginOptions = key => {
return pluginOptions.get(key);
- };
+ };
+
+ newBoard.setPluginOptions = (key, options) => {
+ const oldOptions = newBoard.getPluginOptions(key) || {};
+ pluginOptions.set(key, { ...oldOptions, ...options });
+ };
- newBoard.setPluginOptions = (key, options) => {
- const oldOptions = newBoard.getPluginOptions(key) || {};
- pluginOptions.set(key, { ...oldOptions, ...options });
- };
-
return newBoard;
-};
定义基础定义:
-export interface PlaitPluginOptions {
+};
+定义基础定义:
+export interface PlaitPluginOptions {
disabled?: boolean;
-}
+}
-export interface PlaitOptionsBoard extends PlaitBoard {
+export interface PlaitOptionsBoard extends PlaitBoard {
getPluginOptions: <K = PlaitPluginOptions>(key: string) => K;
setPluginOptions: <K = PlaitPluginOptions>(key: string, value: K) => void;
-}
-理论上 getPluginOptions 和 setPluginOptions 都是可重写方法,但是这只是理论,应该没有场景需要重写这两个方法的基础实现,实现一个插件的自定义设置只需要插件中或者全局调用 setPluginOptions 即可。 setPluginOptions 中实现了基础的属性合并
+}
++-+ 理论上 getPluginOptions 和 setPluginOptions + 都是可重写方法,但是这只是理论,应该没有场景需要重写这两个方法的基础实现,实现一个插件的自定义设置只需要插件中或者全局调用 + setPluginOptions 即可。 setPluginOptions 中实现了基础的属性合并 +
该方案非常简洁,又非常灵活。
-这种方案也可以很容易实现特定时机下的属性的自定义,就是当程序执行进入特定周期后给它设置特定的 Option 然后当程序执行退出该周期时还原 Option 配置。
+该方案非常简洁,又非常灵活。
++ 这种方案也可以很容易实现特定时机下的属性的自定义,就是当程序执行进入特定周期后给它设置特定的 Option 然后当程序执行退出该周期时还原 + Option 配置。 +
需要注意一点的的是,应该避免缓存配置,而应该随时使用随时获取(毕竟读取数据不会有任何性能问题),以便获取的自定配置是最新的。
diff --git a/src/assets/content/docs/guides/solutions/theme.html b/src/assets/content/docs/guides/solutions/theme.html index a5e9e3bac..c55a8d390 100644 --- a/src/assets/content/docs/guides/solutions/theme.html +++ b/src/assets/content/docs/guides/solutions/theme.html @@ -1,60 +1,65 @@ - -board 新增属性
-export interface PlaitBoard {
+
+
+ 整体设计与机制:
+
+board 新增属性
+export interface PlaitBoard {
...
theme: PlaitTheme;//board 主题
-}
+}
-export interface PlaitTheme {
+export interface PlaitTheme {
themeColorMode: ThemeColorMode;
-}
-
新增主题类型:
-export enum ThemeColorMode {
+}
+
+新增主题类型:
+export enum ThemeColorMode {
'default' = 'default',
'colorful' = 'colorful',
'soft' = 'soft',
'retro' = 'retro',
'dark' = 'dark',
'starry' = 'starry'
-}
修改主题的两个方式:
+}
+修改主题的两个方式:
function updateThemeColor(board: PlaitBoard, mode: ThemeColorMode) {
+function updateThemeColor(board: PlaitBoard, mode: ThemeColorMode) {
mode = mode ?? board.theme.themeColorMode;
- setTheme(board, { themeColorMode: mode }); //board 内部处理,添加 set_theme 的操作
+ setTheme(board, { themeColorMode: mode }); //board 内部处理,添加 set_theme 的操作
- depthFirstRecursion((board as unknown) as PlaitElement, element => {
+ depthFirstRecursion((board as unknown) as PlaitElement, element => {
board.applyTheme(element); //遍历处理节点,抹除与颜色相关的属性
- });
-}
-
-
- 使用:
-
-
-- 增加主题配置参数:
+ });
+}
+ options: PlaitBoardOptions = {
+ options: PlaitBoardOptions = {
themeColors?: ThemeColor[];
- };
-- 被动传入:
+ };
+通过传入的主题,和主题配置
<plait-board
[plaitTheme]="theme"
[plaitOptions]="options"
- >BoardTransforms.updateThemeColor(board: PlaitBoard, mode: ThemeColorMode) PlaitBoard.getThemeColors<MindThemeColor>(board)BoardTransforms.updateThemeColor(board: PlaitBoard, mode: ThemeColorMode)PlaitBoard.getThemeColors<MindThemeColor>(board)Plait 前期为了赶进度单元测试一直是缺失的,这就导致一个问题,当我们决定要给某一个功能或者函数添加单元测试时,会感到无从下手,一方面是陌生感不知道哪些该测那些不该测,一方面由于架构设计的原因函数会依赖一些有副作用的代码(WeakMap),这些副作用依赖组件运行后动态构建的上下文,还有一个原因就是单元测试也应该有一些规范,这也是导致无从下手的一个原因。因此,这两天专门花了一些时间考虑了下单元测试到底该怎么写,并封装了一些测试用的工具函数(主要用于模拟组件运行时的副作用上下文、构建 board 实例)。
++ Plait + 前期为了赶进度单元测试一直是缺失的,这就导致一个问题,当我们决定要给某一个功能或者函数添加单元测试时,会感到无从下手,一方面是陌生感不知道哪些该测那些不该测,一方面由于架构设计的原因函数会依赖一些有副作用的代码(WeakMap),这些副作用依赖组件运行后动态构建的上下文,还有一个原因就是单元测试也应该有一些规范,这也是导致无从下手的一个原因。因此,这两天专门花了一些时间考虑了下单元测试到底该怎么写,并封装了一些测试用的工具函数(主要用于模拟组件运行时的副作用上下文、构建 + board 实例)。 +
在 plait/mind 中单元测试应该有一个分类:
| 分类 | -依赖上下文 | -示例 | -
|---|---|---|
| 纯函数 | -- | - |
| 依赖元素父子关系 | -代码中调用:1. PlaitBoard.findPath 2. MindElement.getParent 3. ... | -mind/src/utils/abstract/common.spec.ts | -
| 依赖布局节点(MindNode) | -代码中依赖 MindNode | -mind/src/utils/position/topic.spec.ts | -
| 依赖数据变换/插件 | -代码中调用 Transforms.xxxx 对 board 数据进行修改 | -mind/src/transforms/emoji.spec.ts | -
| 组件实例 | -对组件进行测试,上下文则直接包含 | -暂无 | -
分类 1 - 纯函数:单元测试比较好写,无须模拟上下文依赖;
分类 2 - 依赖元素父子关系:需要模拟构建上下文依赖,这部分代码已经被封装在 plait/core 的 testing 目录中;
-分类 3 - 依赖布局节点:需要在测试中调用布局算法,然后模拟构建 ELEMENT 和 LayoutNode (mindz中的 MindNode 类型)的对应关系,这部分代码被封装在了 plait/mind 的 testing 目录中;
-分类 4 - 依赖数据变换/插件:这里需要构建一个 board 实例,构建数据修改及插件运行环境,并且需要支持传入自定义插件,这也属于框架逻辑,所以被封装在 plait/core 的 testing 目录中;
++ 分类 3 - 依赖布局节点:需要在测试中调用布局算法,然后模拟构建 ELEMENT 和 LayoutNode (mindz中的 MindNode + 类型)的对应关系,这部分代码被封装在了 plait/mind 的 testing 目录中; +
++ 分类 4 - 依赖数据变换/插件:这里需要构建一个 board + 实例,构建数据修改及插件运行环境,并且需要支持传入自定义插件,这也属于框架逻辑,所以被封装在 plait/core 的 testing 目录中; +
分类 5 - 组件实例:对于组件的测试目前还未封装,后续完善;
在实际写测试中分类 2 和分类 3 他们构建依赖上下文的测试可以合并在一起,创建 board 实例、构建元素父子关系:
let board: PlaitBoard;
-beforeEach(() => {
+beforeEach(() => {
const children = getTestingChildren();
board = createTestingBoard([], children);
fakeNodeWeakMap(board);
-});
+});
-afterEach(() => {
+afterEach(() => {
clearNodeWeakMap(board);
-});另外分类 3 在写测试的时候需要注意,如果一个测试用例里面包含超过一次的数据修改,那么在每一次的数据修改后都需要重新调用 fakeNodeWeakmap 构建基于最新数据引用的父子关系依赖:
-it('should replace emoji success', () => {
+});
++ 另外分类 3 在写测试的时候需要注意,如果一个测试用例里面包含超过一次的数据修改,那么在每一次的数据修改后都需要重新调用 fakeNodeWeakmap + 构建基于最新数据引用的父子关系依赖: +
+it('should replace emoji success', () => {
const first = PlaitNode.get<MindElement>(board, [0, 0]);
- const emojiItem: EmojiItem = { name: '😊' };
+ const emojiItem: EmojiItem = { name: '😊' };
addEmoji(board, first, emojiItem);
// need to clear and re-fake weak map because element ref was modified
clearNodeWeakMap(board);
fakeNodeWeakMap(board);
const addFirst = PlaitNode.get<MindElement<EmojiData>>(board, [0, 0]);
- const replaceItem: EmojiItem = { name: '😭' };
+ const replaceItem: EmojiItem = { name: '😭' };
replaceEmoji(board, addFirst, emojiItem, replaceItem);
const replaceFirst = PlaitNode.get<MindElement<EmojiData>>(board, [0, 0]);
expect(replaceFirst.data.emojis.length).toEqual(1);
expect(replaceFirst.data.emojis[0]).toEqual(replaceItem);
-});补充单元测试,也是一次很好的梳理代码结构的机会
-不得不说写单元测试是一件非常难的事情,而把单元测试写的有条理则更难,因为它首先需要你的代码是有条理的(模块划分是否清楚、调用关系/目录关系是否统一、函数职责是否唯一/或者存在重复),如果代码结构、调用关系、职责划分等等不清晰,你的单元测试则会很混乱(无法按照一个统一的思路把所有的分支逻辑全部覆盖),或者出现重复功能的单元测试,必然会造成时间的浪费,也会消磨维护单元测试的信心。
+}); +补充单元测试,也是一次很好的梳理代码结构的机会
++ 不得不说写单元测试是一件非常难的事情,而把单元测试写的有条理则更难,因为它首先需要你的代码是有条理的(模块划分是否清楚、调用关系/目录关系是否统一、函数职责是否唯一/或者存在重复),如果代码结构、调用关系、职责划分等等不清晰,你的单元测试则会很混乱(无法按照一个统一的思路把所有的分支逻辑全部覆盖),或者出现重复功能的单元测试,必然会造成时间的浪费,也会消磨维护单元测试的信心。 +
diff --git a/src/main.ts b/src/main.ts index 5e7d531d8..9204713a6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { enableProdMode, importProvidersFrom } from '@angular/core'; +import { enableProdMode, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; import { environment } from './environments/environment'; import { AppComponent } from './app/app.component'; import { AppRoutingModule } from './app/app-routing.module'; @@ -11,5 +11,5 @@ if (environment.production) { } bootstrapApplication(AppComponent, { - providers: [importProvidersFrom(BrowserModule, FormsModule, AppRoutingModule, SlateModule)] + providers: [provideZoneChangeDetection(), importProvidersFrom(BrowserModule, FormsModule, AppRoutingModule, SlateModule)] }).catch((err) => console.error(err)); diff --git a/tsconfig.app.json b/tsconfig.app.json index ee4a1df0c..b9c594029 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,62 +1,33 @@ /* To learn more about this file see: https://angular.io/config/tsconfig. */ { - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [], - "paths": { - "@plait/flow": [ - "packages/flow/src/public-api" - ], - "@plait/layouts": [ - "packages/layouts/src/public-api" - ], - "@plait/mind": [ - "packages/mind/src/public-api" - ], - "@plait/core": [ - "packages/core/src/public-api" - ], - "@plait/angular-board": [ - "packages/angular-board/src/public-api" - ], - "@plait/angular-text": [ - "packages/angular-text/src/public-api" - ], - "@plait/text-plugins": [ - "packages/text-plugins/src/public-api" - ], - "@plait/draw": [ - "packages/draw/src/public-api" - ], - "@plait/common": [ - "packages/common/src/public-api" - ], - "@plait/graph-viz": [ - "packages/graph-viz/src/public-api" - ] + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [], + "paths": { + "@plait/flow": ["packages/flow/src/public-api"], + "@plait/layouts": ["packages/layouts/src/public-api"], + "@plait/mind": ["packages/mind/src/public-api"], + "@plait/core": ["packages/core/src/public-api"], + "@plait/angular-board": ["packages/angular-board/src/public-api"], + "@plait/angular-text": ["packages/angular-text/src/public-api"], + "@plait/text-plugins": ["packages/text-plugins/src/public-api"], + "@plait/draw": ["packages/draw/src/public-api"], + "@plait/common": ["packages/common/src/public-api"], + "@plait/graph-viz": ["packages/graph-viz/src/public-api"] + }, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "module": "es2020" }, - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "moduleResolution": "bundler", - "importHelpers": true, - "module": "es2020", - "lib": [ - "es2018", - "dom" - ] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ] + "files": ["src/main.ts", "src/polyfills.ts"], + "include": ["src/**/*.d.ts"] } diff --git a/tsconfig.json b/tsconfig.json index ed62fdc24..41ba978ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,63 +1,39 @@ /* To learn more about this file see: https://angular.io/config/tsconfig. */ { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "paths": { - "@plait/flow": [ - "dist/flow" - ], - "@plait/layouts": [ - "dist/layouts" - ], - "@plait/mind": [ - "dist/mind" - ], - "@plait/core": [ - "dist/core" - ], - "@plait/angular-board": [ - "dist/angular-board" - ], - "@plait/angular-text": [ - "dist/angular-text" - ], - "@plait/draw": [ - "dist/draw" - ], - "@plait/common": [ - "dist/common" - ], - "@plait/text-plugins": [ - "dist/text-plugins" - ], - "@plait/graph-viz": [ - "dist/graph-viz" - ] + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@plait/flow": ["dist/flow"], + "@plait/layouts": ["dist/layouts"], + "@plait/mind": ["dist/mind"], + "@plait/core": ["dist/core"], + "@plait/angular-board": ["dist/angular-board"], + "@plait/angular-text": ["dist/angular-text"], + "@plait/draw": ["dist/draw"], + "@plait/common": ["dist/common"], + "@plait/text-plugins": ["dist/text-plugins"], + "@plait/graph-viz": ["dist/graph-viz"] + }, + "outDir": "./dist/out-tsc", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "target": "es2017", + "module": "es2020", + "useDefineForClassFields": false }, - "outDir": "./dist/out-tsc", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "sourceMap": true, - "declaration": false, - "experimentalDecorators": true, - "moduleResolution": "bundler", - "importHelpers": true, - "target": "es2017", - "module": "es2020", - "lib": [ - "es2018", - "dom" - ], - "useDefineForClassFields": false - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } }