@netless/appliance-plugin 1.1.2 → 1.1.3-4.beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/READMA.zh-CN.md +993 -0
  2. package/README.md +963 -176
  3. package/cdn/cdn.js +1 -1
  4. package/cdn/fullWorker-Dwf7nY.js +486 -0
  5. package/cdn/subWorker-DXBnNm.js +486 -0
  6. package/dist/ObserverMap-BudneEfB.mjs +58 -0
  7. package/dist/ObserverMap-DTz9zucn.js +1 -0
  8. package/dist/appliance-plugin.js +1 -1
  9. package/dist/appliance-plugin.mjs +27 -18
  10. package/dist/cdn.d.ts +2 -2
  11. package/dist/collector/base.d.ts +3 -1
  12. package/dist/collector/collector.d.ts +57 -14
  13. package/dist/collector/index.d.ts +4 -4
  14. package/dist/collector/types.d.ts +49 -10
  15. package/dist/component/autoDraw/index.d.ts +0 -0
  16. package/dist/component/miniMap/index.d.ts +1 -0
  17. package/dist/component/miniMap/manager.d.ts +37 -0
  18. package/dist/component/miniMap/view.d.ts +14 -0
  19. package/dist/component/svg/base.d.ts +30 -0
  20. package/dist/component/svg/manager.d.ts +44 -0
  21. package/dist/component/svg/markmap.d.ts +41 -0
  22. package/dist/component/svg/mermaid-check.d.ts +18 -0
  23. package/dist/component/svg/mermaid-loader.d.ts +29 -0
  24. package/dist/component/svg/mermaid.d.ts +109 -0
  25. package/dist/component/svg/snapshot.d.ts +31 -0
  26. package/dist/component/svg/svgElemt.d.ts +43 -0
  27. package/dist/component/svg/svgToImageLoader.d.ts +25 -0
  28. package/dist/component/svg/types.d.ts +10 -0
  29. package/dist/component/svg/utils.d.ts +9 -0
  30. package/dist/component/svg/vNodeManager.d.ts +28 -0
  31. package/dist/component/textEditor/index.d.ts +2 -2
  32. package/dist/component/textEditor/manager.d.ts +22 -3
  33. package/dist/component/textEditor/types.d.ts +5 -4
  34. package/dist/component/textEditor/utils.d.ts +1 -0
  35. package/dist/component/textEditor/view.d.ts +28 -48
  36. package/dist/core/autoShape/index.d.ts +12 -0
  37. package/dist/core/autoShape/mapper.d.ts +3 -0
  38. package/dist/core/autoShape/recognizer.d.ts +8 -0
  39. package/dist/core/autoShape/registry.d.ts +2 -0
  40. package/dist/core/autoShape/templates.d.ts +2 -0
  41. package/dist/core/autoShape/types.d.ts +78 -0
  42. package/dist/core/backGroundThread/index.d.ts +35 -0
  43. package/dist/core/backGroundThread/types.d.ts +12 -0
  44. package/dist/core/enum.d.ts +74 -13
  45. package/dist/core/index.d.ts +3 -3
  46. package/dist/core/mainEngine.d.ts +101 -27
  47. package/dist/core/mainThread/base.d.ts +10 -6
  48. package/dist/core/mainThread/index.d.ts +2 -2
  49. package/dist/core/mainThread/snapshotThread.d.ts +8 -4
  50. package/dist/core/mainThread/subLocalThread.d.ts +21 -16
  51. package/dist/core/mainThread/subServiceThread.d.ts +2 -2
  52. package/dist/core/mainThread/subTopThread.d.ts +2 -2
  53. package/dist/core/msgEvent/base.d.ts +1 -0
  54. package/dist/core/msgEvent/baseForBackgroundThread.d.ts +9 -0
  55. package/dist/core/msgEvent/baseForWorker.d.ts +2 -1
  56. package/dist/core/msgEvent/copyNode/forBackgroundThread.d.ts +8 -0
  57. package/dist/core/msgEvent/copyNode/forMain.d.ts +2 -2
  58. package/dist/core/msgEvent/forBackgroundThread.d.ts +12 -0
  59. package/dist/core/msgEvent/forMainThread.d.ts +4 -4
  60. package/dist/core/msgEvent/forWorker.d.ts +4 -4
  61. package/dist/core/msgEvent/index.d.ts +4 -4
  62. package/dist/core/msgEvent/rotateNode/forMain.d.ts +2 -2
  63. package/dist/core/msgEvent/scaleNode/forMain.d.ts +2 -2
  64. package/dist/core/msgEvent/setColor/forMain.d.ts +2 -2
  65. package/dist/core/msgEvent/setPoint/forMain.d.ts +2 -2
  66. package/dist/core/msgEvent/setZIndex/forMain.d.ts +7 -0
  67. package/dist/core/msgEvent/translateNode/forMain.d.ts +2 -2
  68. package/dist/core/plugin.d.ts +49 -0
  69. package/dist/core/renderCotrol.d.ts +12 -11
  70. package/dist/core/tools/arrow.d.ts +10 -22
  71. package/dist/core/tools/base.d.ts +52 -9
  72. package/dist/core/tools/ellipse.d.ts +10 -22
  73. package/dist/core/tools/image.d.ts +9 -7
  74. package/dist/core/tools/index.d.ts +14 -13
  75. package/dist/core/tools/laserPen.d.ts +1 -1
  76. package/dist/core/tools/pencil.d.ts +18 -8
  77. package/dist/core/tools/pencilEraser.d.ts +36 -41
  78. package/dist/core/tools/pencilEraserBitMap.d.ts +20 -27
  79. package/dist/core/tools/polygon.d.ts +10 -22
  80. package/dist/core/tools/rectangle.d.ts +10 -22
  81. package/dist/core/tools/selector.d.ts +42 -24
  82. package/dist/core/tools/shadowSvg.d.ts +36 -0
  83. package/dist/core/tools/speechBalloon.d.ts +10 -22
  84. package/dist/core/tools/star.d.ts +10 -22
  85. package/dist/core/tools/straight.d.ts +10 -22
  86. package/dist/core/tools/text.d.ts +2 -1
  87. package/dist/core/tools/utils.d.ts +11 -12
  88. package/dist/core/types.d.ts +98 -18
  89. package/dist/core/utils/ObserverMap.d.ts +19 -0
  90. package/dist/core/utils/clipper.d.ts +8 -0
  91. package/dist/core/utils/getSvgPathFromPoints.d.ts +1 -1
  92. package/dist/core/utils/index.d.ts +6 -5
  93. package/dist/core/utils/indexDB.d.ts +17 -0
  94. package/dist/core/utils/math.d.ts +1 -0
  95. package/dist/core/utils/polygonUtils.d.ts +8 -0
  96. package/dist/core/utils/primitives/Box2d.d.ts +4 -4
  97. package/dist/core/utils/proxy.d.ts +3 -4
  98. package/dist/core/utils/spriteNode.d.ts +8 -1
  99. package/dist/core/vNodeManager.d.ts +13 -9
  100. package/dist/core/worker/base.d.ts +43 -21
  101. package/dist/core/worker/fullWorkerLocal.d.ts +29 -25
  102. package/dist/core/worker/fullWorkerService.d.ts +9 -9
  103. package/dist/core/worker/simpleWorker.d.ts +28 -0
  104. package/dist/core/worker/snapshotWork.d.ts +27 -0
  105. package/dist/core/worker/subWorkerLocal.d.ts +2 -3
  106. package/dist/core/worker/subWorkerTopLayer.d.ts +2 -2
  107. package/dist/core/worker/workerManager.d.ts +52 -36
  108. package/dist/cursors/index.d.ts +9 -11
  109. package/dist/displayer/const.d.ts +1 -0
  110. package/dist/displayer/cursor/index.d.ts +1 -0
  111. package/dist/displayer/floatBar/dragBox/index.d.ts +9 -3
  112. package/dist/displayer/floatBar/index.d.ts +2 -1
  113. package/dist/displayer/floatBtns/index.d.ts +2 -0
  114. package/dist/displayer/utils.d.ts +1 -0
  115. package/dist/extend.d.ts +1 -0
  116. package/dist/fullWorker.js +242 -195
  117. package/dist/index-BCI9ZJly.mjs +9884 -0
  118. package/dist/index-CRWsZj1z.mjs +16601 -0
  119. package/dist/index-D2XqHUO-.js +1 -0
  120. package/dist/index-Dfujq78k.js +3 -0
  121. package/dist/index-TQPJgovl.mjs +1263 -0
  122. package/dist/index-ZvQrVWzu.js +1 -0
  123. package/dist/index.d.ts +1 -0
  124. package/dist/plugin/applianceMultiPlugin.d.ts +2 -3
  125. package/dist/plugin/applianceSinglePlugin.d.ts +3 -4
  126. package/dist/plugin/baseApplianceManager.d.ts +58 -13
  127. package/dist/plugin/baseViewContainerManager.d.ts +72 -73
  128. package/dist/plugin/const.d.ts +2 -22
  129. package/dist/plugin/displayerView.d.ts +18 -4
  130. package/dist/plugin/index.d.ts +1 -0
  131. package/dist/plugin/multi/applianceMultiManager.d.ts +0 -1
  132. package/dist/plugin/multi/containerManager.d.ts +8 -1
  133. package/dist/plugin/multi/displayer/appViewDisplayerManager.d.ts +3 -2
  134. package/dist/plugin/multi/displayer/mainViewDisplayerManager.d.ts +4 -2
  135. package/dist/plugin/single/displayer/mainViewDisplayerManager.d.ts +4 -3
  136. package/dist/plugin/types.d.ts +393 -32
  137. package/dist/style.css +1 -1
  138. package/dist/subWorker.js +242 -195
  139. package/dist/svgToImageLoader-DPRAAhwW.js +1 -0
  140. package/dist/svgToImageLoader-mXH53h-l.mjs +18 -0
  141. package/dist/undo/index.d.ts +25 -31
  142. package/dist/undo/proxyArray.d.ts +37 -0
  143. package/package.json +31 -9
  144. package/cdn/fullWorker-DMz46H.js +0 -439
  145. package/cdn/subWorker-B_zAQR.js +0 -439
  146. package/dist/collector/eventCollector.d.ts +0 -31
  147. package/dist/collector/magixEventCollector.d.ts +0 -31
  148. package/dist/core/mainThread/workerMainThread.d.ts +0 -99
  149. package/dist/core/msgEvent/deleteNode/forMainThread.d.ts +0 -6
  150. package/dist/core/msgEvent/deleteNode/forWorker.d.ts +0 -6
  151. package/dist/index-B-Nowr4E.mjs +0 -14865
  152. package/dist/index-BDVvqw7g.js +0 -1
  153. package/dist/index-DwakKeHT.mjs +0 -2497
  154. package/dist/index-cRE5FY5s.js +0 -1
