@tmagic/editor 1.5.0-beta.14 → 1.5.0-beta.15

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 (42) hide show
  1. package/dist/style.css +61 -6
  2. package/dist/tmagic-editor.js +521 -242
  3. package/dist/tmagic-editor.umd.cjs +522 -243
  4. package/package.json +12 -10
  5. package/src/components/CodeBlockEditor.vue +11 -5
  6. package/src/components/CodeParams.vue +3 -3
  7. package/src/fields/Code.vue +1 -2
  8. package/src/fields/CodeSelect.vue +13 -11
  9. package/src/fields/CodeSelectCol.vue +32 -5
  10. package/src/fields/CondOpSelect.vue +5 -2
  11. package/src/fields/DataSourceFields.vue +31 -12
  12. package/src/fields/DataSourceMethods.vue +72 -14
  13. package/src/fields/DisplayConds.vue +8 -3
  14. package/src/fields/EventSelect.vue +25 -8
  15. package/src/fields/KeyValue.vue +15 -10
  16. package/src/hooks/index.ts +0 -1
  17. package/src/hooks/use-code-block-edit.ts +1 -1
  18. package/src/hooks/use-data-source-edit.ts +3 -2
  19. package/src/initService.ts +103 -24
  20. package/src/layouts/Framework.vue +0 -3
  21. package/src/layouts/PropsPanel.vue +3 -3
  22. package/src/layouts/page-bar/PageBar.vue +46 -3
  23. package/src/layouts/page-bar/PageBarScrollContainer.vue +49 -24
  24. package/src/layouts/page-bar/PageList.vue +4 -2
  25. package/src/layouts/sidebar/Sidebar.vue +3 -0
  26. package/src/layouts/sidebar/code-block/CodeBlockList.vue +6 -1
  27. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +7 -5
  28. package/src/layouts/sidebar/data-source/DataSourceList.vue +6 -1
  29. package/src/layouts/workspace/Workspace.vue +5 -2
  30. package/src/layouts/workspace/viewer/Stage.vue +12 -5
  31. package/src/services/dataSource.ts +12 -5
  32. package/src/services/dep.ts +49 -11
  33. package/src/services/editor.ts +26 -11
  34. package/src/theme/page-bar.scss +24 -9
  35. package/src/theme/search-input.scss +1 -0
  36. package/src/utils/data-source/formConfigs/http.ts +2 -0
  37. package/src/utils/data-source/index.ts +2 -2
  38. package/src/utils/editor.ts +94 -2
  39. package/src/utils/idle-task.ts +34 -3
  40. package/src/utils/props.ts +3 -3
  41. package/types/index.d.ts +803 -1169
  42. package/src/hooks/use-data-source-method.ts +0 -100
@@ -4,7 +4,7 @@ import type { Writable } from 'type-fest';
4
4
 
5
5
  import type { DataSourceSchema, EventOption, Id, MNode, TargetOptions } from '@tmagic/core';
6
6
  import { Target, Watcher } from '@tmagic/core';
7
- import type { FormConfig } from '@tmagic/form';
7
+ import type { ChangeRecord, FormConfig } from '@tmagic/form';
8
8
  import { guid, toLine } from '@tmagic/utils';
9
9
 
10
10
  import editorService from '@editor/services/editor';
@@ -115,15 +115,22 @@ class DataSource extends BaseService {
115
115
  return newConfig;
116
116
  }
117
117
 
