@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,124 @@
|
|
|
1
|
+
import type { BpmnEdge, BpmnNode, ElementSelection, EngineId, ProcessModel } from '../core/index.js'
|
|
2
|
+
|
|
3
|
+
export type PropertyEntryType =
|
|
4
|
+
| 'text' | 'textarea' | 'code' | 'expression' | 'number' | 'select' | 'switch' | 'range' | 'readonly' | 'action' | 'tags'
|
|
5
|
+
| 'table' | 'branchList' | 'flow-list' | 'node-list' | 'user-select' | 'form-select' | 'process-select' | 'resource-select' | 'node-select'
|
|
6
|
+
| (string & {})
|
|
7
|
+
export interface PropertiesStudio {
|
|
8
|
+
model: ProcessModel
|
|
9
|
+
selection: ElementSelection | null
|
|
10
|
+
propertiesProfile?: 'business' | 'developer'
|
|
11
|
+
getSelectedElement(): ProcessModel | BpmnNode | BpmnEdge | Array<BpmnNode | BpmnEdge> | null
|
|
12
|
+
updateProcess?(patch: Partial<ProcessModel>): unknown
|
|
13
|
+
updateNode?(id: string, patch: Partial<BpmnNode>): unknown
|
|
14
|
+
updateEdge?(id: string, patch: Partial<BpmnEdge>): unknown
|
|
15
|
+
commands?: Record<string, Function>
|
|
16
|
+
}
|
|
17
|
+
export interface PropertiesContext {
|
|
18
|
+
registry: PropertiesRegistry
|
|
19
|
+
studio: PropertiesStudio
|
|
20
|
+
designer: PropertiesStudio
|
|
21
|
+
model: ProcessModel
|
|
22
|
+
selection: ElementSelection | null
|
|
23
|
+
element: ProcessModel | BpmnNode | BpmnEdge
|
|
24
|
+
kind: 'none' | 'process' | 'node' | 'edge' | 'multi'
|
|
25
|
+
profile: 'business' | 'developer'
|
|
26
|
+
definition?: { bpmnType: string; label: string; kind: string; [key: string]: unknown } | null
|
|
27
|
+
engine: EngineId
|
|
28
|
+
is(value: string | ((context: PropertiesContext) => boolean)): boolean
|
|
29
|
+
get(path: string, fallback?: unknown): unknown
|
|
30
|
+
set(path: string, value: unknown): void
|
|
31
|
+
extensions: { get(name: string, fallback?: unknown): unknown; set(name: string, value: unknown): void }
|
|
32
|
+
data(name: string): unknown
|
|
33
|
+
}
|
|
34
|
+
export interface PropertyOption { value: unknown; label: string; disabled?: boolean }
|
|
35
|
+
export interface PropertyEntry {
|
|
36
|
+
id: string
|
|
37
|
+
type?: PropertyEntryType
|
|
38
|
+
label?: string
|
|
39
|
+
iconId?: string
|
|
40
|
+
fields?: Array<'name' | 'condition' | 'default'>
|
|
41
|
+
relation?: 'source' | 'target'
|
|
42
|
+
emptyText?: string
|
|
43
|
+
path?: string
|
|
44
|
+
component?: string
|
|
45
|
+
placeholder?: string
|
|
46
|
+
note?: string
|
|
47
|
+
required?: boolean
|
|
48
|
+
readonly?: boolean
|
|
49
|
+
level?: 'business' | 'developer'
|
|
50
|
+
visible?: (context: PropertiesContext) => boolean
|
|
51
|
+
appliesTo?: AppliesTo
|
|
52
|
+
getValue?: (context: PropertiesContext) => unknown
|
|
53
|
+
setValue?: (context: PropertiesContext, value: unknown) => void
|
|
54
|
+
validate?: (value: unknown, context: PropertiesContext) => string | null
|
|
55
|
+
parse?: (value: unknown, context: PropertiesContext) => unknown
|
|
56
|
+
options?: Array<PropertyOption | [unknown, string] | string> | ((context: PropertiesContext) => Array<PropertyOption | [unknown, string] | string>)
|
|
57
|
+
[key: string]: unknown
|
|
58
|
+
}
|
|
59
|
+
export interface PropertyPosition { prepend?: boolean; append?: boolean; before?: string; after?: string; replace?: string; remove?: string }
|
|
60
|
+
export interface PropertyGroup {
|
|
61
|
+
id: string
|
|
62
|
+
label: string
|
|
63
|
+
entries: PropertyEntry[]
|
|
64
|
+
description?: string
|
|
65
|
+
collapsed?: boolean
|
|
66
|
+
collapsible?: boolean
|
|
67
|
+
level?: 'business' | 'developer'
|
|
68
|
+
position?: PropertyPosition
|
|
69
|
+
appliesTo?: AppliesTo
|
|
70
|
+
[key: string]: unknown
|
|
71
|
+
}
|
|
72
|
+
export interface AppliesTo {
|
|
73
|
+
kind?: 'none' | 'process' | 'node' | 'edge' | 'multi' | Array<'none' | 'process' | 'node' | 'edge' | 'multi'>
|
|
74
|
+
nodeType?: string | string[]
|
|
75
|
+
bpmnType?: string | string[]
|
|
76
|
+
engine?: EngineId
|
|
77
|
+
engines?: EngineId[]
|
|
78
|
+
predicate?: (context: PropertiesContext) => boolean
|
|
79
|
+
[key: string]: unknown
|
|
80
|
+
}
|
|
81
|
+
export interface PropertiesProvider {
|
|
82
|
+
id: string
|
|
83
|
+
priority?: number
|
|
84
|
+
appliesTo?: AppliesTo | ((context: PropertiesContext) => boolean)
|
|
85
|
+
getGroups?: (context: PropertiesContext) => PropertyGroup[] | ((groups: PropertyGroup[], context: PropertiesContext) => PropertyGroup[])
|
|
86
|
+
groups?: PropertyGroup[] | ((context: PropertiesContext) => PropertyGroup[])
|
|
87
|
+
}
|
|
88
|
+
export type PropertyComponentRenderer = (context: {
|
|
89
|
+
container: HTMLElement
|
|
90
|
+
entry: PropertyEntry
|
|
91
|
+
context: PropertiesContext
|
|
92
|
+
value: unknown
|
|
93
|
+
commit(value: unknown): void
|
|
94
|
+
}) => void | (() => void)
|
|
95
|
+
export interface PropertiesRegistryOptions {
|
|
96
|
+
studio?: PropertiesStudio | null
|
|
97
|
+
designer?: PropertiesStudio | null
|
|
98
|
+
components?: Record<string, PropertyComponentRenderer>
|
|
99
|
+
dataProviders?: Record<string, unknown>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getPath(object: unknown, path: string, fallback?: unknown): unknown
|
|
103
|
+
export function setPath<T extends object>(object: T, path: string, value: unknown): T
|
|
104
|
+
export function normalizeOptions(options?: Array<PropertyOption | [unknown, string] | string>): PropertyOption[]
|
|
105
|
+
export function matchesAppliesTo(appliesTo: AppliesTo | ((context: PropertiesContext) => boolean) | undefined, context: PropertiesContext): boolean
|
|
106
|
+
export function propertyEntry(config: PropertyEntry): PropertyEntry
|
|
107
|
+
export function propertyGroup(config: PropertyGroup): PropertyGroup
|
|
108
|
+
export class PropertiesRegistry {
|
|
109
|
+
constructor(options?: PropertiesRegistryOptions)
|
|
110
|
+
studio: PropertiesStudio | null
|
|
111
|
+
designer: PropertiesStudio | null
|
|
112
|
+
setStudio(studio: PropertiesStudio): this
|
|
113
|
+
setDesigner(designer: PropertiesStudio): this
|
|
114
|
+
registerProvider(priority: number, provider: PropertiesProvider): () => void
|
|
115
|
+
registerProvider(provider: PropertiesProvider): () => void
|
|
116
|
+
registerComponent(name: string, renderer: PropertyComponentRenderer): () => void
|
|
117
|
+
registerDataProvider(name: string, provider: unknown): () => void
|
|
118
|
+
getDataProvider(name: string): unknown
|
|
119
|
+
createContext(selection?: ElementSelection | null, element?: BpmnNode | BpmnEdge | null): PropertiesContext
|
|
120
|
+
getGroups(selection?: ElementSelection | null, element?: BpmnNode | BpmnEdge | null): { groups: PropertyGroup[]; context: PropertiesContext }
|
|
121
|
+
getValue(entry: PropertyEntry, context: PropertiesContext): unknown
|
|
122
|
+
setValue(entry: PropertyEntry, value: unknown, context: PropertiesContext): unknown
|
|
123
|
+
}
|
|
124
|
+
export function createPropertiesRegistry(options?: PropertiesRegistryOptions): PropertiesRegistry
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { NODE_DEFINITIONS } from '../core/index.js';
|
|
2
|
+
|
|
3
|
+
function isBlank(value) {
|
|
4
|
+
if (value === undefined || value === null || value === '') return true;
|
|
5
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
6
|
+
if (typeof value === 'object') return Object.keys(value).length === 0;
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getPath(object, path, fallback) {
|
|
11
|
+
if (!path) return object ?? fallback;
|
|
12
|
+
const value = String(path).split('.').reduce((current, part) => current == null ? undefined : current[part], object);
|
|
13
|
+
return value === undefined ? fallback : value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function setPath(object, path, value) {
|
|
17
|
+
const parts = String(path).split('.');
|
|
18
|
+
const root = { ...(object || {}) };
|
|
19
|
+
let target = root;
|
|
20
|
+
let source = object || {};
|
|
21
|
+
parts.forEach((part, index) => {
|
|
22
|
+
if (index === parts.length - 1) {
|
|
23
|
+
target[part] = value;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const sourceValue = source?.[part];
|
|
27
|
+
target[part] = Array.isArray(sourceValue) ? [...sourceValue] : { ...(sourceValue || {}) };
|
|
28
|
+
target = target[part];
|
|
29
|
+
source = sourceValue || {};
|
|
30
|
+
});
|
|
31
|
+
return root;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeOptions(options = []) {
|
|
35
|
+
return (options || []).map((option) => {
|
|
36
|
+
if (Array.isArray(option)) return { value: option[0], label: option[1] };
|
|
37
|
+
if (typeof option === 'string') return { value: option, label: option };
|
|
38
|
+
return option;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function matchList(value, list) {
|
|
43
|
+
if (!list || !list.length) return true;
|
|
44
|
+
return list.includes(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function matchesAppliesTo(appliesTo, context) {
|
|
48
|
+
if (!appliesTo) return true;
|
|
49
|
+
if (typeof appliesTo === 'function') return !!appliesTo(context);
|
|
50
|
+
const { selection, element, definition, model } = context;
|
|
51
|
+
if (appliesTo.kind && appliesTo.kind !== context.kind) return false;
|
|
52
|
+
if (appliesTo.kinds && !matchList(context.kind, appliesTo.kinds)) return false;
|
|
53
|
+
if (appliesTo.engine && appliesTo.engine !== model.engine) return false;
|
|
54
|
+
if (appliesTo.engines && !matchList(model.engine, appliesTo.engines)) return false;
|
|
55
|
+
if (appliesTo.nodeType && element?.type !== appliesTo.nodeType) return false;
|
|
56
|
+
if (appliesTo.nodeTypes && !matchList(element?.type, appliesTo.nodeTypes)) return false;
|
|
57
|
+
if (appliesTo.bpmnType && element?.bpmnType !== appliesTo.bpmnType) return false;
|
|
58
|
+
if (appliesTo.bpmnTypes && !matchList(element?.bpmnType, appliesTo.bpmnTypes)) return false;
|
|
59
|
+
if (appliesTo.elementKind && definition?.kind !== appliesTo.elementKind) return false;
|
|
60
|
+
if (appliesTo.elementKinds && !matchList(definition?.kind, appliesTo.elementKinds)) return false;
|
|
61
|
+
if (appliesTo.eventDefinition && definition?.eventDefinition !== appliesTo.eventDefinition) return false;
|
|
62
|
+
if (appliesTo.eventDefinitions && !matchList(definition?.eventDefinition, appliesTo.eventDefinitions)) return false;
|
|
63
|
+
if (appliesTo.selectionKind && selection?.kind !== appliesTo.selectionKind) return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function insertGroup(groups, group) {
|
|
68
|
+
if (!group || !group.id) return groups;
|
|
69
|
+
const position = group.position || {};
|
|
70
|
+
if (position.remove) return groups.filter((item) => item.id !== position.remove && item.id !== group.id);
|
|
71
|
+
|
|
72
|
+
let next = [...groups];
|
|
73
|
+
let index = next.length;
|
|
74
|
+
const existingIndex = next.findIndex((item) => item.id === group.id);
|
|
75
|
+
if (existingIndex >= 0) {
|
|
76
|
+
index = existingIndex;
|
|
77
|
+
next.splice(existingIndex, 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (position.replace) {
|
|
81
|
+
const found = next.findIndex((item) => item.id === position.replace);
|
|
82
|
+
if (found >= 0) {
|
|
83
|
+
index = found;
|
|
84
|
+
next.splice(found, 1);
|
|
85
|
+
}
|
|
86
|
+
} else if (position.prepend) index = 0;
|
|
87
|
+
else if (position.before) {
|
|
88
|
+
const found = next.findIndex((item) => item.id === position.before);
|
|
89
|
+
if (found >= 0) index = found;
|
|
90
|
+
} else if (position.after) {
|
|
91
|
+
const found = next.findIndex((item) => item.id === position.after);
|
|
92
|
+
if (found >= 0) index = found + 1;
|
|
93
|
+
} else if (position.append) index = next.length;
|
|
94
|
+
|
|
95
|
+
index = Math.max(0, Math.min(index, next.length));
|
|
96
|
+
next.splice(index, 0, group);
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function propertyEntry(config) {
|
|
101
|
+
return {
|
|
102
|
+
type: 'text',
|
|
103
|
+
...config,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function propertyGroup(config) {
|
|
108
|
+
return {
|
|
109
|
+
entries: [],
|
|
110
|
+
collapsible: true,
|
|
111
|
+
...config,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const DEVELOPER_GROUPS = new Set([
|
|
116
|
+
'namespaces', 'extension-elements', 'extension-attributes', 'flowable-process-execution', 'activiti-process-execution',
|
|
117
|
+
'engine-execution', 'execution-listeners', 'task-listeners', 'field-injection', 'edge-listeners',
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
function profileAllows(item, profile) {
|
|
121
|
+
const level = item.level || (DEVELOPER_GROUPS.has(item.id) ? 'developer' : 'business');
|
|
122
|
+
if (profile === 'developer') return true;
|
|
123
|
+
return level !== 'developer';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class PropertiesRegistry {
|
|
127
|
+
constructor({ studio = null, designer = null, components = {}, dataProviders = {} } = {}) {
|
|
128
|
+
this.studio = studio || designer;
|
|
129
|
+
this.designer = this.studio;
|
|
130
|
+
this._providers = [];
|
|
131
|
+
this.components = new Map(Object.entries(components));
|
|
132
|
+
this.dataProviders = new Map(Object.entries(dataProviders));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setDesigner(designer) {
|
|
136
|
+
this.studio = designer;
|
|
137
|
+
this.designer = designer;
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setStudio(studio) {
|
|
142
|
+
this.studio = studio;
|
|
143
|
+
this.designer = studio;
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
registerProvider(priority = 500, provider) {
|
|
148
|
+
if (typeof priority === 'object' && provider === undefined) {
|
|
149
|
+
provider = priority;
|
|
150
|
+
priority = provider.priority ?? 500;
|
|
151
|
+
}
|
|
152
|
+
const record = { priority, provider, order: this._providers.length };
|
|
153
|
+
this._providers.push(record);
|
|
154
|
+
this._providers.sort((a, b) => b.priority - a.priority || a.order - b.order);
|
|
155
|
+
return () => { this._providers = this._providers.filter((item) => item !== record); };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
registerComponent(name, renderer) {
|
|
159
|
+
this.components.set(name, renderer);
|
|
160
|
+
return () => this.components.delete(name);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
registerDataProvider(name, provider) {
|
|
164
|
+
this.dataProviders.set(name, provider);
|
|
165
|
+
return () => this.dataProviders.delete(name);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getDataProvider(name) {
|
|
169
|
+
return this.dataProviders.get(name) || null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
createContext(selection, element) {
|
|
173
|
+
const studio = this.studio;
|
|
174
|
+
if (selection === undefined) selection = studio?.selection ?? null;
|
|
175
|
+
if (element === undefined) element = selection ? studio?.getSelectedElement?.() : null;
|
|
176
|
+
const designer = studio;
|
|
177
|
+
const model = studio?.model || null;
|
|
178
|
+
let kind = selection?.kind || 'none';
|
|
179
|
+
if (kind !== 'process' && !element) kind = 'none';
|
|
180
|
+
const definition = kind === 'node' ? (NODE_DEFINITIONS[element.type] || NODE_DEFINITIONS.generic) : null;
|
|
181
|
+
const context = {
|
|
182
|
+
registry: this,
|
|
183
|
+
studio,
|
|
184
|
+
designer,
|
|
185
|
+
model,
|
|
186
|
+
selection,
|
|
187
|
+
element: kind === 'process' ? model : kind === 'none' ? null : element,
|
|
188
|
+
kind,
|
|
189
|
+
definition,
|
|
190
|
+
engine: model?.engine,
|
|
191
|
+
profile: studio?.propertiesProfile || 'business',
|
|
192
|
+
is(value) {
|
|
193
|
+
if (typeof value === 'function') return !!value(context);
|
|
194
|
+
if (value === kind) return true;
|
|
195
|
+
if (kind === 'node') return value === element?.type || value === element?.bpmnType || value === definition?.kind;
|
|
196
|
+
return false;
|
|
197
|
+
},
|
|
198
|
+
get(path, fallback) {
|
|
199
|
+
const root = kind === 'process' ? model : element;
|
|
200
|
+
return getPath(root, path, fallback);
|
|
201
|
+
},
|
|
202
|
+
set(path, value) {
|
|
203
|
+
if (!studio || !model || kind === 'none') return;
|
|
204
|
+
const parts = String(path).split('.');
|
|
205
|
+
if (kind === 'process') {
|
|
206
|
+
if (path === 'id') { studio.commands?.changeElementId('process', model.id, value); return; }
|
|
207
|
+
if (parts[0] === 'properties') {
|
|
208
|
+
const next = setPath(model.properties || {}, parts.slice(1).join('.'), value);
|
|
209
|
+
studio.commands?.updateProcess({ properties: next });
|
|
210
|
+
} else if (parts[0] === 'settings') {
|
|
211
|
+
const next = setPath(model.settings || {}, parts.slice(1).join('.'), value);
|
|
212
|
+
studio.commands?.updateProcess({ settings: next });
|
|
213
|
+
} else if (parts[0] === 'resources') {
|
|
214
|
+
const next = setPath(model.resources || {}, parts.slice(1).join('.'), value);
|
|
215
|
+
studio.commands?.updateProcess({ resources: next });
|
|
216
|
+
} else {
|
|
217
|
+
studio.commands?.updateProcess(setPath({}, path, value));
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (kind === 'node') {
|
|
222
|
+
if (path === 'id') studio.commands?.changeElementId('node', element.id, value);
|
|
223
|
+
else if (parts[0] === 'properties') {
|
|
224
|
+
const next = setPath(element.properties || {}, parts.slice(1).join('.'), value);
|
|
225
|
+
studio.commands?.updateNode(element.id, { properties: next });
|
|
226
|
+
} else studio.commands?.updateNode(element.id, setPath({}, path, value));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (kind === 'edge') {
|
|
230
|
+
if (path === 'id') studio.commands?.changeElementId('edge', element.id, value);
|
|
231
|
+
else if (parts[0] === 'properties') {
|
|
232
|
+
const next = setPath(element.properties || {}, parts.slice(1).join('.'), value);
|
|
233
|
+
studio.commands?.updateEdge(element.id, { properties: next });
|
|
234
|
+
} else studio.commands?.updateEdge(element.id, setPath({}, path, value));
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
extensions: {
|
|
238
|
+
get(name, fallback = '') {
|
|
239
|
+
return getPath(kind === 'process' ? model?.properties?.extensionAttributes : element?.properties?.extensionAttributes, name, fallback);
|
|
240
|
+
},
|
|
241
|
+
set(name, value) {
|
|
242
|
+
const current = kind === 'process' ? (model?.properties?.extensionAttributes || {}) : (element?.properties?.extensionAttributes || {});
|
|
243
|
+
const next = { ...current, [name]: value };
|
|
244
|
+
context.set('properties.extensionAttributes', next);
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
data(name) { return context.registry.getDataProvider(name); },
|
|
248
|
+
};
|
|
249
|
+
return context;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
getGroups(selection, element) {
|
|
253
|
+
const context = this.createContext(selection, element);
|
|
254
|
+
if (context.kind === 'none' || context.kind === 'multi') return { groups: [], context };
|
|
255
|
+
let groups = [];
|
|
256
|
+
for (const { provider } of this._providers) {
|
|
257
|
+
if (!provider || !matchesAppliesTo(provider.appliesTo, context)) continue;
|
|
258
|
+
let output;
|
|
259
|
+
if (typeof provider.getGroups === 'function') output = provider.getGroups(context);
|
|
260
|
+
else if (typeof provider.groups === 'function') output = provider.groups(context);
|
|
261
|
+
else output = provider.groups;
|
|
262
|
+
if (typeof output === 'function') {
|
|
263
|
+
const result = output(groups, context);
|
|
264
|
+
if (Array.isArray(result)) groups = result;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const providerGroups = Array.isArray(output) ? output : output ? [output] : [];
|
|
268
|
+
for (const group of providerGroups) {
|
|
269
|
+
if (!group || !matchesAppliesTo(group.appliesTo, context) || !profileAllows(group, context.profile)) continue;
|
|
270
|
+
const resolved = {
|
|
271
|
+
...group,
|
|
272
|
+
entries: (group.entries || []).filter((entry) => entry && profileAllows(entry, context.profile) && matchesAppliesTo(entry.appliesTo, context) && (typeof entry.visible !== 'function' || entry.visible(context))),
|
|
273
|
+
};
|
|
274
|
+
if (!resolved.entries.length && !resolved.alwaysShow) continue;
|
|
275
|
+
groups = insertGroup(groups, resolved);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { groups, context };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
getValue(entry, context) {
|
|
282
|
+
if (typeof entry.getValue === 'function') return entry.getValue(context);
|
|
283
|
+
if (entry.path) return context.get(entry.path, entry.defaultValue);
|
|
284
|
+
return entry.value ?? entry.defaultValue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
setValue(entry, value, context) {
|
|
288
|
+
if (typeof entry.parse === 'function') value = entry.parse(value, context);
|
|
289
|
+
if (typeof entry.setValue === 'function') return entry.setValue(context, value);
|
|
290
|
+
if (entry.path) return context.set(entry.path, value);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
validateEntry(entry, context) {
|
|
294
|
+
if (typeof entry.validate !== 'function') return null;
|
|
295
|
+
return entry.validate(this.getValue(entry, context), context);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
configuredCount(group, context) {
|
|
299
|
+
return (group.entries || []).reduce((count, entry) => {
|
|
300
|
+
if (['readonly', 'action'].includes(entry.type)) return count;
|
|
301
|
+
const value = this.getValue(entry, context);
|
|
302
|
+
if (typeof entry.isConfigured === 'function') return count + (entry.isConfigured(value, context) ? 1 : 0);
|
|
303
|
+
if (Object.prototype.hasOwnProperty.call(entry, 'defaultValue') && value === entry.defaultValue) return count;
|
|
304
|
+
if (value === false || isBlank(value)) return count;
|
|
305
|
+
return count + 1;
|
|
306
|
+
}, 0);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function createPropertiesRegistry(options) {
|
|
311
|
+
return new PropertiesRegistry(options);
|
|
312
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { propertyEntry, propertyGroup } from '../properties-core/index.js';
|
|
2
|
+
|
|
3
|
+
const activityTypes = new Set(['task', 'userTask', 'manualTask', 'serviceTask', 'scriptTask', 'businessRuleTask', 'sendTask', 'receiveTask', 'callActivity', 'subProcess', 'eventSubProcess', 'transaction', 'adHocSubProcess']);
|
|
4
|
+
const implementationTypes = [['class', 'Java Class'], ['delegateExpression', 'Delegate Expression'], ['expression', 'Expression']];
|
|
5
|
+
const implementationColumns = [
|
|
6
|
+
{ key: 'event', label: 'Event', type: 'select', options: [['start', 'start'], ['end', 'end'], ['take', 'take']] },
|
|
7
|
+
{ key: 'implementationType', label: '类型', type: 'select', options: implementationTypes },
|
|
8
|
+
{ key: 'implementation', label: '实现' },
|
|
9
|
+
];
|
|
10
|
+
const taskListenerColumns = [
|
|
11
|
+
{ key: 'event', label: 'Event', type: 'select', options: [['create', 'create'], ['assignment', 'assignment'], ['complete', 'complete'], ['delete', 'delete'], ['all', 'all']] },
|
|
12
|
+
{ key: 'implementationType', label: '类型', type: 'select', options: implementationTypes },
|
|
13
|
+
{ key: 'implementation', label: '实现' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const flowableProcessProvider = {
|
|
17
|
+
id: 'flowable-process', priority: 640, appliesTo: { kind: 'process', engine: 'flowable' },
|
|
18
|
+
getGroups() {
|
|
19
|
+
return [propertyGroup({
|
|
20
|
+
id: 'flowable-process-execution', label: 'Flowable 执行监听器', collapsed: true, position: { after: 'global-resources' },
|
|
21
|
+
entries: [propertyEntry({ id: 'process-listeners', type: 'table', label: 'Execution Listeners', path: 'properties.executionListeners', rowLabel: '监听器', defaultRow: () => ({ event: 'start', implementationType: 'class', implementation: '' }), columns: implementationColumns.filter((c) => c.key !== 'event' || true) })],
|
|
22
|
+
})];
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const flowableUserTaskProvider = {
|
|
27
|
+
id: 'flowable-user-task', priority: 660, appliesTo: { kind: 'node', nodeType: 'userTask', engine: 'flowable' },
|
|
28
|
+
getGroups() {
|
|
29
|
+
return [
|
|
30
|
+
propertyGroup({
|
|
31
|
+
id: 'assignment', label: '人员分配 · Flowable', position: { after: 'basic' },
|
|
32
|
+
entries: [
|
|
33
|
+
propertyEntry({ id: 'assignee', type: 'user-select', label: '负责人 Assignee', path: 'properties.assignee', dataProvider: 'identity', allowExpression: true, placeholder: '${manager}' }),
|
|
34
|
+
propertyEntry({ id: 'owner', type: 'user-select', label: 'Owner', path: 'properties.owner', dataProvider: 'identity', allowExpression: true }),
|
|
35
|
+
propertyEntry({ id: 'candidate-users', type: 'tags', label: '候选用户', path: 'properties.candidateUsers', dataProvider: 'identity', placeholder: 'user1,user2' }),
|
|
36
|
+
propertyEntry({ id: 'candidate-groups', type: 'tags', label: '候选组', path: 'properties.candidateGroups', dataProvider: 'identity', placeholder: 'manager,finance' }),
|
|
37
|
+
],
|
|
38
|
+
}),
|
|
39
|
+
propertyGroup({
|
|
40
|
+
id: 'task-config', label: '任务属性', position: { after: 'assignment' },
|
|
41
|
+
entries: [
|
|
42
|
+
propertyEntry({ id: 'form-key', type: 'form-select', label: 'Form Key', path: 'properties.formKey', dataProvider: 'forms', allowCustom: true }),
|
|
43
|
+
propertyEntry({ id: 'due-date', type: 'expression', label: 'Due Date', path: 'properties.dueDate', placeholder: '${now().plusDays(1)}' }),
|
|
44
|
+
propertyEntry({ id: 'priority', type: 'number', label: 'Priority', path: 'properties.priority', min: 0, max: 100 }),
|
|
45
|
+
propertyEntry({ id: 'category', label: 'Category', path: 'properties.category' }),
|
|
46
|
+
propertyEntry({ id: 'skip-expression', type: 'expression', label: 'Skip Expression', path: 'properties.skipExpression', placeholder: '${skipApproval}' }),
|
|
47
|
+
],
|
|
48
|
+
}),
|
|
49
|
+
propertyGroup({
|
|
50
|
+
id: 'task-listeners', label: 'Task Listeners', collapsed: true, position: { after: 'multi-instance' },
|
|
51
|
+
entries: [propertyEntry({ id: 'task-listener-table', type: 'table', label: '任务监听器', path: 'properties.taskListeners', rowLabel: 'Task Listener', defaultRow: () => ({ event: 'create', implementationType: 'class', implementation: '' }), columns: taskListenerColumns })],
|
|
52
|
+
}),
|
|
53
|
+
];
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const flowableImplementationProvider = {
|
|
58
|
+
id: 'flowable-implementation', priority: 660,
|
|
59
|
+
appliesTo: (ctx) => ctx.kind === 'node' && ctx.model.engine === 'flowable' && ['serviceTask', 'sendTask', 'businessRuleTask'].includes(ctx.element.type),
|
|
60
|
+
getGroups(context) {
|
|
61
|
+
const label = context.element.type === 'businessRuleTask' ? '业务规则实现 · Flowable' : context.element.type === 'sendTask' ? '发送实现 · Flowable' : '服务实现 · Flowable';
|
|
62
|
+
return [
|
|
63
|
+
propertyGroup({ id: 'engine-implementation', label, position: { after: 'basic' }, entries: [
|
|
64
|
+
propertyEntry({ id: 'implementation-type', type: 'select', label: '实现方式', path: 'properties.implementationType', options: implementationTypes }),
|
|
65
|
+
propertyEntry({ id: 'implementation', type: 'expression', label: '实现值', path: 'properties.implementation', placeholder: 'com.example.MyDelegate / ${delegateBean}' }),
|
|
66
|
+
propertyEntry({ id: 'result-variable', label: 'Result Variable', path: 'properties.resultVariable', placeholder: 'result' }),
|
|
67
|
+
] }),
|
|
68
|
+
propertyGroup({ id: 'field-injection', label: 'Field Injection', collapsed: true, position: { after: 'engine-implementation' }, entries: [
|
|
69
|
+
propertyEntry({ id: 'fields', type: 'table', label: '字段注入', path: 'properties.fields', rowLabel: 'Field', defaultRow: () => ({ name: '', type: 'string', value: '' }), columns: [{ key: 'name', label: 'Name' }, { key: 'type', label: '类型', type: 'select', options: [['string', 'String'], ['expression', 'Expression']] }, { key: 'value', label: 'Value' }] }),
|
|
70
|
+
] }),
|
|
71
|
+
];
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const flowableActivityExecutionProvider = {
|
|
76
|
+
id: 'flowable-activity-execution', priority: 610,
|
|
77
|
+
appliesTo: (ctx) => ctx.kind === 'node' && ctx.model.engine === 'flowable' && activityTypes.has(ctx.element.type),
|
|
78
|
+
getGroups() {
|
|
79
|
+
return [
|
|
80
|
+
propertyGroup({ id: 'engine-execution', label: '执行配置 · Flowable', collapsed: true, position: { after: 'multi-instance' }, entries: [
|
|
81
|
+
propertyEntry({ id: 'async', type: 'switch', label: 'Async', path: 'properties.async' }),
|
|
82
|
+
propertyEntry({ id: 'exclusive', type: 'switch', label: 'Exclusive Job', path: 'properties.exclusive' }),
|
|
83
|
+
propertyEntry({ id: 'async-leave', type: 'switch', label: 'Async Leave', path: 'properties.asyncLeave' }),
|
|
84
|
+
propertyEntry({ id: 'failed-retry', type: 'expression', label: 'Failed Job Retry Cycle', path: 'properties.failedJobRetryTimeCycle', placeholder: 'R3/PT5M' }),
|
|
85
|
+
] }),
|
|
86
|
+
propertyGroup({ id: 'execution-listeners', label: 'Execution Listeners', collapsed: true, position: { after: 'engine-execution' }, entries: [
|
|
87
|
+
propertyEntry({ id: 'execution-listener-table', type: 'table', label: '执行监听器', path: 'properties.executionListeners', rowLabel: 'Execution Listener', defaultRow: () => ({ event: 'start', implementationType: 'class', implementation: '' }), columns: implementationColumns.filter((column) => column.key !== 'event' || true).map((column) => column.key === 'event' ? { ...column, options: [['start', 'start'], ['end', 'end']] } : column) }),
|
|
88
|
+
] }),
|
|
89
|
+
];
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const flowableCallActivityProvider = {
|
|
94
|
+
id: 'flowable-call-activity', priority: 650, appliesTo: { kind: 'node', nodeType: 'callActivity', engine: 'flowable' },
|
|
95
|
+
getGroups() { return [propertyGroup({ id: 'flowable-call', label: 'Flowable 调用选项', collapsed: true, position: { after: 'call-activity' }, entries: [
|
|
96
|
+
propertyEntry({ id: 'same-deployment', type: 'switch', label: 'Same Deployment', path: 'properties.sameDeployment' }),
|
|
97
|
+
propertyEntry({ id: 'process-instance-name', type: 'expression', label: 'Process Instance Name', path: 'properties.processInstanceName' }),
|
|
98
|
+
] })]; },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const flowableEdgeProvider = {
|
|
102
|
+
id: 'flowable-edge-listeners', priority: 620,
|
|
103
|
+
appliesTo: (ctx) => ctx.kind === 'edge' && ctx.model.engine === 'flowable' && (ctx.element.type || 'sequenceFlow') === 'sequenceFlow',
|
|
104
|
+
getGroups() { return [propertyGroup({ id: 'edge-listeners', label: 'Take Listener · Flowable', collapsed: true, position: { after: 'condition' }, entries: [
|
|
105
|
+
propertyEntry({ id: 'edge-execution-listeners', type: 'table', label: 'Execution Listeners', path: 'properties.executionListeners', rowLabel: 'Take Listener', defaultRow: () => ({ event: 'take', implementationType: 'class', implementation: '' }), columns: implementationColumns.map((column) => column.key === 'event' ? { ...column, options: [['take', 'take']] } : column) }),
|
|
106
|
+
] })]; },
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const flowablePropertiesProviders = [flowableProcessProvider, flowableUserTaskProvider, flowableImplementationProvider, flowableActivityExecutionProvider, flowableCallActivityProvider, flowableEdgeProvider];
|
|
110
|
+
|
|
111
|
+
export function registerFlowableProperties(registry) {
|
|
112
|
+
const disposers = flowablePropertiesProviders.map((provider) => registry.registerProvider(provider.priority ?? 500, provider));
|
|
113
|
+
return () => disposers.forEach((dispose) => dispose());
|
|
114
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BpmnEdge, BpmnNode, ElementSelection } from '../core/index.js'
|
|
2
|
+
import type { IconRegistry } from '../icons/index.js'
|
|
3
|
+
import type { PropertiesContext, PropertiesRegistry, PropertiesStudio, PropertyGroup } from '../properties-core/index.js'
|
|
4
|
+
import type { NovaThemeInput, NovaThemeMode, NovaThemeState, ThemeController } from '../theme/index.js'
|
|
5
|
+
|
|
6
|
+
export interface PropertiesPanelOptions {
|
|
7
|
+
container: HTMLElement
|
|
8
|
+
registry: PropertiesRegistry
|
|
9
|
+
studio?: PropertiesStudio | null
|
|
10
|
+
designer?: PropertiesStudio | null
|
|
11
|
+
canvas?: unknown
|
|
12
|
+
iconRegistry?: IconRegistry
|
|
13
|
+
emptyText?: string
|
|
14
|
+
themeController?: ThemeController | null
|
|
15
|
+
theme?: NovaThemeInput | null
|
|
16
|
+
onThemeChange?: ((state: NovaThemeState) => void) | null
|
|
17
|
+
}
|
|
18
|
+
export class PropertiesPanel {
|
|
19
|
+
constructor(options: PropertiesPanelOptions)
|
|
20
|
+
render(selection?: ElementSelection | null, element?: BpmnNode | BpmnEdge | null): { groups: PropertyGroup[]; context: PropertiesContext }
|
|
21
|
+
setTheme(theme: NovaThemeInput): NovaThemeState
|
|
22
|
+
setThemeMode(mode: NovaThemeMode): NovaThemeState
|
|
23
|
+
getThemeState(): NovaThemeState
|
|
24
|
+
destroy(): void
|
|
25
|
+
}
|