@@ -0,0 +1,993 @@
1
+ # appliance-plugin
2
+
3
+ [English Documentation](https://github.com/netless-io/fastboard/blob/main/docs/en/appliance-plugin.md)
4
+
5
+ 该插件基于 white-web-sdk 的插件机制,实现了一套功能丰富的白板教具绘制工具。同时也基于 @netless/window-manager,实现了可在多窗口上使用。
6
+
7
+ ## 简介
8
+
9
+ appliance-plugin 是一个高性能的白板绘制插件,依赖 [white-web-sdk](https://www.npmjs.com/package/white-web-sdk) 和 [@netless/window-manager](https://www.npmjs.com/package/@netless/window-manager),并基于 Web API 对 [OffscreenCanvas](https://developer.mozilla.org/zh-CN/docs/Web/API/OffscreenCanvas) 的支持。
10
+
11
+ ### 主要特性
12
+
13
+ - 🎨 **丰富的绘制工具**:支持铅笔、橡皮擦、形状工具、文字、图片等多种绘制工具
14
+ - ⚡ **高性能渲染**:采用双 WebWorker + OffscreenCanvas 机制,绘制效率比主线程提升 40% 以上
15
+ - 🖼️ **多窗口支持**:支持多窗口场景,可在不同窗口上独立使用
16
+ - 🎯 **激光笔工具**:支持激光笔功能,适合演示场景
17
+ - 📝 **文字编辑**:支持文字插入、编辑、样式设置等功能
18
+ - 🗺️ **小地图功能**:提供小地图导航功能,方便查看整体内容
19
+ - 🔄 **撤销重做**:支持全局撤销重做功能
20
+ - 🎭 **自定义样式**:支持自定义画笔样式、文字样式等
21
+ - 🔌 **插件扩展**:支持通过插件机制扩展功能(如 autoDraw 手写图形自动联想)
22
+
23
+ ## 原理
24
+
25
+ 1. **渲染引擎**:该插件主要是基于 SpriteJS 的 2D 功能,支持 WebGL2 渲染,并可向后兼容降级为 WebGL 和 Canvas2D。
26
+ 2. **多线程架构**:该插件通过双 WebWorker + OffscreenCanvas 机制,把绘制计算和渲染逻辑都放在独立的 worker 线程中处理,不占用主线程的 CPU 任务。
27
+ - **Full Worker**:负责绘制完整数据的线程
28
+ - **Sub Worker**:负责绘制一帧数据的线程
29
+ 3. **兼容性处理**:针对移动端有些终端不支持 OffscreenCanvas,则会自动降级到主线程处理。
30
+
31
+ ### 支持的绘制工具
32
+
33
+ 插件支持以下绘制工具:
34
+
35
+ - **基础工具**:铅笔、橡皮擦、局部橡皮擦、位图橡皮擦、选择工具、抓手工具
36
+ - **形状工具**:直线、箭头、矩形、圆形、三角形、菱形、多边形、星形、聊天泡泡框
37
+ - **文字工具**:支持文字输入、编辑、样式设置
38
+ - **图片工具**:支持图片插入和编辑
39
+ - **特殊工具**:激光笔、背景 SVG
40
+ - **互动工具**:点击互动工具(可供插件自定义行为)
41
+
42
+ ## 插件用法
43
+
44
+ ### 安装
45
+
46
+ ```bash
47
+ npm install @netless/appliance-plugin
48
+ ```
49
+
50
+ ### 注册插件
51
+
52
+ 插件可以支持两种场景,它们接入插件命名不同:
53
+ - 多窗口 `ApplianceMultiPlugin`
54
+ ```js
55
+ import { ApplianceMultiPlugin } from '@netless/appliance-plugin';
56
+ ```
57
+ - 单白板 `ApplianceSinglePlugin`
58
+ ```js
59
+ import { ApplianceSinglePlugin } from '@netless/appliance-plugin';
60
+ ```
61
+
62
+ > **worker.js 文件与 CDN / 静态资源**
63
+ >
64
+ > 我们采用双 worker 并发来提高绘制效率,比主线程效率提高 40% 以上。两个 worker 文件的公共依赖重复,直接打进包体会显著增加体积,因此推荐通过 `options.cdn` 指定 worker 地址,有两种常见方式:
65
+ >
66
+ > 1. **CDN 方式**:将 `@netless/appliance-plugin/cdn` 下的 `fullWorker.js`、`subWorker.js` 部署到 CDN,在插件的 `getInstance` 的第二个参数 `options.cdn` 中传入这两个文件的 CDN 地址(`fullWorkerUrl`、`subWorkerUrl`)。
67
+ > ***注意***:CDN 配置的 URL 必须同源,否则 worker 线程会无法加载。
68
+ >
69
+ > 2. **静态资源方式**:将 `fullWorker.js`、`subWorker.js` 放到项目的静态目录(如 Vite 的 `public/`),不参与打包。在代码中基于 `import.meta.env.BASE_URL`(或等效的 publicPath)拼出完整 URL,传入 `options.cdn`。同源请求、不占打包体积,也无需 Blob 内联,内存占用更小。
70
+ >
71
+ > 若需控制包体积,请使用上述任一方式配置 `options.cdn`。
72
+
73
+ ### 接入方式参考
74
+
75
+ #### 引入worker.js
76
+ ```js
77
+ // 引入 worker 方式三选一:
78
+ // 方式一:静态资源(推荐)- 将 fullWorker.js、subWorker.js 放到 public 等静态目录,同源请求、不占打包体积
79
+ const workerBase = (import.meta.env.BASE_URL || '/').replace(/\/?$/, '/');
80
+ const fullWorkerUrl = workerBase + 'fullWorker.js';
81
+ const subWorkerUrl = workerBase + 'subWorker.js';
82
+
83
+ // 方式二:CDN - 将 @netless/appliance-plugin/cdn 下文件部署到 CDN 后,此处写 CDN 地址、不占打包体积 注意: 域名一定要同源
84
+ const fullWorkerUrl = 'https://your-cdn.com/fullWorker.js';
85
+ const subWorkerUrl = 'https://your-cdn.com/subWorker.js';
86
+
87
+ // 方式三:从 dist 以 ?raw 内联为 Blob(需打包器支持 ?raw,如 Vite / webpack raw-loader)这种方式会占用一定内存.
88
+ import fullWorkerString from '@netless/appliance-plugin/dist/fullWorker.js?raw';
89
+ import subWorkerString from '@netless/appliance-plugin/dist/subWorker.js?raw';
90
+ const fullWorkerUrl = URL.createObjectURL(new Blob([fullWorkerString], { type: 'text/javascript' }));
91
+ const subWorkerUrl = URL.createObjectURL(new Blob([subWorkerString], { type: 'text/javascript' }));
92
+ ```
93
+
94
+ #### fastboard(直接对接fastboard)
95
+ ```js
96
+ // 对接 fastboard-react
97
+ // 全打包方式引用
98
+ // import { useFastboard, Fastboard } from "@netless/fastboard-react/full";
99
+ // 分包引用
100
+ import { useFastboard, Fastboard } from "@netless/fastboard-react";
101
+
102
+ // 引入 worker 方式三选一:
103
+ const fullWorkerUrl = ...;
104
+ const subWorkerUrl = ...;
105
+
106
+ const app = useFastboard(() => ({
107
+ sdkConfig: {
108
+ ...
109
+ },
110
+ joinRoom: {
111
+ ...
112
+ },
113
+ managerConfig: {
114
+ cursor: true,
115
+ enableAppliancePlugin: true,
116
+ ...
117
+ },
118
+ enableAppliancePlugin: {
119
+ cdn: {
120
+ fullWorkerUrl,
121
+ subWorkerUrl,
122
+ }
123
+ ...
124
+ }
125
+ }));
126
+
127
+ // 对接 fastboard
128
+ // 全打包方式引用
129
+ // import { createFastboard, createUI } from "@netless/fastboard/full";
130
+ // 分包引用
131
+ import { createFastboard, createUI } from "@netless/fastboard";
132
+
133
+ // 引入 worker 方式三选一:
134
+ const fullWorkerUrl = ...;
135
+ const subWorkerUrl = ...;
136
+
137
+ const fastboard = await createFastboard({
138
+ sdkConfig: {
139
+ ...
140
+ },
141
+ joinRoom: {
142
+ ...
143
+ },
144
+ managerConfig: {
145
+ cursor: true,
146
+ supportAppliancePlugin: true,
147
+ ...
148
+ },
149
+ enableAppliancePlugin: {
150
+ cdn: {
151
+ fullWorkerUrl,
152
+ subWorkerUrl,
153
+ }
154
+ ...
155
+ }
156
+ });
157
+ ```
158
+
159
+ #### 多窗口(直接对接window-manager)
160
+
161
+ ```js
162
+
163
+ import '@netless/window-manager/dist/style.css';
164
+ import '@netless/appliance-plugin/dist/style.css';
165
+
166
+ import { WhiteWebSdk } from "white-web-sdk";
167
+ import { WindowManager } from "@netless/window-manager";
168
+ import { ApplianceMultiPlugin } from '@netless/appliance-plugin';
169
+
170
+ // 引入 worker 方式三选一:
171
+ const fullWorkerUrl = ...;
172
+ const subWorkerUrl = ...;
173
+
174
+ const whiteWebSdk = new WhiteWebSdk(...)
175
+ const room = await whiteWebSdk.joinRoom({
176
+ ...
177
+ invisiblePlugins: [WindowManager, ApplianceMultiPlugin],
178
+ useMultiViews: true,
179
+ })
180
+ const manager = await WindowManager.mount({ room, container: elm, chessboard: true, cursor: true, supportAppliancePlugin: true});
181
+ if (manager) {
182
+ await manager.switchMainViewToWriter();
183
+ await ApplianceMultiPlugin.getInstance(manager,
184
+ {
185
+ options: {
186
+ cdn: {
187
+ fullWorkerUrl,
188
+ subWorkerUrl,
189
+ },
190
+ ...
191
+ }
192
+ }
193
+ );
194
+ }
195
+ ```
196
+
197
+ > **注意** 项目中需要引入css文件 `import '@netless/appliance-plugin/dist/style.css';`
198
+
199
+ #### 单白板(直接对接white-web-sdk)
200
+
201
+ ```js
202
+
203
+ import '@netless/appliance-plugin/dist/style.css';
204
+
205
+ import { WhiteWebSdk } from "white-web-sdk";
206
+ import { ApplianceSinglePlugin, ApplianceSigleWrapper } from '@netless/appliance-plugin';
207
+
208
+ // 引入 worker 方式三选一:
209
+ const fullWorkerUrl = ...;
210
+ const subWorkerUrl = ...;
211
+
212
+ const whiteWebSdk = new WhiteWebSdk(...)
213
+ const room = await whiteWebSdk.joinRoom({
214
+ ...
215
+ invisiblePlugins: [ApplianceSinglePlugin],
216
+ wrappedComponents: [ApplianceSigleWrapper]
217
+ })
218
+ await ApplianceSinglePlugin.getInstance(room,
219
+ {
220
+ options: {
221
+ cdn: {
222
+ fullWorkerUrl,
223
+ subWorkerUrl,
224
+ }
225
+ ...
226
+ }
227
+ }
228
+ );
229
+ ```
230
+
231
+ > **注意** 项目中需要引入css文件 `import '@netless/appliance-plugin/dist/style.css';`
232
+
233
+ #### 关于?raw的webpack配置
234
+
235
+ ```js
236
+ module: {
237
+ rules: [
238
+ // ...
239
+ {
240
+ test: /\.m?js$/,
241
+ resourceQuery: { not: [/raw/] },
242
+ use: [ ... ]
243
+ },
244
+ {
245
+ resourceQuery: /raw/,
246
+ type: 'asset/source',
247
+ }
248
+ ]
249
+ },
250
+ ```
251
+
252
+ ## 调用介绍
253
+
254
+ ### api介绍
255
+
256
+ #### 优化原有接口
257
+
258
+ 插件重新实现了一些 room 或 windowmanager 上的同名接口,但是我们内部已经通过 `injectMethodToObject` 重新注入回原来的对象中。从而外部用户无需任何改动。如以下几个:
259
+ ```js
260
+ // 内部 hack
261
+ injectMethodToObject(windowmanager, 'undo');
262
+ injectMethodToObject(windowmanager, 'redo');
263
+ injectMethodToObject(windowmanager,'cleanCurrentScene');
264
+ injectMethodToObject(windowmanager,'insertImage');
265
+ injectMethodToObject(windowmanager,'completeImageUpload');
266
+ injectMethodToObject(windowmanager,'lockImage');
267
+ injectMethodToObject(windowmanager, "insertText");
268
+ injectMethodToObject(windowmanager, "updateText");
269
+ injectMethodToObject(room,'getImagesInformation');
270
+ injectMethodToObject(room,'callbacks');
271
+ injectMethodToObject(room,'screenshotToCanvasAsync');
272
+ injectMethodToObject(room,'getBoundingRectAsync');
273
+ injectMethodToObject(room,'scenePreviewAsync');
274
+ injectMethodToObject(room, "fillSceneSnapshotAsync");
275
+ injectMethodToObject(room, "setWritable");
276
+ injectMethodToObject(windowmanager.mainView,'setMemberState');
277
+ // 这些我们可以通过前端日志看到调用行为,例如:
278
+ // [ApplianceMultiPlugin] setMemberState
279
+ // [ApplianceMultiPlugin] cleanCurrentScene
280
+ ```
281
+
282
+ 具体涉及以下接口:
283
+
284
+ 1. room上接口
285
+ - [`setMemberState`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#setmemberstate)
286
+ - [`undo`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#undo)
287
+ - [`redo`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#redo)
288
+ - [`callbacks`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#callbacks)
289
+ - [`insertImage`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#insertImage)
290
+ - [`lockImage`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#lockImage)
291
+ - [`completeImageUpload`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#completeImageUpload)
292
+ - `getImagesInformation`
293
+ - [`cleanCurrentScene`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#cleanCurrentScene)
294
+
295
+ 2. WindowManager 接口
296
+ - [`cleanCurrentScene`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#cleanCurrentScene)
297
+ - [`canUndoSteps`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#canUndoSteps)
298
+ - [`canRedoSteps`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#canUndoSteps)
299
+
300
+ 3. WindowManager 的 mainView 上的接口
301
+ - [`setMemberState`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#setmemberstate)
302
+ - [`undo`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#undo)
303
+ - [`redo`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#redo)
304
+ - [`callbacks`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#callbacks)
305
+ - [`insertImage`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#insertImage)
306
+ - [`lockImage`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#lockImage)
307
+ - [`completeImageUpload`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#completeImageUpload)
308
+ - `getImagesInformation`
309
+ - [`cleanCurrentScene`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#cleanCurrentScene)
310
+
311
+ 4. 自定义接口
312
+ - `getBoundingRectAsync` - 替代接口 `room.getBoundingRect`
313
+ - `screenshotToCanvasAsync` - 替代接口 [room.screenshotToCanvas](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#screenshotToCanvas)
314
+ - `scenePreviewAsync` - 替代接口 [room.scenePreview](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#scenePreview)
315
+ - `fillSceneSnapshotAsync` - 替代接口 [room.fillSceneSnapshot](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#fillSceneSnapshot)
316
+ - `destroy` - 销毁 appliance-plugin 的实例
317
+ - `addListener` - 添加 appliance-plugin 内部事件监听器
318
+ - `removeListener` - 移除 appliance-plugin 内部事件监听器
319
+ - `disableDeviceInputs` - 替代接口 [room.disableDeviceInputs](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#disableDeviceInputs)
320
+ - `disableEraseImage` - 替代接口 [room.disableEraseImage](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#disableEraseImage) **该方法只禁止整体擦除的橡皮擦对图片的擦除,局部橡皮擦无效**
321
+ - `disableCameraTransform` - 替代接口 [room.disableCameraTransform](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#disableCameraTransform) (Version >=1.1.17)
322
+ - `insertText` - 在指定位置插入文字 (Version >=1.1.18)
323
+ - `updateText` - 编辑指定文字的内容 (Version >=1.1.18)
324
+ - `blurText` - 失去文本焦点 (Version >=1.1.19)
325
+ - `hasElements` - 指定场景下是否存在元素 (Version >=1.1.19)
326
+ - `getElements` - 获取场景下的所有元素 (Version >=1.1.19)
327
+ - `stopDraw` - 停止Draw事件 (Version >=1.1.19)
328
+ - `setViewLocalScenePathChange` - 设置白板本地场景路径变化 (Version >=1.1.27)
329
+ - `insertMarkmap` - 插入markdow文本到白板 (Version >=1.1.32) **该方法需要开启extras.useBackgroundThread**
330
+ - `updateMarkmap` - 修改白板中的markdow文本 (Version >=1.1.32) **该方法需要开启extras.useBackgroundThread**
331
+ - `insertBackgroundImage` - 插入白板的背景图片 (Version >=1.1.32) **该方法需要开启extras.useBackgroundThread**
332
+
333
+ 5. 不兼容接口
334
+ - [`exportScene`](https://doc.shengwang.cn/api-ref/whiteboard/javascript/interfaces/room#exportScene) - appliance-plugin 开启后,笔记不能按 room 的方式导出
335
+ - [服务端截图](https://doc.shengwang.cn/doc/whiteboard/restful/fastboard-sdk/restful-wb/operations/post-v5-rooms-uuid-screenshots) - appliance-plugin 开启后,笔记不能通过调用服务端截图方式获取截图,而需要改用 `screenshotToCanvasAsync` 获取
336
+
337
+ #### 新功能
338
+ ##### 激光铅笔教具 (Version >=1.1.1)
339
+ ```js
340
+ import { EStrokeType, ApplianceNames } from '@netless/appliance-plugin';
341
+ room.setMemberState({currentApplianceName: ApplianceNames.laserPen, strokeType: EStrokeType.Normal});
342
+ ```
343
+ ![Image](https://github.com/user-attachments/assets/3cd10c3a-b17b-4c01-b9d4-868c69116d96)
344
+
345
+ ##### Auto Shape: 一笔快速绘制成图形 (Version >=1.1.33)
346
+ 开启后,用户仍使用 `Pencil` 教具,抬笔时会尝试把这一笔手势识别成规则图形,并直接生成对应的 shape,而不是普通铅笔路径。
347
+
348
+ ```js
349
+ import { ApplianceNames, EStrokeType } from '@netless/appliance-plugin';
350
+
351
+ room.setMemberState({
352
+ currentApplianceName: ApplianceNames.pencil,
353
+ autoShape: true,
354
+ strokeType: EStrokeType.Normal,
355
+ });
356
+ ```
357
+
358
+ 当前版本支持单笔识别以下图形:
359
+
360
+ - `Straight`
361
+ - `Arrow`
362
+ - `Rectangle`
363
+ - `Ellipse / Circle`
364
+ - `Triangle`
365
+ - `Rhombus`
366
+ - `Five-point Star`
367
+
368
+ 建议:
369
+
370
+ - 使用 `Pencil` 教具
371
+ - 尽量一笔完成
372
+ - 闭合绘制矩形、圆/椭圆、三角形、五角星
373
+ - 箭头和直线保持单笔非闭合
374
+
375
+ ##### 扩展教具 (Version >=1.1.1)
376
+ 在原来的[白板教具](https://doc.shengwang.cn/api-ref/whiteboard/javascript/globals.html#memberstate)类型上,增加了一些扩展功能属性,如下:
377
+
378
+ ```js
379
+ export enum EStrokeType {
380
+ /** 实心线条 */
381
+ Normal = 'Normal',
382
+ /** 带笔锋线条 */
383
+ Stroke = 'Stroke',
384
+ /** 虚线线条 */
385
+ Dotted = 'Dotted',
386
+ /** 长虚线线条 */
387
+ LongDotted = 'LongDotted'
388
+ };
389
+ export type ExtendMemberState = {
390
+ /** 当前用户所选择的教具 */
391
+ currentApplianceName: ApplianceNames;
392
+ /** 是否开启笔锋 */
393
+ strokeType?: EStrokeType;
394
+ /** 是否删除整条线段 */
395
+ isLine?: boolean;
396
+ /** 线框透明度 */
397
+ strokeOpacity?: number;
398
+ /** 是否开启激光笔 */
399
+ useLaserPen?: boolean;
400
+ /** 是否开启单笔自动识别图形 */
401
+ autoShape?: boolean;
402
+ /** 激光笔保持时间, second */
403
+ duration?: number;
404
+ /** 填充样式 */
405
+ fillColor?: Color;
406
+ /** 填充透明度 */
407
+ fillOpacity?: number;
408
+ /** 使用 ``shape`` 教具时,绘制图形的具体类型 */
409
+ shapeType?: ShapeType;
410
+ /** 多边形顶点数 */
411
+ vertices?:number;
412
+ /** 多边形向内顶点步长 */
413
+ innerVerticeStep?:number;
414
+ /** 多边形向内顶点与外顶点半径比率 */
415
+ innerRatio?: number;
416
+ /** 文字透明度 */
417
+ textOpacity?: number;
418
+ /** 文字背景颜色 */
419
+ textBgColor?: Color;
420
+ /** 文字背景颜色透明度 */
421
+ textBgOpacity?: number;
422
+ /** 位置 */
423
+ placement?: SpeechBalloonPlacement;
424
+ };
425
+ import { ExtendMemberState, ApplianceNames } from '@netless/appliance-plugin';
426
+ /** 设置教具状态 */
427
+ room.setMemberState({ ... } as ExtendMemberState);
428
+ manager.mainView.setMemberState({ ... } as ExtendMemberState);
429
+ appliance.setMemberState({ ... } as ExtendMemberState);
430
+ ```
431
+ 1. 设置笔记类型:
432
+ ```js
433
+ // 实心线条
434
+ setMemberState({strokeType: EStrokeType.Normal });
435
+ // 带笔锋线条
436
+ setMemberState({strokeType: EStrokeType.Stroke });
437
+ // 虚线线条
438
+ setMemberState({strokeType: EStrokeType.Dotted });
439
+ // 长虚线线条
440
+ setMemberState({strokeType: EStrokeType.LongDotted });
441
+ ```
442
+ ![Image](https://github.com/user-attachments/assets/fabe4ea7-db42-4c31-a751-10df4dd82807)
443
+
444
+ 2. 设置笔记、图形边框透明度(马克笔):
445
+ ```js
446
+ setMemberState({strokeOpacity: 0.5 });
447
+ ```
448
+ ![Image](https://github.com/user-attachments/assets/1aac265d-9643-4858-bcc6-a43af94ed73e)
449
+
450
+ 3. 设置文字颜色、透明度、背景颜色、透明度
451
+ ```js
452
+ setMemberState({textOpacity: 0.5, textBgOpacity: 0.5, textBgColor:[0, 0, 0]});
453
+ ```
454
+ ![Image](https://github.com/user-attachments/assets/b59a9864-8f3f-4700-abee-2ccbe264cc86)
455
+
456
+ 4. 设置图形填充色及透明度
457
+ ```js
458
+ setMemberState({fillOpacity: 0.5, fillColor:[0, 0, 0]});
459
+ ```
460
+ ![Image](https://github.com/user-attachments/assets/468b930c-3db0-4355-87be-6b55af764799)
461
+
462
+ 5. 自定义正多边形
463
+ ```js
464
+ // 正五边形
465
+ setMemberState({currentApplianceName: ApplianceNames.shape, shapeType: ShapeType.Polygon, vertices: 5});
466
+ ```
467
+ ![Image](https://github.com/user-attachments/assets/f34540f5-d779-42f9-bb8a-91250fcfe4e1)
468
+
469
+ 6. 自定义星形
470
+ ```js
471
+ // 胖六角星
472
+ setMemberState({currentApplianceName: ApplianceNames.shape, shapeType: ShapeType.Star, vertices: 12, innerVerticeStep: 2, innerRatio: 0.8});
473
+ ```
474
+ ![Image](https://github.com/user-attachments/assets/49215362-722a-47d3-998f-cc933a2b5126)
475
+
476
+ 7. 自定义泡泡框方向
477
+ ```js
478
+ // 左下角提示框
479
+ setMemberState({currentApplianceName: ApplianceNames.shape, shapeType: ShapeType.SpeechBalloon, placement: 'bottomLeft'});
480
+ ```
481
+ ![Image](https://github.com/user-attachments/assets/6d52dedf-ca21-406d-a353-d801273b98bf)
482
+
483
+ ##### 分屏显示笔记(小白板功能),需要结合[`@netless/app-little-white-board`](https://github.com/netless-io/app-little-white-board) (Version >=1.1.3)
484
+ ![Image](https://github.com/user-attachments/assets/20810ea6-7d85-4e72-b75f-185599fffaf8)
485
+
486
+ ##### 小地图功能 (Version >=1.1.6)
487
+ ```js
488
+ /** 创建小地图
489
+ * @param viewId 多白板下白板ID, 主白板ID为 `mainView`, 其他白板ID为 addApp() return 的appID
490
+ * @param div 小地图DOM容器
491
+ */
492
+ createMiniMap(viewId: string, div: HTMLElement): Promise<void>;
493
+ /** 销毁小地图 */
494
+ destroyMiniMap(viewId: string): Promise<boolean>;
495
+ ```
496
+ ![Image](https://github.com/user-attachments/assets/8888dc2f-ba66-4807-aa12-16530b3b8a3c)
497
+
498
+ ##### 文字编辑API (Version >=1.1.18)
499
+ ```js
500
+ /** 在指定位置插入文字
501
+ * @param x 第一个字的的左侧边中点,世界坐标系中的 x 坐标
502
+ * @param y 第一个字的的左侧边中点,世界坐标系中的 y 坐标
503
+ * @param textContent 初始化文字的内容,不传则为空
504
+ * @returns 该文字的标识符
505
+ */
506
+ insertText(x: number, y: number, textContent?: string): string | undefined;
507
+
508
+ /** 编辑指定文字的内容
509
+ * @param identifier 文字的标识符。为 insertText() 的返回值。
510
+ * @param textContent 文字要改成的内容
511
+ */
512
+ updateText(identifier: string, textContent: string): void;
513
+
514
+ /** 失去文本焦点 */
515
+ blurText(): void;
516
+ ```
517
+
518
+ ##### 元素查询API (Version >=1.1.19)
519
+ ```js
520
+ /** 指定场景下是否存在元素
521
+ * @param scenePath 场景路径, 默认取当前聚焦的场景
522
+ * @param filter 过滤条件
523
+ * @returns 是否存在元素
524
+ */
525
+ hasElements(
526
+ scenePath?: string,
527
+ filter?: (toolsType: EToolsKey) => boolean,
528
+ ): boolean;
529
+
530
+ /** 获取场景下的所有元素
531
+ * @param scenePath 场景路径, 默认取当前聚焦的场景
532
+ * @param filter 过滤条件
533
+ * @returns 所有元素
534
+ */
535
+ getElements(
536
+ scenePath?: string,
537
+ filter?: (toolsType: EToolsKey) => boolean,
538
+ ): BaseCollectorReducerAction[];
539
+ ```
540
+
541
+ ##### 过滤笔记 (Version >=1.1.6)
542
+ ```js
543
+ /** 过滤笔记
544
+ * @param viewId 多白板下的白板ID, 主白板ID为 `mainView`, 其他白板ID为 addApp() return 的appID
545
+ * @param filter 过滤条件
546
+ * render: 笔记是否能要渲染, [uid1, uid2, ...] 或 true. true, 即都会渲染; [uid1, uid2, ...] 为指定渲染的用户uid集合
547
+ * hide: 笔记是否隐藏, [uid1, uid2, ...] 或 true. true, 即都要隐藏; [uid1, uid2, ...] 为指定隐藏的用户uid集合
548
+ * clear: 笔记是否可被擦除, [uid1, uid2, ...] 或 true. true, 即都可以被擦除; [uid1, uid2, ...] 为指定可被擦除的用户uid集合
549
+ * @param isSync 是否同步到白板房间中, 默认为true, 即设置会同步给所有用户
550
+ */
551
+ filterRenderByUid(viewId: string, filter: { render?: _ArrayTrue, hide?: _ArrayTrue, clear?: _ArrayTrue}, isSync?:boolean): void;
552
+ /** 取消过滤笔记
553
+ * @param viewId 多白板下白板ID, 主白板ID为 `mainView`, 其他白板ID为 addApp() return 的appID
554
+ * @param isSync 是否同步到白板房间中, 默认为true, 即会同步到其他用户. 请保持和filterRenderByUid设置的一致
555
+ */
556
+ cancelFilterRender(viewId: string, isSync?:boolean): void;
557
+ ```
558
+ ![Image](https://github.com/user-attachments/assets/7952ee1d-4f9c-4e86-802a-bac8e4ae6a51)
559
+
560
+ ##### 设置白板本地场景路径变化 (Version >=1.1.27)
561
+ ```js
562
+ /** 设置白板本地场景路径变化
563
+ * @param viewId 多白板下白板ID, 主白板ID为 `mainView`, 其他白板ID为 addApp() return 的appID
564
+ * @param scenePath 要设置的场景路径
565
+ */
566
+ setViewLocalScenePathChange(viewId: string, scenePath: string): Promise<void>;
567
+ ```
568
+
569
+ ##### ExtrasOption自定义教具配置
570
+ 1. 自定义画笔样式
571
+ - 短虚线样式
572
+ ```ts
573
+ export type DottedOpt = {
574
+ /** 虚线端点样式, square: 平头, round: 圆头, 默认值为 round */
575
+ lineCap: "square" | "round";
576
+ /** 虚线,单线段长度, 默认值为 1, 即单线段长度为 1 */
577
+ segment: number;
578
+ /** 虚线,单线段间隔, 默认值为 2, 即单线段间隔为 2 * thickness */
579
+ gap: number;
580
+ };
581
+ /** 短虚线样式 */
582
+ dottedStroke: {
583
+ lineCap: "round",
584
+ segment: 1,
585
+ gap: 2,
586
+ },
587
+ ```
588
+ ![Image](https://github.com/user-attachments/assets/5dc7e2bf-c285-45f0-89d2-849b4792dc7e)
589
+ - 长虚线样式
590
+ ```ts
591
+ export type LongDottedOpt = {
592
+ /** 长虚线端点样式, square: 平头, round: 圆头, 默认值为 round */
593
+ lineCap: "square" | "round";
594
+ /** 长虚线,单线段长度, 默认值为 1, 即单线段长度为 1 * thickness */
595
+ segment: number;
596
+ /** 长虚线,单线段间隔, 默认值为 2, 即单线段间隔为 2 * thickness */
597
+ gap: number;
598
+ };
599
+ /** 长虚线线样式 */
600
+ longDottedStroke: {
601
+ lineCap: "round",
602
+ segment: 2,
603
+ gap: 3,
604
+ },
605
+ ```
606
+ ![Image](https://github.com/user-attachments/assets/a305c1a1-b366-444a-ace6-3e0ecbf5ad19)
607
+ - 普通画笔样式
608
+ ```ts
609
+ export type NormalOpt = {
610
+ /** 端点样式, square: 平头, round: 圆头, 默认值为 round */
611
+ lineCap: "square" | "round";
612
+ };
613
+ /** 普通画笔样式 */
614
+ normalStroke: {
615
+ lineCap: "round",
616
+ }
617
+ ```
618
+ ![Image](https://github.com/user-attachments/assets/23979f81-057a-408f-8302-de228ef00b4f)
619
+
620
+ 2. 文字自定义样式
621
+ ```ts
622
+ export type TextEditorOpt = {
623
+ /** 是否显示浮动栏 */
624
+ showFloatBar?: boolean;
625
+ /** 是否可以通过selector教具切换 */
626
+ canSelectorSwitch?: boolean;
627
+ /** 是否右边界自动换行 */
628
+ rightBoundBreak?: boolean;
629
+ /** 扩展字体列表 */
630
+ extendFontFaces?: { fontFamily: string; src: string }[];
631
+ /** 加载字体超时时间,单位:毫秒 */
632
+ loadFontFacesTimeout?: number;
633
+ };
634
+ // 比如: 设置统一字体库
635
+ textEditor: {
636
+ showFloatBar: false,
637
+ canSelectorSwitch: false,
638
+ rightBoundBreak: true,
639
+ extendFontFaces: [
640
+ {
641
+ fontFamily: "Noto Sans SC",
642
+ src: "https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-mu0SC55I.woff2",
643
+ },
644
+ ],
645
+ loadFontFacesTimeout: 20000,
646
+ },
647
+ ```
648
+ 需要结合css style实现
649
+ ```css
650
+ @font-face {
651
+ font-family: "Noto Sans SC";
652
+ src: url("https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-mu0SC55I.woff2")
653
+ format("woff2");
654
+ font-display: swap;
655
+ }
656
+ html {
657
+ font-family: "Noto Sans SC";
658
+ }
659
+ ```
660
+
661
+ ##### 手写图形自动联想功能:`autoDraw`,需要结合[@netless/appliance-extend-auto-draw-plugin](https://www.npmjs.com/package/@netless/appliance-extend-auto-draw-plugin)
662
+ ```js
663
+ export interface AutoDrawOptions {
664
+ /** 用于访问 OpenRouter 所有模型的 API 密钥 */
665
+ apiKey?: string;
666
+ /** 自定义使用的模型 */
667
+ customModel?: string;
668
+ /** 用于渲染图标的容器 */
669
+ container: HTMLDivElement;
670
+ /** 渲染图标的延迟时间,默认为 2000ms */
671
+ delay?: number;
672
+ /**
673
+ * 将文件上传到 OSS 服务器并返回 URL 地址,如果返回 undefined 则不会使用此功能
674
+ * @param file 文件对象
675
+ * @returns 图片 URL 字符串
676
+ */
677
+ uploadFile?: (file: File) => Promise<string | undefined>;
678
+ }
679
+ import { ApplianceMultiPlugin } from '@netless/appliance-plugin';
680
+ import { AutoDrawPlugin } from '@netless/appliance-extend-auto-draw-plugin';
681
+ const plugin = await ApplianceMultiPlugin.getInstance(...);
682
+ const autoDrawPlugin = new AutoDrawPlugin({
683
+ container: topBarDiv,
684
+ delay: 2000
685
+ });
686
+ plugin.usePlugin(autoDrawPlugin);
687
+ ```
688
+ ![Image](https://github.com/user-attachments/assets/c388691c-ae72-44ec-bbb7-e92c3a73c9c7)
689
+
690
+ ##### 插入思维导图(需要markdown文本) (Version >=1.1.32)
691
+ ```ts
692
+ import { ApplianceMultiPlugin } from '@netless/appliance-plugin';
693
+ const plugin = await ApplianceMultiPlugin.getInstance(manager, {
694
+ options: {
695
+ cdn: {...}
696
+ extras: {
697
+ ...,
698
+ useBackgroundThread: true,
699
+ }
700
+ },
701
+ });
702
+ const markId = await plugin.insertMarkmap(viewId, {
703
+ data: `# 一集标题
704
+ ## 二级标题1
705
+ ### 三级标题1
706
+ ### 三级标题2
707
+ #### 四级标题1
708
+ #### 四级标题2
709
+ #### 四级标题3
710
+ ## 二级标题2
711
+ ### 三级标题1
712
+ ### 三级标题2`,
713
+ uuid: '唯一标识',
714
+ centerX: 0,
715
+ centerY: 0,
716
+ width: 200,
717
+ height: 200,
718
+ locked: false,
719
+ });
720
+ plugin.updateMarkmap(viewId, markId, {
721
+ data: `# 一集标题
722
+ ## 二级标题1
723
+ ## 二级标题2
724
+ ### 三级标题1
725
+ ### 三级标题2`,
726
+ uuid: '唯一标识',
727
+ centerX: 0,
728
+ centerY: 0,
729
+ width: 200,
730
+ height: 200,
731
+ locked: false,
732
+ } )
733
+
734
+ ```
735
+ ![Image](https://github.com/user-attachments/assets/0d278bd5-1cc7-413f-881c-8a43ef1429e3)
736
+ ##### 插入背景图 (Version >=1.1.32)
737
+ ```ts
738
+ import { ApplianceMultiPlugin } from '@netless/appliance-plugin';
739
+ const plugin = await ApplianceMultiPlugin.getInstance(manager, {
740
+ options: {
741
+ cdn: {...}
742
+ extras: {
743
+ ...,
744
+ useBackgroundThread: true,
745
+ }
746
+ },
747
+ });
748
+ plugin.insertBackgroundImage(viewId, {
749
+ src: 'https://example.com/background.png'
750
+ uuid: '唯一标识',
751
+ centerX: 0,
752
+ centerY: 0,
753
+ width: 200,
754
+ height: 200,
755
+ locked: true,
756
+ })
757
+ ```
758
+
759
+ ### 配置参数
760
+ `getInstance(wm: WindowManager | Room | Player, adaptor: ApplianceAdaptor)`
761
+ - `wm`: `WindowManager | Room | Player`。多窗口模式下传入的是 `WindowManager`,单窗口模式下传入的是 `Room` 或者 `Player`(白板回放模式)。
762
+ - `adaptor`: 配置适配器。
763
+ - `options: AppliancePluginOptions` - 必须配置,其中 `cdn` 为必填项。
764
+ ```js
765
+ export type AppliancePluginOptions = {
766
+ /** cdn配置项 */
767
+ cdn: CdnOpt;
768
+ /** 额外配置项 */
769
+ extras?: ExtrasOptions;
770
+ };
771
+ export type CdnOpt = {
772
+ /** full worker url 地址, 绘制完整数据的线程 */
773
+ fullWorkerUrl?: string;
774
+ /** sub worker url 地址, 绘制一帧数据的线程 */
775
+ subWorkerUrl?: string;
776
+ };
777
+ export type ExtrasOptions = {
778
+ /** 是否使用简单模式, 默认值为 ``false``
779
+ * true: 简单模式:
780
+ 1、绘制将使用单worker绘制,画笔过程中无法使用贝塞尔圆滑处理。
781
+ 2、移除部分新功能:小地图、pointerPen(激光笔)、autoDraw插件。
782
+ */
783
+ useSimple?: boolean;
784
+ /** 是否使用 worker, 默认值为 ``auto``
785
+ * auto: 自动选择(如果浏览器支持 offscreenCanvas 则使用 webWorker, 否则使用主线程)
786
+ * mainThread: 使用主线程, canvas 绘制数据。
787
+ */
788
+ useWorker?: UseWorkerType;
789
+ /** 是否使用 backgroundThread, 默认值为 ``false``
790
+ * true: 使用 backgroundThread, 可以调用 ``insertMarkmap``, ``updateMarkmap``, ``insertBackgroundImage``
791
+ * false: 不使用 backgroundThread
792
+ */
793
+ useBackgroundThread?: boolean;
794
+ /** 同步数据配置项 */
795
+ syncOpt?: SyncOpt;
796
+ /** 画布配置项 */
797
+ canvasOpt?: CanvasOpt;
798
+ /** 指针配置项 */
799
+ cursor?: CursorOpt;
800
+ /** 画布缓存配置项 */
801
+ bufferSize?: BufferSizeOpt;
802
+ /** 贝塞尔优化配置项 */
803
+ bezier?: BezierOpt;
804
+ /** 局部橡皮擦配置项 */
805
+ pencilEraser?: PencilEraserOpt;
806
+ /** 线条粗细范围配置项 */
807
+ strokeWidth?: StrokeWidthOpt,
808
+ /** 文字编辑器配置项 */
809
+ textEditor?: TextEditorOpt;
810
+ /** 撤销重做配置项 */
811
+ undoRedo?: {
812
+ /** 是否启用全局撤销重做, 默认值为 false (Version >=1.1.27) */
813
+ enableGlobal?: boolean;
814
+ /** 撤销重做最大堆栈长度, 默认值为 20 */
815
+ maxStackLength?: number;
816
+ };
817
+ }
818
+ ```
819
+ - `cursorAdapter?: CursorAdapter` - 非必填,单白板模式下,配置的自定义鼠标样式。
820
+ - `logger?: Logger` - 非必填,配置日志打印器对象。不填写默认在本地 console 输出,如果需要把日志上传到指定 server,则需要手动配置。
821
+ > 如需要上传到白板日志服务器,可以把 `room.logger` 配置到该项目。
822
+
823
+ ### 前端调试
824
+
825
+ 对接过程中如果想了解和跟踪插件内部状态,可以通过以下几个控制台指令,查看内部数据。
826
+
827
+ ```js
828
+ const appliancePlugin = await ApplianceSinglePlugin.getInstance(...)
829
+ appliancePlugin.currentManager // 可以查看到包版本号,内部状态等
830
+ appliancePlugin.currentManager.consoleWorkerInfo() // 可以查看到 worker 上的绘制信息
831
+ ```
832
+
833
+ ## 使用示例
834
+
835
+ ### 基础使用示例
836
+
837
+ ```js
838
+ import { ApplianceSinglePlugin } from '@netless/appliance-plugin';
839
+ import '@netless/appliance-plugin/dist/style.css';
840
+
841
+ // 方式1: 使用 CDN(推荐生产环境)
842
+ const plugin = await ApplianceSinglePlugin.getInstance(room, {
843
+ options: {
844
+ cdn: {
845
+ fullWorkerUrl: 'https://your-cdn.com/fullWorker.js',
846
+ subWorkerUrl: 'https://your-cdn.com/subWorker.js',
847
+ },
848
+ },
849
+ });
850
+
851
+ // 方式2: 使用本地 worker 文件(适合开发环境)
852
+ import fullWorkerString from '@netless/appliance-plugin/dist/fullWorker.js?raw';
853
+ import subWorkerString from '@netless/appliance-plugin/dist/subWorker.js?raw';
854
+ const fullWorkerBlob = new Blob([fullWorkerString], {type: 'text/javascript'});
855
+ const fullWorkerUrl = URL.createObjectURL(fullWorkerBlob);
856
+ const subWorkerBlob = new Blob([subWorkerString], {type: 'text/javascript'});
857
+ const subWorkerUrl = URL.createObjectURL(subWorkerBlob);
858
+
859
+ const plugin = await ApplianceSinglePlugin.getInstance(room, {
860
+ options: {
861
+ cdn: {
862
+ fullWorkerUrl,
863
+ subWorkerUrl,
864
+ },
865
+ },
866
+ });
867
+ ```
868
+
869
+ ### 切换绘制工具
870
+
871
+ ```js
872
+ import { ApplianceNames, EStrokeType } from '@netless/appliance-plugin';
873
+
874
+ // 切换到铅笔工具
875
+ room.setMemberState({ currentApplianceName: ApplianceNames.pencil });
876
+
877
+ // 切换到矩形工具
878
+ room.setMemberState({ currentApplianceName: ApplianceNames.rectangle });
879
+
880
+ // 切换到激光笔工具
881
+ room.setMemberState({
882
+ currentApplianceName: ApplianceNames.laserPen,
883
+ strokeType: EStrokeType.Normal
884
+ });
885
+
886
+ // 切换到文字工具
887
+ room.setMemberState({ currentApplianceName: ApplianceNames.text });
888
+ ```
889
+
890
+ ### 自定义样式示例
891
+
892
+ ```js
893
+ // 设置画笔样式为虚线
894
+ room.setMemberState({
895
+ strokeType: EStrokeType.Dotted,
896
+ strokeOpacity: 0.8
897
+ });
898
+
899
+ // 设置图形填充
900
+ room.setMemberState({
901
+ fillColor: [255, 0, 0], // 红色
902
+ fillOpacity: 0.5
903
+ });
904
+
905
+ // 设置文字样式
906
+ room.setMemberState({
907
+ textOpacity: 0.9,
908
+ textBgColor: [255, 255, 0], // 黄色背景
909
+ textBgOpacity: 0.3
910
+ });
911
+ ```
912
+
913
+ ### 文字编辑示例
914
+
915
+ ```js
916
+ // 在指定位置插入文字
917
+ const textId = plugin.insertText(100, 100, 'Hello World');
918
+
919
+ // 编辑文字内容
920
+ plugin.updateText(textId, 'Updated Text');
921
+
922
+ // 移除文字焦点
923
+ plugin.blurText();
924
+ ```
925
+
926
+ ### 小地图功能示例
927
+
928
+ ```js
929
+ // 创建小地图
930
+ const minimapDiv = document.getElementById('minimap');
931
+ await plugin.createMiniMap('mainView', minimapDiv);
932
+
933
+ // 销毁小地图
934
+ await plugin.destroyMiniMap('mainView');
935
+ ```
936
+
937
+ ### 撤销重做示例
938
+
939
+ ```js
940
+ // 撤销操作
941
+ const undoSteps = plugin.undo();
942
+
943
+ // 重做操作
944
+ const redoSteps = plugin.redo();
945
+
946
+ // 检查是否可以撤销/重做
947
+ const canUndo = plugin.canUndoSteps() > 0;
948
+ const canRedo = plugin.canRedoSteps() > 0;
949
+ ```
950
+
951
+ ## 常见问题
952
+
953
+ ### 1. 如何选择合适的接入方式?
954
+
955
+ - **fastboard**:如果你使用的是 fastboard 框架,推荐使用 fastboard 的集成方式,配置最简单
956
+ - **多窗口场景**:如果需要多窗口功能,使用 `ApplianceMultiPlugin`
957
+ - **单白板场景**:如果只需要单白板功能,使用 `ApplianceSinglePlugin`
958
+
959
+ ### 2. Worker 文件部署方式选择?
960
+
961
+ - **CDN 部署**(推荐):适合生产环境,可以减少主包体积(主包约 400kB,两个 worker 各约 800kB)
962
+ - **本地打包**:适合开发环境或对包体积不敏感的场景
963
+
964
+ ### 3. 性能优化建议
965
+
966
+ - 使用 CDN 部署 worker 文件,减少主包体积
967
+ - 合理配置 `bufferSize`,根据设备性能调整画布缓存大小
968
+ - 在移动端或低性能设备上,可以考虑使用 `useSimple: true` 简单模式
969
+ - 如有非必要的功能, 可以不用开启 `useBackgroundThread: true`
970
+
971
+ ### 4. 兼容性说明
972
+
973
+ - 支持现代浏览器(Chrome、Firefox、Safari、Edge)
974
+ - 移动端浏览器支持情况取决于 OffscreenCanvas 支持情况
975
+ - 不支持 OffscreenCanvas 的设备会自动降级到主线程模式
976
+
977
+ ## 版本历史
978
+
979
+ 详细的版本更新记录请查看 [CHANGELOG.md](./CHANGELOG.md)
980
+
981
+ ## 许可证
982
+
983
+ MIT License
984
+
985
+ ## 相关链接
986
+
987
+ - [white-web-sdk](https://www.npmjs.com/package/white-web-sdk)
988
+ - [@netless/window-manager](https://www.npmjs.com/package/@netless/window-manager)
989
+ - [fastboard](https://github.com/netless-io/fastboard)
990
+ - [官方文档](https://doc.shengwang.cn/)
991
+
992
+
993
+ ## [changelog](https://github.com/duty-os/appliance-plugin/blob/master/CHANGELOG.md)