@tmagic/editor 1.8.0-manmanyu.9 → 1.8.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 (279) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +22 -13
  2. package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +10 -5
  3. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +7 -6
  4. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +21 -122
  5. package/dist/es/components/ContentMenu.vue_vue_type_script_setup_true_lang.js +21 -5
  6. package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +62 -16
  7. package/dist/es/components/Icon.vue_vue_type_script_setup_true_lang.js +1 -1
  8. package/dist/es/components/Resizer.vue_vue_type_script_setup_true_lang.js +3 -2
  9. package/dist/es/components/ScrollBar.js +0 -1
  10. package/dist/es/components/ScrollBar.vue_vue_type_script_setup_true_lang.js +5 -4
  11. package/dist/es/components/ScrollViewer.vue_vue_type_script_setup_true_lang.js +1 -1
  12. package/dist/es/components/SearchInput.vue_vue_type_script_setup_true_lang.js +1 -1
  13. package/dist/es/components/SplitView.vue_vue_type_script_setup_true_lang.js +1 -1
  14. package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +1 -1
  15. package/dist/es/components/Tree.vue_vue_type_script_setup_true_lang.js +1 -1
  16. package/dist/es/components/TreeNode.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/es/components/ViewForm.js +5 -0
  18. package/dist/es/components/ViewForm.vue_vue_type_script_setup_true_lang.js +56 -0
  19. package/dist/es/editorProps.js +7 -1
  20. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +1 -1
  21. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +1 -1
  22. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +30 -113
  23. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +2 -2
  24. package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +7 -19
  25. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +7 -6
  26. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +4 -2
  27. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +12 -4
  28. package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +9 -20
  29. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +2 -2
  30. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +5 -3
  31. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +9 -4
  32. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +2 -2
  33. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +10 -110
  34. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +52 -363
  35. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +1 -1
  36. package/dist/es/fields/PageFragmentSelect.vue_vue_type_script_setup_true_lang.js +1 -1
  37. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +3 -6
  38. package/dist/es/fields/StyleSetter/components/BackgroundPosition.vue_vue_type_script_setup_true_lang.js +1 -1
  39. package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +4 -35
  40. package/dist/es/fields/StyleSetter/components/Box.vue_vue_type_script_setup_true_lang.js +3 -3
  41. package/dist/es/fields/StyleSetter/configs.js +663 -0
  42. package/dist/es/fields/StyleSetter/icons/align-items/Center.js +1 -1
  43. package/dist/es/fields/StyleSetter/icons/align-items/FlexEnd.js +1 -1
  44. package/dist/es/fields/StyleSetter/icons/align-items/FlexStart.js +1 -1
  45. package/dist/es/fields/StyleSetter/icons/align-items/SpaceAround.js +1 -1
  46. package/dist/es/fields/StyleSetter/icons/align-items/SpaceBetween.js +1 -1
  47. package/dist/es/fields/StyleSetter/icons/background-repeat/NoRepeat.js +1 -1
  48. package/dist/es/fields/StyleSetter/icons/background-repeat/Repeat.js +1 -1
  49. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatX.js +1 -1
  50. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatY.js +1 -1
  51. package/dist/es/fields/StyleSetter/icons/display/Block.js +1 -1
  52. package/dist/es/fields/StyleSetter/icons/display/Flex.js +1 -1
  53. package/dist/es/fields/StyleSetter/icons/display/Inline.js +1 -1
  54. package/dist/es/fields/StyleSetter/icons/display/InlineBlock.js +1 -1
  55. package/dist/es/fields/StyleSetter/icons/display/None.js +1 -1
  56. package/dist/es/fields/StyleSetter/icons/flex-direction/Column.js +1 -1
  57. package/dist/es/fields/StyleSetter/icons/flex-direction/ColumnReverse.js +1 -1
  58. package/dist/es/fields/StyleSetter/icons/flex-direction/Row.js +1 -1
  59. package/dist/es/fields/StyleSetter/icons/flex-direction/RowReverse.js +1 -1
  60. package/dist/es/fields/StyleSetter/icons/justify-content/Center.js +1 -1
  61. package/dist/es/fields/StyleSetter/icons/justify-content/FlexEnd.js +1 -1
  62. package/dist/es/fields/StyleSetter/icons/justify-content/FlexStart.js +1 -1
  63. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceAround.js +1 -1
  64. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceBetween.js +1 -1
  65. package/dist/es/fields/StyleSetter/icons/text-align/Center.js +1 -1
  66. package/dist/es/fields/StyleSetter/icons/text-align/Left.js +1 -1
  67. package/dist/es/fields/StyleSetter/icons/text-align/Right.js +1 -1
  68. package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +29 -106
  69. package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +8 -11
  70. package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +30 -89
  71. package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +29 -297
  72. package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +29 -82
  73. package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +8 -24
  74. package/dist/es/fields/UISelect.js +0 -1
  75. package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +1 -1
  76. package/dist/es/fields/configs/codeSelect.js +98 -0
  77. package/dist/es/fields/configs/displayConds.js +158 -0
  78. package/dist/es/fields/configs/eventSelect.js +225 -0
  79. package/dist/es/fields/configs/stickyAddButton.js +21 -0
  80. package/dist/es/fields/headless-validation.js +139 -0
  81. package/dist/es/headless.js +2 -0
  82. package/dist/es/hooks/use-code-block-edit.js +4 -2
  83. package/dist/es/hooks/use-compare-form.js +101 -0
  84. package/dist/es/hooks/use-form-context.js +29 -0
  85. package/dist/es/hooks/use-stage.js +1 -0
  86. package/dist/es/icons/CenterIcon.vue_vue_type_script_setup_true_lang.js +1 -1
  87. package/dist/es/icons/CodeIcon.vue_vue_type_script_setup_true_lang.js +1 -1
  88. package/dist/es/icons/DatasourceIcon.js +1 -1
  89. package/dist/es/icons/FolderMinusIcon.vue_vue_type_script_setup_true_lang.js +1 -1
  90. package/dist/es/index.js +16 -8
  91. package/dist/es/initService.js +52 -16
  92. package/dist/es/layouts/AddPageBox.vue_vue_type_script_setup_true_lang.js +1 -1
  93. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +26 -10
  94. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +11 -8
  95. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  96. package/dist/es/layouts/NavMenuColumn.vue_vue_type_script_setup_true_lang.js +1 -1
  97. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +1 -1
  98. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +8 -5
  99. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +1 -1
  100. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +26 -10
  101. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +11 -13
  102. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +1 -1
  103. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +1 -1
  104. package/dist/es/layouts/history-list/composables.js +1 -1
  105. package/dist/es/layouts/history-list/useHistoryRevert.js +38 -33
  106. package/dist/es/layouts/page-bar/AddButton.vue_vue_type_script_setup_true_lang.js +1 -1
  107. package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +1 -1
  108. package/dist/es/layouts/page-bar/PageBarScrollContainer.vue_vue_type_script_setup_true_lang.js +1 -1
  109. package/dist/es/layouts/page-bar/PageList.vue_vue_type_script_setup_true_lang.js +1 -1
  110. package/dist/es/layouts/page-bar/Search.vue_vue_type_script_setup_true_lang.js +2 -2
  111. package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +50 -20
  112. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +53 -35
  113. package/dist/es/layouts/props-panel/use-style-panel.js +3 -2
  114. package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +3 -3
  115. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +21 -36
  116. package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +1 -1
  117. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +4 -4
  118. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +19 -4
  119. package/dist/es/layouts/sidebar/data-source/DataSourceAddButton.vue_vue_type_script_setup_true_lang.js +1 -1
  120. package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +9 -4
  121. package/dist/es/layouts/sidebar/data-source/DataSourceList.vue_vue_type_script_setup_true_lang.js +1 -1
  122. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +4 -4
  123. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +19 -4
  124. package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +3 -3
  125. package/dist/es/layouts/sidebar/layer/LayerNodeContent.js +5 -0
  126. package/dist/es/layouts/sidebar/layer/LayerNodeContent.vue_vue_type_script_setup_true_lang.js +46 -0
  127. package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +1 -1
  128. package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +6 -4
  129. package/dist/es/layouts/sidebar/layer/use-click.js +3 -3
  130. package/dist/es/layouts/sidebar/layer/use-drag.js +11 -10
  131. package/dist/es/layouts/sidebar/layer/use-node-status.js +2 -2
  132. package/dist/es/layouts/workspace/Breadcrumb.vue_vue_type_script_setup_true_lang.js +1 -1
  133. package/dist/es/layouts/workspace/Workspace.vue_vue_type_script_setup_true_lang.js +1 -1
  134. package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +9 -3
  135. package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +55 -9
  136. package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -1
  137. package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +8 -8
  138. package/dist/es/plugin.js +35 -23
  139. package/dist/es/services/codeBlock.js +8 -3
  140. package/dist/es/services/dataSource.js +3 -0
  141. package/dist/es/services/dep.js +162 -41
  142. package/dist/es/services/editor.js +344 -113
  143. package/dist/es/services/events.js +4 -4
  144. package/dist/es/services/history.js +17 -1
  145. package/dist/es/services/keybinding.js +3 -3
  146. package/dist/es/services/props.js +2 -1
  147. package/dist/es/services/storage.js +2 -2
  148. package/dist/es/services/ui.js +7 -2
  149. package/dist/es/style.css +277 -114
  150. package/dist/es/utils/code-block.js +14 -7
  151. package/dist/es/utils/config.js +4 -1
  152. package/dist/es/utils/const.js +2 -2
  153. package/dist/es/utils/content-menu.js +2 -2
  154. package/dist/es/utils/data-source/index.js +34 -12
  155. package/dist/es/utils/dep/collect-worker-client.js +154 -0
  156. package/dist/es/utils/dep/idle-task.js +70 -22
  157. package/dist/es/utils/dep/worker.js +1 -1
  158. package/dist/es/utils/editor.js +4 -2
  159. package/dist/es/utils/event.js +135 -0
  160. package/dist/es/utils/monaco-editor.js +22 -6
  161. package/dist/es/utils/operator.js +2 -2
  162. package/dist/es/utils/props.js +50 -106
  163. package/dist/es/utils/type-match-rules.js +356 -0
  164. package/dist/style.css +277 -114
  165. package/dist/themes/magic-admin.css +978 -175
  166. package/dist/tmagic-editor-headless.umd.cjs +10880 -0
  167. package/dist/tmagic-editor.umd.cjs +12695 -10896
  168. package/package.json +18 -13
  169. package/src/Editor.vue +23 -6
  170. package/src/components/CodeBlockEditor.vue +3 -0
  171. package/src/components/CodeParams.vue +10 -4
  172. package/src/components/CompareForm.vue +20 -190
  173. package/src/components/ContentMenu.vue +34 -6
  174. package/src/components/FloatingBox.vue +71 -7
  175. package/src/components/ScrollBar.vue +0 -67
  176. package/src/components/ViewForm.vue +54 -0
  177. package/src/editorProps.ts +23 -10
  178. package/src/fields/Code.vue +1 -1
  179. package/src/fields/CodeSelect.vue +20 -113
  180. package/src/fields/CodeSelectCol.vue +1 -1
  181. package/src/fields/CondOpSelect.vue +5 -26
  182. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +2 -2
  183. package/src/fields/DataSourceFieldSelect/Index.vue +4 -1
  184. package/src/fields/DataSourceFields.vue +5 -1
  185. package/src/fields/DataSourceInput.vue +10 -28
  186. package/src/fields/DataSourceMethodSelect.vue +1 -1
  187. package/src/fields/DataSourceMethods.vue +6 -2
  188. package/src/fields/DataSourceMocks.vue +3 -0
  189. package/src/fields/DataSourceSelect.vue +1 -1
  190. package/src/fields/DisplayConds.vue +17 -139
  191. package/src/fields/EventSelect.vue +29 -416
  192. package/src/fields/KeyValue.vue +1 -1
  193. package/src/fields/StyleSetter/Index.vue +2 -8
  194. package/src/fields/StyleSetter/components/Border.vue +4 -38
  195. package/src/fields/StyleSetter/components/Box.vue +1 -1
  196. package/src/fields/StyleSetter/components/Position.vue +1 -1
  197. package/src/fields/StyleSetter/configs.ts +690 -0
  198. package/src/fields/StyleSetter/pro/Background.vue +9 -64
  199. package/src/fields/StyleSetter/pro/Border.vue +5 -16
  200. package/src/fields/StyleSetter/pro/Font.vue +11 -71
  201. package/src/fields/StyleSetter/pro/Layout.vue +10 -230
  202. package/src/fields/StyleSetter/pro/Position.vue +11 -82
  203. package/src/fields/StyleSetter/pro/Transform.vue +6 -34
  204. package/src/fields/UISelect.vue +0 -12
  205. package/src/fields/configs/codeSelect.ts +127 -0
  206. package/src/fields/configs/displayConds.ts +184 -0
  207. package/src/fields/configs/eventSelect.ts +309 -0
  208. package/src/fields/configs/stickyAddButton.ts +32 -0
  209. package/src/fields/headless-validation.ts +177 -0
  210. package/src/headless.ts +33 -0
  211. package/src/hooks/index.ts +1 -0
  212. package/src/hooks/use-compare-form.ts +166 -0
  213. package/src/hooks/use-form-context.ts +56 -0
  214. package/src/hooks/use-stage.ts +1 -0
  215. package/src/index.ts +9 -0
  216. package/src/initService.ts +73 -18
  217. package/src/layouts/CodeEditor.vue +32 -6
  218. package/src/layouts/Framework.vue +7 -6
  219. package/src/layouts/history-list/BucketTab.vue +2 -2
  220. package/src/layouts/history-list/HistoryDiffDialog.vue +33 -15
  221. package/src/layouts/history-list/HistoryListPanel.vue +16 -18
  222. package/src/layouts/history-list/useHistoryRevert.ts +57 -41
  223. package/src/layouts/props-panel/FormPanel.vue +75 -20
  224. package/src/layouts/props-panel/PropsPanel.vue +81 -30
  225. package/src/layouts/props-panel/use-style-panel.ts +4 -3
  226. package/src/layouts/sidebar/ComponentListPanel.vue +6 -2
  227. package/src/layouts/sidebar/Sidebar.vue +5 -3
  228. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +5 -2
  229. package/src/layouts/sidebar/code-block/useContentMenu.ts +14 -2
  230. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +3 -0
  231. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +3 -3
  232. package/src/layouts/sidebar/data-source/useContentMenu.ts +14 -2
  233. package/src/layouts/sidebar/layer/LayerMenu.vue +2 -2
  234. package/src/layouts/sidebar/layer/LayerNodeContent.vue +57 -0
  235. package/src/layouts/sidebar/layer/LayerPanel.vue +5 -2
  236. package/src/layouts/sidebar/layer/use-click.ts +3 -3
  237. package/src/layouts/sidebar/layer/use-node-status.ts +2 -2
  238. package/src/layouts/workspace/viewer/NodeListMenu.vue +4 -1
  239. package/src/layouts/workspace/viewer/Stage.vue +80 -10
  240. package/src/layouts/workspace/viewer/ViewerMenu.vue +7 -7
  241. package/src/plugin.ts +35 -25
  242. package/src/services/codeBlock.ts +4 -0
  243. package/src/services/dataSource.ts +3 -0
  244. package/src/services/dep.ts +292 -47
  245. package/src/services/editor.ts +383 -120
  246. package/src/services/events.ts +8 -7
  247. package/src/services/history.ts +22 -1
  248. package/src/services/keybinding.ts +3 -3
  249. package/src/services/ui.ts +26 -5
  250. package/src/theme/code-block.scss +0 -37
  251. package/src/theme/display-conds.scss +4 -0
  252. package/src/theme/event.scss +25 -33
  253. package/src/theme/floating-box.scss +9 -1
  254. package/src/theme/framework.scss +4 -1
  255. package/src/theme/layer-node-content.scss +14 -0
  256. package/src/theme/nav-menu.scss +5 -1
  257. package/src/theme/props-panel.scss +17 -3
  258. package/src/theme/scroll-bar.scss +64 -0
  259. package/src/theme/theme.scss +3 -0
  260. package/src/theme/themes/magic-admin/index.scss +109 -9
  261. package/src/theme/ui-select.scss +9 -0
  262. package/src/type.ts +216 -6
  263. package/src/utils/code-block.ts +1 -0
  264. package/src/utils/config.ts +9 -1
  265. package/src/utils/const.ts +3 -3
  266. package/src/utils/content-menu.ts +2 -2
  267. package/src/utils/data-source/index.ts +31 -12
  268. package/src/utils/dep/collect-worker-client.ts +215 -0
  269. package/src/utils/dep/idle-task.ts +104 -35
  270. package/src/utils/dep/worker.ts +113 -9
  271. package/src/utils/event.ts +237 -0
  272. package/src/utils/index.ts +2 -0
  273. package/src/utils/monaco-editor.ts +27 -0
  274. package/src/utils/operator.ts +2 -2
  275. package/src/utils/props.ts +61 -122
  276. package/src/utils/type-match-rules.ts +697 -0
  277. package/types/headless.d.ts +17 -0
  278. package/types/index.d.ts +1199 -724
  279. package/src/fields/StyleSetter/icons/align-items/index copy.ts +0 -5
