@bpmn-nova/studio 0.3.0-preview → 0.3.2-preview
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.
- package/README.md +245 -20
- package/dist/canvas.js +7 -4
- package/dist/context-menu.js +2 -2
- package/dist/controller.js +84 -6
- package/dist/index.d.ts +138 -38
- package/dist/index.js +12 -11
- package/dist/interactions.js +1 -1
- package/dist/modules/bpmn-model/index.d.ts +11 -0
- package/dist/modules/bpmn-model/index.js +703 -0
- package/dist/modules/core/containment.js +590 -0
- package/dist/modules/core/gateway.js +72 -0
- package/dist/modules/core/history.js +32 -0
- package/dist/modules/core/index.d.ts +268 -0
- package/dist/modules/core/index.js +7 -0
- package/dist/modules/core/layout.js +644 -0
- package/dist/modules/core/model.js +287 -0
- package/dist/modules/core/runtime-transition-route.js +360 -0
- package/dist/modules/core/scope.js +99 -0
- package/dist/modules/designer/index.d.ts +79 -0
- package/dist/modules/designer/index.js +607 -0
- package/dist/modules/engine-activiti/index.d.ts +19 -0
- package/dist/modules/engine-activiti/index.js +160 -0
- package/dist/modules/engine-flowable/index.d.ts +19 -0
- package/dist/modules/engine-flowable/index.js +160 -0
- package/dist/modules/export-svg/index.d.ts +112 -0
- package/dist/modules/export-svg/index.js +2 -0
- package/dist/modules/export-svg/preview.js +327 -0
- package/dist/modules/export-svg/render.js +718 -0
- package/dist/modules/icons/index.d.ts +24 -0
- package/dist/modules/icons/index.js +264 -0
- package/dist/modules/palette/index.d.ts +74 -0
- package/dist/modules/palette/index.js +99 -0
- package/dist/modules/palette/panel.js +99 -0
- package/dist/modules/properties/index.d.ts +20 -0
- package/dist/modules/properties/index.js +19 -0
- package/dist/modules/properties-activiti/index.d.ts +3 -0
- package/dist/modules/properties-activiti/index.js +97 -0
- package/dist/modules/properties-bpmn/index.d.ts +3 -0
- package/dist/modules/properties-bpmn/index.js +518 -0
- package/dist/modules/properties-core/index.d.ts +124 -0
- package/dist/modules/properties-core/index.js +312 -0
- package/dist/modules/properties-flowable/index.d.ts +3 -0
- package/dist/modules/properties-flowable/index.js +114 -0
- package/dist/modules/properties-renderer/index.d.ts +25 -0
- package/dist/modules/properties-renderer/index.js +491 -0
- package/dist/modules/renderer-svg/index.d.ts +118 -0
- package/dist/modules/renderer-svg/index.js +1460 -0
- package/dist/modules/runtime/index.d.ts +169 -0
- package/dist/modules/runtime/index.js +535 -0
- package/dist/modules/theme/index.d.ts +95 -0
- package/dist/modules/theme/index.js +368 -0
- package/dist/modules/viewer/index.d.ts +265 -0
- package/dist/modules/viewer/index.js +1011 -0
- package/dist/modules/viewer/runtime-content.js +123 -0
- package/dist/modules/viewer/runtime-details-motion.js +228 -0
- package/dist/modules/viewer/runtime-trace.js +574 -0
- package/dist/modules/viewer/timeline.js +276 -0
- package/dist/selection-layout.js +1 -1
- package/dist/shell.js +210 -26
- package/dist/styles.css +116 -7
- package/llms-full.txt +3082 -0
- package/llms.txt +225 -0
- package/package.json +39 -16
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { NODE_DEFINITIONS, resolveGatewayRole, resolveSwimlaneLabelPlacement } from '../core/index.js';
|
|
2
|
+
import { propertyEntry, propertyGroup } from '../properties-core/index.js';
|
|
3
|
+
|
|
4
|
+
const activityKinds = new Set(['task', 'container']);
|
|
5
|
+
const eventRefs = {
|
|
6
|
+
message: { resource: 'messages', label: '消息 Message' },
|
|
7
|
+
signal: { resource: 'signals', label: '信号 Signal' },
|
|
8
|
+
error: { resource: 'errors', label: '错误 Error' },
|
|
9
|
+
escalation: { resource: 'escalations', label: '升级 Escalation' },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function nodeTypeOptions(context) {
|
|
13
|
+
const currentKind = context.definition?.kind;
|
|
14
|
+
const compatible = currentKind === 'event' || currentKind === 'boundary'
|
|
15
|
+
? ['event', 'boundary']
|
|
16
|
+
: currentKind === 'task' || currentKind === 'container'
|
|
17
|
+
? ['task', 'container']
|
|
18
|
+
: [currentKind];
|
|
19
|
+
return Object.entries(NODE_DEFINITIONS)
|
|
20
|
+
.filter(([type, def]) => type !== 'generic'
|
|
21
|
+
&& compatible.includes(def.kind)
|
|
22
|
+
&& context.studio?.allowsNodeType?.(type) !== false)
|
|
23
|
+
.map(([value, def]) => ({ value, label: def.label }));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function idValidator(value, context) {
|
|
27
|
+
if (!value) return 'ID 不能为空';
|
|
28
|
+
if (!/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value)) return 'ID 只能包含字母、数字、_、-、.,且不能以数字开头';
|
|
29
|
+
const model = context.model;
|
|
30
|
+
const processConflict = context.kind !== 'process' && model.id === value;
|
|
31
|
+
const nodeConflict = model.nodes.some((item) => item.id === value && !(context.kind === 'node' && item === context.element));
|
|
32
|
+
const edgeConflict = model.edges.some((item) => item.id === value && !(context.kind === 'edge' && item === context.element));
|
|
33
|
+
if (processConflict || nodeConflict || edgeConflict) return 'ID 已存在;BPMN Element ID 必须在 Definitions 内唯一';
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const basicProvider = {
|
|
38
|
+
id: 'bpmn-basic',
|
|
39
|
+
priority: 1000,
|
|
40
|
+
getGroups(context) {
|
|
41
|
+
if (context.kind === 'process') {
|
|
42
|
+
return [propertyGroup({
|
|
43
|
+
id: 'basic', label: '基本信息', collapsible: false,
|
|
44
|
+
entries: [
|
|
45
|
+
propertyEntry({ id: 'process-name', label: '流程名称', path: 'name', required: true }),
|
|
46
|
+
propertyEntry({ id: 'process-id', label: 'Process ID', path: 'id', required: true, validate: idValidator, note: '部署到流程引擎时作为 processDefinitionKey。', level: 'developer' }),
|
|
47
|
+
propertyEntry({ id: 'namespace', label: 'Target Namespace', path: 'targetNamespace', placeholder: 'urn:company:workflow', level: 'developer' }),
|
|
48
|
+
propertyEntry({ id: 'executable', type: 'switch', label: '可执行流程', path: 'isExecutable' }),
|
|
49
|
+
propertyEntry({
|
|
50
|
+
id: 'engine', type: 'select', label: '流程引擎',
|
|
51
|
+
options: [{ value: 'flowable', label: 'Flowable' }, { value: 'activiti', label: 'Activiti' }],
|
|
52
|
+
getValue: (ctx) => ctx.model.engine,
|
|
53
|
+
setValue: (ctx, value) => ctx.studio.setEngine(value),
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
})];
|
|
57
|
+
}
|
|
58
|
+
if (context.kind === 'edge') {
|
|
59
|
+
return [propertyGroup({
|
|
60
|
+
id: 'basic', label: context.element.type === 'sequenceFlow' ? '分支定义' : '连线信息', collapsible: false,
|
|
61
|
+
entries: [
|
|
62
|
+
propertyEntry({ id: 'edge-name', label: '名称', path: 'name', placeholder: '例如:同意 / 驳回 / 金额 ≥ 5万' }),
|
|
63
|
+
propertyEntry({ id: 'edge-id', label: 'Flow ID', path: 'id', required: true, validate: idValidator }),
|
|
64
|
+
propertyEntry({ id: 'edge-type', type: 'readonly', label: 'BPMN Type', getValue: (ctx) => ctx.element.type === 'messageFlow' ? 'bpmn:MessageFlow' : ctx.element.type === 'association' ? 'bpmn:Association' : 'bpmn:SequenceFlow' }),
|
|
65
|
+
],
|
|
66
|
+
})];
|
|
67
|
+
}
|
|
68
|
+
const def = context.definition;
|
|
69
|
+
return [propertyGroup({
|
|
70
|
+
id: 'basic', label: '基本信息', collapsible: false,
|
|
71
|
+
entries: [
|
|
72
|
+
propertyEntry({ id: 'name', label: '名称', path: 'name', required: true }),
|
|
73
|
+
propertyEntry({ id: 'id', label: 'Element ID', path: 'id', required: true, validate: idValidator, level: 'developer' }),
|
|
74
|
+
propertyEntry({
|
|
75
|
+
id: 'node-type', type: 'select', label: '节点类型', options: nodeTypeOptions,
|
|
76
|
+
getValue: (ctx) => ctx.element.type,
|
|
77
|
+
setValue: (ctx, value) => ctx.designer.changeNodeType(ctx.element.id, value),
|
|
78
|
+
}),
|
|
79
|
+
propertyEntry({ id: 'bpmn-type', type: 'readonly', label: 'BPMN Type', getValue: (ctx) => ctx.definition?.bpmnType || ctx.element.bpmnType, level: 'developer' }),
|
|
80
|
+
],
|
|
81
|
+
})];
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const documentationProvider = {
|
|
86
|
+
id: 'bpmn-documentation',
|
|
87
|
+
priority: 200,
|
|
88
|
+
getGroups(context) {
|
|
89
|
+
return [propertyGroup({
|
|
90
|
+
id: 'documentation', label: '文档说明', collapsed: true, position: { append: true },
|
|
91
|
+
entries: [propertyEntry({
|
|
92
|
+
id: 'documentation', type: 'textarea', label: 'Documentation', path: context.kind === 'process' ? 'properties.documentation' : 'properties.documentation',
|
|
93
|
+
placeholder: '节点说明、业务规则、实现备注或流程文档。', rows: 5,
|
|
94
|
+
})],
|
|
95
|
+
})];
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const processProvider = {
|
|
100
|
+
id: 'bpmn-process',
|
|
101
|
+
priority: 800,
|
|
102
|
+
appliesTo: { kind: 'process' },
|
|
103
|
+
getGroups() {
|
|
104
|
+
return [
|
|
105
|
+
propertyGroup({
|
|
106
|
+
id: 'process-start-permissions', label: '启动权限', collapsed: true, position: { after: 'basic' },
|
|
107
|
+
entries: [
|
|
108
|
+
propertyEntry({ id: 'starter-users', type: 'tags', label: '候选启动用户', path: 'properties.candidateStarterUsers', placeholder: 'user1,user2' }),
|
|
109
|
+
propertyEntry({ id: 'starter-groups', type: 'tags', label: '候选启动组', path: 'properties.candidateStarterGroups', placeholder: 'manager,finance' }),
|
|
110
|
+
],
|
|
111
|
+
}),
|
|
112
|
+
propertyGroup({
|
|
113
|
+
id: 'global-resources', label: '全局事件资源', collapsed: true, position: { after: 'process-start-permissions' },
|
|
114
|
+
description: '集中管理 Message / Signal / Error / Escalation,事件节点通过 ID 引用。',
|
|
115
|
+
entries: [
|
|
116
|
+
propertyEntry({ id: 'messages', type: 'table', label: 'Messages', path: 'resources.messages', rowLabel: '消息', defaultRow: () => ({ id: `Message_${Date.now().toString(36)}`, name: '新消息' }), columns: [{ key: 'id', label: 'ID' }, { key: 'name', label: '名称' }] }),
|
|
117
|
+
propertyEntry({ id: 'signals', type: 'table', label: 'Signals', path: 'resources.signals', rowLabel: '信号', defaultRow: () => ({ id: `Signal_${Date.now().toString(36)}`, name: '新信号' }), columns: [{ key: 'id', label: 'ID' }, { key: 'name', label: '名称' }] }),
|
|
118
|
+
propertyEntry({ id: 'errors', type: 'table', label: 'Errors', path: 'resources.errors', rowLabel: '错误', defaultRow: () => ({ id: `Error_${Date.now().toString(36)}`, name: '业务错误', errorCode: 'BUSINESS_ERROR' }), columns: [{ key: 'id', label: 'ID' }, { key: 'name', label: '名称' }, { key: 'errorCode', label: 'Error Code' }] }),
|
|
119
|
+
propertyEntry({ id: 'escalations', type: 'table', label: 'Escalations', path: 'resources.escalations', rowLabel: '升级', defaultRow: () => ({ id: `Escalation_${Date.now().toString(36)}`, name: '业务升级', escalationCode: 'ESCALATE' }), columns: [{ key: 'id', label: 'ID' }, { key: 'name', label: '名称' }, { key: 'escalationCode', label: 'Code' }] }),
|
|
120
|
+
],
|
|
121
|
+
}),
|
|
122
|
+
propertyGroup({
|
|
123
|
+
id: 'namespaces', label: '命名空间', collapsed: true, position: { after: 'global-resources' },
|
|
124
|
+
description: '自定义属性 / Extension Element 使用的 XML Namespace。填写前缀时使用 xmlns:company 形式。',
|
|
125
|
+
entries: [propertyEntry({
|
|
126
|
+
id: 'namespace-table', type: 'table', label: 'Namespace Declarations',
|
|
127
|
+
getValue: (ctx) => Object.entries(ctx.model.namespaceDeclarations || {}).map(([name, uri]) => ({ name, uri })),
|
|
128
|
+
setValue: (ctx, rows) => ctx.designer.updateProcess({ namespaceDeclarations: Object.fromEntries((rows || []).filter((row) => row.name && row.uri).map((row) => [row.name.startsWith('xmlns:') ? row.name : `xmlns:${row.name}`, row.uri])) }),
|
|
129
|
+
rowLabel: '命名空间', defaultRow: () => ({ name: 'xmlns:company', uri: 'urn:company:bpmn-extension' }), columns: [{ key: 'name', label: 'Prefix' }, { key: 'uri', label: 'URI' }],
|
|
130
|
+
})],
|
|
131
|
+
}),
|
|
132
|
+
propertyGroup({
|
|
133
|
+
id: 'canvas-display', label: '画布显示', position: { after: 'basic' },
|
|
134
|
+
entries: [
|
|
135
|
+
propertyEntry({ id: 'show-grid', type: 'switch', label: '显示网格', path: 'settings.showGrid', defaultValue: true }),
|
|
136
|
+
propertyEntry({ id: 'grid-size', type: 'range', label: '网格尺寸', path: 'settings.gridSize', defaultValue: 16, min: 8, max: 40, step: 4, unit: 'px' }),
|
|
137
|
+
propertyEntry({ id: 'snap-to-grid', type: 'switch', label: '吸附到网格', path: 'settings.snapToGrid', defaultValue: false }),
|
|
138
|
+
propertyEntry({ id: 'alignment-guides', type: 'switch', label: '智能对齐辅助线', path: 'settings.alignmentGuides', defaultValue: true, note: '关闭后不再进行磁性对齐,也不显示拖动辅助线。' }),
|
|
139
|
+
],
|
|
140
|
+
}),
|
|
141
|
+
propertyGroup({
|
|
142
|
+
id: 'nova-layout', label: '布局与连线', position: { after: 'canvas-display' },
|
|
143
|
+
entries: [
|
|
144
|
+
propertyEntry({ id: 'direction', type: 'select', label: '默认布局方向', path: 'settings.direction', options: [['horizontal', '横向 Left → Right'], ['vertical', '纵向 Top → Bottom']] }),
|
|
145
|
+
propertyEntry({ id: 'density', type: 'select', label: '布局紧凑度', path: 'settings.layoutDensity', options: [['compact', '紧凑'], ['balanced', '标准'], ['spacious', '舒展']], note: '影响快捷添加和一键美化的节点间距。' }),
|
|
146
|
+
propertyEntry({ id: 'edge-style', type: 'select', label: '默认连线风格', path: 'settings.edgeStyle', options: [['rounded', '圆角折线'], ['smooth', '柔和曲线'], ['straight', '直角折线']] }),
|
|
147
|
+
propertyEntry({ id: 'beautify', type: 'action', label: '一键整理全部节点与连线', actionLabel: '一键美化', iconId: 'ui.magic', action: (ctx) => ctx.designer.beautify({ direction: ctx.model.settings?.direction, density: ctx.model.settings?.layoutDensity, edgeStyle: ctx.model.settings?.edgeStyle }) }),
|
|
148
|
+
],
|
|
149
|
+
}),
|
|
150
|
+
];
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const sequenceFlowProvider = {
|
|
155
|
+
id: 'bpmn-sequence-flow',
|
|
156
|
+
priority: 850,
|
|
157
|
+
appliesTo: (ctx) => ctx.kind === 'edge' && (ctx.element.type || 'sequenceFlow') === 'sequenceFlow',
|
|
158
|
+
getGroups(context) {
|
|
159
|
+
const source = context.model.nodes.find((node) => node.id === context.element.source);
|
|
160
|
+
const sourceDef = NODE_DEFINITIONS[source?.type] || {};
|
|
161
|
+
return [
|
|
162
|
+
propertyGroup({
|
|
163
|
+
id: 'condition', label: '条件与默认出口', position: { after: 'basic' },
|
|
164
|
+
entries: [
|
|
165
|
+
propertyEntry({ id: 'condition-expression', type: 'expression', label: '条件表达式', path: 'condition', placeholder: '${amount >= 50000}', note: source?.type === 'parallelGateway' ? '并行网关不会根据条件选择出口;仅供开发者清理历史数据。' : '导出为 bpmn:conditionExpression。', visible: (ctx) => source?.type === 'parallelGateway' ? ctx.profile === 'developer' : sourceDef.kind === 'gateway' || !!context.element.condition }),
|
|
166
|
+
propertyEntry({
|
|
167
|
+
id: 'default-flow', type: 'switch', label: '默认线路', getValue: (ctx) => !!ctx.element.isDefault,
|
|
168
|
+
setValue: (ctx, value) => {
|
|
169
|
+
const patches = [];
|
|
170
|
+
if (value) {
|
|
171
|
+
for (const edge of ctx.model.edges.filter((edge) => edge.source === ctx.element.source && edge.id !== ctx.element.id && edge.isDefault)) patches.push({ id: edge.id, patch: { isDefault: false } });
|
|
172
|
+
}
|
|
173
|
+
patches.push({ id: ctx.element.id, patch: { isDefault: !!value } });
|
|
174
|
+
ctx.designer.updateEdges(patches);
|
|
175
|
+
},
|
|
176
|
+
note: source?.type === 'parallelGateway' ? '并行网关不支持默认出口;仅供开发者清理历史数据。' : '',
|
|
177
|
+
visible: (ctx) => ['exclusiveGateway', 'inclusiveGateway', 'complexGateway'].includes(source?.type) || (source?.type === 'parallelGateway' && ctx.profile === 'developer'),
|
|
178
|
+
}),
|
|
179
|
+
],
|
|
180
|
+
}),
|
|
181
|
+
propertyGroup({
|
|
182
|
+
id: 'nova-edge-style', label: '流程图显示', collapsed: true, position: { after: 'condition' },
|
|
183
|
+
entries: [
|
|
184
|
+
propertyEntry({ id: 'route-style', type: 'select', label: '线路样式', path: 'routeStyle', options: [['rounded', '圆角折线'], ['smooth', '柔和曲线'], ['straight', '直角折线']] }),
|
|
185
|
+
propertyEntry({ id: 'corner-radius', type: 'range', label: '圆角半径', path: 'cornerRadius', min: 0, max: 28, step: 1, unit: 'px' }),
|
|
186
|
+
propertyEntry({ id: 'reset-route', type: 'action', label: '重算最佳路径', actionLabel: '重算线路', iconId: 'ui.routeSmooth', action: (ctx) => ctx.designer.resetEdgeRoute(ctx.element.id) }),
|
|
187
|
+
],
|
|
188
|
+
}),
|
|
189
|
+
];
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const activityProvider = {
|
|
194
|
+
id: 'bpmn-activity',
|
|
195
|
+
priority: 700,
|
|
196
|
+
appliesTo: (ctx) => ctx.kind === 'node' && activityKinds.has(ctx.definition?.kind),
|
|
197
|
+
getGroups() {
|
|
198
|
+
return [
|
|
199
|
+
propertyGroup({
|
|
200
|
+
id: 'multi-instance', label: '循环 / 多实例', collapsed: true, position: { after: 'basic' },
|
|
201
|
+
entries: [
|
|
202
|
+
propertyEntry({ id: 'loop-type', type: 'select', label: '循环类型', path: 'properties.loopType', options: [['none', '无'], ['standard', '标准循环'], ['parallel', '并行多实例'], ['sequential', '串行多实例']] }),
|
|
203
|
+
propertyEntry({ id: 'loop-condition', type: 'expression', label: 'Loop Condition', path: 'properties.loopCondition', placeholder: '${retry == true}', visible: (ctx) => ctx.get('properties.loopType') === 'standard' }),
|
|
204
|
+
propertyEntry({ id: 'loop-test-before', type: 'switch', label: 'Test Before', path: 'properties.testBefore', visible: (ctx) => ctx.get('properties.loopType') === 'standard' }),
|
|
205
|
+
propertyEntry({ id: 'loop-maximum', type: 'number', label: 'Loop Maximum', path: 'properties.loopMaximum', min: 0, visible: (ctx) => ctx.get('properties.loopType') === 'standard' }),
|
|
206
|
+
propertyEntry({ id: 'collection', label: 'Collection', path: 'properties.collection', placeholder: '${approvers}', visible: (ctx) => ['parallel', 'sequential'].includes(ctx.get('properties.loopType')) }),
|
|
207
|
+
propertyEntry({ id: 'element-variable', label: 'Element Variable', path: 'properties.elementVariable', placeholder: 'approver', visible: (ctx) => ['parallel', 'sequential'].includes(ctx.get('properties.loopType')) }),
|
|
208
|
+
propertyEntry({ id: 'element-index-variable', label: 'Element Index Variable', path: 'properties.elementIndexVariable', placeholder: 'index', visible: (ctx) => ['parallel', 'sequential'].includes(ctx.get('properties.loopType')) }),
|
|
209
|
+
propertyEntry({ id: 'loop-cardinality', type: 'expression', label: 'Loop Cardinality', path: 'properties.loopCardinality', placeholder: '${3}', visible: (ctx) => ['parallel', 'sequential'].includes(ctx.get('properties.loopType')) }),
|
|
210
|
+
propertyEntry({ id: 'completion-condition', type: 'expression', label: 'Completion Condition', path: 'properties.completionCondition', placeholder: '${nrOfCompletedInstances >= 2}', visible: (ctx) => ['parallel', 'sequential'].includes(ctx.get('properties.loopType')) }),
|
|
211
|
+
propertyEntry({ id: 'compensation', type: 'switch', label: '补偿活动', path: 'properties.isForCompensation' }),
|
|
212
|
+
],
|
|
213
|
+
}),
|
|
214
|
+
];
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const scriptProvider = {
|
|
219
|
+
id: 'bpmn-script-task', priority: 720, appliesTo: { kind: 'node', nodeType: 'scriptTask' },
|
|
220
|
+
getGroups() { return [propertyGroup({ id: 'script', label: '脚本', position: { after: 'basic' }, entries: [
|
|
221
|
+
propertyEntry({ id: 'script-format', label: 'Script Format', path: 'properties.scriptFormat', placeholder: 'javascript / groovy' }),
|
|
222
|
+
propertyEntry({ id: 'script-body', type: 'code', label: 'Script', path: 'properties.script', rows: 9, placeholder: '// script body' }),
|
|
223
|
+
propertyEntry({ id: 'script-result', label: 'Result Variable', path: 'properties.resultVariable', placeholder: 'result' }),
|
|
224
|
+
] })]; },
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const callActivityProvider = {
|
|
228
|
+
id: 'bpmn-call-activity', priority: 720, appliesTo: { kind: 'node', nodeType: 'callActivity' },
|
|
229
|
+
getGroups() { return [propertyGroup({ id: 'call-activity', label: '调用流程', position: { after: 'basic' }, entries: [
|
|
230
|
+
propertyEntry({ id: 'called-element', type: 'process-select', label: 'Called Element', path: 'properties.calledElement', placeholder: 'Process_Sub_1', dataProvider: 'processes', allowCustom: true }),
|
|
231
|
+
propertyEntry({ id: 'called-business-key', type: 'expression', label: 'Business Key', path: 'properties.businessKey', placeholder: '${businessKey}' }),
|
|
232
|
+
propertyEntry({ id: 'inherit-business-key', type: 'switch', label: '继承 Business Key', path: 'properties.inheritBusinessKey' }),
|
|
233
|
+
propertyEntry({ id: 'inherit-variables', type: 'switch', label: '继承流程变量', path: 'properties.inheritVariables' }),
|
|
234
|
+
propertyEntry({ id: 'input-mapping', type: 'table', label: '输入参数 In', path: 'properties.inParameters', rowLabel: '输入映射', defaultRow: () => ({ source: '', sourceExpression: '', target: '' }), columns: [{ key: 'source', label: 'Source' }, { key: 'sourceExpression', label: 'Expression' }, { key: 'target', label: 'Target' }] }),
|
|
235
|
+
propertyEntry({ id: 'output-mapping', type: 'table', label: '输出参数 Out', path: 'properties.outParameters', rowLabel: '输出映射', defaultRow: () => ({ source: '', sourceExpression: '', target: '' }), columns: [{ key: 'source', label: 'Source' }, { key: 'sourceExpression', label: 'Expression' }, { key: 'target', label: 'Target' }] }),
|
|
236
|
+
] })]; },
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const messageTaskProvider = {
|
|
240
|
+
id: 'bpmn-message-task', priority: 725,
|
|
241
|
+
appliesTo: (ctx) => ctx.kind === 'node' && ['sendTask', 'receiveTask'].includes(ctx.element.type),
|
|
242
|
+
getGroups(context) {
|
|
243
|
+
const entries = [
|
|
244
|
+
propertyEntry({
|
|
245
|
+
id: 'message-ref', type: 'resource-select', label: 'Message Ref', path: 'properties.messageRef', resource: 'messages', allowCustom: true,
|
|
246
|
+
options: (ctx) => (ctx.model.resources?.messages || []).map((item) => ({ value: item.id, label: `${item.name || item.id} · ${item.id}` })),
|
|
247
|
+
placeholder: '选择或输入 Message ID',
|
|
248
|
+
}),
|
|
249
|
+
propertyEntry({ id: 'operation-ref', label: 'Operation Ref', path: 'properties.operationRef', placeholder: 'Operation_1' }),
|
|
250
|
+
];
|
|
251
|
+
if (context.element.type === 'receiveTask') entries.push(propertyEntry({ id: 'instantiate-receive', type: 'switch', label: 'Instantiate', path: 'properties.instantiate' }));
|
|
252
|
+
return [propertyGroup({ id: 'message-task', label: context.element.type === 'sendTask' ? '消息发送' : '消息接收', position: { after: 'basic' }, entries })];
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const subProcessProvider = {
|
|
257
|
+
id: 'bpmn-sub-process', priority: 710,
|
|
258
|
+
appliesTo: (ctx) => ctx.kind === 'node' && ctx.definition?.kind === 'container',
|
|
259
|
+
getGroups(context) {
|
|
260
|
+
const entries = [];
|
|
261
|
+
if (context.element.type === 'eventSubProcess') entries.push(propertyEntry({ id: 'triggered-by-event', type: 'readonly', label: 'Triggered By Event', value: 'true' }));
|
|
262
|
+
if (context.element.type === 'adHocSubProcess') entries.push(
|
|
263
|
+
propertyEntry({ id: 'adhoc-ordering', type: 'select', label: 'Ordering', path: 'properties.ordering', options: [['Parallel', 'Parallel'], ['Sequential', 'Sequential']] }),
|
|
264
|
+
propertyEntry({ id: 'adhoc-completion', type: 'expression', label: 'Completion Condition', path: 'properties.adHocCompletionCondition', placeholder: '${completed}' }),
|
|
265
|
+
propertyEntry({ id: 'adhoc-cancel', type: 'switch', label: 'Cancel Remaining Instances', path: 'properties.cancelRemainingInstances' }),
|
|
266
|
+
);
|
|
267
|
+
if (!entries.length) return [];
|
|
268
|
+
return [propertyGroup({ id: 'sub-process', label: '子流程行为', position: { after: 'basic' }, entries })];
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const gatewayProvider = {
|
|
273
|
+
id: 'bpmn-gateway', priority: 760, appliesTo: { kind: 'node', elementKind: 'gateway' },
|
|
274
|
+
getGroups(context) {
|
|
275
|
+
const role = resolveGatewayRole(context.model, context.element);
|
|
276
|
+
const isParallel = context.element.type === 'parallelGateway';
|
|
277
|
+
const entries = [];
|
|
278
|
+
if (isParallel && context.profile === 'business') entries.push(propertyEntry({
|
|
279
|
+
id: 'parallel-role', type: 'select', label: '并行角色', path: 'properties.gatewayDirection',
|
|
280
|
+
options: (ctx) => [
|
|
281
|
+
['Unspecified', '自动判断'], ['Diverging', '并行分支'], ['Converging', '并行汇聚'],
|
|
282
|
+
...(ctx.element.properties?.gatewayDirection === 'Mixed' ? [['Mixed', '混合(开发者配置)']] : []),
|
|
283
|
+
],
|
|
284
|
+
note: role.effective === 'Converging' ? '并行汇聚会等待所有入口到达后再继续。' : '',
|
|
285
|
+
}));
|
|
286
|
+
if (context.profile === 'developer' || !isParallel) entries.push(propertyEntry({ id: 'gateway-direction', type: 'select', label: 'Gateway Direction', path: 'properties.gatewayDirection', options: [['Unspecified', 'Unspecified'], ['Diverging', 'Diverging / 分支'], ['Converging', 'Converging / 汇聚'], ['Mixed', 'Mixed']] }));
|
|
287
|
+
if (context.element.type === 'eventBasedGateway') entries.push(
|
|
288
|
+
propertyEntry({ id: 'instantiate', type: 'switch', label: 'Instantiate', path: 'properties.instantiate' }),
|
|
289
|
+
propertyEntry({ id: 'event-gateway-type', type: 'select', label: 'Event Gateway Type', path: 'properties.eventGatewayType', options: [['Exclusive', 'Exclusive'], ['Parallel', 'Parallel']] }),
|
|
290
|
+
);
|
|
291
|
+
if (context.element.type === 'complexGateway') entries.push(propertyEntry({ id: 'activation-condition', type: 'expression', label: 'Activation Condition', path: 'properties.activationCondition' }));
|
|
292
|
+
const groups = [];
|
|
293
|
+
if (entries.length) groups.push(propertyGroup({ id: 'gateway', label: '网关配置', position: { after: 'basic' }, entries }));
|
|
294
|
+
if (!isParallel) {
|
|
295
|
+
groups.push(propertyGroup({
|
|
296
|
+
id: 'gateway-branches', label: `分支线路 · ${role.outgoing.length}`, collapsed: false, position: { after: 'gateway' },
|
|
297
|
+
description: '直接在网关面板维护出口名称、条件和默认线路;点击定位可选中对应 Sequence Flow。',
|
|
298
|
+
entries: [propertyEntry({
|
|
299
|
+
id: 'branches', type: 'branchList', label: '',
|
|
300
|
+
getValue: () => role.outgoing.map((edge) => ({ id: edge.id, name: edge.name || '', condition: edge.condition || '', isDefault: !!edge.isDefault, target: edge.target })),
|
|
301
|
+
setValue: (ctx, rows) => {
|
|
302
|
+
const patches = rows.map((row) => ({ id: row.id, patch: { name: row.name, condition: row.condition, isDefault: !!row.isDefault } }));
|
|
303
|
+
const defaultRow = rows.find((row) => row.isDefault);
|
|
304
|
+
if (defaultRow) {
|
|
305
|
+
for (const edge of role.outgoing) if (edge.id !== defaultRow.id && edge.isDefault) patches.push({ id: edge.id, patch: { isDefault: false } });
|
|
306
|
+
}
|
|
307
|
+
ctx.designer.updateEdges(patches);
|
|
308
|
+
},
|
|
309
|
+
selectRow: (ctx, row) => ctx.designer.select({ kind: 'edge', id: row.id }),
|
|
310
|
+
})],
|
|
311
|
+
}));
|
|
312
|
+
return groups;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let structureAfter = 'gateway';
|
|
316
|
+
if (role.effective === 'Diverging') {
|
|
317
|
+
groups.push(propertyGroup({
|
|
318
|
+
id: 'gateway-branches', label: `并行出口 · ${role.outgoing.length}`, collapsed: false, position: { after: 'gateway' },
|
|
319
|
+
description: '并行分支会同时激活所有出口;可维护线路名称或定位对应 Sequence Flow。',
|
|
320
|
+
entries: [
|
|
321
|
+
propertyEntry({
|
|
322
|
+
id: 'branches', type: 'branchList', label: '', fields: ['name'],
|
|
323
|
+
getValue: () => role.outgoing.map((edge) => ({ id: edge.id, name: edge.name || '', condition: edge.condition || '', isDefault: !!edge.isDefault, target: edge.target })),
|
|
324
|
+
setValue: (ctx, rows) => ctx.designer.updateEdges(rows.map((row) => ({ id: row.id, patch: { name: row.name } }))),
|
|
325
|
+
selectRow: (ctx, row) => ctx.designer.select({ kind: 'edge', id: row.id }),
|
|
326
|
+
}),
|
|
327
|
+
propertyEntry({ id: 'connect-parallel-branch', type: 'action', label: '连接已有节点', actionLabel: '连接并行分支', iconId: 'ui.connect', action: (ctx) => ctx.designer.startConnect(ctx.element.id) }),
|
|
328
|
+
],
|
|
329
|
+
}));
|
|
330
|
+
structureAfter = 'gateway-branches';
|
|
331
|
+
} else {
|
|
332
|
+
const isJoin = role.effective === 'Converging';
|
|
333
|
+
groups.push(propertyGroup({
|
|
334
|
+
id: 'parallel-incoming', label: `${isJoin ? '汇聚入口' : '入口线路'} · ${role.incoming.length}`, collapsed: false, position: { after: 'gateway' },
|
|
335
|
+
description: isJoin ? '并行汇聚会等待所有入口到达后再继续。' : '当前角色尚未确定,仅展示已有入口线路。',
|
|
336
|
+
entries: [propertyEntry({ id: 'incoming-flows', type: 'flow-list', label: '', relation: 'source', emptyText: '当前并行网关没有入口线路', getValue: () => role.incoming })],
|
|
337
|
+
}));
|
|
338
|
+
groups.push(propertyGroup({
|
|
339
|
+
id: 'parallel-outgoing', label: `${isJoin ? '后续出口' : '出口线路'} · ${role.outgoing.length}`, collapsed: false, position: { after: 'parallel-incoming' },
|
|
340
|
+
description: isJoin ? '并行汇聚通常只连接一个后续节点。' : '当前角色尚未确定,仅展示已有出口线路。',
|
|
341
|
+
entries: [
|
|
342
|
+
propertyEntry({ id: 'outgoing-flows', type: 'flow-list', label: '', relation: 'target', emptyText: '当前并行网关没有出口线路', getValue: () => role.outgoing }),
|
|
343
|
+
...(isJoin && role.outgoing.length === 0 ? [propertyEntry({ id: 'connect-parallel-next', type: 'action', label: '连接后续节点', actionLabel: '连接后续节点', iconId: 'ui.connect', action: (ctx) => ctx.designer.startConnect(ctx.element.id) })] : []),
|
|
344
|
+
],
|
|
345
|
+
}));
|
|
346
|
+
structureAfter = 'parallel-outgoing';
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const statusLabels = { valid: '结构正常', incomplete: '结构未完成', conflict: '结构冲突', ambiguous: '角色待判断' };
|
|
350
|
+
groups.push(propertyGroup({
|
|
351
|
+
id: 'parallel-structure', label: '结构校验', collapsed: false, position: { after: structureAfter },
|
|
352
|
+
entries: [
|
|
353
|
+
propertyEntry({ id: 'parallel-structure-summary', type: 'readonly', label: '线路结构', value: `${role.incoming.length} 入 · ${role.outgoing.length} 出` }),
|
|
354
|
+
propertyEntry({ id: 'parallel-structure-status', type: 'readonly', label: '状态', variant: role.status, value: statusLabels[role.status] }),
|
|
355
|
+
...(role.issues.length ? [propertyEntry({ id: 'parallel-structure-issues', type: 'readonly', label: '提示', variant: role.status, value: role.issues.join(';') })] : []),
|
|
356
|
+
],
|
|
357
|
+
}));
|
|
358
|
+
return groups;
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const eventProvider = {
|
|
363
|
+
id: 'bpmn-events', priority: 750,
|
|
364
|
+
appliesTo: (ctx) => ctx.kind === 'node' && ['event', 'boundary'].includes(ctx.definition?.kind),
|
|
365
|
+
getGroups(context) {
|
|
366
|
+
const def = context.definition;
|
|
367
|
+
const entries = [];
|
|
368
|
+
if (def.eventDefinition === 'timer') entries.push(
|
|
369
|
+
propertyEntry({ id: 'timer-type', type: 'select', label: '定时类型', path: 'properties.timerType', options: [['duration', 'Duration'], ['date', 'Date'], ['cycle', 'Cycle']] }),
|
|
370
|
+
propertyEntry({ id: 'timer-value', type: 'expression', label: '时间表达式', path: 'properties.timerValue', placeholder: 'PT1H / 2026-08-26T09:00 / R3/PT10M' }),
|
|
371
|
+
);
|
|
372
|
+
if (def.eventDefinition === 'conditional') entries.push(propertyEntry({ id: 'event-condition', type: 'expression', label: '条件表达式', path: 'properties.condition', placeholder: '${approved == true}' }));
|
|
373
|
+
if (eventRefs[def.eventDefinition]) {
|
|
374
|
+
const meta = eventRefs[def.eventDefinition];
|
|
375
|
+
entries.push(propertyEntry({
|
|
376
|
+
id: 'event-ref', type: 'resource-select', label: meta.label, path: 'properties.eventRef', resource: meta.resource, allowCustom: true,
|
|
377
|
+
options: (ctx) => (ctx.model.resources?.[meta.resource] || []).map((item) => ({ value: item.id, label: `${item.name || item.id} · ${item.id}` })),
|
|
378
|
+
placeholder: '选择或输入资源 ID',
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
if (def.eventDefinition === 'link') entries.push(propertyEntry({ id: 'link-name', label: 'Link Name', path: 'properties.eventRef', placeholder: 'Link_A' }));
|
|
382
|
+
if (def.eventDefinition === 'compensate') entries.push(
|
|
383
|
+
propertyEntry({ id: 'activity-ref', type: 'node-select', label: 'Activity Ref', path: 'properties.activityRef', nodeFilter: (node) => activityKinds.has(NODE_DEFINITIONS[node.type]?.kind) }),
|
|
384
|
+
propertyEntry({ id: 'wait-for-completion', type: 'switch', label: 'Wait For Completion', path: 'properties.waitForCompletion' }),
|
|
385
|
+
);
|
|
386
|
+
if ((def.eventStage === 'start' || (def.eventStage === 'intermediate' && def.eventRole === 'catch')) && ['message', 'signal'].includes(def.eventDefinition)) {
|
|
387
|
+
entries.push(propertyEntry({ id: 'parallel-multiple', type: 'switch', label: 'Parallel Multiple', path: 'properties.parallelMultiple' }));
|
|
388
|
+
}
|
|
389
|
+
if (!entries.length) entries.push(propertyEntry({ id: 'event-info', type: 'readonly', label: '事件定义', getValue: () => def.eventDefinition ? def.eventDefinition : 'None' }));
|
|
390
|
+
const groups = [propertyGroup({ id: 'event-definition', label: '事件定义', position: { after: 'basic' }, entries })];
|
|
391
|
+
if (def.kind === 'boundary') groups.push(propertyGroup({
|
|
392
|
+
id: 'boundary', label: '边界事件', position: { after: 'event-definition' }, entries: [
|
|
393
|
+
propertyEntry({ id: 'attached-to', type: 'node-select', label: '附着到活动', path: 'properties.attachedToRef', nodeFilter: (node) => activityKinds.has(NODE_DEFINITIONS[node.type]?.kind) }),
|
|
394
|
+
propertyEntry({ id: 'cancel-activity', type: 'switch', label: '中断主活动', path: 'properties.cancelActivity' }),
|
|
395
|
+
],
|
|
396
|
+
}));
|
|
397
|
+
return groups;
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const artifactProvider = {
|
|
402
|
+
id: 'bpmn-artifacts', priority: 730,
|
|
403
|
+
appliesTo: (ctx) => ctx.kind === 'node' && ['data', 'dataStore', 'annotation', 'group', 'participant', 'lane'].includes(ctx.definition?.kind),
|
|
404
|
+
getGroups(context) {
|
|
405
|
+
const type = context.element.type;
|
|
406
|
+
const entries = [];
|
|
407
|
+
if (type === 'textAnnotation') entries.push(propertyEntry({ id: 'annotation-text', type: 'textarea', label: '文本', path: 'properties.text', rows: 6 }), propertyEntry({ id: 'annotation-format', label: 'Text Format', path: 'properties.textFormat', placeholder: 'text/plain' }));
|
|
408
|
+
if (type === 'dataObjectReference') entries.push(propertyEntry({ id: 'data-object-ref', label: 'Data Object Ref', path: 'properties.dataObjectRef' }), propertyEntry({ id: 'data-state', label: 'Data State', path: 'properties.dataState' }));
|
|
409
|
+
if (type === 'dataStoreReference') entries.push(propertyEntry({ id: 'data-store-ref', label: 'Data Store Ref', path: 'properties.dataStoreRef' }), propertyEntry({ id: 'data-state', label: 'Data State', path: 'properties.dataState' }));
|
|
410
|
+
if (type === 'participant') entries.push(
|
|
411
|
+
propertyEntry({
|
|
412
|
+
id: 'swimlane-label-placement', type: 'select', label: '标题位置', path: 'properties.swimlaneLabelPlacement', defaultValue: 'side',
|
|
413
|
+
options: [['side', '左侧竖排'], ['top', '上方横排']],
|
|
414
|
+
note: '挂载到此 Pool 的 Lane 会继承该标题布局。',
|
|
415
|
+
}),
|
|
416
|
+
propertyEntry({ id: 'process-ref', label: 'Process Ref', path: 'properties.processRef' }),
|
|
417
|
+
);
|
|
418
|
+
if (type === 'lane') entries.push(
|
|
419
|
+
context.element.containerId
|
|
420
|
+
? propertyEntry({
|
|
421
|
+
id: 'swimlane-label-placement-inherited', type: 'readonly', label: '标题位置',
|
|
422
|
+
getValue: (ctx) => resolveSwimlaneLabelPlacement(ctx.model, ctx.element) === 'top' ? '上方横排(跟随 Pool)' : '左侧竖排(跟随 Pool)',
|
|
423
|
+
note: '挂载 Lane 的标题布局由所属 Pool 统一控制。',
|
|
424
|
+
})
|
|
425
|
+
: propertyEntry({
|
|
426
|
+
id: 'swimlane-label-placement', type: 'select', label: '标题位置', path: 'properties.swimlaneLabelPlacement', defaultValue: 'side',
|
|
427
|
+
options: [['side', '左侧竖排'], ['top', '上方横排']],
|
|
428
|
+
note: '该设置仅影响编辑器展示,不写入标准 BPMN XML。',
|
|
429
|
+
}),
|
|
430
|
+
propertyEntry({ id: 'lane-members', type: 'node-list', label: '包含节点', getValue: (ctx) => ctx.element.properties?.flowNodeRefs || [], note: '节点拖入 Lane 后可由布局/containment 模块维护;此处用于检查当前引用。' }),
|
|
431
|
+
);
|
|
432
|
+
if (type === 'group') entries.push(propertyEntry({ id: 'category-value', label: 'Category Value', path: 'properties.categoryValue', placeholder: context.element.name }));
|
|
433
|
+
return entries.length ? [propertyGroup({ id: 'artifact', label: '元素配置', position: { after: 'basic' }, entries })] : [];
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
const nonSequenceEdgeProvider = {
|
|
440
|
+
id: 'bpmn-non-sequence-edge', priority: 845,
|
|
441
|
+
appliesTo: (ctx) => ctx.kind === 'edge' && ['messageFlow', 'association'].includes(ctx.element.type),
|
|
442
|
+
getGroups(context) {
|
|
443
|
+
const entries = context.element.type === 'messageFlow'
|
|
444
|
+
? [propertyEntry({
|
|
445
|
+
id: 'message-flow-ref', type: 'resource-select', label: 'Message Ref', path: 'properties.messageRef', resource: 'messages', allowCustom: true,
|
|
446
|
+
options: (ctx) => (ctx.model.resources?.messages || []).map((item) => ({ value: item.id, label: `${item.name || item.id} · ${item.id}` })),
|
|
447
|
+
})]
|
|
448
|
+
: [propertyEntry({ id: 'association-direction', type: 'select', label: 'Association Direction', path: 'properties.associationDirection', options: [['None', 'None'], ['One', 'One'], ['Both', 'Both']] })];
|
|
449
|
+
return [propertyGroup({ id: 'edge-semantics', label: context.element.type === 'messageFlow' ? '消息流' : '关联', position: { after: 'basic' }, entries })];
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const extensionElementsProvider = {
|
|
454
|
+
id: 'bpmn-extension-elements', priority: 110,
|
|
455
|
+
getGroups(context) {
|
|
456
|
+
return [propertyGroup({
|
|
457
|
+
id: 'extension-elements', label: 'Extension Elements', collapsed: true, position: { append: true },
|
|
458
|
+
description: '原始扩展 XML 会无损保留。推荐通过 Properties Provider + Engine/Extension serializer 管理结构化扩展;此处适合查看或维护少量未知扩展。',
|
|
459
|
+
entries: [propertyEntry({
|
|
460
|
+
id: 'raw-extension-elements', type: 'table', label: 'Raw XML',
|
|
461
|
+
getValue: (ctx) => (ctx.get('extensionElements', []) || []).map((xml) => ({ xml })),
|
|
462
|
+
setValue: (ctx, rows) => ctx.set('extensionElements', (rows || []).map((row) => row.xml).filter(Boolean)),
|
|
463
|
+
rowLabel: 'Extension Element', defaultRow: () => ({ xml: '<company:property value="" />' }), columns: [{ key: 'xml', label: 'XML' }],
|
|
464
|
+
})],
|
|
465
|
+
})];
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const extensionAttributesProvider = {
|
|
470
|
+
id: 'bpmn-extension-attributes', priority: 120,
|
|
471
|
+
getGroups(context) {
|
|
472
|
+
const rootPath = 'properties.extensionAttributes';
|
|
473
|
+
return [propertyGroup({
|
|
474
|
+
id: 'extension-attributes', label: '扩展属性', collapsed: true, position: { append: true },
|
|
475
|
+
description: '用于企业自定义 namespace attribute。请同时在 Process 的 namespaceDeclarations 中注册对应 xmlns 前缀。未知 Extension Elements 在导入/导出时仍会保留。',
|
|
476
|
+
entries: [propertyEntry({
|
|
477
|
+
id: 'extension-attribute-table', type: 'table', label: 'Custom Attributes',
|
|
478
|
+
getValue: (ctx) => Object.entries(ctx.get(rootPath, {}) || {}).map(([name, value]) => ({ name, value })),
|
|
479
|
+
setValue: (ctx, rows) => ctx.set(rootPath, Object.fromEntries((rows || []).filter((row) => row.name).map((row) => [row.name, row.value || '']))),
|
|
480
|
+
rowLabel: '属性', defaultRow: () => ({ name: 'company:property', value: '' }), columns: [{ key: 'name', label: 'QName' }, { key: 'value', label: 'Value' }],
|
|
481
|
+
})],
|
|
482
|
+
})];
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const destructiveActionsProvider = {
|
|
487
|
+
id: 'bpmn-actions', priority: 20,
|
|
488
|
+
appliesTo: (ctx) => ctx.kind === 'node' || ctx.kind === 'edge',
|
|
489
|
+
getGroups() {
|
|
490
|
+
return [propertyGroup({
|
|
491
|
+
id: 'element-actions', label: '元素操作', collapsed: true, position: { append: true },
|
|
492
|
+
entries: [propertyEntry({ id: 'delete-element', type: 'action', label: '删除当前元素', actionLabel: '删除元素', variant: 'danger', action: (ctx) => ctx.studio.commands.remove() })],
|
|
493
|
+
})];
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
export const bpmnPropertiesProviders = [
|
|
497
|
+
basicProvider,
|
|
498
|
+
processProvider,
|
|
499
|
+
sequenceFlowProvider,
|
|
500
|
+
gatewayProvider,
|
|
501
|
+
eventProvider,
|
|
502
|
+
scriptProvider,
|
|
503
|
+
callActivityProvider,
|
|
504
|
+
messageTaskProvider,
|
|
505
|
+
subProcessProvider,
|
|
506
|
+
artifactProvider,
|
|
507
|
+
activityProvider,
|
|
508
|
+
documentationProvider,
|
|
509
|
+
nonSequenceEdgeProvider,
|
|
510
|
+
extensionAttributesProvider,
|
|
511
|
+
extensionElementsProvider,
|
|
512
|
+
destructiveActionsProvider,
|
|
513
|
+
];
|
|
514
|
+
|
|
515
|
+
export function registerBpmnProperties(registry) {
|
|
516
|
+
const disposers = bpmnPropertiesProviders.map((provider) => registry.registerProvider(provider.priority ?? 500, provider));
|
|
517
|
+
return () => disposers.forEach((dispose) => dispose());
|
|
518
|
+
}
|