118
- public update(config: DataSourceSchema) {
118
+ public update(config: DataSourceSchema, { changeRecords = [] }: { changeRecords?: ChangeRecord[] } = {}) {
119
119
  const dataSources = this.get('dataSources');
120
120
 
121
121
  const index = dataSources.findIndex((ds) => ds.id === config.id);
122
- dataSources[index] = cloneDeep(config);
123
122
 
124
- this.emit('update', config);
123
+ const oldConfig = dataSources[index];
124
+ const newConfig = cloneDeep(config);
125
125
 
126
- return config;
126
+ dataSources[index] = newConfig;
127
+
128
+ this.emit('update', newConfig, {
129
+ oldConfig,
130
+ changeRecords,
131
+ });
132
+
133
+ return newConfig;
127
134
  }
128
135
 
129
136
  public remove(id: string) {
@@ -31,11 +31,29 @@ export interface DepEvents {
31
31
  collected: [nodes: MNode[], deep: boolean];
32
32
  }
33
33
 
34
+ interface State {
35
+ collecting: boolean;
36
+ }
37
+
38
+ type StateKey = keyof State;
39
+
34
40
  const idleTask = new IdleTask<{ node: TargetNode; deep: boolean; target: Target }>();
35
41
 
36
42
  class Dep extends BaseService {
43
+ private state = reactive<State>({
44
+ collecting: false,
45
+ });
46
+
37
47
  private watcher = new Watcher({ initialTargets: reactive({}) });
38
48
 
49
+ public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
50
+ this.state[name] = value;
51
+ }
52
+
53
+ public get<K extends StateKey>(name: K): State[K] {
54
+ return this.state[name];
55
+ }
56
+
39
57
  public removeTargets(type: string = DepTargetType.DEFAULT) {
40
58
  this.watcher.removeTargets(type);
41
59
 
@@ -71,43 +89,40 @@ class Dep extends BaseService {
71
89
  }
72
90
 
73
91
  public collect(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
92
+ this.set('collecting', true);
74
93
  this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
75
94
  this.collectNode(node, target, depExtendedData, deep);
76
95
  });
96
+ this.set('collecting', false);
77
97
 
78
98
  this.emit('collected', nodes, deep);
79
99
  }
80
100
 
81
101
  public collectIdle(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
102
+ this.set('collecting', true);
82
103
  let startTask = false;
83
104
  this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
84
105
  startTask = true;
85
- idleTask.enqueueTask(
86
- ({ node, deep, target }) => {
87
- this.collectNode(node, target, depExtendedData, deep);
88
- },
89
- {
90
- node,
91
- deep,
92
- target,
93
- },
94
- );
106
+
107
+ this.enqueueTask(node, target, depExtendedData, deep);
95
108
  });
96
109
 
97
110
  return new Promise<void>((resolve) => {
98
111
  if (!startTask) {
99
112
  this.emit('collected', nodes, deep);
113
+ this.set('collecting', false);
100
114
  resolve();
101
115
  return;
102
116
  }
103
117
  idleTask.once('finish', () => {
104
118
  this.emit('collected', nodes, deep);
119
+ this.set('collecting', false);
105
120
  resolve();
106
121
  });
107
122
  });
108
123
  }
109
124
 