@@ -0,0 +1,215 @@
1
+ import serialize from 'serialize-javascript';
2
+
3
+ import type { Id, MApp } from '@tmagic/core';
4
+
5
+ import { error } from '../logger';
6
+
7
+ import type { CollectWorkerPayload, CollectWorkerRequest, CollectWorkerResponse, DepsData } from './worker';
8
+ import CollectWorker from './worker.ts?worker&inline';
9
+
10
+ export interface CollectWorkerResult {
11
+ deps: DepsData;
12
+ nodeIds: Id[];
13
+ }
14
+
15
+ interface CollectTask {
16
+ id: number;
17
+ createRequest: (id: number) => CollectWorkerRequest;
18
+ resolve: (response: CollectWorkerResponse | null) => void;
19
+ /** 已被 abort 丢弃:调用方已按 null 结算,结果回来后不再回传 */
20
+ discarded: boolean;
21
+ }
22
+
23
+ /**
24
+ * 依赖收集 worker 的主线程客户端
25
+ *
26
+ * 依赖收集需要深度遍历整棵节点树并对每个属性做匹配,节点/数据源多时会长时间占用主线程导致页面卡死,
27
+ * 这里把遍历匹配放到 worker 中执行,主线程只负责把结果写回 target。
28
+ *
29
+ * 全量收集与增量收集共用一个常驻 worker:增量收集调用频繁(每次节点更新都会触发),
30
+ * 按次创建 worker 的启动开销无法接受,也容易漏掉销毁导致线程泄漏。
31
+ *
32
+ * 请求在主线程侧排队后逐个投递(worker 本身也是串行处理),这样 abort 时能直接丢掉尚未投递的请求,
33
+ * 无需销毁线程:worker 只在真正异常或销毁时才重建。
34
+ */
35
+ export class CollectWorkerClient {
36
+ private worker: Worker | null = null;
37
+
38
+ private seed = 0;
39
+
40
+ /** 等待投递给 worker 的请求 */
41
+ private queue: CollectTask[] = [];
42
+
43
+ /** 已投递、等待 worker 响应的请求,worker 串行处理,同一时刻最多一个 */
44
+ private inflight: CollectTask | null = null;
45
+
46
+ public get isSupported() {
47
+ return typeof Worker !== 'undefined';
48
+ }
49
+
50
+ /**
51
+ * 在 worker 中全量收集整个 DSL 的依赖,target 由 worker 根据 DSL 重建
52
+ * @returns 收集结果,返回 null 表示 worker 不可用或执行失败
53
+ */
54
+ public collectDsl(dsl: MApp): Promise<DepsData | null> {
55
+ return this.request((id) => ({ id, dsl: serialize(dsl) })).then((response) => response?.deps || null);
56
+ }
57
+
58
+ /**
59
+ * 在 worker 中增量收集指定节点的依赖,target 以可序列化描述的形式传入
60
+ * @returns 收集结果,返回 null 表示 worker 不可用或执行失败,调用方需要回退到主线程收集
61
+ */
62
+ public collect(payload: CollectWorkerPayload): Promise<CollectWorkerResult | null> {
63
+ return this.request((id) => ({ id, payload: serialize(payload) })).then((response) =>
64
+ response ? { deps: response.deps || {}, nodeIds: response.nodeIds || [] } : null,
65
+ );
66
+ }
67
+
68
+ public terminate() {
69
+ this.worker?.terminate();
70
+ this.worker = null;
71
+ this.settleAll();
72
+ }
73
+
74
+ /**
75
+ * 丢弃在途请求,但保留常驻 worker
76
+ *
77
+ * clearIdleTasks / reset 中断收集时调用。这里不能销毁 worker:worker 重建要重新加载并启动整份收集逻辑,
78
+ * 而 clearIdleTasks / reset 触发频繁(root 更新、数据源变更、历史回滚等),一旦下一次 abort 落在
79
+ * 上一个 worker 还没启动完的窗口内,就会陷入「加载中被销毁 → 重建 → 又被销毁」的循环,
80
+ * 表现为 worker 反复加载且依赖始终收集不完。
81
+ *
82
+ * 因此 abort 只做「不要这些结果」:尚未投递的请求直接丢掉,已投递的那一个标记为丢弃,
83
+ * 结果回来后忽略(不写回主线程),worker 继续复用。
84
+ */
85
+ public abort() {
86
+ const { queue } = this;
87
+ this.queue = [];
88
+
89
+ for (const task of queue) {
90
+ task.resolve(null);
91
+ }
92
+
93
+ // 已投递的请求无法从外部打断(worker 忙时收不到取消消息),只能丢弃其结果
94
+ if (this.inflight && !this.inflight.discarded) {
95
+ this.inflight.discarded = true;
96
+ this.inflight.resolve(null);
97
+ }
98
+ }
99
+
100
+ private request(createRequest: (id: number) => CollectWorkerRequest): Promise<CollectWorkerResponse | null> {
101
+ if (!this.isSupported) {
102
+ return Promise.resolve(null);
103
+ }
104
+
105
+ this.seed += 1;
106
+ const id = this.seed;
107
+
108
+ return new Promise<CollectWorkerResponse | null>((resolve) => {
109
+ this.queue.push({ id, createRequest, resolve, discarded: false });
110
+ this.flush();
111
+ });
112
+ }
113
+
114
+ /**
115
+ * 投递队首请求,worker 串行处理,因此同一时刻只投递一个,用 id 把结果分发回各自的调用方
116
+ */
117
+ private flush() {
118
+ if (this.inflight || !this.queue.length) {
119
+ return;
120
+ }
121
+
122
+ const worker = this.getWorker();
123
+
124
+ if (!worker) {
125
+ this.settleAll();
126
+ return;
127
+ }
128
+
129
+ const task = this.queue.shift()!;
130
+ this.inflight = task;
131
+
132
+ try {
133
+ // 节点配置中可能存在函数等无法结构化克隆的值,需要先序列化再传递
134
+ worker.postMessage(task.createRequest(task.id));
135
+ } catch (e) {
136
+ error('magic editor: 依赖收集 worker 通信失败', e);
137
+ this.inflight = null;
138
+
139
+ if (!task.discarded) {
140
+ task.resolve(null);
141
+ }
142
+
143
+ this.flush();
144
+ }
145
+ }
146
+
147
+ private getWorker() {
148
+ if (!this.isSupported) {
149
+ return null;
150
+ }
151
+
152
+ if (this.worker) {
153
+ return this.worker;
154
+ }
155
+
156
+ try {
157
+ const worker: Worker = new CollectWorker();
158
+ worker.onmessage = (e: MessageEvent<CollectWorkerResponse>) => {
159
+ this.handleResponse(e.data);
160
+ };
161
+ // worker 整体异常时无法定位到具体请求,只能结算全部在途请求并重建 worker
162
+ worker.onerror = () => {
163
+ this.handleFatalError();
164
+ };
165
+ worker.onmessageerror = () => {
166
+ this.handleFatalError();
167
+ };
168
+ this.worker = worker;
169
+ } catch (e) {
170
+ error('magic editor: 依赖收集 worker 创建失败', e);
171
+ return null;
172
+ }
173
+
174
+ return this.worker;
175
+ }
176
+
177
+ private handleResponse(response: CollectWorkerResponse) {
178
+ const task = this.inflight;
179
+
180
+ if (task?.id !== response?.id) {
181
+ return;
182
+ }
183
+
184
+ this.inflight = null;
185
+
186
+ // 被 abort 丢弃的请求已按 null 结算,结果不能再写回主线程
187
+ if (!task.discarded) {
188
+ task.resolve(response.failed ? null : response);
189
+ }
190
+
191
+ this.flush();
192
+ }
193
+
194
+ private handleFatalError() {
195
+ this.worker?.terminate();
196
+ this.worker = null;
197
+ this.settleAll();
198
+ }
199
+
200
+ private settleAll() {
201
+ const tasks = this.queue;
202
+ this.queue = [];
203
+
204
+ if (this.inflight) {
205
+ tasks.push(this.inflight);
206
+ this.inflight = null;
207
+ }
208
+
209
+ for (const task of tasks) {
210
+ if (!task.discarded) {
211
+ task.resolve(null);
212
+ }
213
+ }
214
+ }
215
+ }
@@ -1,5 +1,7 @@
1
1
  import { EventEmitter } from 'events';