110
- collectNode(node: MNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
125
+ public collectNode(node: MNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
111
126
  // 先删除原有依赖,重新收集
112
127
  if (isPage(node)) {
113
128
  Object.entries(target.deps).forEach(([depKey, dep]) => {
@@ -138,6 +153,10 @@ class Dep extends BaseService {
138
153
  return this.watcher.hasSpecifiedTypeTarget(type);
139
154
  }
140
155
 
156
+ public clearIdleTasks() {
157
+ idleTask.clearTasks();
158
+ }
159
+
141
160
  public on<Name extends keyof DepEvents, Param extends DepEvents[Name]>(
142
161
  eventName: Name,
143
162
  listener: (...args: Param) => void | Promise<void>,
@@ -155,6 +174,25 @@ class Dep extends BaseService {
155
174
  public emit<Name extends keyof DepEvents, Param extends DepEvents[Name]>(eventName: Name, ...args: Param) {
156
175
  return super.emit(eventName, ...args);
157
176
  }
177
+
178
+ private enqueueTask(node: MNode, target: Target, depExtendedData: DepExtendedData, deep: boolean) {
179
+ idleTask.enqueueTask(
180
+ ({ node, deep, target }) => {
181
+ this.collectNode(node, target, depExtendedData, deep);
182
+ },
183
+ {
184
+ node,
185
+ deep: false,
186
+ target,
187
+ },
188
+ );
189
+
190
+ if (deep && Array.isArray(node.items) && node.items.length) {
191
+ node.items.forEach((item) => {
192
+ this.enqueueTask(item, target, depExtendedData, deep);
193
+ });
194
+ }
195
+ }
158
196
  }
159
197
 
160
198
  export type DepService = Dep;
@@ -22,6 +22,7 @@ import type { Writable } from 'type-fest';
22
22
 
23
23
  import type { Id, MApp, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
24
24
  import { NodeType, Target, Watcher } from '@tmagic/core';
25
+ import type { ChangeRecord } from '@tmagic/form';
25
26
  import { isFixed } from '@tmagic/stage';
26
27
  import {
27
28
  calcValueByFontsize,
@@ -68,7 +69,7 @@ export interface EditorEvents {
68
69
  select: [node: MNode | null];
69
70
  add: [nodes: MNode[]];
70
71
  remove: [nodes: MNode[]];
71
- update: [nodes: MNode[]];
72
+ update: [nodes: { newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }[]];
72
73
  'move-layer': [offset: number | LayerOffset];
73
74
  'drag-to': [data: { targetIndex: number; configs: MNode | MNode[]; targetParent: MContainer }];
74
75
  'history-change': [data: MPage | MPageFragment];
@@ -509,7 +510,10 @@ class Editor extends BaseService {
509
510
  this.emit('remove', nodes);
510
511
  }
511
512
 
512
- public async doUpdate(config: MNode) {
513
+ public async doUpdate(
514
+ config: MNode,
515
+ { changeRecords = [] }: { changeRecords?: ChangeRecord[] } = {},
516
+ ): Promise<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }> {
513
517
  const root = this.get('root');
514
518
  if (!root) throw new Error('root为空');
515
519
 
@@ -519,7 +523,7 @@ class Editor extends BaseService {
519
523
 
520
524
  if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
521
525
 
522
- const node = cloneDeep(toRaw(info.node));
526
+ const node = toRaw(info.node);
523
527
 
524
528
  let newConfig = await this.toggleFixedPosition(toRaw(config), node, root);
525
529
 
@@ -541,7 +545,11 @@ class Editor extends BaseService {
541
545
 
542
546
  if (newConfig.type === NodeType.ROOT) {
543
547
  this.set('root', newConfig as MApp);
544
- return newConfig;
548
+ return {
549
+ oldNode: node,
550
+ newNode: newConfig,
551
+ changeRecords,
552
+ };
545
553
  }
546
554
 
547
555
  const { parent } = info;
@@ -574,7 +582,11 @@ class Editor extends BaseService {
574
582
 
575
583
  this.addModifiedNodeId(newConfig.id);
576
584
 
577
- return newConfig;
585
+ return {
586
+ oldNode: node,
587
+ newNode: newConfig,
588
+ changeRecords,
589
+ };
578
590
  }
579
591
 
580
592
  /**
@@ -582,17 +594,20 @@ class Editor extends BaseService {
582
594
  * @param config 新的节点配置,配置中需要有id信息
583
595
  * @returns 更新后的节点配置
584
596
  */
585
- public async update(config: MNode | MNode[]): Promise<MNode | MNode[]> {
597
+ public async update(
598
+ config: MNode | MNode[],
599
+ data: { changeRecords?: ChangeRecord[] } = {},
600
+ ): Promise<MNode | MNode[]> {
586
601
  const nodes = Array.isArray(config) ? config : [config];
587
602
 
588
- const newNodes = await Promise.all(nodes.map((node) => this.doUpdate(node)));
603
+ const updateData = await Promise.all(nodes.map((node) => this.doUpdate(node, data)));
589
604
 
590
- if (newNodes[0]?.type !== NodeType.ROOT) {
605
+ if (updateData[0].oldNode?.type !== NodeType.ROOT) {
591
606
  this.pushHistoryState();
592
607
  }
593
608
 
594
- this.emit('update', newNodes);
595
- return Array.isArray(config) ? newNodes : newNodes[0];
609
+ this.emit('update', updateData);
610
+ return Array.isArray(config) ? updateData.map((item) => item.newNode) : updateData[0].newNode;
596
611
  }
597
612
 
598
613
  /**
@@ -839,7 +854,7 @@ class Editor extends BaseService {
839
854
 
840
855
  const layout = await this.getLayout(target);
841
856
 
842
- const newConfig = mergeWith(cloneDeep(node), config, (objValue, srcValue) => {
857
+ const newConfig = mergeWith(cloneDeep(node), config, (_objValue, srcValue) => {
843
858
  if (Array.isArray(srcValue)) {
844
859
  return srcValue;
845
860
  }
@@ -6,13 +6,6 @@
6
6
  user-select: none;
7
7
  }
8
8
 
9
- .m-editor-page-bar-item-icon {
10
- .icon-active {
11
- font-weight: bold;
12
- color: $--theme-color;
13
- }
14
- }
15
-
16
9
  .m-editor-page-list-item {
17
10
  display: flex;
18
11
  width: 100%;
@@ -64,6 +57,11 @@
64
57
  &-icon {
65
58
  position: relative;
66
59
  z-index: 1;
60
+
61
+ .icon-active {
62
+ font-weight: bold;
63
+ color: $--theme-color;
64
+ }
67
65
  }
68
66
 
69
67
  &-title {
@@ -72,6 +70,18 @@
72
70
  white-space: nowrap;
73
71
  overflow: hidden;
74
72
  }
73
+
74
+ &-left-icon,
75
+ &-right-icon {
76
+ position: absolute;
77
+ right: 0;
78
+ top: 0;
79
+ height: 100%;
80
+ }
81
+
82
+ &-left-icon {
83
+ right: 34px;
84
+ }
75
85
  }
76
86
  }
77
87
 
@@ -85,14 +95,19 @@
85
95
  transition: all 0.2s ease 0s;
86
96
  padding: 5px 14px;
87
97
 
88
- .el-button--text,
89
- i {
98
+ .tmagic-design-button {
90
99
  color: $--font-color;
91
100
  }
92
101
 
93
102
  &:hover {
94
103
  background-color: $--hover-color;
95
104
  }
105
+
106
+ &.active {
107
+ .tmagic-design-button {
108
+ color: $--theme-color;
109
+ }
110
+ }
96
111
  }
97
112
  }
98
113
 
@@ -7,6 +7,7 @@
7
7
  left: 0;
8
8
  box-sizing: border-box;
9
9
  z-index: 1;
10
+ background: transparent;
10
11
 
11
12
  .el-input__prefix {
12
13
  padding: 7px;
@@ -40,6 +40,7 @@ export default [
40
40
  name: 'params',
41
41
  type: 'key-value',
42
42
  defaultValue: {},
43
+ advanced: true,
43
44
  text: '参数',
44
45
  },
45
46
  {
@@ -53,6 +54,7 @@ export default [
53
54
  name: 'headers',
54
55
  type: 'key-value',
55
56
  defaultValue: {},
57
+ advanced: true,
56
58
  text: '请求头',
57
59
  },
58
60
  ],
@@ -58,7 +58,7 @@ const fillConfig = (config: FormConfig): FormConfig => [
58
58
  },
59
59
  {
60
60
  title: '请求参数裁剪',
61
- display: (formState: FormState, { model }: any) => model.type === 'http',
61
+ display: (_formState: FormState, { model }: any) => model.type === 'http',
62
62
  items: [
63
63
  {
64
64
  name: 'beforeRequest',
@@ -70,7 +70,7 @@ const fillConfig = (config: FormConfig): FormConfig => [
70
70
  },
71
71
  {
72
72
  title: '响应数据裁剪',
73
- display: (formState: FormState, { model }: any) => model.type === 'http',
73
+ display: (_formState: FormState, { model }: any) => model.type === 'http',
74
74
  items: [
75
75
  {
76
76
  name: 'afterResponse',
@@ -16,12 +16,23 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
+ import { detailedDiff } from 'deep-object-diff';
20
+ import { isObject } from 'lodash-es';
19
21
  import serialize from 'serialize-javascript';
20
22
 
21
23
  import type { Id, MApp, MContainer, MNode, MPage, MPageFragment } from '@tmagic/core';
22
- import { NodeType } from '@tmagic/core';
24
+ import { NODE_CONDS_KEY, NodeType } from '@tmagic/core';
23
25
  import type StageCore from '@tmagic/stage';
24
- import { calcValueByFontsize, getElById, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
26
+ import {
27
+ calcValueByFontsize,
28
+ DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
29
+ getElById,
30
+ getNodePath,
31
+ isNumber,
32
+ isPage,
33
+ isPageFragment,
34
+ isPop,
35
+ } from '@tmagic/utils';
25
36
 
26
37
  import { Layout } from '@editor/type';
27
38
 
@@ -290,3 +301,84 @@ export const moveItemsInContainer = (sourceIndices: number[], parent: MContainer
290
301
  }
291
302
  }
292
303
  };
304
+
305
+ export const isValueIncludeDataSource = (value: any) => {
306
+ if (typeof value === 'string' && /\$\{([\s\S]+?)\}/.test(value)) {
307
+ return true;
308
+ }
309
+ if (Array.isArray(value) && `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX)) {
310
+ return true;
311
+ }
312
+ if (value?.isBindDataSource && value.dataSourceId) {
313
+ return true;
314
+ }
315
+ if (value?.isBindDataSourceField && value.dataSourceId) {
316
+ return true;
317
+ }
318
+ return false;
319
+ };
320
+
321
+ const isIncludeDataSourceByDiffAddResult = (diffResult: any) => {
322
+ for (const value of Object.values(diffResult)) {
323
+ const result = isValueIncludeDataSource(value);
324
+
325
+ if (result) {
326
+ return true;
327
+ }
328
+
329
+ if (isObject(value)) {
330
+ return isIncludeDataSourceByDiffAddResult(value);
331
+ }
332
+ }
333
+ return false;
334
+ };
335
+
336
+ const isIncludeDataSourceByDiffUpdatedResult = (diffResult: any, oldNode: any) => {
337
+ for (const [key, value] of Object.entries<any>(diffResult)) {
338
+ if (isValueIncludeDataSource(value)) {
339
+ return true;
340
+ }
341
+
342
+ if (isValueIncludeDataSource(oldNode[key])) {
343
+ return true;
344
+ }
345
+
346
+ if (isObject(value)) {
347
+ return isIncludeDataSourceByDiffUpdatedResult(value, oldNode[key]);
348
+ }
349
+ }
350
+ return false;
351
+ };
352
+
353
+ export const isIncludeDataSource = (node: MNode, oldNode: MNode) => {
354
+ const diffResult = detailedDiff(oldNode, node);
355
+
356
+ let isIncludeDataSource = false;
357
+
358
+ if (diffResult.updated) {
359
+ // 修改了显示条件
360
+ if ((diffResult.updated as any)[NODE_CONDS_KEY]) {
361
+ return true;
362
+ }
363
+
364
+ isIncludeDataSource = isIncludeDataSourceByDiffUpdatedResult(diffResult.updated, oldNode);
365
+ if (isIncludeDataSource) return true;
366
+ }
367
+
368
+ if (diffResult.added) {
369
+ isIncludeDataSource = isIncludeDataSourceByDiffAddResult(diffResult.added);
370
+ if (isIncludeDataSource) return true;
371
+ }
372
+
373
+ if (diffResult.deleted) {
374
+ // 删除了显示条件
375
+ if ((diffResult.deleted as any)[NODE_CONDS_KEY]) {
376
+ return true;
377
+ }
378
+
379
+ isIncludeDataSource = isIncludeDataSourceByDiffAddResult(diffResult.deleted);
380
+ if (isIncludeDataSource) return true;
381
+ }
382
+
383
+ return isIncludeDataSource;
384
+ };
@@ -26,6 +26,11 @@ export class IdleTask<T = any> extends EventEmitter {
26
26
 
27
27
  private taskHandle: number | null = null;
28
28
 
29
+ constructor() {
30
+ super();
31
+ this.setMaxListeners(1000);
32
+ }
33
+
29
34
  public enqueueTask(taskHandler: (data: T) => void, taskData: T) {
30
35
  this.taskList.push({
31
36
  handler: taskHandler,
@@ -37,6 +42,10 @@ export class IdleTask<T = any> extends EventEmitter {
37
42
  }
38
43
  }
39
44
 
45
+ public clearTasks() {
46
+ this.taskList = [];
47
+ }
48
+
40
49
  public on<Name extends keyof IdleTaskEvents, Param extends IdleTaskEvents[Name]>(
41
50
  eventName: Name,
42
51
  listener: (...args: Param) => void | Promise<void>,
@@ -57,9 +66,31 @@ export class IdleTask<T = any> extends EventEmitter {
57
66
 
58
67
  private runTaskQueue(deadline: IdleDeadline) {
59
68
  // 动画会占用空闲时间,当任务一直无法执行时,看看是否有动画正在播放
60
- while ((deadline.timeRemaining() > 10 || deadline.didTimeout) && this.taskList.length) {
61
- const task = this.taskList.shift();
62
- task!.handler(task!.data);
69
+ // 根据空闲时间的多少来决定执行的任务数,保证页面不卡死的情况下尽量多执行任务,不然当任务数巨大时,执行时间会很久
70
+ // 执行不完不会影响配置,但是会影响画布渲染
71
+ while (deadline.timeRemaining() > 0 && this.taskList.length) {
72
+ const timeRemaining = deadline.timeRemaining();
73
+ let times = 0;
74
+ if (timeRemaining <= 5) {
75
+ times = 10;
76
+ } else if (timeRemaining <= 10) {
77
+ times = 100;
78
+ } else if (timeRemaining <= 15) {
79
+ times = 300;
80
+ } else {
81
+ times = 600;
82
+ }
83
+
84
+ for (let i = 0; i < times; i++) {
85
+ const task = this.taskList.shift();
86
+ if (task) {
87
+ task.handler(task.data);
88
+ }
89
+
90
+ if (this.taskList.length === 0) {
91
+ break;
92
+ }
93
+ }
63
94
  }
64
95
 
65
96
  if (this.taskList.length) {
@@ -81,7 +81,7 @@ export const styleTabConfig: TabPaneConfig = {
81
81
  fieldConfig: {
82
82
  type: 'text',
83
83
  },
84
- disabled: (vm: FormState, { model }: any) =>
84
+ disabled: (_vm: FormState, { model }: any) =>
85
85
  model.position === 'fixed' && model._magic_position === 'fixedBottom',
86
86
  },
87
87
  {
@@ -103,7 +103,7 @@ export const styleTabConfig: TabPaneConfig = {
103
103
  fieldConfig: {
104
104
  type: 'text',
105
105
  },
106
- disabled: (vm: FormState, { model }: any) =>
106
+ disabled: (_vm: FormState, { model }: any) =>
107
107
  model.position === 'fixed' && model._magic_position === 'fixedTop',
108
108
  },
109
109
  ],
@@ -356,7 +356,7 @@ export const advancedTabConfig: TabPaneConfig = {
356
356
 
357
357
  export const displayTabConfig: TabPaneConfig = {
358
358
  title: '显示条件',
359
- display: (vm: FormState, { model }: any) => model.type !== 'page',
359
+ display: (_vm: FormState, { model }: any) => model.type !== 'page',
360
360
  items: [
361
361
  {
362
362
  type: 'display-conds',