2
2
 
3
+ import { error } from '../logger';
4
+
3
5
  export interface IdleTaskEvents {
4
6
  finish: [];
5
7
  'hight-level-finish': [];
@@ -11,6 +13,18 @@ type TaskList<T> = {
11
13
  data: T;
12
14
  }[];
13
15
 
16
+ /**
17
+ * 回调因 timeout 触发(主线程一直没有空闲)时,单次回调执行任务的时间预算,单位 ms
18
+ * 参考一帧内可让出的余量:既保证队列有进展,又不长时间阻塞主线程
19
+ */
20
+ const TIMEOUT_RUN_BUDGET = 5;
21
+
22
+ /**
23
+ * 回调因 timeout 触发时,每两次读取时钟之间执行的任务数
24
+ * 与空闲时间 <=5ms 时的批量保持一致,避免每个任务都读一次时钟
25
+ */
26
+ const TIMEOUT_BATCH_SIZE = 10;
27
+
14
28
  globalThis.requestIdleCallback =
15
29
  globalThis.requestIdleCallback ||
16
30
  function (cb) {
@@ -25,6 +39,12 @@ globalThis.requestIdleCallback =
25
39
  }, 1);
26
40
  };
27
41
 
42
+ globalThis.cancelIdleCallback =
43
+ globalThis.cancelIdleCallback ||
44
+ function (handle) {
45
+ clearTimeout(handle as unknown as ReturnType<typeof setTimeout>);
46
+ };
47
+
28
48
  export class IdleTask<T = any> extends EventEmitter {
29
49
  private taskList: TaskList<T> = [];
30
50
 
@@ -58,7 +78,7 @@ export class IdleTask<T = any> extends EventEmitter {
58
78
  this.taskHandle = null;
59
79
 
60
80
  this.emit('update-task-length', {
61
- length: this.taskList.length + this.hightLevelTaskList.length,
81
+ length: this.getTaskLength(),
62
82
  hightLevelLength: this.hightLevelTaskList.length,
63
83
  });
64
84
  }
@@ -82,51 +102,100 @@ export class IdleTask<T = any> extends EventEmitter {
82
102
  }
83
103
 
84
104
  private runTaskQueue(deadline: IdleDeadline) {
85
- const { hightLevelTaskList, taskList } = this;
86
-
87
- // 动画会占用空闲时间,当任务一直无法执行时,看看是否有动画正在播放
88
- // 根据空闲时间的多少来决定执行的任务数,保证页面不卡死的情况下尽量多执行任务,不然当任务数巨大时,执行时间会很久
89
- // 执行不完不会影响配置,但是会影响画布渲染
90
- while (deadline.timeRemaining() > 0 && (taskList.length || hightLevelTaskList.length)) {
91
- const timeRemaining = deadline.timeRemaining();
92
- let times = 0;
93
- if (timeRemaining <= 5) {
94
- times = 10;
95
- } else if (timeRemaining <= 10) {
96
- times = 100;
97
- } else if (timeRemaining <= 15) {
98
- times = 300;
99
- } else {
100
- times = 600;
105
+ // 本次回调已触发,句柄随之失效;置空后 clearTasks / enqueueTask 才能正确判断是否需要重新调度
106
+ this.taskHandle = null;
107
+
108
+ try {
109
+ // 主线程一直没有空闲时,回调只会因 timeout 触发,此时 deadline.timeRemaining() 恒为 0(规范行为)。
110
+ // 若仍以它作为循环条件,一个任务都执行不了,而 finishRun 又会接着重新调度,
111
+ // 队列就会无限空转下去:依赖收集永远不结束,collecting 卡在 true,画布也不再更新。
112
+ // 这种情况下改用自有时间预算推进,既保证有进展,又不会像放开循环那样长时间阻塞主线程。
113
+ if (deadline.didTimeout) {
114
+ const start = Date.now();
115
+ do {
116
+ this.runTaskBatch(TIMEOUT_BATCH_SIZE);
117
+ } while (this.getTaskLength() && Date.now() - start < TIMEOUT_RUN_BUDGET);
118
+
119
+ return;
101
120
  }
102
121
 
103
- for (let i = 0; i < times; i++) {
104
- const task = hightLevelTaskList.length > 0 ? hightLevelTaskList.shift() : taskList.shift();
105
- if (task) {
106
- task.handler(task.data);
122
+ // 动画会占用空闲时间,当任务一直无法执行时,看看是否有动画正在播放
123
+ // 根据空闲时间的多少来决定执行的任务数,保证页面不卡死的情况下尽量多执行任务,不然当任务数巨大时,执行时间会很久
124
+ // 执行不完不会影响配置,但是会影响画布渲染
125
+ while (deadline.timeRemaining() > 0 && this.getTaskLength()) {
126
+ const timeRemaining = deadline.timeRemaining();
127
+ let times = 0;
128
+ if (timeRemaining <= 5) {
129
+ times = 10;
130
+ } else if (timeRemaining <= 10) {
131
+ times = 100;
132
+ } else if (timeRemaining <= 15) {
133
+ times = 300;
134
+ } else {
135
+ times = 600;
107
136
  }
108
137
 
109
- if (hightLevelTaskList.length === 0 && taskList.length === 0) {
110
- break;
111
- }
138
+ this.runTaskBatch(times);
112
139
  }
140
+ } finally {
141
+ // 必须放在 finally 中:一旦这里被跳过,taskHandle 会一直是真值,
142
+ // enqueueTask 也就不会再重新调度,剩余任务与任务数将永久停在当前状态
143
+ this.finishRun();
113
144
  }
145
+ }
114
146
 
115
- if (!hightLevelTaskList.length) {
116
- this.emit('hight-level-finish');
147
+ /**
148
+ * 执行一批任务,队列被清空时提前结束
149
+ */
150
+ private runTaskBatch(times: number) {
151
+ for (let i = 0; i < times; i++) {
152
+ // 每次都从实例上取队列,任务执行过程中调用 clearTasks 能立即生效,不会继续消费已被清空的旧队列
153
+ const task = this.hightLevelTaskList.length > 0 ? this.hightLevelTaskList.shift() : this.taskList.shift();
154
+ if (task) {
155
+ this.runTask(task);
156
+ }
157
+
158
+ if (!this.getTaskLength()) {
159
+ break;
160
+ }
117
161
  }
162
+ }
163
+
164
+ /**
165
+ * 单个任务失败不能中断整个队列,否则后续任务永远不会被执行,
166
+ * 依赖收集会停在半路(收集中状态与剩余任务数都不再变化)
167
+ */
168
+ private runTask(task: TaskList<T>[number]) {
169
+ try {
170
+ task.handler(task.data);
171
+ } catch (e) {
172
+ error('magic editor: 空闲任务执行失败', e);
173
+ }
174
+ }
118
175
 
119
- if (hightLevelTaskList.length || taskList.length) {
120
- this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 300 });
121
- } else {
122
- this.taskHandle = 0;
176
+ private finishRun() {
177
+ try {
178
+ if (!this.hightLevelTaskList.length) {
179
+ this.emit('hight-level-finish');
180
+ }
181
+ } finally {
182
+ if (this.getTaskLength()) {
183
+ // 任务执行或事件监听中可能已经重新调度过
184
+ if (!this.taskHandle) {
185
+ this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 300 });
186
+ }
187
+ } else {
188
+ this.emit('finish');
189
+ }
123
190
 
124
- this.emit('finish');
191
+ this.emit('update-task-length', {
192
+ length: this.getTaskLength(),
193
+ hightLevelLength: this.hightLevelTaskList.length,
194
+ });
125
195
  }
196
+ }
126
197
 
127
- this.emit('update-task-length', {
128
- length: taskList.length + hightLevelTaskList.length,
129
- hightLevelLength: hightLevelTaskList.length,
130
- });
198
+ private getTaskLength() {
199
+ return this.taskList.length + this.hightLevelTaskList.length;
131
200
  }
132
201
  }
@@ -3,30 +3,85 @@ import {
3
3
  createDataSourceCondTarget,
4
4
  createDataSourceMethodTarget,
5
5
  createDataSourceTarget,
6
+ createTargetByDescriptor,
6
7
  type DepData,
8
+ type DepExtendedData,
7
9
  DepTargetType,
8
10
  type Id,
9
11
  type MApp,
12
+ type TargetDescriptor,
13
+ type TargetNode,
10
14
  traverseTarget,
11
15
  Watcher,
12
16
  } from '@tmagic/core';
13
17
 
14
18
  import { error } from '../logger';
15
19
 
16
- onmessage = (e) => {
17
- const watcher = new Watcher({ initialTargets: {} });
20
+ export type DepsData = Record<string, Record<Id, DepData>>;
18
21
 
19
- const { dsl } = e.data;
22
+ /** 全量收集请求:worker 中根据 DSL 重建代码块/数据源 target */
23
+ export interface DslCollectRequest {
24
+ /** 请求 id,用于把结果与调用方对应起来 */
25
+ id: number;
26
+ /** serialize-javascript 序列化后的 MApp */
27
+ dsl: string;
28
+ }
20
29
 
30
+ export interface CollectWorkerPayload {
31
+ /** 待收集的节点 */
32
+ nodes: TargetNode[];
33
+ /** 参与本次收集的 target 描述,worker 中据此重建 target */
34
+ targets: TargetDescriptor[];
35
+ depExtendedData: DepExtendedData;
36
+ deep: boolean;
37
+ }
38
+
39
+ /** 增量收集请求:target 由主线程以可序列化描述的形式传入 */
40
+ export interface NodesCollectRequest {
41
+ id: number;
42
+ /** serialize-javascript 序列化后的 CollectWorkerPayload */
43
+ payload: string;
44
+ }
45
+
46
+ export type CollectWorkerRequest = DslCollectRequest | NodesCollectRequest;
47
+
48
+ export interface CollectWorkerResponse {
49
+ id: number;
50
+ /** 收集结果,结构为 { [target.type]: { [target.id]: deps } } */
51
+ deps: DepsData;
52
+ /** 增量收集时本次覆盖到的节点 id(deep=true 时含子孙),主线程据此删除旧依赖 */
53
+ nodeIds?: Id[];
54
+ /** 收集失败,调用方需要回退到主线程收集 */
55
+ failed?: boolean;
56
+ }
57
+
58
+ /**
59
+ * 收集本次会覆盖到的节点 id
60
+ * 必须与 deep 一致:deep=false 时不能带上子孙 id,否则写回阶段 removeDep 会清掉未重收的子节点依赖
61
+ */
62
+ const getNodeIds = (nodes: TargetNode[], deep: boolean, ids: Id[] = []) => {
63
+ for (const node of nodes) {
64
+ ids.push(node.id);
65
+
66
+ if (deep && Array.isArray(node.items) && node.items.length) {
67
+ getNodeIds(node.items, deep, ids);
68
+ }
69
+ }
70
+
71
+ return ids;
72
+ };
73
+
74
+ const collectDsl = ({ id, dsl }: DslCollectRequest) => {
21
75
  try {
22
76
  // eslint-disable-next-line no-eval
23
77
  const mApp: MApp = eval(`(${dsl})`);
24
78
 
25
79
  if (!mApp) {
26
- postMessage({});
80
+ postMessage({ id, deps: {} });
81
+ return;
27
82
  }
28
83
 
29
- watcher.clearTargets();
84
+ const watcher = new Watcher({ initialTargets: {} });
30
85
 
31
86
  if (mApp.codeBlocks) {
32
87
  for (const [id, code] of Object.entries(mApp.codeBlocks)) {
@@ -48,7 +103,7 @@ onmessage = (e) => {
48
103
  watcher.collectItems(page, targets, { pageId: page.id }, true);
49
104
  }
50
105
 
51
- const data: Record<string, Record<Id, DepData>> = {
106
+ const deps: DepsData = {
52
107
  [DepTargetType.DATA_SOURCE]: {},
53
108
  [DepTargetType.DATA_SOURCE_METHOD]: {},
54
109
  [DepTargetType.DATA_SOURCE_COND]: {},
@@ -56,12 +111,61 @@ onmessage = (e) => {
56
111
  };
57
112
 
58
113
  traverseTarget(watcher.getTargetsList(), (target) => {
59
- data[target.type][target.id] = target.deps;
114
+ deps[target.type][target.id] = target.deps;
60
115
  });
61
116
 
62
- postMessage(data);
117
+ const response: CollectWorkerResponse = { id, deps };
118
+ postMessage(response);
63
119
  } catch (e: any) {
64
120
  error(e);
65
- postMessage({});
121
+ const response: CollectWorkerResponse = { id, deps: {}, failed: true };
122
+ postMessage(response);
66
123
  }
67
124
  };
125
+
126
+ const collectNodes = ({ id, payload }: NodesCollectRequest) => {
127
+ try {
128
+ // eslint-disable-next-line no-eval
129
+ const { nodes, targets: descriptors, depExtendedData, deep }: CollectWorkerPayload = eval(`(${payload})`);
130
+
131
+ const watcher = new Watcher({ initialTargets: {} });
132
+ // worker 中 target 均为新建(deps 为空),无需删除阶段,直接批量收集
133
+ const targets = descriptors.map((descriptor) => createTargetByDescriptor(descriptor));
134
+
135
+ for (const node of nodes) {
136
+ watcher.collectItems(node, targets, depExtendedData, deep);
137
+ }
138
+
139
+ const deps: DepsData = {};
140
+ for (const target of targets) {
141
+ if (!deps[target.type]) {
142
+ deps[target.type] = {};
143
+ }
144
+ deps[target.type][target.id] = target.deps;
145
+ }
146
+
147
+ const response: CollectWorkerResponse = { id, deps, nodeIds: getNodeIds(nodes, deep) };
148
+ postMessage(response);
149
+ } catch (e: any) {
150
+ error(e);
151
+ const response: CollectWorkerResponse = { id, deps: {}, nodeIds: [], failed: true };
152
+ postMessage(response);
153
+ }
154
+ };
155
+
156
+ /**
157
+ * 依赖收集 worker
158
+ *
159
+ * 依赖收集需要深度遍历整棵节点树并对每个属性做 target 匹配,节点/数据源多时会长时间占用主线程导致页面卡死,
160
+ * 因此把遍历匹配放到 worker 中执行。
161
+ * 全量与增量收集共用一个常驻 worker:收集逻辑只打进一份 inline worker 产物,
162
+ * 也不会因为频繁收集而反复创建线程,每个请求用 id 与调用方对应。
163
+ */
164
+ onmessage = (e: MessageEvent<CollectWorkerRequest>) => {
165
+ if ('dsl' in e.data) {
166
+ collectDsl(e.data);
167
+ return;
168
+ }
169
+
170
+ collectNodes(e.data);
171
+ };