@acorex-platform/module-platform-management-client 0.0.21

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 (45) hide show
  1. package/fesm2022/acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-V_ao4WVM.mjs +4599 -0
  2. package/fesm2022/acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-V_ao4WVM.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-platform-management-client-contracts.mjs +179 -0
  4. package/fesm2022/acorex-platform-module-platform-management-client-contracts.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.command-2Qu7aoCp.mjs +1542 -0
  6. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.command-2Qu7aoCp.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.types-DdygAori.mjs +71 -0
  8. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.types-DdygAori.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-module-platform-management-client-customize-menu.command-2319iHsD.mjs +231 -0
  10. package/fesm2022/acorex-platform-module-platform-management-client-customize-menu.command-2319iHsD.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-module-platform-management-client-data-list-pick-entity-item.command-CJVpqPmY.mjs +52 -0
  12. package/fesm2022/acorex-platform-module-platform-management-client-data-list-pick-entity-item.command-CJVpqPmY.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-module-platform-management-client-entity-master-actions-for-ai.util-DmGyoy9o.mjs +55 -0
  14. package/fesm2022/acorex-platform-module-platform-management-client-entity-master-actions-for-ai.util-DmGyoy9o.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-module-platform-management-client-execute-token.command-DNuHR-0y.mjs +152 -0
  16. package/fesm2022/acorex-platform-module-platform-management-client-execute-token.command-DNuHR-0y.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-module-platform-management-client-list-catalog-scopes.query-ByhDTWE6.mjs +71 -0
  18. package/fesm2022/acorex-platform-module-platform-management-client-list-catalog-scopes.query-ByhDTWE6.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-module-platform-management-client-list-editor-widgets.query-DITPMmbw.mjs +68 -0
  20. package/fesm2022/acorex-platform-module-platform-management-client-list-editor-widgets.query-DITPMmbw.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-module-platform-management-client-list-platform-navigation-for-ai.query-2oHMITw-.mjs +194 -0
  22. package/fesm2022/acorex-platform-module-platform-management-client-list-platform-navigation-for-ai.query-2oHMITw-.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-entities.query-DjStd3a8.mjs +136 -0
  24. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-entities.query-DjStd3a8.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-queries.query-C-IfkTel.mjs +122 -0
  26. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-queries.query-C-IfkTel.mjs.map +1 -0
  27. package/fesm2022/acorex-platform-module-platform-management-client-menu-list.component-C5HH5-Dw.mjs +524 -0
  28. package/fesm2022/acorex-platform-module-platform-management-client-menu-list.component-C5HH5-Dw.mjs.map +1 -0
  29. package/fesm2022/acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs +216 -0
  30. package/fesm2022/acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-module-platform-management-client-platform-console-data.util-BIkULtKk.mjs +81 -0
  32. package/fesm2022/acorex-platform-module-platform-management-client-platform-console-data.util-BIkULtKk.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs +137 -0
  34. package/fesm2022/acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs.map +1 -0
  35. package/fesm2022/acorex-platform-module-platform-management-client-rest-api-generator-widget-column.component-CYb2zOSj.mjs +45 -0
  36. package/fesm2022/acorex-platform-module-platform-management-client-rest-api-generator-widget-column.component-CYb2zOSj.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-BBwrzcXa.mjs +178 -0
  38. package/fesm2022/acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-BBwrzcXa.mjs.map +1 -0
  39. package/fesm2022/acorex-platform-module-platform-management-client-rest-api-generator-widget-view.component-DBlZVz6u.mjs +45 -0
  40. package/fesm2022/acorex-platform-module-platform-management-client-rest-api-generator-widget-view.component-DBlZVz6u.mjs.map +1 -0
  41. package/fesm2022/acorex-platform-module-platform-management-client.mjs +2 -0
  42. package/fesm2022/acorex-platform-module-platform-management-client.mjs.map +1 -0
  43. package/package.json +28 -0
  44. package/types/acorex-platform-module-platform-management-client-contracts.d.ts +258 -0
  45. package/types/acorex-platform-module-platform-management-client.d.ts +548 -0
@@ -0,0 +1,4599 @@
1
+ import { AXPSessionService, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
2
+ import { AXPSettingsService, AXPMenuProviderService, AXPTokenDefinitionService, AXP_TOKEN_DEFINITION_PROVIDER, AXP_PROTECTED_ROUTE_GUARDS, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER } from '@acorex-platform/framework-client/common';
3
+ import { AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER, AXP_MODULE_MANIFEST_PROVIDER, AXP_FEATURE_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/core';
4
+ import { AXPRootLayoutComponent } from '@acorex-platform/framework-client/themes/default';
5
+ import { AXP_WIDGETS_EDITOR_CATEGORY, AXPWidgetCoreModule, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/layout/widget-core';
6
+ import { provideCommandSetups, AXPCommandExecutor, provideQuerySetups } from '@acorex-platform/framework-client/runtime';
7
+ import * as i0 from '@angular/core';
8
+ import { Injectable, inject, NgModule, Injector, input, output, viewChild, signal, DestroyRef, computed, afterNextRender, effect, ViewEncapsulation, ChangeDetectionStrategy, Component, Directive } from '@angular/core';
9
+ import { ROUTES } from '@angular/router';
10
+ import { AXPWidgetGroupEnum } from '@acorex-platform/framework-shared/widget-core';
11
+ import { AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY, AXP_READONLY_PROPERTY, AXP_DISABLED_PROPERTY } from '@acorex-platform/framework-client/layout/widgets';
12
+ import { AXPPlatformManagementFeatureKeys, RootConfig, AXM_TOKEN_RESOLVE_COMMAND_KEY, AXPApplicationsPlatformPermissionKeys, AXPPlatformManagementMenuKeys } from '@acorex-platform/module-platform-management-client/contracts';
13
+ import { AXPPlatformScope, runtimeCommandPayloadData } from '@acorex-platform/framework-shared/core';
14
+ import { AXPMenuCustomizerService, AXP_MENU_CUSTOMIZER_SERVICE } from '@acorex-platform/framework-client/layout/components';
15
+ import { cloneDeep } from 'lodash';
16
+ import { AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
17
+ import { AXDialogService } from '@acorex/components/dialog';
18
+ import { AXTranslationService } from '@acorex/core/translation';
19
+ import { SwUpdate } from '@angular/service-worker';
20
+ import { AXPCommonMenuKeys, AXPCommonPermissionGroupKeys } from '@acorex-platform/module-common-client/contracts';
21
+ import { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex-platform/framework-client/domain';
22
+ import { AXPanViewDirective } from '@acorex/cdk/pan-view';
23
+ import * as i1$1 from '@acorex/components/button';
24
+ import { AXButtonModule } from '@acorex/components/button';
25
+ import * as i2 from '@acorex/components/decorators';
26
+ import { AXDecoratorModule } from '@acorex/components/decorators';
27
+ import * as i1 from '@angular/common';
28
+ import { CommonModule } from '@angular/common';
29
+
30
+ const REST_API_GENERATOR_WIDGET_NAME = 'rest-api-generator-editor';
31
+ const REST_API_GENERATOR_IMPORT_DEBOUNCE_MS = 500;
32
+
33
+ //#region ----- Imports -----
34
+ //#endregion
35
+ //#region ----- Config -----
36
+ const AXMRestApiGeneratorWidget = {
37
+ name: REST_API_GENERATOR_WIDGET_NAME,
38
+ title: '@platform-management:query-definitions.wizard.create.rest-api-generator.title',
39
+ description: '@platform-management:query-definitions.wizard.create.rest-api-generator.description',
40
+ icon: 'fa-light fa-cloud-arrow-up',
41
+ categories: AXP_WIDGETS_EDITOR_CATEGORY,
42
+ groups: [AXPWidgetGroupEnum.UtilityWidget],
43
+ type: 'editor',
44
+ defaultFilterWidgetName: 'string-filter',
45
+ options: {
46
+ needLabel: false,
47
+ },
48
+ properties: [AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY, AXP_READONLY_PROPERTY, AXP_DISABLED_PROPERTY],
49
+ components: {
50
+ edit: {
51
+ component: () => import('./acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-BBwrzcXa.mjs').then((c) => c.AXMRestApiGeneratorWidgetEditComponent),
52
+ },
53
+ designer: {
54
+ component: () => import('./acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-BBwrzcXa.mjs').then((c) => c.AXMRestApiGeneratorWidgetEditComponent),
55
+ },
56
+ view: {
57
+ component: () => import('./acorex-platform-module-platform-management-client-rest-api-generator-widget-view.component-DBlZVz6u.mjs').then((c) => c.AXMRestApiGeneratorWidgetViewComponent),
58
+ },
59
+ column: {
60
+ component: () => import('./acorex-platform-module-platform-management-client-rest-api-generator-widget-column.component-CYb2zOSj.mjs').then((c) => c.AXMRestApiGeneratorWidgetColumnComponent),
61
+ },
62
+ },
63
+ };
64
+ //#endregion
65
+
66
+ //#region ----- Constants -----
67
+ const HTTP_METHODS = ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'];
68
+ const VALUE_FLAGS = {
69
+ '-X': 'method',
70
+ '--request': 'method',
71
+ '-H': 'header',
72
+ '--header': 'header',
73
+ '-d': 'data',
74
+ '--data': 'data',
75
+ '--data-raw': 'data',
76
+ '--data-binary': 'data',
77
+ '--data-ascii': 'data',
78
+ '--data-urlencode': 'data',
79
+ '--json': 'json',
80
+ '-u': 'user',
81
+ '--user': 'user',
82
+ '--url': 'url',
83
+ '-A': 'user-agent',
84
+ '--user-agent': 'user-agent',
85
+ '-b': 'cookie',
86
+ '--cookie': 'cookie',
87
+ '-e': 'referer',
88
+ '--referer': 'referer',
89
+ '--oauth2-bearer': 'bearer',
90
+ '-F': 'form',
91
+ '--form': 'form',
92
+ '-o': 'ignore',
93
+ '--output': 'ignore',
94
+ '-w': 'ignore',
95
+ '--write-out': 'ignore',
96
+ '-m': 'ignore',
97
+ '--max-time': 'ignore',
98
+ '--connect-timeout': 'ignore',
99
+ '--retry': 'ignore',
100
+ };
101
+ const SKIP_FLAGS = new Set([
102
+ '-v',
103
+ '--verbose',
104
+ '-k',
105
+ '--insecure',
106
+ '--compressed',
107
+ '-s',
108
+ '--silent',
109
+ '-S',
110
+ '--show-error',
111
+ '-L',
112
+ '--location',
113
+ '-i',
114
+ '--include',
115
+ '-I',
116
+ '--head',
117
+ '-#',
118
+ '--progress-bar',
119
+ '--http1.0',
120
+ '--http1.1',
121
+ '--http2',
122
+ '--globoff',
123
+ '-g',
124
+ '--fail',
125
+ '-f',
126
+ '--remote-name',
127
+ '-O',
128
+ '--location-trusted',
129
+ '--no-progress-meter',
130
+ ]);
131
+ //#endregion
132
+ //#region ----- Public API -----
133
+ function createEmptyRestApiOutput() {
134
+ return {
135
+ url: '',
136
+ method: 'GET',
137
+ body: '',
138
+ headers: {
139
+ Authorization: '',
140
+ ContentType: '',
141
+ },
142
+ queries: [{}],
143
+ };
144
+ }
145
+ function normalizeRestApiOutput(value) {
146
+ const empty = createEmptyRestApiOutput();
147
+ if (value == null || typeof value !== 'object') {
148
+ return empty;
149
+ }
150
+ const headers = value.headers && typeof value.headers === 'object' ? value.headers : {};
151
+ const extraHeaders = {};
152
+ for (const [key, headerValue] of Object.entries(headers)) {
153
+ if (key === 'Authorization' || key === 'ContentType' || key === 'tempId') {
154
+ continue;
155
+ }
156
+ if (typeof headerValue === 'string' && headerValue.trim()) {
157
+ extraHeaders[key] = headerValue;
158
+ }
159
+ }
160
+ const authorization = headers['Authorization'];
161
+ const contentType = headers['ContentType'];
162
+ return {
163
+ url: typeof value.url === 'string' ? value.url.trim() : '',
164
+ method: normalizeMethod(value.method),
165
+ body: typeof value.body === 'string' ? value.body : value.body == null ? '' : String(value.body),
166
+ headers: {
167
+ Authorization: typeof authorization === 'string' ? authorization : '',
168
+ ContentType: typeof contentType === 'string' ? contentType : '',
169
+ ...extraHeaders,
170
+ },
171
+ queries: normalizeQueries(value.queries),
172
+ };
173
+ }
174
+ function formatRestApiSummary(value) {
175
+ const url = value?.url?.trim() ?? '';
176
+ if (!url) {
177
+ return '';
178
+ }
179
+ return `${value?.method ?? 'GET'} ${url}`;
180
+ }
181
+ function parseHttpRequest(input) {
182
+ const source = input.trim();
183
+ if (!source) {
184
+ return { ok: false };
185
+ }
186
+ if (looksLikeCurl(source)) {
187
+ return wrapParse('curl', parseCurl(source));
188
+ }
189
+ if (/\bfetch\s*\(/i.test(source)) {
190
+ return wrapParse('fetch', parseFetch(source));
191
+ }
192
+ if (/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+\S+/i.test(source)) {
193
+ return wrapParse('http', parseRawHttp(source));
194
+ }
195
+ if (source.startsWith('{')) {
196
+ return wrapParse('json', parseJsonRequest(source));
197
+ }
198
+ if (/^https?:\/\//i.test(source.split(/\s/, 1)[0] ?? '')) {
199
+ return wrapParse('url', parseUrlOnly(source));
200
+ }
201
+ return { ok: false };
202
+ }
203
+ //#endregion
204
+ //#region ----- Format parsers -----
205
+ function parseCurl(source) {
206
+ const tokens = tokenizeCommandLine(source);
207
+ if (tokens.length === 0) {
208
+ return null;
209
+ }
210
+ let index = 0;
211
+ if (tokens[index] === '$') {
212
+ index += 1;
213
+ }
214
+ if (/^curl(?:\.exe)?$/i.test(tokens[index] ?? '')) {
215
+ index += 1;
216
+ }
217
+ const output = createEmptyRestApiOutput();
218
+ let rawUrl = '';
219
+ let body = '';
220
+ let hasJsonFlag = false;
221
+ let hasFormFlag = false;
222
+ while (index < tokens.length) {
223
+ const token = tokens[index] ?? '';
224
+ if (token === '--') {
225
+ const rest = tokens[index + 1];
226
+ if (rest && !rawUrl) {
227
+ rawUrl = rest;
228
+ }
229
+ break;
230
+ }
231
+ if (!token.startsWith('-')) {
232
+ if (!rawUrl) {
233
+ rawUrl = token;
234
+ }
235
+ index += 1;
236
+ continue;
237
+ }
238
+ const { name, inlineValue, kind } = resolveFlag(token);
239
+ if (kind === 'skip') {
240
+ index += 1;
241
+ continue;
242
+ }
243
+ let flagValue = inlineValue;
244
+ if (flagValue == null && kind !== 'skip') {
245
+ const next = tokens[index + 1];
246
+ if (next != null && !next.startsWith('-')) {
247
+ flagValue = next;
248
+ index += 2;
249
+ }
250
+ else {
251
+ index += 1;
252
+ continue;
253
+ }
254
+ }
255
+ else {
256
+ index += 1;
257
+ }
258
+ if (kind === 'ignore' || flagValue == null) {
259
+ continue;
260
+ }
261
+ switch (name) {
262
+ case 'method':
263
+ output.method = normalizeMethod(flagValue);
264
+ break;
265
+ case 'header':
266
+ applyHeader(output, flagValue);
267
+ break;
268
+ case 'data':
269
+ body = appendBody(body, flagValue);
270
+ break;
271
+ case 'json':
272
+ body = appendBody(body, flagValue);
273
+ hasJsonFlag = true;
274
+ break;
275
+ case 'form':
276
+ body = appendBody(body, flagValue);
277
+ hasFormFlag = true;
278
+ break;
279
+ case 'user':
280
+ applyUser(output, flagValue);
281
+ break;
282
+ case 'url':
283
+ rawUrl = flagValue;
284
+ break;
285
+ case 'user-agent':
286
+ applyHeader(output, `User-Agent: ${flagValue}`);
287
+ break;
288
+ case 'cookie':
289
+ applyHeader(output, `Cookie: ${flagValue}`);
290
+ break;
291
+ case 'referer':
292
+ applyHeader(output, `Referer: ${flagValue}`);
293
+ break;
294
+ case 'bearer':
295
+ output.headers.Authorization = `Bearer ${flagValue}`;
296
+ break;
297
+ default:
298
+ break;
299
+ }
300
+ }
301
+ if (rawUrl.trim()) {
302
+ const split = splitUrlAndQuery(rawUrl);
303
+ output.url = split.url;
304
+ output.queries = split.queries;
305
+ }
306
+ output.body = body;
307
+ if (hasJsonFlag) {
308
+ output.headers.ContentType = output.headers.ContentType || 'application/json';
309
+ }
310
+ else if (hasFormFlag) {
311
+ output.headers.ContentType = output.headers.ContentType || 'multipart/form-data';
312
+ }
313
+ else if (body && !output.headers.ContentType) {
314
+ output.headers.ContentType = looksLikeJson(body) ? 'application/json' : 'application/x-www-form-urlencoded';
315
+ }
316
+ if (body && output.method === 'GET') {
317
+ output.method = 'POST';
318
+ }
319
+ const normalized = normalizeRestApiOutput(output);
320
+ return hasParsedContent(normalized) ? normalized : null;
321
+ }
322
+ function parseFetch(source) {
323
+ const fetchIndex = source.search(/\bfetch\s*\(/i);
324
+ if (fetchIndex < 0) {
325
+ return null;
326
+ }
327
+ const openParen = source.indexOf('(', fetchIndex);
328
+ if (openParen < 0) {
329
+ return null;
330
+ }
331
+ const args = extractCallArguments(source, openParen);
332
+ if (!args) {
333
+ return null;
334
+ }
335
+ const urlRaw = unquote(args[0]?.trim() ?? '');
336
+ if (!urlRaw) {
337
+ return null;
338
+ }
339
+ const output = createEmptyRestApiOutput();
340
+ const split = splitUrlAndQuery(urlRaw);
341
+ output.url = split.url;
342
+ output.queries = split.queries;
343
+ const optionsSrc = args[1]?.trim();
344
+ if (optionsSrc?.startsWith('{')) {
345
+ applyFetchOptions(output, optionsSrc);
346
+ }
347
+ return output.url ? normalizeRestApiOutput(output) : null;
348
+ }
349
+ function parseRawHttp(source) {
350
+ const normalized = source.replace(/\r\n/g, '\n');
351
+ const headerBodySplit = normalized.split(/\n\n/);
352
+ const headerBlock = headerBodySplit[0] ?? '';
353
+ const body = headerBodySplit.slice(1).join('\n\n');
354
+ const lines = headerBlock.split('\n').map((line) => line.trimEnd());
355
+ const requestLine = lines[0]?.trim() ?? '';
356
+ const requestMatch = requestLine.match(/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\S+)(?:\s+HTTP\/[\d.]+)?$/i);
357
+ if (!requestMatch) {
358
+ return null;
359
+ }
360
+ const output = createEmptyRestApiOutput();
361
+ output.method = normalizeMethod(requestMatch[1] ?? 'GET');
362
+ let path = requestMatch[2] ?? '';
363
+ let host = '';
364
+ for (let i = 1; i < lines.length; i += 1) {
365
+ const line = lines[i]?.trim() ?? '';
366
+ if (!line) {
367
+ continue;
368
+ }
369
+ applyHeader(output, line);
370
+ const headerMatch = line.match(/^([^:]+):\s*(.*)$/);
371
+ if (headerMatch && headerMatch[1]?.toLowerCase() === 'host') {
372
+ host = headerMatch[2]?.trim() ?? '';
373
+ }
374
+ }
375
+ if (/^https?:\/\//i.test(path)) {
376
+ const split = splitUrlAndQuery(path);
377
+ output.url = split.url;
378
+ output.queries = split.queries;
379
+ }
380
+ else {
381
+ const origin = host ? `https://${host}` : '';
382
+ const combined = origin ? `${origin}${path.startsWith('/') ? path : `/${path}`}` : path;
383
+ const split = splitUrlAndQuery(combined);
384
+ output.url = split.url;
385
+ output.queries = split.queries;
386
+ }
387
+ output.body = body.trim();
388
+ if (output.body && output.method === 'GET') {
389
+ output.method = 'POST';
390
+ }
391
+ return output.url ? normalizeRestApiOutput(output) : null;
392
+ }
393
+ function parseJsonRequest(source) {
394
+ let parsed;
395
+ try {
396
+ parsed = JSON.parse(source);
397
+ }
398
+ catch {
399
+ return null;
400
+ }
401
+ if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
402
+ return null;
403
+ }
404
+ const record = parsed;
405
+ const url = pickString(record, ['url', 'raw_url', 'rawUrl']);
406
+ if (!url) {
407
+ return null;
408
+ }
409
+ const output = createEmptyRestApiOutput();
410
+ const split = splitUrlAndQuery(url);
411
+ output.url = split.url;
412
+ output.queries = split.queries;
413
+ output.method = normalizeMethod(pickString(record, ['method']) ?? 'GET');
414
+ const body = record['body'] ?? record['data'];
415
+ if (typeof body === 'string') {
416
+ output.body = body;
417
+ }
418
+ else if (body != null && typeof body === 'object') {
419
+ output.body = JSON.stringify(body);
420
+ output.headers.ContentType = output.headers.ContentType || 'application/json';
421
+ }
422
+ const headers = record['headers'];
423
+ if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
424
+ for (const [key, value] of Object.entries(headers)) {
425
+ if (typeof value === 'string') {
426
+ applyHeader(output, `${key}: ${value}`);
427
+ }
428
+ }
429
+ }
430
+ const queries = record['queries'] ?? record['query'];
431
+ const fromQueries = queriesFromUnknown(queries);
432
+ if (fromQueries.length > 0 && hasNamedQuery(fromQueries)) {
433
+ output.queries = fromQueries;
434
+ }
435
+ return normalizeRestApiOutput(output);
436
+ }
437
+ function parseUrlOnly(source) {
438
+ const firstLine = source.split(/\s/, 1)[0]?.trim() ?? '';
439
+ if (!/^https?:\/\//i.test(firstLine)) {
440
+ return null;
441
+ }
442
+ const output = createEmptyRestApiOutput();
443
+ const split = splitUrlAndQuery(firstLine);
444
+ output.url = split.url;
445
+ output.queries = split.queries;
446
+ return output.url ? normalizeRestApiOutput(output) : null;
447
+ }
448
+ //#endregion
449
+ //#region ----- Curl helpers -----
450
+ function looksLikeCurl(source) {
451
+ const trimmed = source.trim().replace(/^\$\s+/, '');
452
+ if (!trimmed || trimmed.startsWith('{')) {
453
+ return false;
454
+ }
455
+ if (/^(?:curl(?:\.exe)?)\b/i.test(trimmed)) {
456
+ return true;
457
+ }
458
+ if (/\bcurl(?:\.exe)?\b/i.test(trimmed) && hasCurlFlags(trimmed)) {
459
+ return true;
460
+ }
461
+ return hasCurlFlags(trimmed);
462
+ }
463
+ function hasCurlFlags(source) {
464
+ return /(?:^|[\s\\])(?:-H|--header|-X|--request|-d|--data(?:-raw|-binary|-ascii|-urlencode)?|--json|--url|-F|--form|-A|--user-agent|-u|--user|-b|--cookie|-e|--referer|--oauth2-bearer)\b/.test(source);
465
+ }
466
+ function tokenizeCommandLine(input) {
467
+ const normalized = input
468
+ .replace(/\\\r?\n/g, ' ')
469
+ .replace(/\^\r?\n/g, ' ')
470
+ .replace(/`\r?\n/g, ' ')
471
+ .trim();
472
+ const tokens = [];
473
+ let i = 0;
474
+ while (i < normalized.length) {
475
+ while (i < normalized.length && /\s/.test(normalized[i] ?? '')) {
476
+ i += 1;
477
+ }
478
+ if (i >= normalized.length) {
479
+ break;
480
+ }
481
+ if (normalized[i] === '$' && (normalized[i + 1] === "'" || normalized[i + 1] === '"')) {
482
+ const quote = normalized[i + 1] ?? "'";
483
+ const quoted = readQuoted(normalized, i + 2, quote, true);
484
+ tokens.push(quoted.value);
485
+ i = quoted.nextIndex;
486
+ continue;
487
+ }
488
+ const quote = normalized[i];
489
+ if (quote === "'" || quote === '"' || quote === '`') {
490
+ const quoted = readQuoted(normalized, i + 1, quote, false);
491
+ tokens.push(quoted.value);
492
+ i = quoted.nextIndex;
493
+ continue;
494
+ }
495
+ let value = '';
496
+ while (i < normalized.length && !/\s/.test(normalized[i] ?? '')) {
497
+ const current = normalized[i] ?? '';
498
+ const next = normalized[i + 1] ?? '';
499
+ if (current === '$' && (next === "'" || next === '"') && value.length > 0) {
500
+ break;
501
+ }
502
+ if ((current === "'" || current === '"' || current === '`') && value.length > 0) {
503
+ break;
504
+ }
505
+ value += current;
506
+ i += 1;
507
+ }
508
+ tokens.push(value);
509
+ }
510
+ return tokens;
511
+ }
512
+ function readQuoted(source, start, quote, ansiC) {
513
+ let value = '';
514
+ let i = start;
515
+ while (i < source.length) {
516
+ const current = source[i] ?? '';
517
+ if (ansiC && current === '\\' && i + 1 < source.length) {
518
+ const decoded = decodeAnsiCEscape(source, i);
519
+ value += decoded.value;
520
+ i = decoded.nextIndex;
521
+ continue;
522
+ }
523
+ if (!ansiC && current === '\\' && quote !== "'" && i + 1 < source.length) {
524
+ value += source[i + 1] ?? '';
525
+ i += 2;
526
+ continue;
527
+ }
528
+ if (current === quote) {
529
+ i += 1;
530
+ break;
531
+ }
532
+ value += current;
533
+ i += 1;
534
+ }
535
+ return { value, nextIndex: i };
536
+ }
537
+ function decodeAnsiCEscape(source, index) {
538
+ const next = source[index + 1] ?? '';
539
+ switch (next) {
540
+ case 'n':
541
+ return { value: '\n', nextIndex: index + 2 };
542
+ case 'r':
543
+ return { value: '\r', nextIndex: index + 2 };
544
+ case 't':
545
+ return { value: '\t', nextIndex: index + 2 };
546
+ case 'v':
547
+ return { value: '\v', nextIndex: index + 2 };
548
+ case 'b':
549
+ return { value: '\b', nextIndex: index + 2 };
550
+ case 'f':
551
+ return { value: '\f', nextIndex: index + 2 };
552
+ case 'a':
553
+ return { value: '\u0007', nextIndex: index + 2 };
554
+ case '\\':
555
+ case "'":
556
+ case '"':
557
+ case '?':
558
+ return { value: next, nextIndex: index + 2 };
559
+ case 'x': {
560
+ const hex = source.slice(index + 2, index + 4);
561
+ if (/^[0-9a-fA-F]{2}$/.test(hex)) {
562
+ return { value: String.fromCharCode(Number.parseInt(hex, 16)), nextIndex: index + 4 };
563
+ }
564
+ return { value: next, nextIndex: index + 2 };
565
+ }
566
+ default:
567
+ return { value: next, nextIndex: index + 2 };
568
+ }
569
+ }
570
+ function resolveFlag(token) {
571
+ const eq = token.indexOf('=');
572
+ let flag = token;
573
+ let inlineValue;
574
+ if (eq > 1) {
575
+ flag = token.slice(0, eq);
576
+ inlineValue = token.slice(eq + 1);
577
+ }
578
+ if (SKIP_FLAGS.has(flag) || SKIP_FLAGS.has(token)) {
579
+ return { name: flag, kind: 'skip' };
580
+ }
581
+ const mapped = VALUE_FLAGS[flag];
582
+ if (mapped) {
583
+ return { name: mapped, inlineValue, kind: mapped };
584
+ }
585
+ if (flag.startsWith('-X') && flag.length > 2 && !flag.startsWith('--')) {
586
+ return { name: 'method', inlineValue: flag.slice(2), kind: 'method' };
587
+ }
588
+ if (flag.startsWith('-H') && flag.length > 2 && !flag.startsWith('--')) {
589
+ return { name: 'header', inlineValue: flag.slice(2), kind: 'header' };
590
+ }
591
+ if (/^-[a-zA-Z]{2,}$/.test(flag) && !flag.startsWith('--')) {
592
+ return { name: flag, kind: 'skip' };
593
+ }
594
+ return { name: flag, inlineValue, kind: 'skip' };
595
+ }
596
+ function appendBody(current, next) {
597
+ if (!current) {
598
+ return next;
599
+ }
600
+ return `${current}&${next}`;
601
+ }
602
+ function applyUser(output, value) {
603
+ const colon = value.indexOf(':');
604
+ const user = colon >= 0 ? value.slice(0, colon) : value;
605
+ const password = colon >= 0 ? value.slice(colon + 1) : '';
606
+ output.headers.Authorization = `Basic ${encodeBase64(`${user}:${password}`)}`;
607
+ }
608
+ //#endregion
609
+ //#region ----- Fetch helpers -----
610
+ function extractCallArguments(source, openParen) {
611
+ let depthParen = 0;
612
+ let depthBrace = 0;
613
+ let depthBracket = 0;
614
+ let quote = null;
615
+ let escaped = false;
616
+ const args = [];
617
+ let current = '';
618
+ for (let i = openParen; i < source.length; i += 1) {
619
+ const ch = source[i] ?? '';
620
+ if (quote) {
621
+ current += ch;
622
+ if (escaped) {
623
+ escaped = false;
624
+ continue;
625
+ }
626
+ if (ch === '\\') {
627
+ escaped = true;
628
+ continue;
629
+ }
630
+ if (ch === quote) {
631
+ quote = null;
632
+ }
633
+ continue;
634
+ }
635
+ if (ch === '"' || ch === "'" || ch === '`') {
636
+ quote = ch;
637
+ if (depthParen > 0) {
638
+ current += ch;
639
+ }
640
+ continue;
641
+ }
642
+ if (ch === '(') {
643
+ depthParen += 1;
644
+ if (depthParen > 1) {
645
+ current += ch;
646
+ }
647
+ continue;
648
+ }
649
+ if (ch === ')') {
650
+ depthParen -= 1;
651
+ if (depthParen === 0) {
652
+ const trimmed = current.trim();
653
+ if (trimmed) {
654
+ args.push(trimmed);
655
+ }
656
+ return args;
657
+ }
658
+ current += ch;
659
+ continue;
660
+ }
661
+ if (ch === '{') {
662
+ depthBrace += 1;
663
+ current += ch;
664
+ continue;
665
+ }
666
+ if (ch === '}') {
667
+ depthBrace -= 1;
668
+ current += ch;
669
+ continue;
670
+ }
671
+ if (ch === '[') {
672
+ depthBracket += 1;
673
+ current += ch;
674
+ continue;
675
+ }
676
+ if (ch === ']') {
677
+ depthBracket -= 1;
678
+ current += ch;
679
+ continue;
680
+ }
681
+ if (ch === ',' && depthParen === 1 && depthBrace === 0 && depthBracket === 0) {
682
+ args.push(current.trim());
683
+ current = '';
684
+ continue;
685
+ }
686
+ current += ch;
687
+ }
688
+ return null;
689
+ }
690
+ function applyFetchOptions(output, optionsSrc) {
691
+ const methodMatch = optionsSrc.match(/method\s*:\s*(['"`])([^'"`]+)\1/i);
692
+ if (methodMatch?.[2]) {
693
+ output.method = normalizeMethod(methodMatch[2]);
694
+ }
695
+ const headersObject = extractNamedObject(optionsSrc, 'headers');
696
+ if (headersObject) {
697
+ applyObjectLiteralHeaders(output, headersObject);
698
+ }
699
+ const stringifyMatch = optionsSrc.match(/body\s*:\s*JSON\.stringify\s*\(([\s\S]+)\)\s*,?/i);
700
+ if (stringifyMatch?.[1]) {
701
+ const raw = stringifyMatch[1].trim().replace(/,\s*$/, '');
702
+ output.body = jsObjectToJson(raw) ?? raw;
703
+ output.headers.ContentType = output.headers.ContentType || 'application/json';
704
+ if (output.method === 'GET') {
705
+ output.method = 'POST';
706
+ }
707
+ return;
708
+ }
709
+ const bodyMatch = optionsSrc.match(/body\s*:\s*(['"`])([\s\S]*?)\1/);
710
+ if (bodyMatch?.[2] != null) {
711
+ output.body = bodyMatch[2];
712
+ if (output.body && !output.headers.ContentType) {
713
+ output.headers.ContentType = looksLikeJson(output.body) ? 'application/json' : output.headers.ContentType;
714
+ }
715
+ if (output.body && output.method === 'GET') {
716
+ output.method = 'POST';
717
+ }
718
+ }
719
+ }
720
+ function extractNamedObject(source, name) {
721
+ const match = source.match(new RegExp(`${name}\\s*:\\s*\\{`, 'i'));
722
+ if (!match || match.index == null) {
723
+ return null;
724
+ }
725
+ const brace = source.indexOf('{', match.index);
726
+ return extractBalanced(source, brace, '{', '}');
727
+ }
728
+ function applyObjectLiteralHeaders(output, objectSrc) {
729
+ const inner = objectSrc.replace(/^\{/, '').replace(/\}$/, '');
730
+ const pair = /(['"`]?)([A-Za-z0-9\-]+)\1\s*:\s*(['"`])([\s\S]*?)\3/g;
731
+ let match;
732
+ while ((match = pair.exec(inner)) != null) {
733
+ const key = match[2] ?? '';
734
+ const value = match[4] ?? '';
735
+ if (key) {
736
+ applyHeader(output, `${key}: ${value}`);
737
+ }
738
+ }
739
+ }
740
+ function jsObjectToJson(source) {
741
+ const trimmed = source.trim();
742
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
743
+ try {
744
+ return JSON.stringify(JSON.parse(trimmed));
745
+ }
746
+ catch {
747
+ return trimmed;
748
+ }
749
+ }
750
+ return null;
751
+ }
752
+ //#endregion
753
+ //#region ----- Shared helpers -----
754
+ function wrapParse(format, value) {
755
+ if (!hasParsedContent(value)) {
756
+ return { ok: false };
757
+ }
758
+ return { ok: true, format, value };
759
+ }
760
+ function hasParsedContent(value) {
761
+ if (!value) {
762
+ return false;
763
+ }
764
+ if (value.url.trim()) {
765
+ return true;
766
+ }
767
+ if (value.body.trim()) {
768
+ return true;
769
+ }
770
+ if (value.method !== 'GET') {
771
+ return true;
772
+ }
773
+ if (value.headers.Authorization.trim() || value.headers.ContentType.trim()) {
774
+ return true;
775
+ }
776
+ for (const [key, headerValue] of Object.entries(value.headers)) {
777
+ if (key === 'Authorization' || key === 'ContentType' || key === 'tempId') {
778
+ continue;
779
+ }
780
+ if (typeof headerValue === 'string' && headerValue.trim()) {
781
+ return true;
782
+ }
783
+ }
784
+ return (value.queries ?? []).some((query) => Object.entries(query).some(([key, queryValue]) => key !== 'tempId' && String(queryValue ?? '').trim()));
785
+ }
786
+ function normalizeMethod(value) {
787
+ const method = typeof value === 'string' ? value.trim().toUpperCase() : '';
788
+ if (method === 'HEAD') {
789
+ return 'GET';
790
+ }
791
+ if (HTTP_METHODS.includes(method)) {
792
+ return method;
793
+ }
794
+ return 'GET';
795
+ }
796
+ function normalizeQueries(value) {
797
+ const items = queriesFromUnknown(value);
798
+ return items.length > 0 ? items : [{}];
799
+ }
800
+ function queriesFromUnknown(value) {
801
+ if (Array.isArray(value)) {
802
+ return value
803
+ .map((item) => queryFromUnknown(item))
804
+ .filter((item) => item != null);
805
+ }
806
+ if (value && typeof value === 'object') {
807
+ return Object.entries(value)
808
+ .filter(([, queryValue]) => queryValue != null)
809
+ .map(([key, queryValue]) => ({
810
+ [key]: String(queryValue),
811
+ tempId: newTempId(),
812
+ }));
813
+ }
814
+ return [];
815
+ }
816
+ function queryFromUnknown(item) {
817
+ if (item == null || typeof item !== 'object') {
818
+ return null;
819
+ }
820
+ const record = item;
821
+ const named = Object.entries(record).filter(([key]) => key !== 'tempId');
822
+ if (named.length === 0) {
823
+ return { tempId: newTempId() };
824
+ }
825
+ const result = { tempId: newTempId() };
826
+ for (const [key, value] of named) {
827
+ result[key] = value == null ? '' : String(value);
828
+ }
829
+ return result;
830
+ }
831
+ function hasNamedQuery(queries) {
832
+ return queries.some((item) => Object.keys(item).some((key) => key !== 'tempId' && key.trim() !== ''));
833
+ }
834
+ function splitUrlAndQuery(raw) {
835
+ const trimmed = raw.trim();
836
+ try {
837
+ const absolute = /^https?:\/\//i.test(trimmed);
838
+ const parsed = absolute ? new URL(trimmed) : new URL(trimmed, 'https://placeholder.local');
839
+ const queries = queriesFromSearch(parsed.search);
840
+ parsed.search = '';
841
+ const url = absolute ? `${parsed.origin}${parsed.pathname}${parsed.hash}` : `${parsed.pathname}${parsed.hash}`;
842
+ return { url, queries };
843
+ }
844
+ catch {
845
+ return { url: trimmed, queries: [{}] };
846
+ }
847
+ }
848
+ function queriesFromSearch(search) {
849
+ const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
850
+ const items = [];
851
+ params.forEach((value, key) => {
852
+ items.push({ [key]: value, tempId: newTempId() });
853
+ });
854
+ return items.length > 0 ? items : [{}];
855
+ }
856
+ function applyHeader(output, raw) {
857
+ const match = raw.match(/^([^:]+):\s*(.*)$/);
858
+ if (!match) {
859
+ return;
860
+ }
861
+ const name = (match[1] ?? '').trim();
862
+ const value = (match[2] ?? '').trim();
863
+ if (!name) {
864
+ return;
865
+ }
866
+ const lower = name.toLowerCase();
867
+ if (lower === 'content-type' || lower === 'contenttype') {
868
+ output.headers.ContentType = value;
869
+ return;
870
+ }
871
+ if (lower === 'authorization') {
872
+ output.headers.Authorization = value;
873
+ return;
874
+ }
875
+ output.headers[name] = value;
876
+ }
877
+ function looksLikeJson(value) {
878
+ const trimmed = value.trim();
879
+ return (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'));
880
+ }
881
+ function pickString(record, keys) {
882
+ for (const key of keys) {
883
+ const value = record[key];
884
+ if (typeof value === 'string' && value.trim()) {
885
+ return value.trim();
886
+ }
887
+ }
888
+ return undefined;
889
+ }
890
+ function unquote(value) {
891
+ const trimmed = value.trim();
892
+ const quote = trimmed[0];
893
+ if ((quote === "'" || quote === '"' || quote === '`') && trimmed.endsWith(quote) && trimmed.length >= 2) {
894
+ return trimmed.slice(1, -1);
895
+ }
896
+ return trimmed;
897
+ }
898
+ function extractBalanced(source, start, open, close) {
899
+ if (source[start] !== open) {
900
+ return null;
901
+ }
902
+ let depth = 0;
903
+ let quote = null;
904
+ let escaped = false;
905
+ for (let i = start; i < source.length; i += 1) {
906
+ const ch = source[i] ?? '';
907
+ if (quote) {
908
+ if (escaped) {
909
+ escaped = false;
910
+ continue;
911
+ }
912
+ if (ch === '\\') {
913
+ escaped = true;
914
+ continue;
915
+ }
916
+ if (ch === quote) {
917
+ quote = null;
918
+ }
919
+ continue;
920
+ }
921
+ if (ch === '"' || ch === "'" || ch === '`') {
922
+ quote = ch;
923
+ continue;
924
+ }
925
+ if (ch === open) {
926
+ depth += 1;
927
+ }
928
+ else if (ch === close) {
929
+ depth -= 1;
930
+ if (depth === 0) {
931
+ return source.slice(start, i + 1);
932
+ }
933
+ }
934
+ }
935
+ return null;
936
+ }
937
+ function encodeBase64(value) {
938
+ if (typeof btoa !== 'function') {
939
+ return value;
940
+ }
941
+ const bytes = new TextEncoder().encode(value);
942
+ let binary = '';
943
+ bytes.forEach((byte) => {
944
+ binary += String.fromCharCode(byte);
945
+ });
946
+ return btoa(binary);
947
+ }
948
+ function newTempId() {
949
+ return String(Math.random());
950
+ }
951
+ //#endregion
952
+
953
+ //#endregion
954
+ //#region ----- Platform Management Widgets Provider -----
955
+ const WIDGETS = [AXMRestApiGeneratorWidget];
956
+ class AXMPlatformManagementWidgetsProvider {
957
+ getWidgets() {
958
+ return WIDGETS;
959
+ }
960
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformManagementWidgetsProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
961
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformManagementWidgetsProvider }); }
962
+ }
963
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformManagementWidgetsProvider, decorators: [{
964
+ type: Injectable
965
+ }] });
966
+
967
+ //#region ---- Imports ----
968
+ //#endregion
969
+ //#region ---- Feature Definition Provider ----
970
+ /**
971
+ * Platform Management Feature Definition Provider.
972
+ * Provides feature definitions for the Platform Management module.
973
+ */
974
+ class AXPPlatformManagementFeatureDefinitionProvider {
975
+ async provide(context) {
976
+ context.addFeature(AXPPlatformManagementFeatureKeys.MenuCustomization, {
977
+ title: '@platform-management:features.menu-customization.title',
978
+ description: '@platform-management:features.menu-customization.description',
979
+ defaultValue: true,
980
+ type: 'boolean',
981
+ });
982
+ context.addFeature(AXPPlatformManagementFeatureKeys.AutoCheckUpdates, {
983
+ title: '@platform-management:features.auto-check-updates.title',
984
+ description: '@platform-management:features.auto-check-updates.description',
985
+ defaultValue: true,
986
+ type: 'boolean',
987
+ });
988
+ context.addFeature(AXPPlatformManagementFeatureKeys.MaxTokens, {
989
+ title: '@platform-management:features.max-tokens.title',
990
+ description: '@platform-management:features.max-tokens.description',
991
+ defaultValue: 100,
992
+ type: 'number',
993
+ interface: {
994
+ type: 'number-editor',
995
+ options: {
996
+ min: 1,
997
+ max: 1000,
998
+ step: 1,
999
+ },
1000
+ },
1001
+ });
1002
+ }
1003
+ }
1004
+ //#endregion
1005
+
1006
+ //#region ---- Menu Customization Constants ----
1007
+ /**
1008
+ * Setting key for menu customizations
1009
+ */
1010
+ const AXP_MENU_CUSTOMIZATION_KEY = 'axp.platform-management.menu-customization';
1011
+ /**
1012
+ * Current version of menu customization schema
1013
+ */
1014
+ const AXP_MENU_CUSTOMIZATION_VERSION = '1.0.0';
1015
+ /**
1016
+ * Default menu customization
1017
+ */
1018
+ const AXP_MENU_CUSTOMIZATION_DEFAULT = {
1019
+ version: AXP_MENU_CUSTOMIZATION_VERSION,
1020
+ overrides: {},
1021
+ customItems: [],
1022
+ };
1023
+ /**
1024
+ * Menu management feature configuration
1025
+ */
1026
+ const MenuManagementConfig = {
1027
+ route: 'menus',
1028
+ title: `@${RootConfig.config.i18n}:menu-management.components.menu-list.title`,
1029
+ icon: 'fa-light fa-bars',
1030
+ };
1031
+ //#endregion
1032
+
1033
+ //#region ---- Custom Menu Middleware ----
1034
+ /**
1035
+ * Middleware that applies menu customizations using the enhanced context API
1036
+ * Applies user/tenant customizations from settings with priority support
1037
+ */
1038
+ class AXPCustomMenuMiddleware {
1039
+ constructor() {
1040
+ //#region ---- Middleware Properties ----
1041
+ this.name = 'menu-customization';
1042
+ this.priority = 100; // High priority to apply customizations early
1043
+ //#endregion
1044
+ //#region ---- Dependencies ----
1045
+ this.settingsService = inject(AXPSettingsService);
1046
+ //#endregion
1047
+ }
1048
+ //#endregion
1049
+ //#region ---- Middleware Implementation ----
1050
+ async process(context) {
1051
+ try {
1052
+ // Load menu customization from settings
1053
+ const customization = await this.loadCustomization();
1054
+ // Apply overrides to existing menu items using context API
1055
+ this.applyOverrides(context, customization);
1056
+ // Add custom menu items
1057
+ this.addCustomItems(context, customization);
1058
+ // Re-sort after applying priority overrides
1059
+ context.sort();
1060
+ }
1061
+ catch (error) {
1062
+ console.error('Error applying menu customizations:', error);
1063
+ }
1064
+ }
1065
+ //#endregion
1066
+ //#region ---- Private Methods ----
1067
+ /**
1068
+ * Load menu customization from settings
1069
+ * Merges Tenant and User scope customizations (User overrides Tenant)
1070
+ */
1071
+ async loadCustomization() {
1072
+ try {
1073
+ // Load both scopes
1074
+ const tenantSettings = this.settingsService.scope(AXPPlatformScope.Tenant);
1075
+ const userSettings = this.settingsService.scope(AXPPlatformScope.User);
1076
+ const tenantCustomization = await tenantSettings.get(AXP_MENU_CUSTOMIZATION_KEY);
1077
+ const userCustomization = await userSettings.get(AXP_MENU_CUSTOMIZATION_KEY);
1078
+ // Merge customizations: Tenant as base, User overrides
1079
+ const merged = {
1080
+ version: userCustomization?.version || tenantCustomization?.version || AXP_MENU_CUSTOMIZATION_DEFAULT.version,
1081
+ overrides: {
1082
+ ...(tenantCustomization?.overrides || {}),
1083
+ ...(userCustomization?.overrides || {}), // User overrides win
1084
+ },
1085
+ customItems: [...(tenantCustomization?.customItems || []), ...(userCustomization?.customItems || [])],
1086
+ };
1087
+ // Return merged result or default if both are empty
1088
+ const hasCustomizations = Object.keys(merged.overrides).length > 0 || merged.customItems.length > 0;
1089
+ return hasCustomizations ? merged : AXP_MENU_CUSTOMIZATION_DEFAULT;
1090
+ }
1091
+ catch (error) {
1092
+ console.error('Failed to load menu customization:', error);
1093
+ return AXP_MENU_CUSTOMIZATION_DEFAULT;
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Apply overrides to existing menu items using enhanced context API
1098
+ */
1099
+ applyOverrides(context, customization) {
1100
+ Object.entries(customization.overrides).forEach(([menuName, override]) => {
1101
+ const finder = context.findByName(menuName);
1102
+ if (!finder.exists()) {
1103
+ return;
1104
+ }
1105
+ // Hide menu item
1106
+ if (override.hidden) {
1107
+ finder.remove();
1108
+ return;
1109
+ }
1110
+ // Apply priority override
1111
+ if (override.priority !== undefined) {
1112
+ finder.setPriority(override.priority);
1113
+ }
1114
+ // Apply property overrides
1115
+ if (override.properties) {
1116
+ finder.update(override.properties);
1117
+ }
1118
+ // Move to different parent
1119
+ if (override.parentName !== undefined) {
1120
+ finder.moveTo(override.parentName);
1121
+ }
1122
+ });
1123
+ }
1124
+ /**
1125
+ * Add custom menu items using context API
1126
+ */
1127
+ addCustomItems(context, customization) {
1128
+ if (customization.customItems && customization.customItems.length > 0) {
1129
+ context.add(...customization.customItems);
1130
+ }
1131
+ }
1132
+ }
1133
+ //#endregion
1134
+
1135
+ /**
1136
+ * Builds a customization key from a scope key
1137
+ * @param scopeKey The scope key (e.g., 'edition:123', 'tenant:456')
1138
+ * @returns The full customization storage key
1139
+ */
1140
+ function buildCustomizationKey(scopeKey) {
1141
+ return `${AXP_MENU_CUSTOMIZATION_KEY}.${scopeKey}`;
1142
+ }
1143
+ //#region ---- Menu Management Service ----
1144
+ /**
1145
+ * Constants for priority calculation
1146
+ */
1147
+ const PRIORITY_BASE_ROOT = 0;
1148
+ const PRIORITY_BASE_NESTED = 1000;
1149
+ const PRIORITY_STEP = 10;
1150
+ class AXPMenuManagementService extends AXPMenuCustomizerService {
1151
+ constructor() {
1152
+ super(...arguments);
1153
+ //#region ---- Dependencies ----
1154
+ this.settingsService = inject(AXPSettingsService);
1155
+ this.menuProviderService = inject(AXPMenuProviderService);
1156
+ }
1157
+ //#endregion
1158
+ //#region ---- AXPMenuCustomizerService Interface Implementation ----
1159
+ /**
1160
+ * Interface implementation: Get menu tree for the given scope key
1161
+ */
1162
+ async getMenuTree(scopeKey) {
1163
+ return this.getMenuTreeByKey(scopeKey);
1164
+ }
1165
+ /**
1166
+ * Interface implementation: Sync tree changes
1167
+ */
1168
+ async syncTreeChanges(scopeKey, treeNodes) {
1169
+ return this.syncTreeChangesByKey(scopeKey, treeNodes);
1170
+ }
1171
+ /**
1172
+ * Interface implementation: Show menu item
1173
+ */
1174
+ async showMenuItem(scopeKey, menuName) {
1175
+ return this.showMenuItemByKey(scopeKey, menuName);
1176
+ }
1177
+ /**
1178
+ * Interface implementation: Hide menu item
1179
+ */
1180
+ async hideMenuItem(scopeKey, menuName) {
1181
+ return this.hideMenuItemByKey(scopeKey, menuName);
1182
+ }
1183
+ /**
1184
+ * Interface implementation: Add custom menu item
1185
+ */
1186
+ async addCustomMenuItem(scopeKey, menuItem, parentName) {
1187
+ return this.addCustomMenuItemByKey(scopeKey, menuItem, parentName);
1188
+ }
1189
+ /**
1190
+ * Interface implementation: Update custom menu item
1191
+ */
1192
+ async updateCustomMenuItem(scopeKey, menuName, menuItem) {
1193
+ return this.updateCustomMenuItemByKey(scopeKey, menuName, menuItem);
1194
+ }
1195
+ /**
1196
+ * Interface implementation: Update menu properties
1197
+ */
1198
+ async updateMenuProperties(scopeKey, menuName, properties) {
1199
+ return this.updateMenuPropertiesByKey(scopeKey, menuName, properties);
1200
+ }
1201
+ /**
1202
+ * Interface implementation: Delete custom menu item
1203
+ */
1204
+ async deleteCustomMenuItem(scopeKey, menuName) {
1205
+ return this.deleteCustomMenuItemByKey(scopeKey, menuName);
1206
+ }
1207
+ /**
1208
+ * Interface implementation: Reset customizations
1209
+ */
1210
+ async resetCustomizations(scopeKey) {
1211
+ return this.resetCustomizationsByKey(scopeKey);
1212
+ }
1213
+ //#endregion
1214
+ //#region ---- Scope-Based Public Methods ----
1215
+ /**
1216
+ * Get menu tree as AXTreeViewNode[] for ax-tree-view component (scope-based)
1217
+ */
1218
+ async getMenuTreeByScope(scope) {
1219
+ // Get RAW menu items WITHOUT middleware (for management purposes)
1220
+ const baseItems = await this.menuProviderService.rawItems();
1221
+ // Load customizations for the given scope
1222
+ const customization = await this.loadCustomization(scope);
1223
+ // Merge custom items with base items
1224
+ const allItems = [...baseItems, ...customization.customItems];
1225
+ // Apply parent overrides to restructure items BEFORE building tree
1226
+ const restructuredItems = this.applyParentOverrides(allItems, customization);
1227
+ // Convert to tree nodes with metadata
1228
+ return this.convertToTreeNodes(restructuredItems, customization);
1229
+ }
1230
+ /**
1231
+ * Sync tree changes back to customization (scope-based)
1232
+ * Called automatically when tree structure changes via drag-drop
1233
+ */
1234
+ async syncTreeChangesByScope(scope, treeNodes) {
1235
+ const customization = await this.loadCustomization(scope);
1236
+ // Extract and update priorities and parent relationships from tree structure
1237
+ this.updateCustomizationFromTree(treeNodes, customization);
1238
+ await this.saveCustomization(scope, customization);
1239
+ }
1240
+ /**
1241
+ * Load menu customization for a specific scope
1242
+ */
1243
+ async loadCustomization(scope) {
1244
+ try {
1245
+ const scopedSettings = this.settingsService.scope(scope);
1246
+ const saved = await scopedSettings.get(AXP_MENU_CUSTOMIZATION_KEY);
1247
+ if (saved && saved.version) {
1248
+ return saved;
1249
+ }
1250
+ return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
1251
+ }
1252
+ catch {
1253
+ return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
1254
+ }
1255
+ }
1256
+ /**
1257
+ * Save menu customization for a specific scope
1258
+ */
1259
+ async saveCustomization(scope, customization) {
1260
+ const scopedSettings = this.settingsService.scope(scope);
1261
+ await scopedSettings.set(AXP_MENU_CUSTOMIZATION_KEY, customization);
1262
+ // Clear menu cache to force reload
1263
+ this.menuProviderService.clearCache();
1264
+ }
1265
+ /**
1266
+ * Hide a menu item (scope-based)
1267
+ */
1268
+ async hideMenuItemByScope(scope, menuName) {
1269
+ this.validateMenuName(menuName, 'hide');
1270
+ const customization = await this.loadCustomization(scope);
1271
+ this.ensureOverrideExists(customization, menuName);
1272
+ customization.overrides[menuName].hidden = true;
1273
+ await this.saveCustomization(scope, customization);
1274
+ }
1275
+ /**
1276
+ * Show a menu item (scope-based)
1277
+ */
1278
+ async showMenuItemByScope(scope, menuName) {
1279
+ this.validateMenuName(menuName, 'show');
1280
+ const customization = await this.loadCustomization(scope);
1281
+ if (customization.overrides[menuName]) {
1282
+ delete customization.overrides[menuName].hidden;
1283
+ this.cleanupEmptyOverride(customization, menuName);
1284
+ }
1285
+ await this.saveCustomization(scope, customization);
1286
+ }
1287
+ /**
1288
+ * Update menu item priority (scope-based)
1289
+ */
1290
+ async updateMenuPriorityByScope(scope, menuName, priority) {
1291
+ this.validateMenuName(menuName, 'update priority for');
1292
+ const customization = await this.loadCustomization(scope);
1293
+ this.ensureOverrideExists(customization, menuName);
1294
+ customization.overrides[menuName].priority = priority;
1295
+ await this.saveCustomization(scope, customization);
1296
+ }
1297
+ /**
1298
+ * Update menu item properties (scope-based)
1299
+ */
1300
+ async updateMenuPropertiesByScope(scope, menuName, properties) {
1301
+ this.validateMenuName(menuName, 'update properties for');
1302
+ const customization = await this.loadCustomization(scope);
1303
+ this.ensureOverrideExists(customization, menuName);
1304
+ customization.overrides[menuName].properties = {
1305
+ ...customization.overrides[menuName].properties,
1306
+ ...properties,
1307
+ };
1308
+ await this.saveCustomization(scope, customization);
1309
+ }
1310
+ /**
1311
+ * Add custom menu item (scope-based)
1312
+ */
1313
+ async addCustomMenuItemByScope(scope, menuItem, parent) {
1314
+ const customization = await this.loadCustomization(scope);
1315
+ // Generate unique name if not provided
1316
+ if (!menuItem.name) {
1317
+ menuItem.name = `custom-menu-${Date.now()}`;
1318
+ }
1319
+ customization.customItems.push(menuItem);
1320
+ await this.saveCustomization(scope, customization);
1321
+ }
1322
+ /**
1323
+ * Update custom menu item (scope-based)
1324
+ */
1325
+ async updateCustomMenuItemByScope(scope, menuName, menuItem) {
1326
+ this.validateMenuName(menuName, 'update');
1327
+ const customization = await this.loadCustomization(scope);
1328
+ const index = customization.customItems.findIndex((item) => item.name === menuName);
1329
+ if (index === -1) {
1330
+ throw new Error(`Custom menu item '${menuName}' not found`);
1331
+ }
1332
+ customization.customItems[index] = menuItem;
1333
+ await this.saveCustomization(scope, customization);
1334
+ }
1335
+ /**
1336
+ * Delete custom menu item (scope-based)
1337
+ */
1338
+ async deleteCustomMenuItemByScope(scope, menuName) {
1339
+ this.validateMenuName(menuName, 'delete');
1340
+ const customization = await this.loadCustomization(scope);
1341
+ const initialLength = customization.customItems.length;
1342
+ customization.customItems = customization.customItems.filter((item) => item.name !== menuName);
1343
+ if (customization.customItems.length === initialLength) {
1344
+ throw new Error(`Custom menu item '${menuName}' not found`);
1345
+ }
1346
+ await this.saveCustomization(scope, customization);
1347
+ }
1348
+ /**
1349
+ * Reset all customizations for a scope (scope-based)
1350
+ */
1351
+ async resetCustomizationsByScope(scope) {
1352
+ await this.saveCustomization(scope, cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT));
1353
+ }
1354
+ //#endregion
1355
+ //#region ---- Key-Based Public Methods ----
1356
+ /**
1357
+ * Get menu tree using a generic scope key (e.g., 'edition:123', 'tenant:456')
1358
+ */
1359
+ async getMenuTreeByKey(scopeKey) {
1360
+ // Get RAW menu items WITHOUT middleware (for management purposes)
1361
+ const baseItems = await this.menuProviderService.rawItems();
1362
+ // Load customizations for the given scope key
1363
+ const customization = await this.loadCustomizationByKey(scopeKey);
1364
+ // Merge custom items with base items
1365
+ const allItems = [...baseItems, ...customization.customItems];
1366
+ // Apply parent overrides to restructure items BEFORE building tree
1367
+ const restructuredItems = this.applyParentOverrides(allItems, customization);
1368
+ // Convert to tree nodes with metadata
1369
+ return this.convertToTreeNodes(restructuredItems, customization);
1370
+ }
1371
+ /**
1372
+ * Sync tree changes using a generic scope key
1373
+ */
1374
+ async syncTreeChangesByKey(scopeKey, treeNodes) {
1375
+ const customization = await this.loadCustomizationByKey(scopeKey);
1376
+ // Extract and update priorities and parent relationships from tree structure
1377
+ this.updateCustomizationFromTree(treeNodes, customization);
1378
+ await this.saveCustomizationByKey(scopeKey, customization);
1379
+ }
1380
+ /**
1381
+ * Load menu customization using a generic scope key
1382
+ */
1383
+ async loadCustomizationByKey(scopeKey) {
1384
+ try {
1385
+ const storageKey = buildCustomizationKey(scopeKey);
1386
+ const scopedSettings = this.settingsService.scope(AXPPlatformScope.Tenant);
1387
+ const saved = await scopedSettings.get(storageKey);
1388
+ if (saved && saved.version) {
1389
+ return saved;
1390
+ }
1391
+ return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
1392
+ }
1393
+ catch {
1394
+ return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
1395
+ }
1396
+ }
1397
+ /**
1398
+ * Save menu customization using a generic scope key
1399
+ */
1400
+ async saveCustomizationByKey(scopeKey, customization) {
1401
+ const storageKey = buildCustomizationKey(scopeKey);
1402
+ const scopedSettings = this.settingsService.scope(AXPPlatformScope.Tenant);
1403
+ await scopedSettings.set(storageKey, customization);
1404
+ // Clear menu cache to force reload
1405
+ this.menuProviderService.clearCache();
1406
+ }
1407
+ /**
1408
+ * Hide a menu item using a generic scope key
1409
+ */
1410
+ async hideMenuItemByKey(scopeKey, menuName) {
1411
+ this.validateMenuName(menuName, 'hide');
1412
+ const customization = await this.loadCustomizationByKey(scopeKey);
1413
+ this.ensureOverrideExists(customization, menuName);
1414
+ customization.overrides[menuName].hidden = true;
1415
+ await this.saveCustomizationByKey(scopeKey, customization);
1416
+ }
1417
+ /**
1418
+ * Show a menu item using a generic scope key
1419
+ */
1420
+ async showMenuItemByKey(scopeKey, menuName) {
1421
+ this.validateMenuName(menuName, 'show');
1422
+ const customization = await this.loadCustomizationByKey(scopeKey);
1423
+ if (customization.overrides[menuName]) {
1424
+ delete customization.overrides[menuName].hidden;
1425
+ this.cleanupEmptyOverride(customization, menuName);
1426
+ }
1427
+ await this.saveCustomizationByKey(scopeKey, customization);
1428
+ }
1429
+ /**
1430
+ * Update menu item properties using a generic scope key
1431
+ */
1432
+ async updateMenuPropertiesByKey(scopeKey, menuName, properties) {
1433
+ this.validateMenuName(menuName, 'update properties for');
1434
+ const customization = await this.loadCustomizationByKey(scopeKey);
1435
+ this.ensureOverrideExists(customization, menuName);
1436
+ customization.overrides[menuName].properties = {
1437
+ ...customization.overrides[menuName].properties,
1438
+ ...properties,
1439
+ };
1440
+ await this.saveCustomizationByKey(scopeKey, customization);
1441
+ }
1442
+ /**
1443
+ * Add custom menu item using a generic scope key
1444
+ */
1445
+ async addCustomMenuItemByKey(scopeKey, menuItem, parent) {
1446
+ const customization = await this.loadCustomizationByKey(scopeKey);
1447
+ // Generate unique name if not provided
1448
+ if (!menuItem.name) {
1449
+ menuItem.name = `custom-menu-${Date.now()}`;
1450
+ }
1451
+ customization.customItems.push(menuItem);
1452
+ await this.saveCustomizationByKey(scopeKey, customization);
1453
+ }
1454
+ /**
1455
+ * Update custom menu item using a generic scope key
1456
+ */
1457
+ async updateCustomMenuItemByKey(scopeKey, menuName, menuItem) {
1458
+ this.validateMenuName(menuName, 'update');
1459
+ const customization = await this.loadCustomizationByKey(scopeKey);
1460
+ const index = customization.customItems.findIndex((item) => item.name === menuName);
1461
+ if (index === -1) {
1462
+ throw new Error(`Custom menu item '${menuName}' not found`);
1463
+ }
1464
+ customization.customItems[index] = menuItem;
1465
+ await this.saveCustomizationByKey(scopeKey, customization);
1466
+ }
1467
+ /**
1468
+ * Delete custom menu item using a generic scope key
1469
+ */
1470
+ async deleteCustomMenuItemByKey(scopeKey, menuName) {
1471
+ this.validateMenuName(menuName, 'delete');
1472
+ const customization = await this.loadCustomizationByKey(scopeKey);
1473
+ const initialLength = customization.customItems.length;
1474
+ customization.customItems = customization.customItems.filter((item) => item.name !== menuName);
1475
+ if (customization.customItems.length === initialLength) {
1476
+ throw new Error(`Custom menu item '${menuName}' not found`);
1477
+ }
1478
+ await this.saveCustomizationByKey(scopeKey, customization);
1479
+ }
1480
+ /**
1481
+ * Reset all customizations using a generic scope key
1482
+ */
1483
+ async resetCustomizationsByKey(scopeKey) {
1484
+ await this.saveCustomizationByKey(scopeKey, cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT));
1485
+ }
1486
+ //#endregion
1487
+ //#region ---- Private Methods ----
1488
+ /**
1489
+ * Apply parent overrides from customization to restructure menu items
1490
+ * This ensures the tree reflects saved parent changes (moves)
1491
+ */
1492
+ applyParentOverrides(items, customization) {
1493
+ // First, flatten all items into a map for easy lookup
1494
+ const itemMap = this.flattenMenuItems(items);
1495
+ // Build new structure based on effective parent relationships
1496
+ const rootItems = [];
1497
+ const childrenMap = new Map();
1498
+ for (const [itemName, item] of itemMap.entries()) {
1499
+ const override = customization.overrides[itemName];
1500
+ // Determine effective parent (override takes precedence)
1501
+ let effectiveParent;
1502
+ if (override?.parentName !== undefined) {
1503
+ // parentName is explicitly set (could be null for root, or a parent name)
1504
+ effectiveParent = override.parentName;
1505
+ }
1506
+ else {
1507
+ // No override - find original parent by searching original structure
1508
+ effectiveParent = this.findOriginalParent(items, itemName);
1509
+ }
1510
+ if (effectiveParent === null || effectiveParent === undefined) {
1511
+ // Root level item
1512
+ rootItems.push(item);
1513
+ }
1514
+ else {
1515
+ // Child item - add to parent's children
1516
+ if (!childrenMap.has(effectiveParent)) {
1517
+ childrenMap.set(effectiveParent, []);
1518
+ }
1519
+ childrenMap.get(effectiveParent).push(item);
1520
+ }
1521
+ }
1522
+ // Attach children to their parents recursively
1523
+ const attachChildren = (item) => {
1524
+ if (item.name && childrenMap.has(item.name)) {
1525
+ item.children = childrenMap.get(item.name);
1526
+ item.children?.forEach(attachChildren);
1527
+ }
1528
+ };
1529
+ rootItems.forEach(attachChildren);
1530
+ return rootItems;
1531
+ }
1532
+ /**
1533
+ * Find the original parent name of an item in the original structure
1534
+ * Returns undefined for root items, string for items with parents, null for not found
1535
+ */
1536
+ findOriginalParent(items, targetName, currentParent) {
1537
+ for (const item of items) {
1538
+ if (item.name === targetName) {
1539
+ return currentParent; // undefined if root, string if has parent
1540
+ }
1541
+ if (item.children) {
1542
+ const found = this.findOriginalParent(item.children, targetName, item.name);
1543
+ if (found !== null) {
1544
+ // Found in children (could be undefined for root of subtree, or string for nested item)
1545
+ return found;
1546
+ }
1547
+ // found === null means not found in this subtree, continue searching
1548
+ }
1549
+ }
1550
+ return null; // Not found in this level or any children
1551
+ }
1552
+ /**
1553
+ * Convert menu items to AXTreeNode[] with metadata
1554
+ */
1555
+ convertToTreeNodes(items, customization, parentName) {
1556
+ const nodes = items.map((item) => {
1557
+ const itemName = item.name;
1558
+ const override = itemName ? customization.overrides[itemName] : undefined;
1559
+ const isCustom = itemName ? customization.customItems.some((ci) => ci.name === itemName) : false;
1560
+ // Create metadata
1561
+ const metadata = {
1562
+ isBuiltIn: !isCustom,
1563
+ isCustom,
1564
+ isHidden: override?.hidden || false,
1565
+ originalPriority: item.priority,
1566
+ originalParentName: parentName,
1567
+ };
1568
+ // Apply ALL overrides: priority AND properties (text, icon, description, etc.)
1569
+ const effectivePriority = override?.priority !== undefined ? override.priority : item.priority;
1570
+ const effectiveItem = {
1571
+ ...item,
1572
+ ...override?.properties, // Apply property overrides (text, icon, description)
1573
+ priority: effectivePriority,
1574
+ };
1575
+ // Create tree node data with effective (overridden) values
1576
+ const nodeData = {
1577
+ menuItem: effectiveItem,
1578
+ metadata,
1579
+ };
1580
+ // Create tree node using effective (overridden) values
1581
+ const treeNode = {
1582
+ id: itemName || item.path || this.generateNodeId(),
1583
+ title: effectiveItem.text || effectiveItem.path || 'Unnamed',
1584
+ icon: effectiveItem.icon,
1585
+ expanded: false,
1586
+ selected: false,
1587
+ ['children']: item.children ? this.convertToTreeNodes(item.children, customization, itemName) : undefined,
1588
+ childrenCount: item.children?.length,
1589
+ data: nodeData,
1590
+ };
1591
+ return treeNode;
1592
+ });
1593
+ // Sort by priority
1594
+ nodes.sort((a, b) => {
1595
+ const aPriority = a['data']?.menuItem.priority ?? 0;
1596
+ const bPriority = b['data']?.menuItem.priority ?? 0;
1597
+ return aPriority - bPriority;
1598
+ });
1599
+ return nodes;
1600
+ }
1601
+ /**
1602
+ * Update customization from tree structure
1603
+ * Extracts priorities and parent relationships from tree nodes
1604
+ */
1605
+ updateCustomizationFromTree(treeNodes, customization, parentName) {
1606
+ // Calculate base priority for this level to maintain relative ordering
1607
+ const basePriority = parentName ? PRIORITY_BASE_NESTED : PRIORITY_BASE_ROOT;
1608
+ const priorityStep = PRIORITY_STEP;
1609
+ treeNodes.forEach((node, index) => {
1610
+ const nodeData = node['data'] || null;
1611
+ const menuItem = nodeData?.menuItem;
1612
+ if (menuItem?.name) {
1613
+ this.ensureOverrideExists(customization, menuItem.name);
1614
+ // Calculate new priority based on current position
1615
+ const newPriority = basePriority + index * priorityStep;
1616
+ // Get current effective priority (considering existing overrides)
1617
+ const currentEffectivePriority = customization.overrides[menuItem.name]?.priority ?? nodeData.metadata.originalPriority ?? 0;
1618
+ // Always update priority to reflect current position in tree
1619
+ // This ensures reordering works correctly even after multiple moves
1620
+ if (newPriority !== currentEffectivePriority) {
1621
+ customization.overrides[menuItem.name].priority = newPriority;
1622
+ }
1623
+ // Determine current effective parent (considering existing overrides)
1624
+ const currentEffectiveParent = customization.overrides[menuItem.name]?.parentName ?? nodeData.metadata.originalParentName;
1625
+ // Update parent relationship if it changed from current effective parent
1626
+ if (parentName !== currentEffectiveParent) {
1627
+ if (parentName) {
1628
+ customization.overrides[menuItem.name].parentName = parentName;
1629
+ }
1630
+ else {
1631
+ // Moving to root - set to null (not undefined) so middleware will process it
1632
+ // moveTo(null) moves the item to root level
1633
+ customization.overrides[menuItem.name].parentName = null;
1634
+ }
1635
+ }
1636
+ // Clean up empty overrides
1637
+ this.cleanupEmptyOverride(customization, menuItem.name);
1638
+ // Recursively process children
1639
+ const nodeChildren = node['children'];
1640
+ if (nodeChildren && nodeChildren.length > 0) {
1641
+ this.updateCustomizationFromTree(nodeChildren, customization, menuItem.name);
1642
+ }
1643
+ }
1644
+ });
1645
+ }
1646
+ /**
1647
+ * Validate menu name before operations
1648
+ */
1649
+ validateMenuName(menuName, operation) {
1650
+ if (!menuName) {
1651
+ throw new Error(`Menu item must have a name to be ${operation}`);
1652
+ }
1653
+ }
1654
+ /**
1655
+ * Ensure override object exists for a menu item
1656
+ */
1657
+ ensureOverrideExists(customization, menuName) {
1658
+ if (!customization.overrides[menuName]) {
1659
+ customization.overrides[menuName] = {};
1660
+ }
1661
+ }
1662
+ /**
1663
+ * Clean up empty override objects
1664
+ */
1665
+ cleanupEmptyOverride(customization, menuName) {
1666
+ if (Object.keys(customization.overrides[menuName] || {}).length === 0) {
1667
+ delete customization.overrides[menuName];
1668
+ }
1669
+ }
1670
+ /**
1671
+ * Flatten menu items recursively into a map
1672
+ */
1673
+ flattenMenuItems(items) {
1674
+ const itemMap = new Map();
1675
+ const flatten = (itemList) => {
1676
+ for (const item of itemList) {
1677
+ if (item.name) {
1678
+ // Clone item without children to rebuild structure
1679
+ itemMap.set(item.name, { ...item, children: undefined });
1680
+ }
1681
+ if (item.children) {
1682
+ flatten(item.children);
1683
+ }
1684
+ }
1685
+ };
1686
+ flatten(items);
1687
+ return itemMap;
1688
+ }
1689
+ /**
1690
+ * Generate unique ID for tree nodes
1691
+ */
1692
+ generateNodeId() {
1693
+ return `node-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
1694
+ }
1695
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPMenuManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1696
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPMenuManagementService, providedIn: 'root' }); }
1697
+ }
1698
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPMenuManagementService, decorators: [{
1699
+ type: Injectable,
1700
+ args: [{ providedIn: 'root' }]
1701
+ }] });
1702
+
1703
+ //#region ---- Imports ----
1704
+ //#endregion
1705
+ //#region ---- Module ----
1706
+ /**
1707
+ * Module for menu management features including the menu customizer command.
1708
+ */
1709
+ class AXMMenuManagementModule {
1710
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMMenuManagementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1711
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMMenuManagementModule }); }
1712
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMMenuManagementModule, providers: [
1713
+ {
1714
+ provide: AXP_MENU_CUSTOMIZER_SERVICE,
1715
+ useExisting: AXPMenuManagementService,
1716
+ },
1717
+ provideCommandSetups([
1718
+ {
1719
+ key: 'customize-menu',
1720
+ command: () => import('./acorex-platform-module-platform-management-client-customize-menu.command-2319iHsD.mjs').then((c) => c.AXMCustomizeMenuCommand),
1721
+ },
1722
+ ]),
1723
+ ] }); }
1724
+ }
1725
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMMenuManagementModule, decorators: [{
1726
+ type: NgModule,
1727
+ args: [{
1728
+ providers: [
1729
+ {
1730
+ provide: AXP_MENU_CUSTOMIZER_SERVICE,
1731
+ useExisting: AXPMenuManagementService,
1732
+ },
1733
+ provideCommandSetups([
1734
+ {
1735
+ key: 'customize-menu',
1736
+ command: () => import('./acorex-platform-module-platform-management-client-customize-menu.command-2319iHsD.mjs').then((c) => c.AXMCustomizeMenuCommand),
1737
+ },
1738
+ ]),
1739
+ ],
1740
+ }]
1741
+ }] });
1742
+
1743
+ /**
1744
+ * Token that returns current ISO date-time string.
1745
+ */
1746
+ class AXMTokenProvider {
1747
+ constructor() {
1748
+ this.name = 'now';
1749
+ this.title = 'Now';
1750
+ this.description = 'Current date and time in ISO format';
1751
+ }
1752
+ execute() {
1753
+ return Promise.resolve(new Date().toISOString());
1754
+ }
1755
+ }
1756
+
1757
+ //#region ----- Imports -----
1758
+ //#endregion
1759
+ //#region ----- Factory Cache -----
1760
+ /**
1761
+ * Global cache for loaded token providers.
1762
+ * Cleared when token entity changes occur.
1763
+ */
1764
+ let cachedProviders = null;
1765
+ /**
1766
+ * Clears the cached providers, forcing reload on next access.
1767
+ */
1768
+ function clearDynamicTokenProvidersCache() {
1769
+ cachedProviders = null;
1770
+ }
1771
+ //#endregion
1772
+ //#region ----- Token Data Access -----
1773
+ /**
1774
+ * Resolves the token entity data accessor after the current DI cycle completes.
1775
+ * AXPEntityService cannot be resolved synchronously from the dynamic token provider
1776
+ * factory because AXP_TOKEN_DEFINITION_PROVIDER is still being constructed (cycle via
1777
+ * AXPTokenDefinitionService and expression evaluator scope providers).
1778
+ */
1779
+ async function getTokenEntityDataAccessor(injector) {
1780
+ await Promise.resolve();
1781
+ return injector
1782
+ .get(AXPEntityService)
1783
+ .withEntity(RootConfig.module.name, RootConfig.entities.token.name)
1784
+ .data();
1785
+ }
1786
+ //#endregion
1787
+ //#region ----- Factory Function -----
1788
+ /**
1789
+ * Factory function that loads tokens from backend and creates providers for each.
1790
+ * Value resolution always runs on the server (`PlatformManagement.Tokens:Resolve`).
1791
+ */
1792
+ async function createDynamicTokenProviders(injector) {
1793
+ if (cachedProviders) {
1794
+ return cachedProviders;
1795
+ }
1796
+ cachedProviders = loadTokenProviders(injector);
1797
+ return cachedProviders;
1798
+ }
1799
+ async function loadTokenProviders(injector) {
1800
+ const sessionService = injector.get(AXPSessionService);
1801
+ if (!sessionService.isReadyForAppScopedApi) {
1802
+ return [];
1803
+ }
1804
+ const tokensService = await getTokenEntityDataAccessor(injector);
1805
+ try {
1806
+ const queryResult = await tokensService.query({
1807
+ skip: 0,
1808
+ take: 1000,
1809
+ });
1810
+ if (!queryResult?.items?.length) {
1811
+ return [];
1812
+ }
1813
+ return queryResult.items.map((token) => createTokenProvider(token, injector));
1814
+ }
1815
+ catch (error) {
1816
+ console.error('[DynamicTokenProvider] Failed to load tokens from backend:', error);
1817
+ throw error;
1818
+ }
1819
+ }
1820
+ /**
1821
+ * Creates a token provider that resolves values via the server command.
1822
+ */
1823
+ function createTokenProvider(token, injector) {
1824
+ const tokenName = token.name;
1825
+ return {
1826
+ name: tokenName,
1827
+ title: token.title,
1828
+ description: token.description,
1829
+ async execute() {
1830
+ const commandExecutor = injector.get(AXPCommandExecutor);
1831
+ const result = await commandExecutor.execute(AXM_TOKEN_RESOLVE_COMMAND_KEY, { tokenName });
1832
+ const __single_result = runtimeCommandPayloadData(result);
1833
+ if (!__single_result || __single_result.value === undefined || __single_result.value === null) {
1834
+ const text = result?.message?.text;
1835
+ const message = (typeof text === 'string' && text) ||
1836
+ `Token "${tokenName}" not found or returned no value`;
1837
+ throw new Error(message);
1838
+ }
1839
+ return __single_result.value;
1840
+ },
1841
+ };
1842
+ }
1843
+ //#endregion
1844
+
1845
+ //#region ---- Imports ----
1846
+ //#endregion
1847
+ //#region ---- Token Refresh Event Listener ----
1848
+ /**
1849
+ * Event listener provider that listens to token entity changes
1850
+ * and clears the token cache when tokens are updated, inserted, or deleted.
1851
+ */
1852
+ class AXMTokenRefreshProvider {
1853
+ constructor() {
1854
+ this.tokenService = inject(AXPTokenDefinitionService);
1855
+ /**
1856
+ * Event keys to listen to for token entity changes.
1857
+ * Supports wildcard patterns for entity events.
1858
+ * Format: entity.{operation}.{ModuleName}.{EntityName}
1859
+ * Example: entity.updated.PlatformManagement.Tokens
1860
+ */
1861
+ this.key = `entity.*.${RootConfig.module.name}.${RootConfig.entities.token.name}`;
1862
+ }
1863
+ /**
1864
+ * Executes when a token entity event occurs (inserted, updated, or deleted).
1865
+ * Clears both the token definition service cache and the dynamic providers cache.
1866
+ */
1867
+ async execute(data) {
1868
+ // Clear token definition service cache
1869
+ this.tokenService.clearDefinitionsCache();
1870
+ // Clear dynamic token providers cache to force reload
1871
+ clearDynamicTokenProvidersCache();
1872
+ }
1873
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTokenRefreshProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1874
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTokenRefreshProvider }); }
1875
+ }
1876
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTokenRefreshProvider, decorators: [{
1877
+ type: Injectable
1878
+ }] });
1879
+
1880
+ //#region ---- Imports ----
1881
+ //#endregion
1882
+ //#region ---- Token Module ----
1883
+ /**
1884
+ * Token feature module.
1885
+ * Provides token definition providers (static and dynamic) and refresh listeners.
1886
+ */
1887
+ class AXMTokenModule {
1888
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTokenModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1889
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMTokenModule }); }
1890
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTokenModule, providers: [
1891
+ // Static token provider (built-in tokens)
1892
+ {
1893
+ provide: AXP_TOKEN_DEFINITION_PROVIDER,
1894
+ useClass: AXMTokenProvider,
1895
+ multi: true,
1896
+ },
1897
+ // Dynamic token provider (tokens from backend)
1898
+ {
1899
+ provide: AXP_TOKEN_DEFINITION_PROVIDER,
1900
+ useFactory: (injector) => createDynamicTokenProviders(injector),
1901
+ deps: [Injector],
1902
+ multi: true,
1903
+ },
1904
+ // Token refresh listener (clears cache on entity changes)
1905
+ {
1906
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
1907
+ useClass: AXMTokenRefreshProvider,
1908
+ multi: true,
1909
+ },
1910
+ // Execute token command
1911
+ provideCommandSetups([
1912
+ {
1913
+ key: `${RootConfig.module.name}.${RootConfig.entities.token.name}:Execute`,
1914
+ command: () => import('./acorex-platform-module-platform-management-client-execute-token.command-DNuHR-0y.mjs').then((c) => c.ExecuteTokenCommand),
1915
+ },
1916
+ ]),
1917
+ ] }); }
1918
+ }
1919
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTokenModule, decorators: [{
1920
+ type: NgModule,
1921
+ args: [{
1922
+ imports: [],
1923
+ exports: [],
1924
+ declarations: [],
1925
+ providers: [
1926
+ // Static token provider (built-in tokens)
1927
+ {
1928
+ provide: AXP_TOKEN_DEFINITION_PROVIDER,
1929
+ useClass: AXMTokenProvider,
1930
+ multi: true,
1931
+ },
1932
+ // Dynamic token provider (tokens from backend)
1933
+ {
1934
+ provide: AXP_TOKEN_DEFINITION_PROVIDER,
1935
+ useFactory: (injector) => createDynamicTokenProviders(injector),
1936
+ deps: [Injector],
1937
+ multi: true,
1938
+ },
1939
+ // Token refresh listener (clears cache on entity changes)
1940
+ {
1941
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
1942
+ useClass: AXMTokenRefreshProvider,
1943
+ multi: true,
1944
+ },
1945
+ // Execute token command
1946
+ provideCommandSetups([
1947
+ {
1948
+ key: `${RootConfig.module.name}.${RootConfig.entities.token.name}:Execute`,
1949
+ command: () => import('./acorex-platform-module-platform-management-client-execute-token.command-DNuHR-0y.mjs').then((c) => c.ExecuteTokenCommand),
1950
+ },
1951
+ ]),
1952
+ ],
1953
+ }]
1954
+ }] });
1955
+
1956
+ class AXPUpdaterService {
1957
+ constructor() {
1958
+ const swUpdate = inject(SwUpdate, { optional: true });
1959
+ const dialogService = inject(AXDialogService);
1960
+ const translateService = inject(AXTranslationService);
1961
+ if (swUpdate?.isEnabled) {
1962
+ swUpdate.versionUpdates.subscribe((event) => {
1963
+ if (event.type === 'VERSION_READY') {
1964
+ console.log('New version available');
1965
+ const scope = RootConfig.config.i18n;
1966
+ const dialog = dialogService.open({
1967
+ icon: 'fa-regular fa-info',
1968
+ content: translateService.translateSync('updater.content', { scope: scope }),
1969
+ title: translateService.translateSync('updater.title', { scope: scope }),
1970
+ type: 'primary',
1971
+ orientation: 'horizontal',
1972
+ buttons: [
1973
+ {
1974
+ text: translateService.translateSync('updater.update', { scope: scope }),
1975
+ color: 'primary',
1976
+ onClick: async (e) => {
1977
+ try {
1978
+ document.location.reload();
1979
+ dialog.close();
1980
+ }
1981
+ catch (error) {
1982
+ console.error(error);
1983
+ }
1984
+ },
1985
+ },
1986
+ {
1987
+ text: translateService.translateSync('updater.cancel', { scope: scope }),
1988
+ color: 'default',
1989
+ autofocus: true,
1990
+ onClick: (e) => {
1991
+ dialog.close();
1992
+ },
1993
+ },
1994
+ ],
1995
+ closeButton: false,
1996
+ });
1997
+ }
1998
+ });
1999
+ }
2000
+ }
2001
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUpdaterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2002
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUpdaterService, providedIn: 'root' }); }
2003
+ }
2004
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUpdaterService, decorators: [{
2005
+ type: Injectable,
2006
+ args: [{ providedIn: 'root' }]
2007
+ }], ctorParameters: () => [] });
2008
+
2009
+ class AXMUpdaterModule {
2010
+ constructor(updaterService) {
2011
+ this.updaterService = updaterService;
2012
+ }
2013
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMUpdaterModule, deps: [{ token: AXPUpdaterService }], target: i0.ɵɵFactoryTarget.NgModule }); }
2014
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMUpdaterModule }); }
2015
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMUpdaterModule, providers: [AXPUpdaterService] }); }
2016
+ }
2017
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMUpdaterModule, decorators: [{
2018
+ type: NgModule,
2019
+ args: [{
2020
+ imports: [],
2021
+ exports: [],
2022
+ declarations: [],
2023
+ providers: [AXPUpdaterService],
2024
+ }]
2025
+ }], ctorParameters: () => [{ type: AXPUpdaterService }] });
2026
+
2027
+ //#region ---- Imports ----
2028
+ //#endregion
2029
+ //#region ---- Manifest Definition ----
2030
+ /**
2031
+ * Platform Management Module Manifest.
2032
+ * Defines module metadata, features, and provider references.
2033
+ */
2034
+ const PlatformManagementManifest = {
2035
+ name: RootConfig.module.name,
2036
+ version: '1.0.0',
2037
+ title: RootConfig.module.title,
2038
+ icon: RootConfig.module.icon,
2039
+ i18n: RootConfig.config.i18n,
2040
+ type: 'platform',
2041
+ domain: 'platform',
2042
+ dependencies: [
2043
+ 'SecurityManagement',
2044
+ 'ApplicationManagement',
2045
+ 'SystemInsight',
2046
+ 'TenantManagement',
2047
+ 'SubscriptionManagement',
2048
+ ],
2049
+ };
2050
+ //#endregion
2051
+
2052
+ class AXMMenuProvider {
2053
+ constructor() {
2054
+ this.entityService = inject(AXPEntityService);
2055
+ }
2056
+ async provide(context) {
2057
+ const scope = RootConfig.config.i18n;
2058
+ const moduleName = RootConfig.module.name;
2059
+ const platformShellPolicy = {
2060
+ features: [moduleName],
2061
+ permissions: [AXPApplicationsPlatformPermissionKeys.MaintainPlatformShell],
2062
+ };
2063
+ //#region ----- Platform > Tokens -----
2064
+ context.find(AXPCommonMenuKeys.Platform).addItems([
2065
+ {
2066
+ name: AXPPlatformManagementMenuKeys.Tokens,
2067
+ priority: 8,
2068
+ text: `@${scope}:tokens.terms.tokens`,
2069
+ path: this.entityService.createPath(moduleName, RootConfig.entities.token.name),
2070
+ icon: RootConfig.entities.token.icon,
2071
+ policy: platformShellPolicy,
2072
+ },
2073
+ ]);
2074
+ //#endregion
2075
+ //#region ----- Configuration > Platform Definitions -----
2076
+ const platformDefinitionsMenu = context.find(AXPCommonMenuKeys.PlatformDefinitions);
2077
+ if (!platformDefinitionsMenu.exists) {
2078
+ context.find(AXPCommonMenuKeys.Configuration).addItems([
2079
+ {
2080
+ name: AXPCommonMenuKeys.PlatformDefinitions,
2081
+ priority: 8946,
2082
+ text: '@general:menus.platform-definitions.title',
2083
+ icon: 'fa-light fa-books',
2084
+ type: 'menu',
2085
+ children: [],
2086
+ policy: platformShellPolicy,
2087
+ },
2088
+ ]);
2089
+ }
2090
+ context.find(AXPCommonMenuKeys.PlatformDefinitions).addItems([
2091
+ {
2092
+ name: AXPPlatformManagementMenuKeys.EntityDefinitions,
2093
+ text: `@${scope}:menus.entity-definitions.title`,
2094
+ path: this.entityService.createPath(moduleName, RootConfig.entities.entityDefinition.name),
2095
+ icon: RootConfig.entities.entityDefinition.icon,
2096
+ priority: 1,
2097
+ policy: platformShellPolicy,
2098
+ },
2099
+ {
2100
+ name: AXPPlatformManagementMenuKeys.CommandDefinitions,
2101
+ text: `@${scope}:menus.command-definitions.title`,
2102
+ path: this.entityService.createPath(moduleName, RootConfig.entities.commandDefinition.name),
2103
+ icon: RootConfig.entities.commandDefinition.icon,
2104
+ priority: 2,
2105
+ policy: platformShellPolicy,
2106
+ },
2107
+ {
2108
+ name: AXPPlatformManagementMenuKeys.QueryDefinitions,
2109
+ text: `@${scope}:menus.query-definitions.title`,
2110
+ path: this.entityService.createPath(moduleName, RootConfig.entities.queryDefinition.name),
2111
+ icon: RootConfig.entities.queryDefinition.icon,
2112
+ priority: 3,
2113
+ policy: platformShellPolicy,
2114
+ },
2115
+ {
2116
+ name: AXPPlatformManagementMenuKeys.PluginDefinitions,
2117
+ text: `@${scope}:menus.plugin-definitions.title`,
2118
+ path: this.entityService.createPath(moduleName, RootConfig.entities.pluginDefinition.name),
2119
+ icon: RootConfig.entities.pluginDefinition.icon,
2120
+ priority: 4,
2121
+ policy: platformShellPolicy,
2122
+ },
2123
+ {
2124
+ name: AXPPlatformManagementMenuKeys.Widgets,
2125
+ text: `@${scope}:menus.widgets.title`,
2126
+ path: this.entityService.createPath(moduleName, RootConfig.entities.widget.name),
2127
+ icon: RootConfig.entities.widget.icon,
2128
+ priority: 5,
2129
+ policy: platformShellPolicy,
2130
+ },
2131
+ ]);
2132
+ //#endregion
2133
+ }
2134
+ }
2135
+
2136
+ //#endregion
2137
+ //#region ---- Permission Definition Provider ----
2138
+ /**
2139
+ * Registers MaintainPlatformShell under Applications & Platform.
2140
+ */
2141
+ class AXMPermissionDefinitionProvider {
2142
+ async define(context) {
2143
+ const keys = AXPApplicationsPlatformPermissionKeys;
2144
+ const groups = AXPCommonPermissionGroupKeys;
2145
+ const P = '@common:applications-platform.permissions';
2146
+ const G = '@common:permission-groups';
2147
+ context.addGroup(groups.ApplicationsPlatform.Root, `${G}.applications-platform.title`, `${G}.applications-platform.description`, { order: 110 });
2148
+ context
2149
+ .addGroup(groups.ApplicationsPlatform.TenancyShell, `${G}.applications-platform-tenancy-shell.title`, `${G}.applications-platform-tenancy-shell.description`, { parent: groups.ApplicationsPlatform.Root, order: 20 })
2150
+ .addPermission(keys.MaintainPlatformShell, `${P}.maintain-platform-shell.title`, `${P}.maintain-platform-shell.description`, { modules: ['PlatformManagement'], requiredFeatures: ['PlatformManagement'] })
2151
+ .endPermission()
2152
+ .endGroup();
2153
+ }
2154
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2155
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider }); }
2156
+ }
2157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, decorators: [{
2158
+ type: Injectable
2159
+ }] });
2160
+
2161
+ //#region ---- Entity row → registry record ----
2162
+ function mapEntityRowToRegistryRecord(row) {
2163
+ return {
2164
+ name: row.name,
2165
+ active: row.active !== false,
2166
+ description: row.description,
2167
+ catalogTitle: row.catalogTitle,
2168
+ executionMode: row.executionMode,
2169
+ capabilities: row.capabilities,
2170
+ inputOptionDefinitions: row.inputOptionDefinitions,
2171
+ outputOptionDefinitions: row.outputOptionDefinitions,
2172
+ outcomesDefinitionJson: row.outcomesDefinitionJson,
2173
+ aiMetadataJson: row.aiMetadataJson,
2174
+ categories: row.categories,
2175
+ };
2176
+ }
2177
+ //#endregion
2178
+ //#region ---- Platform command catalog CRUD (server-backed) ----
2179
+ /**
2180
+ * Reads command catalog metadata from {@code PlatformManagement.CommandDefinition} entity storage (server DB).
2181
+ * Sidecar source files are never imported in the browser bundle.
2182
+ */
2183
+ class AXMPlatformCommandCatalogCrudService {
2184
+ constructor() {
2185
+ this.entityService = inject(AXPEntityService);
2186
+ }
2187
+ entityData() {
2188
+ return this.entityService
2189
+ .withEntity(RootConfig.module.name, RootConfig.entities.commandDefinition.name)
2190
+ .data();
2191
+ }
2192
+ async listCommands() {
2193
+ const result = await this.entityData().query({ skip: 0, take: 5000 });
2194
+ const items = (result.items ?? []);
2195
+ return items.map((row) => ({
2196
+ id: String(row.id),
2197
+ definition: mapEntityRowToRegistryRecord(row),
2198
+ }));
2199
+ }
2200
+ async getCommand(id) {
2201
+ const row = await this.entityData().byKey(id);
2202
+ return row ? mapEntityRowToRegistryRecord(row) : null;
2203
+ }
2204
+ // #region ---- Unimplemented catalog stores (Phase 1: commands only) ----
2205
+ async list() {
2206
+ return [];
2207
+ }
2208
+ async getRaw() {
2209
+ return null;
2210
+ }
2211
+ async create() {
2212
+ throw new Error('Entity definition CRUD is not available for command catalog rows.');
2213
+ }
2214
+ async update() {
2215
+ throw new Error('Entity definition CRUD is not available for command catalog rows.');
2216
+ }
2217
+ async delete() {
2218
+ throw new Error('Entity definition CRUD is not available for command catalog rows.');
2219
+ }
2220
+ async listInterfaces() {
2221
+ return [];
2222
+ }
2223
+ async listInterfacesByCategory() {
2224
+ return [];
2225
+ }
2226
+ async getInterface() {
2227
+ return null;
2228
+ }
2229
+ async createInterface() {
2230
+ throw new Error('Not implemented');
2231
+ }
2232
+ async updateInterface() {
2233
+ throw new Error('Not implemented');
2234
+ }
2235
+ async deleteInterface() {
2236
+ throw new Error('Not implemented');
2237
+ }
2238
+ async listPlugins() {
2239
+ return [];
2240
+ }
2241
+ async getPlugin() {
2242
+ return null;
2243
+ }
2244
+ async createPlugin() {
2245
+ throw new Error('Not implemented');
2246
+ }
2247
+ async updatePlugin() {
2248
+ throw new Error('Not implemented');
2249
+ }
2250
+ async deletePlugin() {
2251
+ throw new Error('Not implemented');
2252
+ }
2253
+ async listValidations() {
2254
+ return [];
2255
+ }
2256
+ async getValidation() {
2257
+ return null;
2258
+ }
2259
+ async createValidation() {
2260
+ throw new Error('Not implemented');
2261
+ }
2262
+ async updateValidation() {
2263
+ throw new Error('Not implemented');
2264
+ }
2265
+ async deleteValidation() {
2266
+ throw new Error('Not implemented');
2267
+ }
2268
+ async listRelations() {
2269
+ return [];
2270
+ }
2271
+ async getRelation() {
2272
+ return null;
2273
+ }
2274
+ async createRelation() {
2275
+ throw new Error('Not implemented');
2276
+ }
2277
+ async updateRelation() {
2278
+ throw new Error('Not implemented');
2279
+ }
2280
+ async deleteRelation() {
2281
+ throw new Error('Not implemented');
2282
+ }
2283
+ async listModules() {
2284
+ return [];
2285
+ }
2286
+ async getModule() {
2287
+ return null;
2288
+ }
2289
+ async createModule() {
2290
+ throw new Error('Not implemented');
2291
+ }
2292
+ async updateModule() {
2293
+ throw new Error('Not implemented');
2294
+ }
2295
+ async deleteModule() {
2296
+ throw new Error('Not implemented');
2297
+ }
2298
+ async listAggregates() {
2299
+ return [];
2300
+ }
2301
+ async getAggregate() {
2302
+ return null;
2303
+ }
2304
+ async createAggregate() {
2305
+ throw new Error('Not implemented');
2306
+ }
2307
+ async updateAggregate() {
2308
+ throw new Error('Not implemented');
2309
+ }
2310
+ async deleteAggregate() {
2311
+ throw new Error('Not implemented');
2312
+ }
2313
+ async listMenus() {
2314
+ return [];
2315
+ }
2316
+ async getMenu() {
2317
+ return null;
2318
+ }
2319
+ async createMenu() {
2320
+ throw new Error('Not implemented');
2321
+ }
2322
+ async updateMenu() {
2323
+ throw new Error('Not implemented');
2324
+ }
2325
+ async deleteMenu() {
2326
+ throw new Error('Not implemented');
2327
+ }
2328
+ async listPermissionGroups() {
2329
+ return [];
2330
+ }
2331
+ async getPermissionGroup() {
2332
+ return null;
2333
+ }
2334
+ async createPermissionGroup() {
2335
+ throw new Error('Not implemented');
2336
+ }
2337
+ async updatePermissionGroup() {
2338
+ throw new Error('Not implemented');
2339
+ }
2340
+ async deletePermissionGroup() {
2341
+ throw new Error('Not implemented');
2342
+ }
2343
+ async createCommand() {
2344
+ throw new Error('Not implemented');
2345
+ }
2346
+ async updateCommand() {
2347
+ throw new Error('Not implemented');
2348
+ }
2349
+ async deleteCommand() {
2350
+ throw new Error('Not implemented');
2351
+ }
2352
+ async listQueries() {
2353
+ return [];
2354
+ }
2355
+ async getQuery() {
2356
+ return null;
2357
+ }
2358
+ async createQuery() {
2359
+ throw new Error('Not implemented');
2360
+ }
2361
+ async updateQuery() {
2362
+ throw new Error('Not implemented');
2363
+ }
2364
+ async deleteQuery() {
2365
+ throw new Error('Not implemented');
2366
+ }
2367
+ async listWidgets() {
2368
+ return [];
2369
+ }
2370
+ async getWidget() {
2371
+ return null;
2372
+ }
2373
+ async createWidget() {
2374
+ throw new Error('Not implemented');
2375
+ }
2376
+ async updateWidget() {
2377
+ throw new Error('Not implemented');
2378
+ }
2379
+ async deleteWidget() {
2380
+ throw new Error('Not implemented');
2381
+ }
2382
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformCommandCatalogCrudService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2383
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformCommandCatalogCrudService, providedIn: 'root' }); }
2384
+ }
2385
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformCommandCatalogCrudService, decorators: [{
2386
+ type: Injectable,
2387
+ args: [{ providedIn: 'root' }]
2388
+ }] });
2389
+ function providePlatformCommandCatalogCrudService() {
2390
+ return {
2391
+ provide: AXP_ENTITY_DEFINITION_CRUD_SERVICE,
2392
+ useExisting: AXMPlatformCommandCatalogCrudService,
2393
+ };
2394
+ }
2395
+
2396
+ // Entity Modules
2397
+ //#region ---- Routes Factory ----
2398
+ function routesFactory() {
2399
+ return [
2400
+ {
2401
+ path: ':app/platform-management/menus/:scope',
2402
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
2403
+ data: { rootLayout: true },
2404
+ loadComponent: () => AXPRootLayoutComponent,
2405
+ children: [
2406
+ {
2407
+ path: '',
2408
+ loadComponent: () => import('./acorex-platform-module-platform-management-client-menu-list.component-C5HH5-Dw.mjs').then((c) => c.AXMMenuListComponent),
2409
+ data: { reuse: true },
2410
+ },
2411
+ ],
2412
+ },
2413
+ ];
2414
+ }
2415
+ //#endregion
2416
+ class AXMPlatformManagementModule {
2417
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformManagementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2418
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformManagementModule, imports: [AXPWidgetCoreModule,
2419
+ AXMUpdaterModule,
2420
+ AXMTokenModule,
2421
+ AXMMenuManagementModule] }); }
2422
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformManagementModule, providers: [
2423
+ // Module-specific providers (not part of manifest system)
2424
+ {
2425
+ provide: AXP_MENU_MIDDLEWARE,
2426
+ useClass: AXPCustomMenuMiddleware,
2427
+ multi: true,
2428
+ },
2429
+ {
2430
+ provide: ROUTES,
2431
+ multi: true,
2432
+ useFactory: routesFactory,
2433
+ },
2434
+ // Module Manifest Provider
2435
+ {
2436
+ provide: AXP_MODULE_MANIFEST_PROVIDER,
2437
+ useValue: PlatformManagementManifest,
2438
+ multi: true,
2439
+ },
2440
+ // Provider references (now using tokens)
2441
+ {
2442
+ provide: AXP_PERMISSION_DEFINITION_PROVIDER,
2443
+ useClass: AXMPermissionDefinitionProvider,
2444
+ multi: true,
2445
+ },
2446
+ {
2447
+ provide: AXP_MENU_PROVIDER,
2448
+ useClass: AXMMenuProvider,
2449
+ multi: true,
2450
+ },
2451
+ {
2452
+ provide: AXP_FEATURE_DEFINITION_PROVIDER,
2453
+ useClass: AXPPlatformManagementFeatureDefinitionProvider,
2454
+ multi: true,
2455
+ },
2456
+ {
2457
+ provide: AXP_WIDGET_DEFINITION_PROVIDER,
2458
+ useClass: AXMPlatformManagementWidgetsProvider,
2459
+ multi: true,
2460
+ },
2461
+ providePlatformCommandCatalogCrudService(),
2462
+ provideCommandSetups([
2463
+ {
2464
+ key: 'PlatformManagement:DataListPickEntityItem',
2465
+ command: () => import('./acorex-platform-module-platform-management-client-data-list-pick-entity-item.command-CJVpqPmY.mjs').then((c) => c.AXPDataListPickEntityItemCommand),
2466
+ },
2467
+ {
2468
+ key: 'PlatformManagement:CreateUserQuery',
2469
+ command: () => import('./acorex-platform-module-platform-management-client-create-user-query.command-2Qu7aoCp.mjs').then((c) => c.AXMCreateUserQueryCommand),
2470
+ },
2471
+ ]),
2472
+ provideQuerySetups([
2473
+ {
2474
+ key: 'PlatformManagement:ListRegisteredEntities',
2475
+ loader: () => import('./acorex-platform-module-platform-management-client-list-registered-entities.query-DjStd3a8.mjs').then((c) => c.AXMListRegisteredEntitiesQuery),
2476
+ },
2477
+ {
2478
+ key: 'PlatformManagement:ListRegisteredQueries',
2479
+ loader: () => import('./acorex-platform-module-platform-management-client-list-registered-queries.query-C-IfkTel.mjs').then((c) => c.AXMListRegisteredQueriesQuery),
2480
+ },
2481
+ {
2482
+ key: 'PlatformManagement:ListPlatformNavigationForAi',
2483
+ loader: () => import('./acorex-platform-module-platform-management-client-list-platform-navigation-for-ai.query-2oHMITw-.mjs').then((c) => c.AXMListPlatformNavigationForAiQuery),
2484
+ },
2485
+ {
2486
+ key: 'PlatformManagement:ListCatalogScopes',
2487
+ loader: () => import('./acorex-platform-module-platform-management-client-list-catalog-scopes.query-ByhDTWE6.mjs').then((c) => c.AXMListCatalogScopesQuery),
2488
+ },
2489
+ {
2490
+ key: 'PlatformManagement:ListEditorWidgets',
2491
+ loader: () => import('./acorex-platform-module-platform-management-client-list-editor-widgets.query-DITPMmbw.mjs').then((c) => c.AXMListEditorWidgetsQuery),
2492
+ },
2493
+ {
2494
+ key: 'PlatformManagement:Dashboard:TenantsKpi',
2495
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformTenantsKpiQuery),
2496
+ },
2497
+ {
2498
+ key: 'PlatformManagement:Dashboard:UsersKpi',
2499
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformUsersKpiQuery),
2500
+ },
2501
+ {
2502
+ key: 'PlatformManagement:Dashboard:ApplicationsKpi',
2503
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformApplicationsKpiQuery),
2504
+ },
2505
+ {
2506
+ key: 'PlatformManagement:Dashboard:ErrorsKpi',
2507
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformErrorsKpiQuery),
2508
+ },
2509
+ {
2510
+ key: 'PlatformManagement:Dashboard:TenantsByStatusBar',
2511
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformTenantsByStatusBarQuery),
2512
+ },
2513
+ {
2514
+ key: 'PlatformManagement:Dashboard:ApplicationsByEditionDonut',
2515
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformApplicationsByEditionDonutQuery),
2516
+ },
2517
+ {
2518
+ key: 'PlatformManagement:Dashboard:ErrorsTrendLine',
2519
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformErrorsTrendLineQuery),
2520
+ },
2521
+ {
2522
+ key: 'PlatformManagement:Dashboard:JobsByStatusBar',
2523
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformJobsByStatusBarQuery),
2524
+ },
2525
+ ]),
2526
+ ], imports: [AXPWidgetCoreModule,
2527
+ AXMUpdaterModule,
2528
+ AXMTokenModule,
2529
+ AXMMenuManagementModule] }); }
2530
+ }
2531
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformManagementModule, decorators: [{
2532
+ type: NgModule,
2533
+ args: [{
2534
+ imports: [
2535
+ AXPWidgetCoreModule,
2536
+ AXMUpdaterModule,
2537
+ AXMTokenModule,
2538
+ AXMMenuManagementModule,
2539
+ // Entity Modules
2540
+ ],
2541
+ exports: [],
2542
+ declarations: [],
2543
+ providers: [
2544
+ // Module-specific providers (not part of manifest system)
2545
+ {
2546
+ provide: AXP_MENU_MIDDLEWARE,
2547
+ useClass: AXPCustomMenuMiddleware,
2548
+ multi: true,
2549
+ },
2550
+ {
2551
+ provide: ROUTES,
2552
+ multi: true,
2553
+ useFactory: routesFactory,
2554
+ },
2555
+ // Module Manifest Provider
2556
+ {
2557
+ provide: AXP_MODULE_MANIFEST_PROVIDER,
2558
+ useValue: PlatformManagementManifest,
2559
+ multi: true,
2560
+ },
2561
+ // Provider references (now using tokens)
2562
+ {
2563
+ provide: AXP_PERMISSION_DEFINITION_PROVIDER,
2564
+ useClass: AXMPermissionDefinitionProvider,
2565
+ multi: true,
2566
+ },
2567
+ {
2568
+ provide: AXP_MENU_PROVIDER,
2569
+ useClass: AXMMenuProvider,
2570
+ multi: true,
2571
+ },
2572
+ {
2573
+ provide: AXP_FEATURE_DEFINITION_PROVIDER,
2574
+ useClass: AXPPlatformManagementFeatureDefinitionProvider,
2575
+ multi: true,
2576
+ },
2577
+ {
2578
+ provide: AXP_WIDGET_DEFINITION_PROVIDER,
2579
+ useClass: AXMPlatformManagementWidgetsProvider,
2580
+ multi: true,
2581
+ },
2582
+ providePlatformCommandCatalogCrudService(),
2583
+ provideCommandSetups([
2584
+ {
2585
+ key: 'PlatformManagement:DataListPickEntityItem',
2586
+ command: () => import('./acorex-platform-module-platform-management-client-data-list-pick-entity-item.command-CJVpqPmY.mjs').then((c) => c.AXPDataListPickEntityItemCommand),
2587
+ },
2588
+ {
2589
+ key: 'PlatformManagement:CreateUserQuery',
2590
+ command: () => import('./acorex-platform-module-platform-management-client-create-user-query.command-2Qu7aoCp.mjs').then((c) => c.AXMCreateUserQueryCommand),
2591
+ },
2592
+ ]),
2593
+ provideQuerySetups([
2594
+ {
2595
+ key: 'PlatformManagement:ListRegisteredEntities',
2596
+ loader: () => import('./acorex-platform-module-platform-management-client-list-registered-entities.query-DjStd3a8.mjs').then((c) => c.AXMListRegisteredEntitiesQuery),
2597
+ },
2598
+ {
2599
+ key: 'PlatformManagement:ListRegisteredQueries',
2600
+ loader: () => import('./acorex-platform-module-platform-management-client-list-registered-queries.query-C-IfkTel.mjs').then((c) => c.AXMListRegisteredQueriesQuery),
2601
+ },
2602
+ {
2603
+ key: 'PlatformManagement:ListPlatformNavigationForAi',
2604
+ loader: () => import('./acorex-platform-module-platform-management-client-list-platform-navigation-for-ai.query-2oHMITw-.mjs').then((c) => c.AXMListPlatformNavigationForAiQuery),
2605
+ },
2606
+ {
2607
+ key: 'PlatformManagement:ListCatalogScopes',
2608
+ loader: () => import('./acorex-platform-module-platform-management-client-list-catalog-scopes.query-ByhDTWE6.mjs').then((c) => c.AXMListCatalogScopesQuery),
2609
+ },
2610
+ {
2611
+ key: 'PlatformManagement:ListEditorWidgets',
2612
+ loader: () => import('./acorex-platform-module-platform-management-client-list-editor-widgets.query-DITPMmbw.mjs').then((c) => c.AXMListEditorWidgetsQuery),
2613
+ },
2614
+ {
2615
+ key: 'PlatformManagement:Dashboard:TenantsKpi',
2616
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformTenantsKpiQuery),
2617
+ },
2618
+ {
2619
+ key: 'PlatformManagement:Dashboard:UsersKpi',
2620
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformUsersKpiQuery),
2621
+ },
2622
+ {
2623
+ key: 'PlatformManagement:Dashboard:ApplicationsKpi',
2624
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformApplicationsKpiQuery),
2625
+ },
2626
+ {
2627
+ key: 'PlatformManagement:Dashboard:ErrorsKpi',
2628
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-kpi.queries-BkPdhVq_.mjs').then((c) => c.AXMPlatformErrorsKpiQuery),
2629
+ },
2630
+ {
2631
+ key: 'PlatformManagement:Dashboard:TenantsByStatusBar',
2632
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformTenantsByStatusBarQuery),
2633
+ },
2634
+ {
2635
+ key: 'PlatformManagement:Dashboard:ApplicationsByEditionDonut',
2636
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformApplicationsByEditionDonutQuery),
2637
+ },
2638
+ {
2639
+ key: 'PlatformManagement:Dashboard:ErrorsTrendLine',
2640
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformErrorsTrendLineQuery),
2641
+ },
2642
+ {
2643
+ key: 'PlatformManagement:Dashboard:JobsByStatusBar',
2644
+ loader: () => import('./acorex-platform-module-platform-management-client-platform-console-charts.queries-CQ34R13-.mjs').then((c) => c.AXMPlatformJobsByStatusBarQuery),
2645
+ },
2646
+ ]),
2647
+ ],
2648
+ }]
2649
+ }] });
2650
+
2651
+ /** MIME type for native drag payloads carrying `AXPWorkflowActivityPaletteItem` JSON. */
2652
+ const AXP_WORKFLOW_PALETTE_MIME = 'application/x-axp-workflow-palette-item';
2653
+
2654
+ //#region ---- Ids ----
2655
+ let idCounter = 0;
2656
+ function createWorkflowNodeId() {
2657
+ idCounter += 1;
2658
+ return `wf-node-${Date.now()}-${idCounter}`;
2659
+ }
2660
+ function createWorkflowConnectorId() {
2661
+ idCounter += 1;
2662
+ return `wf-conn-${Date.now()}-${idCounter}`;
2663
+ }
2664
+ function createWorkflowPortId(prefix) {
2665
+ idCounter += 1;
2666
+ return `${prefix}-port-${idCounter}`;
2667
+ }
2668
+ //#endregion
2669
+ //#region ---- Defaults ----
2670
+ const DEFAULT_SIZE = { width: 180, height: 72 };
2671
+ const DEFAULT_INBOUND = [{ side: 'left', ratio: 0.5, key: 'in' }];
2672
+ const DEFAULT_OUTBOUND = [{ side: 'right', ratio: 0.5, key: 'out' }];
2673
+ function cloneDiagram(model) {
2674
+ return {
2675
+ nodes: model.nodes.map((n) => ({
2676
+ ...n,
2677
+ position: { ...n.position },
2678
+ size: { ...n.size },
2679
+ inboundPorts: n.inboundPorts.map((p) => ({
2680
+ ...p,
2681
+ data: p.data ? { ...p.data } : undefined,
2682
+ })),
2683
+ outboundPorts: n.outboundPorts.map((p) => ({
2684
+ ...p,
2685
+ data: p.data ? { ...p.data } : undefined,
2686
+ })),
2687
+ data: n.data ? { ...n.data } : undefined,
2688
+ })),
2689
+ connectors: model.connectors.map((c) => ({
2690
+ ...c,
2691
+ data: c.data ? { ...c.data } : undefined,
2692
+ })),
2693
+ data: model.data ? { ...model.data } : undefined,
2694
+ };
2695
+ }
2696
+ function createPortsFromPalette(nodeId, item) {
2697
+ const inboundSrc = item.inboundPorts !== undefined ? item.inboundPorts : DEFAULT_INBOUND;
2698
+ const outboundSrc = item.outboundPorts !== undefined ? item.outboundPorts : DEFAULT_OUTBOUND;
2699
+ const inbound = inboundSrc.map((p) => ({
2700
+ ...p,
2701
+ id: createWorkflowPortId(`${nodeId}-in`),
2702
+ kind: 'inbound',
2703
+ data: p.data ? { ...p.data } : undefined,
2704
+ }));
2705
+ const outbound = outboundSrc.map((p) => ({
2706
+ ...p,
2707
+ id: createWorkflowPortId(`${nodeId}-out`),
2708
+ kind: 'outbound',
2709
+ data: p.data ? { ...p.data } : undefined,
2710
+ }));
2711
+ return { inbound, outbound };
2712
+ }
2713
+ function createNodeFromPaletteItem(item, position) {
2714
+ const id = createWorkflowNodeId();
2715
+ const { inbound, outbound } = createPortsFromPalette(id, item);
2716
+ const shape = item.shape ?? 'rounded-rectangle';
2717
+ const size = item.size ? { ...item.size } : { ...DEFAULT_SIZE };
2718
+ if (shape === 'circle') {
2719
+ const d = Math.min(size.width, size.height);
2720
+ size.width = d;
2721
+ size.height = d;
2722
+ }
2723
+ return {
2724
+ id,
2725
+ type: item.type,
2726
+ title: item.title,
2727
+ icon: item.icon,
2728
+ shape,
2729
+ position: { ...position },
2730
+ size: { ...size },
2731
+ inboundPorts: inbound,
2732
+ outboundPorts: outbound,
2733
+ data: item.data ? { ...item.data } : undefined,
2734
+ lockAspectRatio: item.lockAspectRatio ?? shape === 'circle',
2735
+ snapToGrid: item.snapToGrid,
2736
+ color: item.color,
2737
+ };
2738
+ }
2739
+ /**
2740
+ * Parses drag `dataTransfer` JSON into a partial palette item (requires `type`).
2741
+ * Merge with catalog (`paletteItems`) to obtain `title` and defaults.
2742
+ */
2743
+ function parsePaletteItemDragPayload(raw) {
2744
+ if (!raw)
2745
+ return null;
2746
+ try {
2747
+ const o = JSON.parse(raw);
2748
+ if (typeof o.type !== 'string')
2749
+ return null;
2750
+ return o;
2751
+ }
2752
+ catch {
2753
+ return null;
2754
+ }
2755
+ }
2756
+ //#endregion
2757
+ //#region ---- Snap ----
2758
+ function snapScalar(value, grid) {
2759
+ if (grid <= 0)
2760
+ return value;
2761
+ return Math.round(value / grid) * grid;
2762
+ }
2763
+ function snapPoint(point, grid) {
2764
+ return { x: snapScalar(point.x, grid), y: snapScalar(point.y, grid) };
2765
+ }
2766
+ //#endregion
2767
+ //#region ---- Port anchor (node-local, top-left origin) ----
2768
+ /**
2769
+ * Inbound/outbound anchor on an axis-aligned ellipse inscribed in `w`×`h` (centered),
2770
+ * so ports sit on the true perimeter for circle/ellipse nodes.
2771
+ */
2772
+ function portAnchorOnEllipsePerimeter(w, h, side, ratio) {
2773
+ const r = Math.min(0.999, Math.max(0.001, ratio));
2774
+ const cx = w * 0.5;
2775
+ const cy = h * 0.5;
2776
+ const rx = w * 0.5;
2777
+ const ry = h * 0.5;
2778
+ let theta = 0;
2779
+ switch (side) {
2780
+ case 'right':
2781
+ theta = -Math.PI / 2 + Math.PI * r;
2782
+ break;
2783
+ case 'left':
2784
+ theta = Math.PI / 2 + Math.PI * r;
2785
+ break;
2786
+ case 'top':
2787
+ theta = Math.PI + Math.PI * r;
2788
+ break;
2789
+ case 'bottom':
2790
+ theta = Math.PI * r;
2791
+ break;
2792
+ default:
2793
+ theta = 0;
2794
+ }
2795
+ return {
2796
+ x: cx + rx * Math.cos(theta),
2797
+ y: cy + ry * Math.sin(theta),
2798
+ };
2799
+ }
2800
+ function portAnchorOnBoxEdge(w, h, side, ratio) {
2801
+ const r = Math.min(0.999, Math.max(0.001, ratio));
2802
+ switch (side) {
2803
+ case 'top':
2804
+ return { x: w * r, y: 0 };
2805
+ case 'bottom':
2806
+ return { x: w * r, y: h };
2807
+ case 'left':
2808
+ return { x: 0, y: h * r };
2809
+ case 'right':
2810
+ return { x: w, y: h * r };
2811
+ default:
2812
+ return { x: w * 0.5, y: h * 0.5 };
2813
+ }
2814
+ }
2815
+ /** Diamond perimeter of a rotated square inside the node bounds. */
2816
+ function portAnchorOnDiamondPerimeter(w, h, side, ratio) {
2817
+ const r = Math.min(0.999, Math.max(0.001, ratio));
2818
+ const top = { x: w * 0.5, y: 0 };
2819
+ const right = { x: w, y: h * 0.5 };
2820
+ const bottom = { x: w * 0.5, y: h };
2821
+ const left = { x: 0, y: h * 0.5 };
2822
+ const lerp = (a, b, t) => ({
2823
+ x: a.x + (b.x - a.x) * t,
2824
+ y: a.y + (b.y - a.y) * t,
2825
+ });
2826
+ switch (side) {
2827
+ case 'top':
2828
+ return r <= 0.5 ? lerp(left, top, r * 2) : lerp(top, right, (r - 0.5) * 2);
2829
+ case 'right':
2830
+ return r <= 0.5 ? lerp(top, right, r * 2) : lerp(right, bottom, (r - 0.5) * 2);
2831
+ case 'bottom':
2832
+ return r <= 0.5 ? lerp(right, bottom, r * 2) : lerp(bottom, left, (r - 0.5) * 2);
2833
+ case 'left':
2834
+ return r <= 0.5 ? lerp(bottom, left, r * 2) : lerp(left, top, (r - 0.5) * 2);
2835
+ default:
2836
+ return { x: w * 0.5, y: h * 0.5 };
2837
+ }
2838
+ }
2839
+ function portAnchorOnNode(node, port) {
2840
+ const { width: w, height: h } = node.size;
2841
+ const r = Math.min(0.999, Math.max(0.001, port.ratio));
2842
+ if (node.shape === 'circle' || node.shape === 'ellipse') {
2843
+ return portAnchorOnEllipsePerimeter(w, h, port.side, r);
2844
+ }
2845
+ if (node.shape === 'diamond') {
2846
+ return portAnchorOnDiamondPerimeter(w, h, port.side, r);
2847
+ }
2848
+ return portAnchorOnBoxEdge(w, h, port.side, r);
2849
+ }
2850
+ function portCanvasPosition(node, port) {
2851
+ const local = portAnchorOnNode(node, port);
2852
+ return { x: node.position.x + local.x, y: node.position.y + local.y };
2853
+ }
2854
+ function inflateRect(r, padding) {
2855
+ return {
2856
+ x: r.position.x - padding,
2857
+ y: r.position.y - padding,
2858
+ width: r.size.width + padding * 2,
2859
+ height: r.size.height + padding * 2,
2860
+ };
2861
+ }
2862
+ function pointInsideRect(p, r) {
2863
+ return p.x > r.x && p.x < r.x + r.width && p.y > r.y && p.y < r.y + r.height;
2864
+ }
2865
+ /**
2866
+ * Returns true if an axis-aligned segment crosses the interior of any obstacle.
2867
+ * Endpoints that only touch the rectangle's edge are not considered a crossing.
2868
+ */
2869
+ function segmentCrossesObstacles(a, b, obstacles) {
2870
+ const minX = Math.min(a.x, b.x);
2871
+ const maxX = Math.max(a.x, b.x);
2872
+ const minY = Math.min(a.y, b.y);
2873
+ const maxY = Math.max(a.y, b.y);
2874
+ const tolerance = 0.5;
2875
+ for (const r of obstacles) {
2876
+ const right = r.x + r.width;
2877
+ const bottom = r.y + r.height;
2878
+ if (maxX <= r.x + tolerance || minX >= right - tolerance)
2879
+ continue;
2880
+ if (maxY <= r.y + tolerance || minY >= bottom - tolerance)
2881
+ continue;
2882
+ if (Math.abs(a.y - b.y) < 0.0001) {
2883
+ if (a.y > r.y + tolerance && a.y < bottom - tolerance)
2884
+ return true;
2885
+ }
2886
+ else if (Math.abs(a.x - b.x) < 0.0001) {
2887
+ if (a.x > r.x + tolerance && a.x < right - tolerance)
2888
+ return true;
2889
+ }
2890
+ else {
2891
+ return true;
2892
+ }
2893
+ }
2894
+ return false;
2895
+ }
2896
+ function stepOutward(p, side, length) {
2897
+ switch (side) {
2898
+ case 'left':
2899
+ return { x: p.x - length, y: p.y };
2900
+ case 'right':
2901
+ return { x: p.x + length, y: p.y };
2902
+ case 'top':
2903
+ return { x: p.x, y: p.y - length };
2904
+ case 'bottom':
2905
+ return { x: p.x, y: p.y + length };
2906
+ }
2907
+ }
2908
+ function oppositeSide(side) {
2909
+ switch (side) {
2910
+ case 'left':
2911
+ return 'right';
2912
+ case 'right':
2913
+ return 'left';
2914
+ case 'top':
2915
+ return 'bottom';
2916
+ case 'bottom':
2917
+ return 'top';
2918
+ }
2919
+ }
2920
+ function sideAxis(side) {
2921
+ return side === 'left' || side === 'right' ? 'h' : 'v';
2922
+ }
2923
+ /**
2924
+ * Removes consecutive duplicate points and collapses collinear intermediate waypoints.
2925
+ */
2926
+ function simplifyWaypoints(points) {
2927
+ const deduped = [];
2928
+ for (const p of points) {
2929
+ const last = deduped[deduped.length - 1];
2930
+ if (last && Math.abs(last.x - p.x) < 0.001 && Math.abs(last.y - p.y) < 0.001)
2931
+ continue;
2932
+ deduped.push(p);
2933
+ }
2934
+ if (deduped.length <= 2)
2935
+ return deduped;
2936
+ const out = [deduped[0]];
2937
+ for (let i = 1; i < deduped.length - 1; i++) {
2938
+ const prev = deduped[i - 1];
2939
+ const curr = deduped[i];
2940
+ const next = deduped[i + 1];
2941
+ const collinearH = Math.abs(prev.y - curr.y) < 0.001 && Math.abs(curr.y - next.y) < 0.001;
2942
+ const collinearV = Math.abs(prev.x - curr.x) < 0.001 && Math.abs(curr.x - next.x) < 0.001;
2943
+ if (!collinearH && !collinearV)
2944
+ out.push(curr);
2945
+ }
2946
+ out.push(deduped[deduped.length - 1]);
2947
+ return out;
2948
+ }
2949
+ /**
2950
+ * Generates candidate waypoint sets that connect `a` to `b` while respecting the entry/exit axes.
2951
+ * Strategies are ordered from "most natural" to "more elaborate detour".
2952
+ */
2953
+ function generateRouteStrategies(a, aAxis, b, bAxis, obstacles) {
2954
+ const strategies = [];
2955
+ const midX = (a.x + b.x) / 2;
2956
+ const midY = (a.y + b.y) / 2;
2957
+ const obstacleXCuts = [];
2958
+ const obstacleYCuts = [];
2959
+ for (const o of obstacles) {
2960
+ obstacleXCuts.push(o.x - 6);
2961
+ obstacleXCuts.push(o.x + o.width + 6);
2962
+ obstacleYCuts.push(o.y - 6);
2963
+ obstacleYCuts.push(o.y + o.height + 6);
2964
+ }
2965
+ obstacleXCuts.sort((x, y) => Math.abs(x - midX) - Math.abs(y - midX));
2966
+ obstacleYCuts.sort((x, y) => Math.abs(x - midY) - Math.abs(y - midY));
2967
+ if (aAxis === 'h' && bAxis === 'h') {
2968
+ strategies.push([
2969
+ { x: midX, y: a.y },
2970
+ { x: midX, y: b.y },
2971
+ ]);
2972
+ for (const mx of obstacleXCuts) {
2973
+ strategies.push([
2974
+ { x: mx, y: a.y },
2975
+ { x: mx, y: b.y },
2976
+ ]);
2977
+ }
2978
+ for (const my of obstacleYCuts) {
2979
+ strategies.push([
2980
+ { x: a.x, y: my },
2981
+ { x: b.x, y: my },
2982
+ ]);
2983
+ }
2984
+ }
2985
+ else if (aAxis === 'v' && bAxis === 'v') {
2986
+ strategies.push([
2987
+ { x: a.x, y: midY },
2988
+ { x: b.x, y: midY },
2989
+ ]);
2990
+ for (const my of obstacleYCuts) {
2991
+ strategies.push([
2992
+ { x: a.x, y: my },
2993
+ { x: b.x, y: my },
2994
+ ]);
2995
+ }
2996
+ for (const mx of obstacleXCuts) {
2997
+ strategies.push([
2998
+ { x: mx, y: a.y },
2999
+ { x: mx, y: b.y },
3000
+ ]);
3001
+ }
3002
+ }
3003
+ else if (aAxis === 'h' && bAxis === 'v') {
3004
+ strategies.push([{ x: b.x, y: a.y }]);
3005
+ for (const my of obstacleYCuts) {
3006
+ strategies.push([
3007
+ { x: a.x, y: my },
3008
+ { x: b.x, y: my },
3009
+ ]);
3010
+ }
3011
+ }
3012
+ else {
3013
+ strategies.push([{ x: a.x, y: b.y }]);
3014
+ for (const mx of obstacleXCuts) {
3015
+ strategies.push([
3016
+ { x: mx, y: a.y },
3017
+ { x: mx, y: b.y },
3018
+ ]);
3019
+ }
3020
+ }
3021
+ return strategies;
3022
+ }
3023
+ /**
3024
+ * Picks the first strategy whose segments do not cross any obstacle.
3025
+ * Falls back to the most natural strategy when every candidate is blocked
3026
+ * (best-effort: keeps the connector readable even in tight layouts).
3027
+ */
3028
+ function pickCleanStrategy(a, b, strategies, obstacles) {
3029
+ for (const wp of strategies) {
3030
+ const full = [a, ...wp, b];
3031
+ let clean = true;
3032
+ for (let i = 0; i < full.length - 1; i++) {
3033
+ if (segmentCrossesObstacles(full[i], full[i + 1], obstacles)) {
3034
+ clean = false;
3035
+ break;
3036
+ }
3037
+ }
3038
+ if (clean)
3039
+ return wp;
3040
+ }
3041
+ return strategies[0] ?? [];
3042
+ }
3043
+ /**
3044
+ * Routes an obstacle-aware orthogonal path between two free-space points.
3045
+ * The returned array includes the start (`from`), stub-outs from each port,
3046
+ * intermediate waypoints, and the end (`to`).
3047
+ *
3048
+ * Obstacles whose inflated rect already contains a stub point are dropped so
3049
+ * that source/target nodes can still emit/receive their connector without the
3050
+ * router fighting itself — but the middle of the path still routes around them.
3051
+ */
3052
+ function routeOrthogonalConnector(from, sourceSide, to, targetSide, obstacles = [], options = {}) {
3053
+ const stub = Math.max(0, options.stub ?? 18);
3054
+ const padding = Math.max(0, options.obstaclePadding ?? 10);
3055
+ const aStub = stepOutward(from, sourceSide, stub);
3056
+ const bStub = stepOutward(to, targetSide, stub);
3057
+ const inflated = obstacles.map((o) => inflateRect(o, padding));
3058
+ const safe = inflated.filter((o) => !pointInsideRect(aStub, o) && !pointInsideRect(bStub, o));
3059
+ const strategies = generateRouteStrategies(aStub, sideAxis(sourceSide), bStub, sideAxis(targetSide), safe);
3060
+ const middle = pickCleanStrategy(aStub, bStub, strategies, safe);
3061
+ return simplifyWaypoints([from, aStub, ...middle, bStub, to]);
3062
+ }
3063
+ /** Renders waypoints as straight `L` segments. */
3064
+ function waypointsToOrthogonalPath(points) {
3065
+ if (points.length < 2)
3066
+ return '';
3067
+ const cmds = [`M ${points[0].x} ${points[0].y}`];
3068
+ for (let i = 1; i < points.length; i++) {
3069
+ cmds.push(`L ${points[i].x} ${points[i].y}`);
3070
+ }
3071
+ return cmds.join(' ');
3072
+ }
3073
+ /** Renders waypoints with rounded corners (quadratic curves) between segments. */
3074
+ function waypointsToRoundedPath(points, radius) {
3075
+ if (points.length < 2)
3076
+ return '';
3077
+ if (points.length === 2)
3078
+ return `M ${points[0].x} ${points[0].y} L ${points[1].x} ${points[1].y}`;
3079
+ const cmds = [`M ${points[0].x} ${points[0].y}`];
3080
+ for (let i = 1; i < points.length - 1; i++) {
3081
+ const prev = points[i - 1];
3082
+ const curr = points[i];
3083
+ const next = points[i + 1];
3084
+ const inDx = Math.sign(curr.x - prev.x);
3085
+ const inDy = Math.sign(curr.y - prev.y);
3086
+ const outDx = Math.sign(next.x - curr.x);
3087
+ const outDy = Math.sign(next.y - curr.y);
3088
+ const segIn = Math.hypot(curr.x - prev.x, curr.y - prev.y);
3089
+ const segOut = Math.hypot(next.x - curr.x, next.y - curr.y);
3090
+ const r = Math.max(0, Math.min(radius, segIn * 0.5, segOut * 0.5));
3091
+ const cornerStart = { x: curr.x - inDx * r, y: curr.y - inDy * r };
3092
+ const cornerEnd = { x: curr.x + outDx * r, y: curr.y + outDy * r };
3093
+ cmds.push(`L ${cornerStart.x} ${cornerStart.y}`);
3094
+ cmds.push(`Q ${curr.x} ${curr.y} ${cornerEnd.x} ${cornerEnd.y}`);
3095
+ }
3096
+ const last = points[points.length - 1];
3097
+ cmds.push(`L ${last.x} ${last.y}`);
3098
+ return cmds.join(' ');
3099
+ }
3100
+ /**
3101
+ * Builds the SVG `d` string for a connector. When `options.obstacles` is supplied,
3102
+ * orthogonal/rounded routes detour around the obstacle rectangles instead of
3103
+ * passing through them. The `straight` style always draws a direct line.
3104
+ */
3105
+ function connectorPath(from, to, style, sourceSide, options = {}) {
3106
+ if (style === 'straight') {
3107
+ return `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
3108
+ }
3109
+ const targetSide = options.targetSide ?? oppositeSide(sourceSide);
3110
+ const waypoints = routeOrthogonalConnector(from, sourceSide, to, targetSide, options.obstacles ?? [], {
3111
+ stub: options.stub,
3112
+ obstaclePadding: options.obstaclePadding,
3113
+ });
3114
+ if (style === 'orthogonal') {
3115
+ return waypointsToOrthogonalPath(waypoints);
3116
+ }
3117
+ return waypointsToRoundedPath(waypoints, options.cornerRadius ?? 14);
3118
+ }
3119
+ //#endregion
3120
+ //#region ---- Model helpers ----
3121
+ function findNode(model, nodeId) {
3122
+ return model.nodes.find((n) => n.id === nodeId);
3123
+ }
3124
+ function findPort(node, portId) {
3125
+ const pi = node.inboundPorts.find((p) => p.id === portId);
3126
+ if (pi)
3127
+ return { port: pi, inbound: true };
3128
+ const po = node.outboundPorts.find((p) => p.id === portId);
3129
+ if (po)
3130
+ return { port: po, inbound: false };
3131
+ return undefined;
3132
+ }
3133
+ function findConnector(model, id) {
3134
+ return model.connectors.find((c) => c.id === id);
3135
+ }
3136
+ /**
3137
+ * Built-in rules: valid ports, not disabled, optional single inbound attachment, no duplicate edge.
3138
+ */
3139
+ function defaultCanConnectRequest(request) {
3140
+ const { model, sourceNodeId, sourcePortId, targetNodeId, targetPortId } = request;
3141
+ const sn = findNode(model, sourceNodeId);
3142
+ const tn = findNode(model, targetNodeId);
3143
+ if (!sn || !tn)
3144
+ return false;
3145
+ const spInfo = findPort(sn, sourcePortId);
3146
+ const tpInfo = findPort(tn, targetPortId);
3147
+ if (!spInfo || !tpInfo)
3148
+ return false;
3149
+ const sp = spInfo.port;
3150
+ const tp = tpInfo.port;
3151
+ if (sp.kind !== 'outbound' || tp.kind !== 'inbound')
3152
+ return false;
3153
+ if (sp.disabled || tp.disabled)
3154
+ return false;
3155
+ const duplicate = model.connectors.some((c) => c.sourceNodeId === sourceNodeId &&
3156
+ c.sourcePortId === sourcePortId &&
3157
+ c.targetNodeId === targetNodeId &&
3158
+ c.targetPortId === targetPortId);
3159
+ if (duplicate)
3160
+ return false;
3161
+ if (!tp.allowMultiple) {
3162
+ const inboundTaken = model.connectors.some((c) => c.targetNodeId === targetNodeId && c.targetPortId === targetPortId);
3163
+ if (inboundTaken)
3164
+ return false;
3165
+ }
3166
+ return true;
3167
+ }
3168
+ //#endregion
3169
+ //#region ---- Auto layout ----
3170
+ const DEFAULT_ARRANGE_MARGIN = 80;
3171
+ // Defaults leave room for inbound/outbound port labels that sit ~18px outside the
3172
+ // port anchor and may extend further as text. Larger gaps keep adjacent nodes from
3173
+ // having their port labels collide with each other or with routed connectors.
3174
+ const DEFAULT_ARRANGE_PRIMARY_GAP = 160;
3175
+ const DEFAULT_ARRANGE_CROSS_GAP = 110;
3176
+ const DEFAULT_ARRANGE_COMPONENT_GAP = 220;
3177
+ /**
3178
+ * Auto-arranges workflow nodes in a layered layout (left→right by default).
3179
+ * Honors `direction`, `alignment`, `origin`, `nodeIds`, and crossing-reduction options.
3180
+ */
3181
+ function arrangeWorkflowDiagram(model, options = {}) {
3182
+ if (model.nodes.length === 0) {
3183
+ return model;
3184
+ }
3185
+ const margin = options.margin ?? DEFAULT_ARRANGE_MARGIN;
3186
+ const runtime = {
3187
+ direction: options.direction ?? 'horizontal',
3188
+ alignment: options.alignment ?? 'center',
3189
+ primaryGap: options.horizontalGap ?? DEFAULT_ARRANGE_PRIMARY_GAP,
3190
+ crossGap: options.verticalGap ?? DEFAULT_ARRANGE_CROSS_GAP,
3191
+ componentGap: Math.max(options.verticalGap ?? DEFAULT_ARRANGE_CROSS_GAP, DEFAULT_ARRANGE_COMPONENT_GAP),
3192
+ margin,
3193
+ grid: options.gridSize ?? 0,
3194
+ origin: options.origin ?? { x: margin, y: margin },
3195
+ reduceCrossings: options.reduceCrossings ?? true,
3196
+ groupDisconnected: options.groupDisconnected ?? true,
3197
+ };
3198
+ const subsetIds = options.nodeIds && options.nodeIds.length > 0
3199
+ ? new Set(options.nodeIds.filter((id) => model.nodes.some((n) => n.id === id)))
3200
+ : null;
3201
+ const targetNodes = subsetIds ? model.nodes.filter((n) => subsetIds.has(n.id)) : model.nodes;
3202
+ if (targetNodes.length === 0) {
3203
+ return model;
3204
+ }
3205
+ const targetIds = new Set(targetNodes.map((n) => n.id));
3206
+ const relevantConnectors = model.connectors.filter((c) => targetIds.has(c.sourceNodeId) && targetIds.has(c.targetNodeId));
3207
+ const components = splitComponents(targetNodes, relevantConnectors, runtime, options.startNodeId);
3208
+ const positionById = new Map();
3209
+ let crossCursor = runtime.direction === 'horizontal' ? runtime.origin.y : runtime.origin.x;
3210
+ for (const component of components) {
3211
+ layoutComponent(component, positionById, runtime, crossCursor);
3212
+ crossCursor += component.crossSize + runtime.componentGap;
3213
+ }
3214
+ return {
3215
+ ...model,
3216
+ nodes: model.nodes.map((n) => {
3217
+ const next = positionById.get(n.id);
3218
+ return next ? { ...n, position: { ...next } } : n;
3219
+ }),
3220
+ };
3221
+ }
3222
+ function splitComponents(nodes, connectors, runtime, startNodeId) {
3223
+ const undirected = new Map();
3224
+ const outgoing = new Map();
3225
+ const incomingCount = new Map();
3226
+ for (const n of nodes) {
3227
+ undirected.set(n.id, new Set());
3228
+ outgoing.set(n.id, new Set());
3229
+ incomingCount.set(n.id, 0);
3230
+ }
3231
+ for (const c of connectors) {
3232
+ if (!undirected.has(c.sourceNodeId) || !undirected.has(c.targetNodeId))
3233
+ continue;
3234
+ undirected.get(c.sourceNodeId).add(c.targetNodeId);
3235
+ undirected.get(c.targetNodeId).add(c.sourceNodeId);
3236
+ outgoing.get(c.sourceNodeId).add(c.targetNodeId);
3237
+ incomingCount.set(c.targetNodeId, (incomingCount.get(c.targetNodeId) ?? 0) + 1);
3238
+ }
3239
+ const sortedNodes = [...nodes].sort(compareNodesForLayout);
3240
+ if (startNodeId) {
3241
+ sortedNodes.sort((a, b) => {
3242
+ if (a.id === startNodeId)
3243
+ return -1;
3244
+ if (b.id === startNodeId)
3245
+ return 1;
3246
+ return 0;
3247
+ });
3248
+ }
3249
+ const visited = new Set();
3250
+ const componentBuckets = [];
3251
+ if (runtime.groupDisconnected) {
3252
+ for (const seed of sortedNodes) {
3253
+ if (visited.has(seed.id))
3254
+ continue;
3255
+ const bucket = [];
3256
+ const queue = [seed.id];
3257
+ visited.add(seed.id);
3258
+ while (queue.length > 0) {
3259
+ const id = queue.shift();
3260
+ const node = nodes.find((n) => n.id === id);
3261
+ if (node)
3262
+ bucket.push(node);
3263
+ for (const next of undirected.get(id) ?? []) {
3264
+ if (!visited.has(next)) {
3265
+ visited.add(next);
3266
+ queue.push(next);
3267
+ }
3268
+ }
3269
+ }
3270
+ componentBuckets.push(bucket);
3271
+ }
3272
+ }
3273
+ else {
3274
+ componentBuckets.push([...nodes]);
3275
+ }
3276
+ const components = [];
3277
+ for (const bucket of componentBuckets) {
3278
+ const bucketIds = new Set(bucket.map((n) => n.id));
3279
+ const bucketConnectors = connectors.filter((c) => bucketIds.has(c.sourceNodeId) && bucketIds.has(c.targetNodeId));
3280
+ const ranks = computeRanks(bucket, outgoing, incomingCount, startNodeId);
3281
+ if (runtime.reduceCrossings) {
3282
+ reduceCrossingsInPlace(ranks, bucketConnectors, runtime.direction);
3283
+ }
3284
+ else {
3285
+ for (const rank of ranks)
3286
+ rank.sort(compareNodesForLayout);
3287
+ }
3288
+ const crossSize = estimateCrossSize(ranks, runtime);
3289
+ components.push({ nodes: bucket, ranks, crossSize });
3290
+ }
3291
+ return components;
3292
+ }
3293
+ function computeRanks(nodes, outgoing, incomingCount, startNodeId) {
3294
+ const rank = new Map();
3295
+ const queue = [];
3296
+ const enqueueRoots = (ids) => {
3297
+ for (const id of ids) {
3298
+ if (!rank.has(id)) {
3299
+ rank.set(id, 0);
3300
+ queue.push(id);
3301
+ }
3302
+ }
3303
+ };
3304
+ const localIds = new Set(nodes.map((n) => n.id));
3305
+ if (startNodeId && localIds.has(startNodeId)) {
3306
+ enqueueRoots([startNodeId]);
3307
+ }
3308
+ else {
3309
+ enqueueRoots(nodes.filter((n) => (incomingCount.get(n.id) ?? 0) === 0).map((n) => n.id));
3310
+ }
3311
+ // First-visit BFS handles cycles safely (shortest-distance ranking).
3312
+ const maxSteps = nodes.length * (nodes.length + 1);
3313
+ let steps = 0;
3314
+ while (queue.length > 0 && steps < maxSteps) {
3315
+ steps += 1;
3316
+ const id = queue.shift();
3317
+ const base = rank.get(id) ?? 0;
3318
+ for (const targetId of outgoing.get(id) ?? []) {
3319
+ if (!localIds.has(targetId) || rank.has(targetId))
3320
+ continue;
3321
+ rank.set(targetId, base + 1);
3322
+ queue.push(targetId);
3323
+ }
3324
+ }
3325
+ let orphanRank = nodes.reduce((max, n) => Math.max(max, rank.get(n.id) ?? 0), 0) + 1;
3326
+ const unranked = nodes.filter((n) => !rank.has(n.id)).sort(compareNodesForLayout);
3327
+ for (const n of unranked) {
3328
+ rank.set(n.id, orphanRank);
3329
+ orphanRank += 1;
3330
+ }
3331
+ const byRank = new Map();
3332
+ for (const n of nodes) {
3333
+ const r = rank.get(n.id) ?? 0;
3334
+ const bucket = byRank.get(r) ?? [];
3335
+ bucket.push(n);
3336
+ byRank.set(r, bucket);
3337
+ }
3338
+ return [...byRank.keys()].sort((a, b) => a - b).map((r) => byRank.get(r) ?? []);
3339
+ }
3340
+ /**
3341
+ * Returns a 0–1 fraction along the cross axis (vertical for horizontal flow) where the port sits on the node.
3342
+ */
3343
+ function portCrossAxisFraction(node, portId, direction) {
3344
+ const info = findPort(node, portId);
3345
+ if (!info) {
3346
+ return 0.5;
3347
+ }
3348
+ const port = info.port;
3349
+ const r = Math.min(0.999, Math.max(0.001, port.ratio));
3350
+ if (direction === 'horizontal') {
3351
+ switch (port.side) {
3352
+ case 'left':
3353
+ case 'right':
3354
+ return r;
3355
+ case 'top':
3356
+ return 0;
3357
+ case 'bottom':
3358
+ return 1;
3359
+ }
3360
+ }
3361
+ switch (port.side) {
3362
+ case 'top':
3363
+ case 'bottom':
3364
+ return r;
3365
+ case 'left':
3366
+ return 0;
3367
+ case 'right':
3368
+ return 1;
3369
+ }
3370
+ return r;
3371
+ }
3372
+ /**
3373
+ * Barycenter heuristic with port-aware weights: targets inherit both their predecessor's rank index
3374
+ * and the source port's cross-axis position so top/bottom (or left/right) ports map to matching targets.
3375
+ */
3376
+ function reduceCrossingsInPlace(ranks, connectors, direction) {
3377
+ if (ranks.length === 0)
3378
+ return;
3379
+ ranks[0] = [...ranks[0]].sort(compareNodesForLayout);
3380
+ const nodeById = new Map();
3381
+ for (const rank of ranks) {
3382
+ for (const node of rank) {
3383
+ nodeById.set(node.id, node);
3384
+ }
3385
+ }
3386
+ const indexInRank = ranks.map((rank) => {
3387
+ const map = new Map();
3388
+ for (let i = 0; i < rank.length; i++)
3389
+ map.set(rank[i].id, i);
3390
+ return map;
3391
+ });
3392
+ for (let r = 1; r < ranks.length; r++) {
3393
+ const predRank = ranks[r - 1];
3394
+ const predIndex = indexInRank[r - 1];
3395
+ const incomingFromPred = new Map();
3396
+ const predIds = new Set(predRank.map((n) => n.id));
3397
+ for (const c of connectors) {
3398
+ if (!predIds.has(c.sourceNodeId)) {
3399
+ continue;
3400
+ }
3401
+ const idx = predIndex.get(c.sourceNodeId);
3402
+ if (idx === undefined) {
3403
+ continue;
3404
+ }
3405
+ const sourceNode = nodeById.get(c.sourceNodeId);
3406
+ const portFraction = sourceNode ? portCrossAxisFraction(sourceNode, c.sourcePortId, direction) : 0.5;
3407
+ // Keep port ordering within the same predecessor index (fraction ∈ [0, 1)).
3408
+ const weight = idx + portFraction;
3409
+ const arr = incomingFromPred.get(c.targetNodeId) ?? [];
3410
+ arr.push(weight);
3411
+ incomingFromPred.set(c.targetNodeId, arr);
3412
+ }
3413
+ ranks[r] = [...ranks[r]].sort((a, b) => {
3414
+ const ia = incomingFromPred.get(a.id) ?? [];
3415
+ const ib = incomingFromPred.get(b.id) ?? [];
3416
+ const ba = ia.length > 0 ? ia.reduce((s, v) => s + v, 0) / ia.length : Number.POSITIVE_INFINITY;
3417
+ const bb = ib.length > 0 ? ib.reduce((s, v) => s + v, 0) / ib.length : Number.POSITIVE_INFINITY;
3418
+ if (ba !== bb)
3419
+ return ba - bb;
3420
+ return compareNodesForLayout(a, b);
3421
+ });
3422
+ const refreshed = new Map();
3423
+ for (let i = 0; i < ranks[r].length; i++)
3424
+ refreshed.set(ranks[r][i].id, i);
3425
+ indexInRank[r] = refreshed;
3426
+ }
3427
+ }
3428
+ function estimateCrossSize(ranks, runtime) {
3429
+ let max = 0;
3430
+ for (const rank of ranks) {
3431
+ const total = rank.reduce((sum, n) => sum + (runtime.direction === 'horizontal' ? n.size.height : n.size.width), 0);
3432
+ const gaps = Math.max(0, rank.length - 1) * runtime.crossGap;
3433
+ max = Math.max(max, total + gaps);
3434
+ }
3435
+ return max;
3436
+ }
3437
+ function layoutComponent(component, positionById, runtime, crossStart) {
3438
+ const isHorizontal = runtime.direction === 'horizontal';
3439
+ let primaryCursor = isHorizontal ? runtime.origin.x : runtime.origin.y;
3440
+ for (const rank of component.ranks) {
3441
+ if (rank.length === 0)
3442
+ continue;
3443
+ const crossTotal = rank.reduce((sum, n) => sum + (isHorizontal ? n.size.height : n.size.width), 0);
3444
+ const crossGaps = (rank.length - 1) * runtime.crossGap;
3445
+ const crossLen = crossTotal + crossGaps;
3446
+ let cursor = crossStart;
3447
+ if (runtime.alignment === 'center') {
3448
+ cursor = crossStart + (component.crossSize - crossLen) * 0.5;
3449
+ }
3450
+ else if (runtime.alignment === 'end') {
3451
+ cursor = crossStart + (component.crossSize - crossLen);
3452
+ }
3453
+ let primaryMax = 0;
3454
+ for (const node of rank) {
3455
+ const primary = isHorizontal ? primaryCursor : cursor;
3456
+ const cross = isHorizontal ? cursor : primaryCursor;
3457
+ const raw = isHorizontal ? { x: primary, y: cross } : { x: cross, y: primary };
3458
+ const snapped = runtime.grid > 0 ? snapPoint(raw, runtime.grid) : raw;
3459
+ positionById.set(node.id, snapped);
3460
+ const advance = (isHorizontal ? node.size.height : node.size.width) + runtime.crossGap;
3461
+ cursor += advance;
3462
+ primaryMax = Math.max(primaryMax, isHorizontal ? node.size.width : node.size.height);
3463
+ }
3464
+ primaryCursor += primaryMax + runtime.primaryGap;
3465
+ }
3466
+ }
3467
+ function compareNodesForLayout(a, b) {
3468
+ const titleA = a.title?.trim() ?? '';
3469
+ const titleB = b.title?.trim() ?? '';
3470
+ if (titleA !== titleB) {
3471
+ return titleA.localeCompare(titleB);
3472
+ }
3473
+ return a.id.localeCompare(b.id);
3474
+ }
3475
+ //#endregion
3476
+
3477
+ //#endregion
3478
+ class AXPWorkflowDesignerComponent {
3479
+ //#endregion
3480
+ constructor() {
3481
+ /** Unique SVG fragment id so multiple designers on one page do not clash. */
3482
+ this.svgMarkerId = `axp-wf-arrow-${Math.random().toString(36).slice(2, 10)}`;
3483
+ //#region ---- Inputs / outputs ----
3484
+ this.model = input.required(/* @ts-ignore */
3485
+ ...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
3486
+ this.modelChange = output();
3487
+ /** When true, the whole designer is non-interactive unless a nested entity sets its own readonly flag. */
3488
+ this.readonly = input(false, /* @ts-ignore */
3489
+ ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
3490
+ /** Read-only run/history views: emphasize nodes (e.g. current workflow step) without selection chrome. */
3491
+ this.highlightedNodeIds = input([], /* @ts-ignore */
3492
+ ...(ngDevMode ? [{ debugName: "highlightedNodeIds" }] : /* istanbul ignore next */ []));
3493
+ this.gridSize = input(20, /* @ts-ignore */
3494
+ ...(ngDevMode ? [{ debugName: "gridSize" }] : /* istanbul ignore next */ []));
3495
+ /** Default snap when node does not override `snapToGrid`. */
3496
+ this.snapToGrid = input(true, /* @ts-ignore */
3497
+ ...(ngDevMode ? [{ debugName: "snapToGrid" }] : /* istanbul ignore next */ []));
3498
+ this.connectionPreviewStyle = input('orthogonal', /* @ts-ignore */
3499
+ ...(ngDevMode ? [{ debugName: "connectionPreviewStyle" }] : /* istanbul ignore next */ []));
3500
+ this.backgroundPattern = input('dots', /* @ts-ignore */
3501
+ ...(ngDevMode ? [{ debugName: "backgroundPattern" }] : /* istanbul ignore next */ []));
3502
+ this.zoomChange = output();
3503
+ /** When set, replaces built-in `defaultCanConnectRequest` entirely. */
3504
+ this.canConnect = input(null, /* @ts-ignore */
3505
+ ...(ngDevMode ? [{ debugName: "canConnect" }] : /* istanbul ignore next */ []));
3506
+ /** Used to resolve native palette drag payloads (`type`) into full defaults. */
3507
+ this.paletteItems = input([], /* @ts-ignore */
3508
+ ...(ngDevMode ? [{ debugName: "paletteItems" }] : /* istanbul ignore next */ []));
3509
+ /** When false, port labels are hidden (ports remain interactive). */
3510
+ this.showPortLabels = input(true, /* @ts-ignore */
3511
+ ...(ngDevMode ? [{ debugName: "showPortLabels" }] : /* istanbul ignore next */ []));
3512
+ this.nodeBodyTemplate = input(null, /* @ts-ignore */
3513
+ ...(ngDevMode ? [{ debugName: "nodeBodyTemplate" }] : /* istanbul ignore next */ []));
3514
+ this.designerPointerDown = output();
3515
+ this.designerClick = output();
3516
+ this.designerDoubleClick = output();
3517
+ this.nodePointerDown = output();
3518
+ this.nodeClick = output();
3519
+ this.nodeDoubleClick = output();
3520
+ this.nodeContextMenu = output();
3521
+ this.nodeDragStart = output();
3522
+ this.nodeDragEnd = output();
3523
+ this.portPointerDown = output();
3524
+ this.linkDragStart = output();
3525
+ this.linkDragMove = output();
3526
+ this.linkDragEnd = output();
3527
+ this.connectorClick = output();
3528
+ this.connectorDoubleClick = output();
3529
+ this.connectionCreated = output();
3530
+ this.connectionRejected = output();
3531
+ this.activationChange = output();
3532
+ //#endregion
3533
+ //#region ---- State ----
3534
+ this.surfaceRef = viewChild.required('surface', /* @ts-ignore */
3535
+ ...(ngDevMode ? [{ debugName: "surfaceRef" }] : /* istanbul ignore next */ []));
3536
+ this.panViewRef = viewChild(AXPanViewDirective, /* @ts-ignore */
3537
+ ...(ngDevMode ? [{ debugName: "panViewRef" }] : /* istanbul ignore next */ []));
3538
+ this.activation = signal(null, /* @ts-ignore */
3539
+ ...(ngDevMode ? [{ debugName: "activation" }] : /* istanbul ignore next */ []));
3540
+ this.selectedNodeIds = signal(new Set(), /* @ts-ignore */
3541
+ ...(ngDevMode ? [{ debugName: "selectedNodeIds" }] : /* istanbul ignore next */ []));
3542
+ this.selectedConnectorIds = signal(new Set(), /* @ts-ignore */
3543
+ ...(ngDevMode ? [{ debugName: "selectedConnectorIds" }] : /* istanbul ignore next */ []));
3544
+ this.panX = signal(0, /* @ts-ignore */
3545
+ ...(ngDevMode ? [{ debugName: "panX" }] : /* istanbul ignore next */ []));
3546
+ this.panY = signal(0, /* @ts-ignore */
3547
+ ...(ngDevMode ? [{ debugName: "panY" }] : /* istanbul ignore next */ []));
3548
+ this.panZoomLevel = signal(100, /* @ts-ignore */
3549
+ ...(ngDevMode ? [{ debugName: "panZoomLevel" }] : /* istanbul ignore next */ []));
3550
+ this.linkDrag = signal(null, /* @ts-ignore */
3551
+ ...(ngDevMode ? [{ debugName: "linkDrag" }] : /* istanbul ignore next */ []));
3552
+ /** Inbound port under the pointer while dragging a new connector from an outbound port. */
3553
+ this.linkHoverTarget = signal(null, /* @ts-ignore */
3554
+ ...(ngDevMode ? [{ debugName: "linkHoverTarget" }] : /* istanbul ignore next */ []));
3555
+ this.nodeMove = null;
3556
+ this.resize = null;
3557
+ this.suppressNextNodeClick = false;
3558
+ this.wrapperDragoverListener = null;
3559
+ this.wrapperDropListener = null;
3560
+ this.wrapperElement = signal(null, /* @ts-ignore */
3561
+ ...(ngDevMode ? [{ debugName: "wrapperElement" }] : /* istanbul ignore next */ []));
3562
+ this.wrapperAttachRetryHandle = null;
3563
+ this.wrapperAttachAttempt = 0;
3564
+ this.destroyRef = inject(DestroyRef);
3565
+ //#endregion
3566
+ //#region ---- Computed ----
3567
+ this.gridStyle = computed(() => {
3568
+ const g = this.gridSize();
3569
+ if (this.backgroundPattern() === 'dots') {
3570
+ return {
3571
+ 'background-size': `${g}px ${g}px`,
3572
+ 'background-image': `radial-gradient(circle, rgba(148, 163, 184, 0.35) 1px, transparent 1.2px)`,
3573
+ };
3574
+ }
3575
+ return {
3576
+ 'background-size': `${g}px ${g}px`,
3577
+ 'background-image': `
3578
+ linear-gradient(to right, rgba(148, 163, 184, 0.2) 1px, transparent 1px),
3579
+ linear-gradient(to bottom, rgba(148, 163, 184, 0.2) 1px, transparent 1px)
3580
+ `,
3581
+ };
3582
+ }, /* @ts-ignore */
3583
+ ...(ngDevMode ? [{ debugName: "gridStyle" }] : /* istanbul ignore next */ []));
3584
+ this.canvasViewport = computed(() => {
3585
+ const model = this.model();
3586
+ const contentPadding = 320;
3587
+ const minCanvasWidth = 1600;
3588
+ const minCanvasHeight = 1000;
3589
+ const nodeMinX = model.nodes.reduce((acc, node) => Math.min(acc, node.position.x), 0);
3590
+ const nodeMinY = model.nodes.reduce((acc, node) => Math.min(acc, node.position.y), 0);
3591
+ const nodeMaxRight = model.nodes.reduce((acc, node) => Math.max(acc, node.position.x + node.size.width), 0);
3592
+ const nodeMaxBottom = model.nodes.reduce((acc, node) => Math.max(acc, node.position.y + node.size.height), 0);
3593
+ const minX = Math.min(0, nodeMinX);
3594
+ const minY = Math.min(0, nodeMinY);
3595
+ const maxRight = Math.max(minCanvasWidth, nodeMaxRight);
3596
+ const maxBottom = Math.max(minCanvasHeight, nodeMaxBottom);
3597
+ const offsetX = -minX + contentPadding;
3598
+ const offsetY = -minY + contentPadding;
3599
+ return {
3600
+ minX,
3601
+ minY,
3602
+ offsetX,
3603
+ offsetY,
3604
+ width: Math.ceil(maxRight - minX + contentPadding * 2),
3605
+ height: Math.ceil(maxBottom - minY + contentPadding * 2),
3606
+ };
3607
+ }, /* @ts-ignore */
3608
+ ...(ngDevMode ? [{ debugName: "canvasViewport" }] : /* istanbul ignore next */ []));
3609
+ this.surfaceStyle = computed(() => {
3610
+ const viewport = this.canvasViewport();
3611
+ return {
3612
+ // ...this.gridStyle(),
3613
+ width: `${viewport.width}px`,
3614
+ height: `${viewport.height}px`,
3615
+ };
3616
+ }, /* @ts-ignore */
3617
+ ...(ngDevMode ? [{ debugName: "surfaceStyle" }] : /* istanbul ignore next */ []));
3618
+ this.onWindowPointerMoveForNode = (ev) => {
3619
+ const drag = this.nodeMove;
3620
+ if (!drag)
3621
+ return;
3622
+ const model = this.model();
3623
+ const p = this.canvasPointFromClient(ev.clientX, ev.clientY);
3624
+ const dx = p.x - drag.start.x;
3625
+ const dy = p.y - drag.start.y;
3626
+ if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
3627
+ drag.moved = true;
3628
+ }
3629
+ const nodes = model.nodes.map((n) => {
3630
+ if (!drag.nodeIds.includes(n.id))
3631
+ return n;
3632
+ const origin = drag.origins[n.id];
3633
+ if (!origin)
3634
+ return n;
3635
+ let nx = origin.x + dx;
3636
+ let ny = origin.y + dy;
3637
+ const snapped = this.applySnapForNode(n, { x: nx, y: ny });
3638
+ nx = snapped.x;
3639
+ ny = snapped.y;
3640
+ return { ...n, position: { x: nx, y: ny } };
3641
+ });
3642
+ this.commit({ ...model, nodes });
3643
+ };
3644
+ this.onWindowPointerUpForNode = (ev) => {
3645
+ const drag = this.nodeMove;
3646
+ this.nodeMove = null;
3647
+ window.removeEventListener('pointermove', this.onWindowPointerMoveForNode);
3648
+ window.removeEventListener('pointerup', this.onWindowPointerUpForNode);
3649
+ window.removeEventListener('pointercancel', this.onWindowPointerUpForNode);
3650
+ if (drag?.moved) {
3651
+ this.suppressNextNodeClick = true;
3652
+ }
3653
+ if (drag) {
3654
+ const node = findNode(this.model(), drag.nodeIds[0] ?? '');
3655
+ if (node)
3656
+ this.nodeDragEnd.emit({ node, event: ev });
3657
+ }
3658
+ };
3659
+ this.onWindowPointerMoveForResize = (ev) => {
3660
+ const st = this.resize;
3661
+ if (!st)
3662
+ return;
3663
+ const model = this.model();
3664
+ const node = findNode(model, st.nodeId);
3665
+ if (!node)
3666
+ return;
3667
+ const p = this.canvasPointFromClient(ev.clientX, ev.clientY);
3668
+ const dx = p.x - st.startPointer.x;
3669
+ const dy = p.y - st.startPointer.y;
3670
+ let { width, height } = st.startSize;
3671
+ let { x, y } = st.startPosition;
3672
+ const minW = 72;
3673
+ const minH = 40;
3674
+ const growEast = st.handle.includes('e');
3675
+ const growSouth = st.handle.includes('s');
3676
+ const growWest = st.handle.includes('w');
3677
+ const growNorth = st.handle.includes('n');
3678
+ if (growEast)
3679
+ width = Math.max(minW, st.startSize.width + dx);
3680
+ if (growSouth)
3681
+ height = Math.max(minH, st.startSize.height + dy);
3682
+ if (growWest) {
3683
+ const nw = Math.max(minW, st.startSize.width - dx);
3684
+ x = st.startPosition.x + st.startSize.width - nw;
3685
+ width = nw;
3686
+ }
3687
+ if (growNorth) {
3688
+ const nh = Math.max(minH, st.startSize.height - dy);
3689
+ y = st.startPosition.y + st.startSize.height - nh;
3690
+ height = nh;
3691
+ }
3692
+ if (node.shape === 'circle' || node.lockAspectRatio) {
3693
+ const a = st.aspect;
3694
+ if (growEast || growWest) {
3695
+ height = width / a;
3696
+ if (growNorth)
3697
+ y = st.startPosition.y + st.startSize.height - height;
3698
+ }
3699
+ else {
3700
+ width = height * a;
3701
+ if (growWest)
3702
+ x = st.startPosition.x + st.startSize.width - width;
3703
+ }
3704
+ }
3705
+ const g = this.gridSize();
3706
+ const useSnap = node.snapToGrid ?? this.snapToGrid();
3707
+ if (useSnap && g > 0) {
3708
+ width = Math.max(minW, snapScalar(width, g));
3709
+ height = Math.max(minH, snapScalar(height, g));
3710
+ x = snapScalar(x, g);
3711
+ y = snapScalar(y, g);
3712
+ }
3713
+ const nodes = model.nodes.map((n) => n.id === node.id ? { ...n, position: { x, y }, size: { width, height } } : n);
3714
+ this.commit({ ...model, nodes });
3715
+ };
3716
+ this.onWindowPointerUpForResize = () => {
3717
+ this.resize = null;
3718
+ window.removeEventListener('pointermove', this.onWindowPointerMoveForResize);
3719
+ window.removeEventListener('pointerup', this.onWindowPointerUpForResize);
3720
+ window.removeEventListener('pointercancel', this.onWindowPointerUpForResize);
3721
+ };
3722
+ this.onWindowPointerMoveForLink = (ev) => {
3723
+ const st = this.linkDrag();
3724
+ if (!st)
3725
+ return;
3726
+ const to = this.canvasPointFromClient(ev.clientX, ev.clientY);
3727
+ this.linkDrag.set({ ...st, to });
3728
+ const model = this.model();
3729
+ const node = findNode(model, st.sourceNodeId);
3730
+ const port = node ? findPort(node, st.sourcePortId)?.port : undefined;
3731
+ if (node && port) {
3732
+ this.linkDragMove.emit({ sourceNode: node, sourcePort: port, point: to, event: ev });
3733
+ }
3734
+ const hover = this.pickInboundPortAtPoint(model, to, st.sourceNodeId);
3735
+ this.linkHoverTarget.set(hover ? { nodeId: hover.node.id, portId: hover.port.id } : null);
3736
+ };
3737
+ this.onWindowPointerUpForLink = (ev) => {
3738
+ const st = this.linkDrag();
3739
+ this.linkDrag.set(null);
3740
+ this.linkHoverTarget.set(null);
3741
+ window.removeEventListener('pointermove', this.onWindowPointerMoveForLink);
3742
+ window.removeEventListener('pointerup', this.onWindowPointerUpForLink);
3743
+ window.removeEventListener('pointercancel', this.onWindowPointerUpForLink);
3744
+ if (!st)
3745
+ return;
3746
+ const model = this.model();
3747
+ const sourceNode = findNode(model, st.sourceNodeId);
3748
+ const sourcePort = sourceNode ? findPort(sourceNode, st.sourcePortId)?.port : undefined;
3749
+ if (!sourceNode || !sourcePort)
3750
+ return;
3751
+ const target = this.pickInboundPortAtPoint(model, st.to, sourceNode.id);
3752
+ if (!target) {
3753
+ this.linkDragEnd.emit({ sourceNode, sourcePort, cancelled: true, event: ev });
3754
+ return;
3755
+ }
3756
+ const request = {
3757
+ model,
3758
+ sourceNodeId: st.sourceNodeId,
3759
+ sourcePortId: st.sourcePortId,
3760
+ targetNodeId: target.node.id,
3761
+ targetPortId: target.port.id,
3762
+ };
3763
+ if (!this.evaluateConnect(request)) {
3764
+ this.connectionRejected.emit({ reason: 'canConnect returned false', request, event: ev });
3765
+ this.linkDragEnd.emit({ sourceNode, sourcePort, cancelled: true, event: ev });
3766
+ return;
3767
+ }
3768
+ const connector = {
3769
+ id: createWorkflowConnectorId(),
3770
+ sourceNodeId: st.sourceNodeId,
3771
+ sourcePortId: st.sourcePortId,
3772
+ targetNodeId: target.node.id,
3773
+ targetPortId: target.port.id,
3774
+ data: {},
3775
+ color: sourceNode.color,
3776
+ };
3777
+ const next = {
3778
+ nodes: [...model.nodes],
3779
+ connectors: [...model.connectors, connector],
3780
+ };
3781
+ this.commit(next);
3782
+ this.connectionCreated.emit({ connector, model: next });
3783
+ this.setActivation({ kind: 'connector', id: connector.id });
3784
+ this.linkDragEnd.emit({ sourceNode, sourcePort, cancelled: false, event: ev });
3785
+ };
3786
+ afterNextRender(() => this.attachWrapperDropTargetsWithRetry());
3787
+ effect(() => this.syncWrapperBackgroundStyle());
3788
+ effect(() => {
3789
+ this.zoomChange.emit(this.panZoomLevel());
3790
+ });
3791
+ this.destroyRef.onDestroy(() => this.detachWindowListeners());
3792
+ }
3793
+ zoomIn() {
3794
+ this.panViewRef()?.zoomIn();
3795
+ }
3796
+ zoomOut() {
3797
+ this.panViewRef()?.zoomOut();
3798
+ }
3799
+ zoomReset() {
3800
+ this.panViewRef()?.resetPosition();
3801
+ }
3802
+ getZoomLevel() {
3803
+ return this.panZoomLevel();
3804
+ }
3805
+ //#region ---- Readonly helpers ----
3806
+ isNodeReadonly(node) {
3807
+ return this.readonly() || node.readonly === true;
3808
+ }
3809
+ isConnectorReadonly(c) {
3810
+ return this.readonly() || c.readonly === true;
3811
+ }
3812
+ //#endregion
3813
+ //#region ---- Activation ----
3814
+ isNodeActive(nodeId) {
3815
+ return this.selectedNodeIds().has(nodeId);
3816
+ }
3817
+ isNodeHighlighted(nodeId) {
3818
+ return this.highlightedNodeIds().includes(nodeId);
3819
+ }
3820
+ isConnectorActive(id) {
3821
+ return this.selectedConnectorIds().has(id);
3822
+ }
3823
+ isPortActive(nodeId, portId) {
3824
+ const a = this.activation();
3825
+ return a?.kind === 'port' && a.nodeId === nodeId && a.id === portId;
3826
+ }
3827
+ isPortLinkHoverTarget(nodeId, portId) {
3828
+ if (!this.linkDrag())
3829
+ return false;
3830
+ const h = this.linkHoverTarget();
3831
+ return h !== null && h.nodeId === nodeId && h.portId === portId;
3832
+ }
3833
+ setActivation(next) {
3834
+ this.activation.set(next);
3835
+ this.activationChange.emit(next);
3836
+ }
3837
+ clearSelections() {
3838
+ this.selectedNodeIds.set(new Set());
3839
+ this.selectedConnectorIds.set(new Set());
3840
+ }
3841
+ //#endregion
3842
+ //#region ---- Model commit ----
3843
+ commit(next) {
3844
+ this.modelChange.emit(next);
3845
+ }
3846
+ /**
3847
+ * Auto-arranges nodes into a layered layout and commits the result.
3848
+ *
3849
+ * Honors layout options such as `direction` (`'horizontal'` / `'vertical'`),
3850
+ * `alignment`, `origin`, `nodeIds` (subset arrange), and `reduceCrossings`.
3851
+ * When `gridSize` is omitted the designer's own `gridSize` input is used.
3852
+ */
3853
+ arrangeNicely(options) {
3854
+ if (this.readonly()) {
3855
+ return;
3856
+ }
3857
+ this.commit(this.computeArrangedLayout(options));
3858
+ }
3859
+ /**
3860
+ * Computes an arranged layout without committing it. Useful for previews,
3861
+ * comparing the result before applying it, or rendering ghost positions.
3862
+ */
3863
+ computeArrangedLayout(options) {
3864
+ return arrangeWorkflowDiagram(cloneDiagram(this.model()), {
3865
+ gridSize: this.gridSize(),
3866
+ ...options,
3867
+ });
3868
+ }
3869
+ resolvePaletteItem(type) {
3870
+ return this.paletteItems().find((i) => i.type === type);
3871
+ }
3872
+ /**
3873
+ * Full drag payload merged with `paletteItems()` entry when the payload omits optional fields.
3874
+ */
3875
+ resolveDroppedPaletteItem(raw) {
3876
+ const parsed = parsePaletteItemDragPayload(raw);
3877
+ if (!parsed)
3878
+ return null;
3879
+ const base = this.resolvePaletteItem(parsed.type);
3880
+ const item = { ...(base ?? {}), ...parsed };
3881
+ if (typeof item.title !== 'string')
3882
+ return null;
3883
+ return item;
3884
+ }
3885
+ evaluateConnect(request) {
3886
+ const fn = this.canConnect();
3887
+ if (fn)
3888
+ return fn(request);
3889
+ return defaultCanConnectRequest(request);
3890
+ }
3891
+ //#endregion
3892
+ //#region ---- Canvas / surface ----
3893
+ onSurfacePointerDown(event) {
3894
+ if (!this.isBackgroundInteractionTarget(event.target))
3895
+ return;
3896
+ this.designerPointerDown.emit(event);
3897
+ }
3898
+ onSurfaceClick(event) {
3899
+ if (!this.isBackgroundInteractionTarget(event.target))
3900
+ return;
3901
+ this.clearSelections();
3902
+ this.setActivation({ kind: 'canvas', id: 'canvas' });
3903
+ this.designerClick.emit(event);
3904
+ }
3905
+ onSurfaceDoubleClick(event) {
3906
+ if (!this.isBackgroundInteractionTarget(event.target))
3907
+ return;
3908
+ this.designerDoubleClick.emit(event);
3909
+ }
3910
+ isBackgroundInteractionTarget(target) {
3911
+ if (!target)
3912
+ return false;
3913
+ if (target.closest('.axp-workflow-designer__node'))
3914
+ return false;
3915
+ if (target.closest('.axp-workflow-designer__connector-actions'))
3916
+ return false;
3917
+ if (target.closest('.axp-workflow-designer__port-cluster'))
3918
+ return false;
3919
+ if (target.closest('path.axp-workflow-designer__edge-hit'))
3920
+ return false;
3921
+ if (target.closest('ax-button'))
3922
+ return false;
3923
+ return true;
3924
+ }
3925
+ canvasPointFromClient(clientX, clientY) {
3926
+ const surface = this.surfaceRef().nativeElement;
3927
+ const wrapper = surface.closest('.ax-pan-view-wrapper');
3928
+ const basisRect = (wrapper ?? surface).getBoundingClientRect();
3929
+ const localX = clientX - basisRect.left;
3930
+ const localY = clientY - basisRect.top;
3931
+ const computed = getComputedStyle(surface);
3932
+ const transform = computed.transform;
3933
+ if (!transform || transform === 'none') {
3934
+ return this.surfacePointToModel({ x: localX, y: localY });
3935
+ }
3936
+ const matrix = new DOMMatrixReadOnly(transform);
3937
+ const originParts = computed.transformOrigin.split(' ');
3938
+ const originX = Number.parseFloat(originParts[0] ?? '0') || 0;
3939
+ const originY = Number.parseFloat(originParts[1] ?? '0') || 0;
3940
+ // CSS transform is applied around transform-origin, so include origin translation in matrix inversion.
3941
+ const withOrigin = new DOMMatrixReadOnly()
3942
+ .translate(originX, originY)
3943
+ .multiply(matrix)
3944
+ .translate(-originX, -originY);
3945
+ const point = new DOMPoint(localX, localY).matrixTransform(withOrigin.inverse());
3946
+ return this.surfacePointToModel({ x: point.x, y: point.y });
3947
+ }
3948
+ //#endregion
3949
+ //#region ---- Drag / drop (palette) ----
3950
+ onCanvasDragOver(event) {
3951
+ if (this.readonly())
3952
+ return;
3953
+ event.preventDefault();
3954
+ if (event.dataTransfer)
3955
+ event.dataTransfer.dropEffect = 'copy';
3956
+ }
3957
+ onCanvasDrop(event) {
3958
+ if (this.readonly())
3959
+ return;
3960
+ event.preventDefault();
3961
+ event.stopPropagation();
3962
+ const raw = event.dataTransfer?.getData(AXP_WORKFLOW_PALETTE_MIME);
3963
+ const item = this.resolveDroppedPaletteItem(raw);
3964
+ if (!item)
3965
+ return;
3966
+ const pt = this.canvasPointFromClient(event.clientX, event.clientY);
3967
+ const node = createNodeFromPaletteItem(item, this.applySnapToPointForNewNode(item, pt));
3968
+ const model = this.model();
3969
+ const next = {
3970
+ nodes: [...model.nodes, node],
3971
+ connectors: [...model.connectors],
3972
+ };
3973
+ this.commit(next);
3974
+ this.setActivation({ kind: 'node', id: node.id });
3975
+ }
3976
+ applySnapToPointForNewNode(item, pt) {
3977
+ const nodeSnap = item.snapToGrid;
3978
+ const useSnap = nodeSnap ?? this.snapToGrid();
3979
+ const g = this.gridSize();
3980
+ if (!useSnap || g <= 0)
3981
+ return pt;
3982
+ return snapPoint(pt, g);
3983
+ }
3984
+ applySnapForNode(node, pt) {
3985
+ const useSnap = node.snapToGrid ?? this.snapToGrid();
3986
+ const g = this.gridSize();
3987
+ if (!useSnap || g <= 0)
3988
+ return pt;
3989
+ return snapPoint(pt, g);
3990
+ }
3991
+ //#endregion
3992
+ //#region ---- Wrapper drop bridge ----
3993
+ attachWrapperDropTargetsWithRetry() {
3994
+ this.clearWrapperAttachRetry();
3995
+ this.wrapperAttachAttempt = 0;
3996
+ const tryAttach = () => {
3997
+ const attached = this.attachWrapperDropTargets();
3998
+ if (attached) {
3999
+ return;
4000
+ }
4001
+ this.wrapperAttachAttempt += 1;
4002
+ if (this.wrapperAttachAttempt >= 20) {
4003
+ return;
4004
+ }
4005
+ this.wrapperAttachRetryHandle = setTimeout(tryAttach, 50);
4006
+ };
4007
+ tryAttach();
4008
+ }
4009
+ /**
4010
+ * Binds drag/drop to pan-view wrapper so users can drop while panning far away
4011
+ * from current finite surface bounds.
4012
+ */
4013
+ attachWrapperDropTargets() {
4014
+ const surface = this.surfaceRef().nativeElement;
4015
+ const wrapper = surface.closest('.ax-pan-view-wrapper');
4016
+ if (!wrapper) {
4017
+ return false;
4018
+ }
4019
+ this.detachWrapperDropTargets();
4020
+ this.wrapperElement.set(wrapper);
4021
+ this.wrapperDragoverListener = (event) => {
4022
+ const target = event.target;
4023
+ if (target?.closest('.axp-workflow-designer__surface')) {
4024
+ return;
4025
+ }
4026
+ this.onCanvasDragOver(event);
4027
+ };
4028
+ this.wrapperDropListener = (event) => {
4029
+ const target = event.target;
4030
+ if (target?.closest('.axp-workflow-designer__surface')) {
4031
+ return;
4032
+ }
4033
+ this.onCanvasDrop(event);
4034
+ };
4035
+ wrapper.addEventListener('dragover', this.wrapperDragoverListener);
4036
+ wrapper.addEventListener('drop', this.wrapperDropListener);
4037
+ this.syncWrapperBackgroundStyle();
4038
+ return true;
4039
+ }
4040
+ detachWrapperDropTargets() {
4041
+ const surface = this.surfaceRef().nativeElement;
4042
+ const wrapper = surface.closest('.ax-pan-view-wrapper');
4043
+ if (!wrapper) {
4044
+ this.wrapperElement.set(null);
4045
+ this.wrapperDragoverListener = null;
4046
+ this.wrapperDropListener = null;
4047
+ return;
4048
+ }
4049
+ if (this.wrapperDragoverListener) {
4050
+ wrapper.removeEventListener('dragover', this.wrapperDragoverListener);
4051
+ this.wrapperDragoverListener = null;
4052
+ }
4053
+ if (this.wrapperDropListener) {
4054
+ wrapper.removeEventListener('drop', this.wrapperDropListener);
4055
+ this.wrapperDropListener = null;
4056
+ }
4057
+ this.wrapperElement.set(null);
4058
+ }
4059
+ clearWrapperAttachRetry() {
4060
+ if (this.wrapperAttachRetryHandle != null) {
4061
+ clearTimeout(this.wrapperAttachRetryHandle);
4062
+ this.wrapperAttachRetryHandle = null;
4063
+ }
4064
+ }
4065
+ //#endregion
4066
+ //#region ---- Keyboard ----
4067
+ onWindowKeydown(ev) {
4068
+ if (this.readonly())
4069
+ return;
4070
+ const target = ev.target;
4071
+ if (target?.closest('input,textarea,select,[contenteditable="true"]'))
4072
+ return;
4073
+ if (ev.key === 'Escape') {
4074
+ ev.preventDefault();
4075
+ this.cancelLinkDrag(ev);
4076
+ this.clearSelections();
4077
+ this.setActivation(null);
4078
+ return;
4079
+ }
4080
+ if (ev.key !== 'Delete' && ev.key !== 'Backspace')
4081
+ return;
4082
+ ev.preventDefault();
4083
+ const selectedConnectorIds = this.selectedConnectorIds();
4084
+ const selectedNodeIds = this.selectedNodeIds();
4085
+ const model = this.model();
4086
+ if (selectedConnectorIds.size > 0 || selectedNodeIds.size > 0) {
4087
+ const nodes = model.nodes.filter((n) => !selectedNodeIds.has(n.id));
4088
+ const connectors = model.connectors.filter((c) => !selectedConnectorIds.has(c.id) &&
4089
+ !selectedNodeIds.has(c.sourceNodeId) &&
4090
+ !selectedNodeIds.has(c.targetNodeId));
4091
+ this.commit({ ...model, nodes, connectors });
4092
+ this.clearSelections();
4093
+ this.setActivation(null);
4094
+ return;
4095
+ }
4096
+ const a = this.activation();
4097
+ if (!a || a.kind === 'canvas')
4098
+ return;
4099
+ if (a.kind === 'connector') {
4100
+ const c = findConnector(model, a.id);
4101
+ if (c && !this.isConnectorReadonly(c)) {
4102
+ const connectors = model.connectors.filter((x) => x.id !== c.id);
4103
+ this.commit({ ...model, connectors });
4104
+ this.setActivation(null);
4105
+ }
4106
+ return;
4107
+ }
4108
+ const nodeId = a.kind === 'node' ? a.id : a.nodeId;
4109
+ if (!nodeId)
4110
+ return;
4111
+ const node = findNode(model, nodeId);
4112
+ if (!node || this.isNodeReadonly(node))
4113
+ return;
4114
+ const nodes = model.nodes.filter((n) => n.id !== nodeId);
4115
+ const connectors = model.connectors.filter((c) => c.sourceNodeId !== nodeId && c.targetNodeId !== nodeId);
4116
+ this.commit({ ...model, nodes, connectors });
4117
+ this.setActivation(null);
4118
+ }
4119
+ cancelLinkDrag(ev) {
4120
+ const st = this.linkDrag();
4121
+ if (!st)
4122
+ return;
4123
+ this.linkDrag.set(null);
4124
+ this.linkHoverTarget.set(null);
4125
+ window.removeEventListener('pointermove', this.onWindowPointerMoveForLink);
4126
+ window.removeEventListener('pointerup', this.onWindowPointerUpForLink);
4127
+ window.removeEventListener('pointercancel', this.onWindowPointerUpForLink);
4128
+ const model = this.model();
4129
+ const sourceNode = findNode(model, st.sourceNodeId);
4130
+ const sourcePort = sourceNode ? findPort(sourceNode, st.sourcePortId)?.port : undefined;
4131
+ if (sourceNode && sourcePort) {
4132
+ this.linkDragEnd.emit({ sourceNode, sourcePort, cancelled: true, event: ev });
4133
+ }
4134
+ }
4135
+ //#endregion
4136
+ //#region ---- Node chrome ----
4137
+ shapeClass(shape) {
4138
+ return `axp-workflow-designer__node-shape--${shape}`;
4139
+ }
4140
+ semanticSurfaceClass(color) {
4141
+ return color ? `axp-${color}-surface` : '';
4142
+ }
4143
+ semanticBorderClass(color) {
4144
+ return color ? `axp-${color}-border` : '';
4145
+ }
4146
+ nodeShapeClasses(node) {
4147
+ return ['axp-workflow-designer__node-shape', this.shapeClass(node.shape), this.semanticSurfaceClass(node.color), this.semanticBorderClass(node.borderColor)]
4148
+ .filter(Boolean)
4149
+ .join(' ');
4150
+ }
4151
+ connectorEdgeClasses(c) {
4152
+ const parts = [
4153
+ 'axp-workflow-designer__edge',
4154
+ this.semanticSurfaceClass(c.color),
4155
+ this.semanticBorderClass(c.borderColor),
4156
+ this.isConnectorActive(c.id) ? 'axp-workflow-designer__edge--active' : '',
4157
+ ];
4158
+ return parts.filter(Boolean).join(' ');
4159
+ }
4160
+ onNodePointerDown(node, event) {
4161
+ if (this.isNodeReadonly(node))
4162
+ return;
4163
+ if (event.button !== 0)
4164
+ return;
4165
+ this.nodePointerDown.emit({ node, event });
4166
+ }
4167
+ onNodeClick(node, event) {
4168
+ if (this.suppressNextNodeClick) {
4169
+ this.suppressNextNodeClick = false;
4170
+ event.stopPropagation();
4171
+ return;
4172
+ }
4173
+ event.stopPropagation();
4174
+ const append = event.ctrlKey || event.metaKey;
4175
+ if (append) {
4176
+ const next = new Set(this.selectedNodeIds());
4177
+ if (next.has(node.id))
4178
+ next.delete(node.id);
4179
+ else
4180
+ next.add(node.id);
4181
+ this.selectedNodeIds.set(next);
4182
+ this.selectedConnectorIds.set(new Set());
4183
+ }
4184
+ else {
4185
+ this.selectedNodeIds.set(new Set([node.id]));
4186
+ this.selectedConnectorIds.set(new Set());
4187
+ }
4188
+ this.setActivation({ kind: 'node', id: node.id });
4189
+ this.nodeClick.emit({ node, event: event });
4190
+ }
4191
+ onNodeDoubleClick(node, event) {
4192
+ event.stopPropagation();
4193
+ this.nodeDoubleClick.emit({ node, event });
4194
+ }
4195
+ onNodeContextMenu(node, event) {
4196
+ if (this.isNodeReadonly(node)) {
4197
+ return;
4198
+ }
4199
+ event.preventDefault();
4200
+ event.stopPropagation();
4201
+ this.selectedNodeIds.set(new Set([node.id]));
4202
+ this.selectedConnectorIds.set(new Set());
4203
+ this.setActivation({ kind: 'node', id: node.id });
4204
+ this.nodeContextMenu.emit({ node, event });
4205
+ }
4206
+ onNodeBodyPointerDown(node, event) {
4207
+ if (this.isNodeReadonly(node))
4208
+ return;
4209
+ if (event.button !== 0)
4210
+ return;
4211
+ if (event.target.closest('.axp-workflow-designer__port-cluster'))
4212
+ return;
4213
+ if (event.target.closest('.axp-workflow-designer__resize'))
4214
+ return;
4215
+ // Ctrl/Cmd click is reserved for multi-select toggling in click handler; do not start drag here.
4216
+ if (event.ctrlKey || event.metaKey)
4217
+ return;
4218
+ event.stopPropagation();
4219
+ const selected = this.selectedNodeIds();
4220
+ const moveIds = selected.has(node.id) && selected.size > 0 ? [...selected] : [node.id];
4221
+ if (!selected.has(node.id) || selected.size === 0) {
4222
+ this.selectedNodeIds.set(new Set([node.id]));
4223
+ this.selectedConnectorIds.set(new Set());
4224
+ }
4225
+ this.setActivation({ kind: 'node', id: node.id });
4226
+ const p = this.canvasPointFromClient(event.clientX, event.clientY);
4227
+ const model = this.model();
4228
+ const origins = {};
4229
+ for (const moveId of moveIds) {
4230
+ const n = findNode(model, moveId);
4231
+ if (n)
4232
+ origins[moveId] = { ...n.position };
4233
+ }
4234
+ this.nodeMove = {
4235
+ nodeIds: moveIds,
4236
+ start: p,
4237
+ origins,
4238
+ moved: false,
4239
+ };
4240
+ this.nodeDragStart.emit({ node, event });
4241
+ window.addEventListener('pointermove', this.onWindowPointerMoveForNode);
4242
+ window.addEventListener('pointerup', this.onWindowPointerUpForNode, { once: false });
4243
+ window.addEventListener('pointercancel', this.onWindowPointerUpForNode, { once: false });
4244
+ event.target.setPointerCapture?.(event.pointerId);
4245
+ }
4246
+ //#endregion
4247
+ //#region ---- Resize ----
4248
+ onResizePointerDown(node, handle, event) {
4249
+ if (this.isNodeReadonly(node))
4250
+ return;
4251
+ if (event.button !== 0)
4252
+ return;
4253
+ event.stopPropagation();
4254
+ this.resize = {
4255
+ nodeId: node.id,
4256
+ handle,
4257
+ startPointer: this.canvasPointFromClient(event.clientX, event.clientY),
4258
+ startPosition: { ...node.position },
4259
+ startSize: { ...node.size },
4260
+ aspect: node.size.width / Math.max(1, node.size.height),
4261
+ };
4262
+ window.addEventListener('pointermove', this.onWindowPointerMoveForResize);
4263
+ window.addEventListener('pointerup', this.onWindowPointerUpForResize);
4264
+ window.addEventListener('pointercancel', this.onWindowPointerUpForResize);
4265
+ }
4266
+ //#endregion
4267
+ //#region ---- Ports & linking ----
4268
+ onPortPointerDown(node, port, event) {
4269
+ if (this.isNodeReadonly(node))
4270
+ return;
4271
+ if (port.readonly)
4272
+ return;
4273
+ if (event.button !== 0)
4274
+ return;
4275
+ event.stopPropagation();
4276
+ this.portPointerDown.emit({ node, port, event });
4277
+ this.setActivation({ kind: 'port', id: port.id, nodeId: node.id });
4278
+ if (port.kind === 'outbound' && !port.disabled) {
4279
+ this.linkHoverTarget.set(null);
4280
+ const from = portCanvasPosition(node, port);
4281
+ this.linkDrag.set({
4282
+ sourceNodeId: node.id,
4283
+ sourcePortId: port.id,
4284
+ from,
4285
+ to: from,
4286
+ });
4287
+ this.linkDragStart.emit({ sourceNode: node, sourcePort: port, event });
4288
+ window.addEventListener('pointermove', this.onWindowPointerMoveForLink);
4289
+ window.addEventListener('pointerup', this.onWindowPointerUpForLink);
4290
+ window.addEventListener('pointercancel', this.onWindowPointerUpForLink);
4291
+ }
4292
+ }
4293
+ pickInboundPortAtPoint(model, point, excludeSourceNodeId) {
4294
+ let best = null;
4295
+ const hitR = 18;
4296
+ for (const node of model.nodes) {
4297
+ if (node.id === excludeSourceNodeId)
4298
+ continue;
4299
+ if (this.isNodeReadonly(node))
4300
+ continue;
4301
+ for (const port of node.inboundPorts) {
4302
+ if (port.disabled || port.readonly)
4303
+ continue;
4304
+ const c = portCanvasPosition(node, port);
4305
+ const d = Math.hypot(c.x - point.x, c.y - point.y);
4306
+ if (d <= hitR && (!best || d < best.d))
4307
+ best = { node, port, d };
4308
+ }
4309
+ }
4310
+ return best ? { node: best.node, port: best.port } : null;
4311
+ }
4312
+ //#endregion
4313
+ //#region ---- Connectors UI ----
4314
+ connectorPathD(c) {
4315
+ const model = this.model();
4316
+ const sn = findNode(model, c.sourceNodeId);
4317
+ const tn = findNode(model, c.targetNodeId);
4318
+ if (!sn || !tn)
4319
+ return '';
4320
+ const sp = findPort(sn, c.sourcePortId)?.port;
4321
+ const tp = findPort(tn, c.targetPortId)?.port;
4322
+ if (!sp || !tp)
4323
+ return '';
4324
+ const from = this.modelPointToSurface(portCanvasPosition(sn, sp));
4325
+ const to = this.trimmedConnectorTarget(from, this.modelPointToSurface(portCanvasPosition(tn, tp)));
4326
+ return connectorPath(from, to, this.connectionPreviewStyle(), sp.side, {
4327
+ targetSide: tp.side,
4328
+ obstacles: this.collectObstaclesInSurface(),
4329
+ });
4330
+ }
4331
+ connectorMidpoint(c) {
4332
+ const model = this.model();
4333
+ const sn = findNode(model, c.sourceNodeId);
4334
+ const tn = findNode(model, c.targetNodeId);
4335
+ if (!sn || !tn)
4336
+ return { x: 0, y: 0 };
4337
+ const sp = findPort(sn, c.sourcePortId)?.port;
4338
+ const tp = findPort(tn, c.targetPortId)?.port;
4339
+ if (!sp || !tp)
4340
+ return { x: 0, y: 0 };
4341
+ const a = this.modelPointToSurface(portCanvasPosition(sn, sp));
4342
+ const b = this.modelPointToSurface(portCanvasPosition(tn, tp));
4343
+ return { x: (a.x + b.x) * 0.5, y: (a.y + b.y) * 0.5 };
4344
+ }
4345
+ onConnectorClick(c, event) {
4346
+ event.stopPropagation();
4347
+ event.preventDefault();
4348
+ const append = event.ctrlKey || event.metaKey;
4349
+ if (append) {
4350
+ const next = new Set(this.selectedConnectorIds());
4351
+ if (next.has(c.id))
4352
+ next.delete(c.id);
4353
+ else
4354
+ next.add(c.id);
4355
+ this.selectedConnectorIds.set(next);
4356
+ this.selectedNodeIds.set(new Set());
4357
+ }
4358
+ else {
4359
+ this.selectedConnectorIds.set(new Set([c.id]));
4360
+ this.selectedNodeIds.set(new Set());
4361
+ }
4362
+ this.setActivation({ kind: 'connector', id: c.id });
4363
+ this.connectorClick.emit({ connector: c, event });
4364
+ }
4365
+ onConnectorDoubleClick(c, event) {
4366
+ event.stopPropagation();
4367
+ event.preventDefault();
4368
+ this.connectorDoubleClick.emit({ connector: c, event });
4369
+ }
4370
+ removeConnector(c) {
4371
+ if (this.isConnectorReadonly(c))
4372
+ return;
4373
+ const model = this.model();
4374
+ const connectors = model.connectors.filter((x) => x.id !== c.id);
4375
+ this.commit({ ...model, connectors });
4376
+ this.setActivation(null);
4377
+ }
4378
+ previewPath() {
4379
+ const st = this.linkDrag();
4380
+ if (!st)
4381
+ return '';
4382
+ const model = this.model();
4383
+ const sn = findNode(model, st.sourceNodeId);
4384
+ const sp = sn ? findPort(sn, st.sourcePortId)?.port : undefined;
4385
+ if (!sn || !sp)
4386
+ return '';
4387
+ const from = this.modelPointToSurface(st.from);
4388
+ const to = this.modelPointToSurface(st.to);
4389
+ const hover = this.linkHoverTarget();
4390
+ const hoverNode = hover ? findNode(model, hover.nodeId) : undefined;
4391
+ const hoverPort = hover && hoverNode ? findPort(hoverNode, hover.portId)?.port : undefined;
4392
+ return connectorPath(from, this.trimmedConnectorTarget(from, to), this.connectionPreviewStyle(), sp.side, {
4393
+ targetSide: hoverPort?.side,
4394
+ obstacles: this.collectObstaclesInSurface(),
4395
+ });
4396
+ }
4397
+ //#endregion
4398
+ //#region ---- Template context ----
4399
+ templateContext(node) {
4400
+ return { $implicit: node, node };
4401
+ }
4402
+ portClusterStyle(node, port) {
4403
+ const local = portAnchorOnNode(node, port);
4404
+ const w = Math.max(1, node.size.width);
4405
+ const h = Math.max(1, node.size.height);
4406
+ return {
4407
+ position: 'absolute',
4408
+ left: `${(local.x / w) * 100}%`,
4409
+ top: `${(local.y / h) * 100}%`,
4410
+ 'z-index': '3',
4411
+ };
4412
+ }
4413
+ portLabelText(port) {
4414
+ return (port.label ?? port.key ?? '').trim();
4415
+ }
4416
+ portLabelX(node, port) {
4417
+ return this.portLabelCanvasPoint(node, port).x;
4418
+ }
4419
+ portLabelY(node, port) {
4420
+ return this.portLabelCanvasPoint(node, port).y;
4421
+ }
4422
+ portLabelAnchor(node, port) {
4423
+ const centerX = this.modelPointToSurface({ x: node.position.x + node.size.width * 0.5, y: node.position.y }).x;
4424
+ const anchor = this.modelPointToSurface(portCanvasPosition(node, port));
4425
+ const dx = anchor.x - centerX;
4426
+ if (Math.abs(dx) < 6)
4427
+ return 'middle';
4428
+ return dx < 0 ? 'end' : 'start';
4429
+ }
4430
+ nodeSurfaceLeft(node) {
4431
+ return node.position.x + this.canvasViewport().offsetX;
4432
+ }
4433
+ nodeSurfaceTop(node) {
4434
+ return node.position.y + this.canvasViewport().offsetY;
4435
+ }
4436
+ trimmedConnectorTarget(from, to) {
4437
+ const dx = to.x - from.x;
4438
+ const dy = to.y - from.y;
4439
+ const len = Math.hypot(dx, dy);
4440
+ if (len < 0.001)
4441
+ return to;
4442
+ // Keep arrowhead just outside the 14px port without creating a visible gap.
4443
+ const trim = 7;
4444
+ return {
4445
+ x: to.x - (dx / len) * trim,
4446
+ y: to.y - (dy / len) * trim,
4447
+ };
4448
+ }
4449
+ portLabelCanvasPoint(node, port) {
4450
+ const anchor = this.modelPointToSurface(portCanvasPosition(node, port));
4451
+ const center = {
4452
+ x: node.position.x + node.size.width * 0.5 + this.canvasViewport().offsetX,
4453
+ y: node.position.y + node.size.height * 0.5 + this.canvasViewport().offsetY,
4454
+ };
4455
+ const vx = anchor.x - center.x;
4456
+ const vy = anchor.y - center.y;
4457
+ const len = Math.hypot(vx, vy);
4458
+ if (len < 0.001)
4459
+ return anchor;
4460
+ const outward = 18;
4461
+ return {
4462
+ x: anchor.x + (vx / len) * outward,
4463
+ y: anchor.y + (vy / len) * outward,
4464
+ };
4465
+ }
4466
+ //#endregion
4467
+ //#region ---- Canvas mapping ----
4468
+ modelPointToSurface(point) {
4469
+ const viewport = this.canvasViewport();
4470
+ return {
4471
+ x: point.x + viewport.offsetX,
4472
+ y: point.y + viewport.offsetY,
4473
+ };
4474
+ }
4475
+ surfacePointToModel(point) {
4476
+ const viewport = this.canvasViewport();
4477
+ return {
4478
+ x: point.x - viewport.offsetX,
4479
+ y: point.y - viewport.offsetY,
4480
+ };
4481
+ }
4482
+ /**
4483
+ * Returns node bounding boxes in surface coordinates.
4484
+ *
4485
+ * All nodes are returned so the router can route AROUND every node, including
4486
+ * the source and target of a connector. The router itself drops obstacles that
4487
+ * already contain the stub-out point so the connector can still exit/enter its port.
4488
+ */
4489
+ collectObstaclesInSurface() {
4490
+ const viewport = this.canvasViewport();
4491
+ const obstacles = [];
4492
+ for (const node of this.model().nodes) {
4493
+ obstacles.push({
4494
+ position: { x: node.position.x + viewport.offsetX, y: node.position.y + viewport.offsetY },
4495
+ size: { width: node.size.width, height: node.size.height },
4496
+ });
4497
+ }
4498
+ return obstacles;
4499
+ }
4500
+ //#endregion
4501
+ //#region ---- Wrapper visuals ----
4502
+ syncWrapperBackgroundStyle() {
4503
+ const wrapper = this.wrapperElement();
4504
+ if (!wrapper) {
4505
+ return;
4506
+ }
4507
+ const gridStyle = this.gridStyle();
4508
+ wrapper.style.backgroundColor = 'var(--ax-sys-color-lightest-surface)';
4509
+ wrapper.style.backgroundImage = String(gridStyle['background-image'] ?? '');
4510
+ wrapper.style.backgroundSize = String(gridStyle['background-size'] ?? '');
4511
+ wrapper.style.borderRadius = '10px';
4512
+ }
4513
+ //#endregion
4514
+ detachWindowListeners() {
4515
+ this.clearWrapperAttachRetry();
4516
+ this.detachWrapperDropTargets();
4517
+ this.linkHoverTarget.set(null);
4518
+ window.removeEventListener('pointermove', this.onWindowPointerMoveForNode);
4519
+ window.removeEventListener('pointerup', this.onWindowPointerUpForNode);
4520
+ window.removeEventListener('pointercancel', this.onWindowPointerUpForNode);
4521
+ window.removeEventListener('pointermove', this.onWindowPointerMoveForResize);
4522
+ window.removeEventListener('pointerup', this.onWindowPointerUpForResize);
4523
+ window.removeEventListener('pointercancel', this.onWindowPointerUpForResize);
4524
+ window.removeEventListener('pointermove', this.onWindowPointerMoveForLink);
4525
+ window.removeEventListener('pointerup', this.onWindowPointerUpForLink);
4526
+ window.removeEventListener('pointercancel', this.onWindowPointerUpForLink);
4527
+ }
4528
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPWorkflowDesignerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4529
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPWorkflowDesignerComponent, isStandalone: true, selector: "axp-workflow-designer", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, highlightedNodeIds: { classPropertyName: "highlightedNodeIds", publicName: "highlightedNodeIds", isSignal: true, isRequired: false, transformFunction: null }, gridSize: { classPropertyName: "gridSize", publicName: "gridSize", isSignal: true, isRequired: false, transformFunction: null }, snapToGrid: { classPropertyName: "snapToGrid", publicName: "snapToGrid", isSignal: true, isRequired: false, transformFunction: null }, connectionPreviewStyle: { classPropertyName: "connectionPreviewStyle", publicName: "connectionPreviewStyle", isSignal: true, isRequired: false, transformFunction: null }, backgroundPattern: { classPropertyName: "backgroundPattern", publicName: "backgroundPattern", isSignal: true, isRequired: false, transformFunction: null }, canConnect: { classPropertyName: "canConnect", publicName: "canConnect", isSignal: true, isRequired: false, transformFunction: null }, paletteItems: { classPropertyName: "paletteItems", publicName: "paletteItems", isSignal: true, isRequired: false, transformFunction: null }, showPortLabels: { classPropertyName: "showPortLabels", publicName: "showPortLabels", isSignal: true, isRequired: false, transformFunction: null }, nodeBodyTemplate: { classPropertyName: "nodeBodyTemplate", publicName: "nodeBodyTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modelChange: "modelChange", zoomChange: "zoomChange", designerPointerDown: "designerPointerDown", designerClick: "designerClick", designerDoubleClick: "designerDoubleClick", nodePointerDown: "nodePointerDown", nodeClick: "nodeClick", nodeDoubleClick: "nodeDoubleClick", nodeContextMenu: "nodeContextMenu", nodeDragStart: "nodeDragStart", nodeDragEnd: "nodeDragEnd", portPointerDown: "portPointerDown", linkDragStart: "linkDragStart", linkDragMove: "linkDragMove", linkDragEnd: "linkDragEnd", connectorClick: "connectorClick", connectorDoubleClick: "connectorDoubleClick", connectionCreated: "connectionCreated", connectionRejected: "connectionRejected", activationChange: "activationChange" }, host: { listeners: { "window:keydown": "onWindowKeydown($event)" }, classAttribute: "axp-workflow-designer-host" }, viewQueries: [{ propertyName: "surfaceRef", first: true, predicate: ["surface"], descendants: true, isSignal: true }, { propertyName: "panViewRef", first: true, predicate: AXPanViewDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"axp-workflow-designer\" [ngStyle]=\"gridStyle()\">\n <div\n #surface\n class=\"axp-workflow-designer__surface\"\n axPanView\n [(panX)]=\"panX\"\n [(panY)]=\"panY\"\n [(zoom)]=\"panZoomLevel\"\n [maxZoom]=\"10000\"\n [freeMode]=\"true\"\n [fitContent]=\"true\"\n wrapperClasses=\"h\"\n [ngStyle]=\"surfaceStyle()\"\n (pointerdown)=\"onSurfacePointerDown($event)\"\n (click)=\"onSurfaceClick($event)\"\n (dblclick)=\"onSurfaceDoubleClick($event)\"\n (dragover)=\"onCanvasDragOver($event)\"\n (drop)=\"onCanvasDrop($event)\"\n >\n <svg\n class=\"axp-workflow-designer__svg\"\n width=\"100%\"\n height=\"100%\"\n preserveAspectRatio=\"xMinYMin meet\"\n >\n <defs>\n <marker\n [attr.id]=\"svgMarkerId\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n refX=\"3.9\"\n refY=\"3\"\n orient=\"auto\"\n >\n <path\n d=\"M0,0 L4,3 L0,6 Q1.5,3 0,0 Z\"\n class=\"axp-workflow-designer__arrow-head\"\n ></path>\n </marker>\n </defs>\n @for (c of model().connectors; track c.id) {\n <path\n class=\"axp-workflow-designer__edge-hit\"\n [attr.d]=\"connectorPathD(c)\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"16\"\n pointer-events=\"stroke\"\n (click)=\"onConnectorClick(c, $event)\"\n (dblclick)=\"onConnectorDoubleClick(c, $event)\"\n />\n <path\n [class]=\"connectorEdgeClasses(c)\"\n [attr.d]=\"connectorPathD(c)\"\n fill=\"none\"\n [attr.marker-end]=\"'url(#' + svgMarkerId + ')'\"\n />\n }\n @if (linkDrag()) {\n <path\n class=\"axp-workflow-designer__edge-preview\"\n [attr.d]=\"previewPath()\"\n fill=\"none\"\n />\n }\n @if (showPortLabels()) {\n @for (node of model().nodes; track node.id) {\n @for (port of node.inboundPorts; track port.id) {\n @if (portLabelText(port)) {\n <text\n class=\"axp-workflow-designer__port-label-svg\"\n [attr.x]=\"portLabelX(node, port)\"\n [attr.y]=\"portLabelY(node, port)\"\n [attr.text-anchor]=\"portLabelAnchor(node, port)\"\n >\n {{ portLabelText(port) }}\n </text>\n }\n }\n @for (port of node.outboundPorts; track port.id) {\n @if (portLabelText(port)) {\n <text\n class=\"axp-workflow-designer__port-label-svg\"\n [attr.x]=\"portLabelX(node, port)\"\n [attr.y]=\"portLabelY(node, port)\"\n [attr.text-anchor]=\"portLabelAnchor(node, port)\"\n >\n {{ portLabelText(port) }}\n </text>\n }\n }\n }\n }\n </svg>\n\n @for (c of model().connectors; track c.id) {\n @if (isConnectorActive(c.id) && !isConnectorReadonly(c)) {\n <div\n class=\"axp-workflow-designer__connector-actions\"\n [style.left.px]=\"connectorMidpoint(c).x - 16\"\n [style.top.px]=\"connectorMidpoint(c).y - 16\"\n >\n <ax-button\n look=\"blank\"\n class=\"ax-sm axp-workflow-designer__icon-btn\"\n (onClick)=\"removeConnector(c)\"\n >\n <ax-icon class=\"fa-solid fa-minus\"></ax-icon>\n </ax-button>\n </div>\n }\n }\n\n @for (node of model().nodes; track node.id) {\n <div\n class=\"axp-workflow-designer__node\"\n [class.axp-workflow-designer__node--active]=\"isNodeActive(node.id)\"\n [class.axp-workflow-designer__node--highlighted]=\"\n isNodeHighlighted(node.id)\n \"\n [style.left.px]=\"nodeSurfaceLeft(node)\"\n [style.top.px]=\"nodeSurfaceTop(node)\"\n [style.width.px]=\"node.size.width\"\n [style.height.px]=\"node.size.height\"\n (pointerdown)=\"onNodePointerDown(node, $event)\"\n (click)=\"onNodeClick(node, $event)\"\n (dblclick)=\"onNodeDoubleClick(node, $event)\"\n (contextmenu)=\"onNodeContextMenu(node, $event)\"\n >\n <div [class]=\"nodeShapeClasses(node)\">\n <div\n class=\"axp-workflow-designer__node-body\"\n (pointerdown)=\"onNodeBodyPointerDown(node, $event)\"\n >\n @if (nodeBodyTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"templateContext(node)\"\n />\n } @else {\n <div class=\"axp-workflow-designer__node-default\">\n @if (node.icon) {\n <ax-icon\n class=\"axp-workflow-designer__node-default-icon\"\n [class]=\"node.icon\"\n ></ax-icon>\n }\n <span class=\"axp-workflow-designer__node-default-title\">{{\n node.title\n }}</span>\n </div>\n }\n </div>\n </div>\n\n @for (port of node.inboundPorts; track port.id) {\n <div\n class=\"axp-workflow-designer__port-cluster\"\n [class.axp-workflow-designer__port-cluster--side-top]=\"\n port.side === 'top'\n \"\n [class.axp-workflow-designer__port-cluster--side-right]=\"\n port.side === 'right'\n \"\n [class.axp-workflow-designer__port-cluster--side-bottom]=\"\n port.side === 'bottom'\n \"\n [class.axp-workflow-designer__port-cluster--side-left]=\"\n port.side === 'left'\n \"\n [ngStyle]=\"portClusterStyle(node, port)\"\n >\n <button\n type=\"button\"\n class=\"axp-workflow-designer__port axp-workflow-designer__port--inbound\"\n [class.axp-workflow-designer__port--active]=\"\n isPortActive(node.id, port.id)\n \"\n [class.axp-workflow-designer__port--link-hover]=\"\n isPortLinkHoverTarget(node.id, port.id)\n \"\n [class.axp-workflow-designer__port--disabled]=\"port.disabled\"\n [disabled]=\"isNodeReadonly(node) || port.disabled === true\"\n (pointerdown)=\"onPortPointerDown(node, port, $event)\"\n ></button>\n </div>\n }\n @for (port of node.outboundPorts; track port.id) {\n <div\n class=\"axp-workflow-designer__port-cluster\"\n [class.axp-workflow-designer__port-cluster--side-top]=\"\n port.side === 'top'\n \"\n [class.axp-workflow-designer__port-cluster--side-right]=\"\n port.side === 'right'\n \"\n [class.axp-workflow-designer__port-cluster--side-bottom]=\"\n port.side === 'bottom'\n \"\n [class.axp-workflow-designer__port-cluster--side-left]=\"\n port.side === 'left'\n \"\n [ngStyle]=\"portClusterStyle(node, port)\"\n >\n <button\n type=\"button\"\n class=\"axp-workflow-designer__port axp-workflow-designer__port--outbound\"\n [class.axp-workflow-designer__port--active]=\"\n isPortActive(node.id, port.id)\n \"\n [class.axp-workflow-designer__port--disabled]=\"port.disabled\"\n [disabled]=\"isNodeReadonly(node) || port.disabled === true\"\n (pointerdown)=\"onPortPointerDown(node, port, $event)\"\n ></button>\n </div>\n }\n\n @if (isNodeActive(node.id) && !isNodeReadonly(node)) {\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--nw\"\n (pointerdown)=\"onResizePointerDown(node, 'nw', $event)\"\n ></span>\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--ne\"\n (pointerdown)=\"onResizePointerDown(node, 'ne', $event)\"\n ></span>\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--sw\"\n (pointerdown)=\"onResizePointerDown(node, 'sw', $event)\"\n ></span>\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--se\"\n (pointerdown)=\"onResizePointerDown(node, 'se', $event)\"\n ></span>\n }\n </div>\n }\n </div>\n</div>\n", styles: ["@layer properties;.axp-workflow-designer-host{box-sizing:border-box;display:block;height:100%;width:100%;min-height:360px;user-select:none;-webkit-user-select:none}.axp-workflow-designer{display:flex;height:100%;width:100%;min-height:inherit}.axp-workflow-designer .axp-workflow-designer__surface{position:relative;min-height:100%;min-width:100%;flex:auto;overflow:auto;border-radius:10px;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__surface::ng-deep .pan-view__wrapper{height:100%;width:100%}.axp-workflow-designer .axp-workflow-designer__svg{pointer-events:none;position:absolute;inset:0;height:100%;width:100%;z-index:1}.axp-workflow-designer .axp-workflow-designer__edge-hit{pointer-events:stroke;cursor:pointer}.axp-workflow-designer .axp-workflow-designer__edge{pointer-events:none;stroke:rgb(var(--ax-sys-color-neutral-500));stroke-width:2;transition:stroke .12s ease}.axp-workflow-designer .axp-workflow-designer__edge.primary-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-primary-500))}.axp-workflow-designer .axp-workflow-designer__edge.secondary-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-secondary-500))}.axp-workflow-designer .axp-workflow-designer__edge.success-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-success-500))}.axp-workflow-designer .axp-workflow-designer__edge.warning-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-warning-500))}.axp-workflow-designer .axp-workflow-designer__edge.danger-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-danger-500))}.axp-workflow-designer .axp-workflow-designer__edge--active{stroke:rgb(var(--ax-sys-color-primary-500));stroke-dasharray:2}.axp-workflow-designer .axp-workflow-designer__edge-preview{pointer-events:none;stroke:rgb(var(--ax-sys-color-primary-400));stroke-width:2;stroke-dasharray:6 4}.axp-workflow-designer .axp-workflow-designer__port-label-svg{fill:rgb(var(--ax-sys-color-on-surface));font-size:10px;font-weight:600;dominant-baseline:middle;pointer-events:none;paint-order:stroke;stroke:rgb(var(--ax-sys-color-surface));stroke-width:4px;stroke-linejoin:round}.axp-workflow-designer .axp-workflow-designer__arrow-head{fill:rgb(var(--ax-sys-color-neutral-500))}.axp-workflow-designer .axp-workflow-designer__connector-actions{position:absolute;display:flex;align-items:center;justify-content:center;z-index:4}.axp-workflow-designer .axp-workflow-designer__icon-btn{border-radius:999px;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__node{position:absolute;box-sizing:border-box;z-index:2;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__node.axp-workflow-designer__node--active .axp-workflow-designer__node-shape{border:2px dotted rgb(var(--ax-sys-color-primary-500))}.axp-workflow-designer .axp-workflow-designer__node.axp-workflow-designer__node--highlighted .axp-workflow-designer__node-shape{border:2px solid rgb(var(--ax-sys-color-primary-500));box-shadow:0 0 0 3px rgb(var(--ax-sys-color-primary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape{position:absolute;inset:0;box-sizing:border-box;overflow:visible;background:rgb(var(--ax-sys-color-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-primary-surface{background:rgb(var(--ax-sys-color-primary-surface));color:rgb(var(--ax-sys-color-on-primary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-secondary-surface{background:rgb(var(--ax-sys-color-secondary-surface));color:rgb(var(--ax-sys-color-on-secondary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-success-surface{background:rgb(var(--ax-sys-color-success-surface));color:rgb(var(--ax-sys-color-on-success-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-warning-surface{background:rgb(var(--ax-sys-color-warning-surface));color:rgb(var(--ax-sys-color-on-warning-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-danger-surface{background:rgb(var(--ax-sys-color-danger-surface));color:rgb(var(--ax-sys-color-on-danger-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-primary-border{border:2px solid rgb(var(--ax-sys-color-border-primary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-secondary-border{border:2px solid rgb(var(--ax-sys-color-border-secondary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-success-border{border:2px solid rgb(var(--ax-sys-color-border-success-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-warning-border{border:2px solid rgb(var(--ax-sys-color-border-warning-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-danger-border{border:2px solid rgb(var(--ax-sys-color-border-danger-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape--rectangle{border-radius:4px}.axp-workflow-designer .axp-workflow-designer__node-shape--rounded-rectangle{border-radius:14px}.axp-workflow-designer .axp-workflow-designer__node-shape--circle,.axp-workflow-designer .axp-workflow-designer__node-shape--ellipse{border-radius:50%}.axp-workflow-designer .axp-workflow-designer__node-shape--diamond{border-radius:0;clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}.axp-workflow-designer .axp-workflow-designer__node-body{position:absolute;inset:0;display:flex;cursor:grab;align-items:center;justify-content:center;padding:8px 12px;touch-action:none;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__node-default{display:flex;min-width:0px;align-items:center;gap:8px}.axp-workflow-designer .axp-workflow-designer__node-default-icon{flex-shrink:0}.axp-workflow-designer .axp-workflow-designer__node-default-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-workflow-designer .axp-workflow-designer__port-cluster{pointer-events:none;position:absolute;overflow:visible;width:0;height:0;transform:translate(-50%,-50%);z-index:3}.axp-workflow-designer .axp-workflow-designer__port{position:absolute;flex-shrink:0;cursor:crosshair;border-radius:calc(infinity * 1px);left:50%;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;padding:0;border:1px solid rgb(var(--ax-sys-color-surface));touch-action:none;pointer-events:auto;transition:transform .12s ease}.axp-workflow-designer .axp-workflow-designer__port:disabled{opacity:.45;cursor:not-allowed}.axp-workflow-designer .axp-workflow-designer__port--inbound{background:rgb(var(--ax-sys-color-success-500))}.axp-workflow-designer .axp-workflow-designer__port--outbound{background:rgb(var(--ax-sys-color-primary-500))}.axp-workflow-designer .axp-workflow-designer__port--active{box-shadow:0 0 0 3px #3b82f659}.axp-workflow-designer .axp-workflow-designer__port--link-hover{border:1px solid rgb(var(--ax-sys-color-primary-500));transform:translate(-50%,-50%) scale(1.12)}.axp-workflow-designer .axp-workflow-designer__port--disabled{opacity:.45;cursor:not-allowed}.axp-workflow-designer .axp-workflow-designer__resize{position:absolute;border-radius:calc(infinity * 1px);width:7px;height:7px;background:rgb(var(--ax-sys-color-primary-500));border:1px solid rgb(var(--ax-sys-color-surface));z-index:4;touch-action:none;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__resize--nw{left:-6px;top:-6px;cursor:nwse-resize}.axp-workflow-designer .axp-workflow-designer__resize--ne{right:-6px;top:-6px;cursor:nesw-resize}.axp-workflow-designer .axp-workflow-designer__resize--sw{left:-6px;bottom:-6px;cursor:nesw-resize}.axp-workflow-designer .axp-workflow-designer__resize--se{right:-6px;bottom:-6px;cursor:nwse-resize}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "directive", type: AXPanViewDirective, selector: "[axPanView]", inputs: ["zoomStep", "minZoom", "maxZoom", "freeMode", "fitContent", "disablePan", "disableZoom", "wrapperClasses", "panX", "panY", "zoom"], outputs: ["panXChange", "panYChange", "zoomChange", "positionChange"], exportAs: ["axPanView"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
4530
+ }
4531
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPWorkflowDesignerComponent, decorators: [{
4532
+ type: Component,
4533
+ args: [{ selector: 'axp-workflow-designer', imports: [CommonModule, AXDecoratorModule, AXButtonModule, AXPanViewDirective], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
4534
+ class: 'axp-workflow-designer-host',
4535
+ '(window:keydown)': 'onWindowKeydown($event)',
4536
+ }, template: "<div class=\"axp-workflow-designer\" [ngStyle]=\"gridStyle()\">\n <div\n #surface\n class=\"axp-workflow-designer__surface\"\n axPanView\n [(panX)]=\"panX\"\n [(panY)]=\"panY\"\n [(zoom)]=\"panZoomLevel\"\n [maxZoom]=\"10000\"\n [freeMode]=\"true\"\n [fitContent]=\"true\"\n wrapperClasses=\"h\"\n [ngStyle]=\"surfaceStyle()\"\n (pointerdown)=\"onSurfacePointerDown($event)\"\n (click)=\"onSurfaceClick($event)\"\n (dblclick)=\"onSurfaceDoubleClick($event)\"\n (dragover)=\"onCanvasDragOver($event)\"\n (drop)=\"onCanvasDrop($event)\"\n >\n <svg\n class=\"axp-workflow-designer__svg\"\n width=\"100%\"\n height=\"100%\"\n preserveAspectRatio=\"xMinYMin meet\"\n >\n <defs>\n <marker\n [attr.id]=\"svgMarkerId\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n refX=\"3.9\"\n refY=\"3\"\n orient=\"auto\"\n >\n <path\n d=\"M0,0 L4,3 L0,6 Q1.5,3 0,0 Z\"\n class=\"axp-workflow-designer__arrow-head\"\n ></path>\n </marker>\n </defs>\n @for (c of model().connectors; track c.id) {\n <path\n class=\"axp-workflow-designer__edge-hit\"\n [attr.d]=\"connectorPathD(c)\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"16\"\n pointer-events=\"stroke\"\n (click)=\"onConnectorClick(c, $event)\"\n (dblclick)=\"onConnectorDoubleClick(c, $event)\"\n />\n <path\n [class]=\"connectorEdgeClasses(c)\"\n [attr.d]=\"connectorPathD(c)\"\n fill=\"none\"\n [attr.marker-end]=\"'url(#' + svgMarkerId + ')'\"\n />\n }\n @if (linkDrag()) {\n <path\n class=\"axp-workflow-designer__edge-preview\"\n [attr.d]=\"previewPath()\"\n fill=\"none\"\n />\n }\n @if (showPortLabels()) {\n @for (node of model().nodes; track node.id) {\n @for (port of node.inboundPorts; track port.id) {\n @if (portLabelText(port)) {\n <text\n class=\"axp-workflow-designer__port-label-svg\"\n [attr.x]=\"portLabelX(node, port)\"\n [attr.y]=\"portLabelY(node, port)\"\n [attr.text-anchor]=\"portLabelAnchor(node, port)\"\n >\n {{ portLabelText(port) }}\n </text>\n }\n }\n @for (port of node.outboundPorts; track port.id) {\n @if (portLabelText(port)) {\n <text\n class=\"axp-workflow-designer__port-label-svg\"\n [attr.x]=\"portLabelX(node, port)\"\n [attr.y]=\"portLabelY(node, port)\"\n [attr.text-anchor]=\"portLabelAnchor(node, port)\"\n >\n {{ portLabelText(port) }}\n </text>\n }\n }\n }\n }\n </svg>\n\n @for (c of model().connectors; track c.id) {\n @if (isConnectorActive(c.id) && !isConnectorReadonly(c)) {\n <div\n class=\"axp-workflow-designer__connector-actions\"\n [style.left.px]=\"connectorMidpoint(c).x - 16\"\n [style.top.px]=\"connectorMidpoint(c).y - 16\"\n >\n <ax-button\n look=\"blank\"\n class=\"ax-sm axp-workflow-designer__icon-btn\"\n (onClick)=\"removeConnector(c)\"\n >\n <ax-icon class=\"fa-solid fa-minus\"></ax-icon>\n </ax-button>\n </div>\n }\n }\n\n @for (node of model().nodes; track node.id) {\n <div\n class=\"axp-workflow-designer__node\"\n [class.axp-workflow-designer__node--active]=\"isNodeActive(node.id)\"\n [class.axp-workflow-designer__node--highlighted]=\"\n isNodeHighlighted(node.id)\n \"\n [style.left.px]=\"nodeSurfaceLeft(node)\"\n [style.top.px]=\"nodeSurfaceTop(node)\"\n [style.width.px]=\"node.size.width\"\n [style.height.px]=\"node.size.height\"\n (pointerdown)=\"onNodePointerDown(node, $event)\"\n (click)=\"onNodeClick(node, $event)\"\n (dblclick)=\"onNodeDoubleClick(node, $event)\"\n (contextmenu)=\"onNodeContextMenu(node, $event)\"\n >\n <div [class]=\"nodeShapeClasses(node)\">\n <div\n class=\"axp-workflow-designer__node-body\"\n (pointerdown)=\"onNodeBodyPointerDown(node, $event)\"\n >\n @if (nodeBodyTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"templateContext(node)\"\n />\n } @else {\n <div class=\"axp-workflow-designer__node-default\">\n @if (node.icon) {\n <ax-icon\n class=\"axp-workflow-designer__node-default-icon\"\n [class]=\"node.icon\"\n ></ax-icon>\n }\n <span class=\"axp-workflow-designer__node-default-title\">{{\n node.title\n }}</span>\n </div>\n }\n </div>\n </div>\n\n @for (port of node.inboundPorts; track port.id) {\n <div\n class=\"axp-workflow-designer__port-cluster\"\n [class.axp-workflow-designer__port-cluster--side-top]=\"\n port.side === 'top'\n \"\n [class.axp-workflow-designer__port-cluster--side-right]=\"\n port.side === 'right'\n \"\n [class.axp-workflow-designer__port-cluster--side-bottom]=\"\n port.side === 'bottom'\n \"\n [class.axp-workflow-designer__port-cluster--side-left]=\"\n port.side === 'left'\n \"\n [ngStyle]=\"portClusterStyle(node, port)\"\n >\n <button\n type=\"button\"\n class=\"axp-workflow-designer__port axp-workflow-designer__port--inbound\"\n [class.axp-workflow-designer__port--active]=\"\n isPortActive(node.id, port.id)\n \"\n [class.axp-workflow-designer__port--link-hover]=\"\n isPortLinkHoverTarget(node.id, port.id)\n \"\n [class.axp-workflow-designer__port--disabled]=\"port.disabled\"\n [disabled]=\"isNodeReadonly(node) || port.disabled === true\"\n (pointerdown)=\"onPortPointerDown(node, port, $event)\"\n ></button>\n </div>\n }\n @for (port of node.outboundPorts; track port.id) {\n <div\n class=\"axp-workflow-designer__port-cluster\"\n [class.axp-workflow-designer__port-cluster--side-top]=\"\n port.side === 'top'\n \"\n [class.axp-workflow-designer__port-cluster--side-right]=\"\n port.side === 'right'\n \"\n [class.axp-workflow-designer__port-cluster--side-bottom]=\"\n port.side === 'bottom'\n \"\n [class.axp-workflow-designer__port-cluster--side-left]=\"\n port.side === 'left'\n \"\n [ngStyle]=\"portClusterStyle(node, port)\"\n >\n <button\n type=\"button\"\n class=\"axp-workflow-designer__port axp-workflow-designer__port--outbound\"\n [class.axp-workflow-designer__port--active]=\"\n isPortActive(node.id, port.id)\n \"\n [class.axp-workflow-designer__port--disabled]=\"port.disabled\"\n [disabled]=\"isNodeReadonly(node) || port.disabled === true\"\n (pointerdown)=\"onPortPointerDown(node, port, $event)\"\n ></button>\n </div>\n }\n\n @if (isNodeActive(node.id) && !isNodeReadonly(node)) {\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--nw\"\n (pointerdown)=\"onResizePointerDown(node, 'nw', $event)\"\n ></span>\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--ne\"\n (pointerdown)=\"onResizePointerDown(node, 'ne', $event)\"\n ></span>\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--sw\"\n (pointerdown)=\"onResizePointerDown(node, 'sw', $event)\"\n ></span>\n <span\n class=\"axp-workflow-designer__resize axp-workflow-designer__resize--se\"\n (pointerdown)=\"onResizePointerDown(node, 'se', $event)\"\n ></span>\n }\n </div>\n }\n </div>\n</div>\n", styles: ["@layer properties;.axp-workflow-designer-host{box-sizing:border-box;display:block;height:100%;width:100%;min-height:360px;user-select:none;-webkit-user-select:none}.axp-workflow-designer{display:flex;height:100%;width:100%;min-height:inherit}.axp-workflow-designer .axp-workflow-designer__surface{position:relative;min-height:100%;min-width:100%;flex:auto;overflow:auto;border-radius:10px;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__surface::ng-deep .pan-view__wrapper{height:100%;width:100%}.axp-workflow-designer .axp-workflow-designer__svg{pointer-events:none;position:absolute;inset:0;height:100%;width:100%;z-index:1}.axp-workflow-designer .axp-workflow-designer__edge-hit{pointer-events:stroke;cursor:pointer}.axp-workflow-designer .axp-workflow-designer__edge{pointer-events:none;stroke:rgb(var(--ax-sys-color-neutral-500));stroke-width:2;transition:stroke .12s ease}.axp-workflow-designer .axp-workflow-designer__edge.primary-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-primary-500))}.axp-workflow-designer .axp-workflow-designer__edge.secondary-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-secondary-500))}.axp-workflow-designer .axp-workflow-designer__edge.success-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-success-500))}.axp-workflow-designer .axp-workflow-designer__edge.warning-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-warning-500))}.axp-workflow-designer .axp-workflow-designer__edge.danger-surface:not(.axp-workflow-designer__edge--active){stroke:rgb(var(--ax-sys-color-danger-500))}.axp-workflow-designer .axp-workflow-designer__edge--active{stroke:rgb(var(--ax-sys-color-primary-500));stroke-dasharray:2}.axp-workflow-designer .axp-workflow-designer__edge-preview{pointer-events:none;stroke:rgb(var(--ax-sys-color-primary-400));stroke-width:2;stroke-dasharray:6 4}.axp-workflow-designer .axp-workflow-designer__port-label-svg{fill:rgb(var(--ax-sys-color-on-surface));font-size:10px;font-weight:600;dominant-baseline:middle;pointer-events:none;paint-order:stroke;stroke:rgb(var(--ax-sys-color-surface));stroke-width:4px;stroke-linejoin:round}.axp-workflow-designer .axp-workflow-designer__arrow-head{fill:rgb(var(--ax-sys-color-neutral-500))}.axp-workflow-designer .axp-workflow-designer__connector-actions{position:absolute;display:flex;align-items:center;justify-content:center;z-index:4}.axp-workflow-designer .axp-workflow-designer__icon-btn{border-radius:999px;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__node{position:absolute;box-sizing:border-box;z-index:2;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__node.axp-workflow-designer__node--active .axp-workflow-designer__node-shape{border:2px dotted rgb(var(--ax-sys-color-primary-500))}.axp-workflow-designer .axp-workflow-designer__node.axp-workflow-designer__node--highlighted .axp-workflow-designer__node-shape{border:2px solid rgb(var(--ax-sys-color-primary-500));box-shadow:0 0 0 3px rgb(var(--ax-sys-color-primary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape{position:absolute;inset:0;box-sizing:border-box;overflow:visible;background:rgb(var(--ax-sys-color-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-primary-surface{background:rgb(var(--ax-sys-color-primary-surface));color:rgb(var(--ax-sys-color-on-primary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-secondary-surface{background:rgb(var(--ax-sys-color-secondary-surface));color:rgb(var(--ax-sys-color-on-secondary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-success-surface{background:rgb(var(--ax-sys-color-success-surface));color:rgb(var(--ax-sys-color-on-success-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-warning-surface{background:rgb(var(--ax-sys-color-warning-surface));color:rgb(var(--ax-sys-color-on-warning-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-danger-surface{background:rgb(var(--ax-sys-color-danger-surface));color:rgb(var(--ax-sys-color-on-danger-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-primary-border{border:2px solid rgb(var(--ax-sys-color-border-primary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-secondary-border{border:2px solid rgb(var(--ax-sys-color-border-secondary-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-success-border{border:2px solid rgb(var(--ax-sys-color-border-success-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-warning-border{border:2px solid rgb(var(--ax-sys-color-border-warning-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape.axp-danger-border{border:2px solid rgb(var(--ax-sys-color-border-danger-surface))}.axp-workflow-designer .axp-workflow-designer__node-shape--rectangle{border-radius:4px}.axp-workflow-designer .axp-workflow-designer__node-shape--rounded-rectangle{border-radius:14px}.axp-workflow-designer .axp-workflow-designer__node-shape--circle,.axp-workflow-designer .axp-workflow-designer__node-shape--ellipse{border-radius:50%}.axp-workflow-designer .axp-workflow-designer__node-shape--diamond{border-radius:0;clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}.axp-workflow-designer .axp-workflow-designer__node-body{position:absolute;inset:0;display:flex;cursor:grab;align-items:center;justify-content:center;padding:8px 12px;touch-action:none;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__node-default{display:flex;min-width:0px;align-items:center;gap:8px}.axp-workflow-designer .axp-workflow-designer__node-default-icon{flex-shrink:0}.axp-workflow-designer .axp-workflow-designer__node-default-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-workflow-designer .axp-workflow-designer__port-cluster{pointer-events:none;position:absolute;overflow:visible;width:0;height:0;transform:translate(-50%,-50%);z-index:3}.axp-workflow-designer .axp-workflow-designer__port{position:absolute;flex-shrink:0;cursor:crosshair;border-radius:calc(infinity * 1px);left:50%;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;padding:0;border:1px solid rgb(var(--ax-sys-color-surface));touch-action:none;pointer-events:auto;transition:transform .12s ease}.axp-workflow-designer .axp-workflow-designer__port:disabled{opacity:.45;cursor:not-allowed}.axp-workflow-designer .axp-workflow-designer__port--inbound{background:rgb(var(--ax-sys-color-success-500))}.axp-workflow-designer .axp-workflow-designer__port--outbound{background:rgb(var(--ax-sys-color-primary-500))}.axp-workflow-designer .axp-workflow-designer__port--active{box-shadow:0 0 0 3px #3b82f659}.axp-workflow-designer .axp-workflow-designer__port--link-hover{border:1px solid rgb(var(--ax-sys-color-primary-500));transform:translate(-50%,-50%) scale(1.12)}.axp-workflow-designer .axp-workflow-designer__port--disabled{opacity:.45;cursor:not-allowed}.axp-workflow-designer .axp-workflow-designer__resize{position:absolute;border-radius:calc(infinity * 1px);width:7px;height:7px;background:rgb(var(--ax-sys-color-primary-500));border:1px solid rgb(var(--ax-sys-color-surface));z-index:4;touch-action:none;user-select:none;-webkit-user-select:none}.axp-workflow-designer .axp-workflow-designer__resize--nw{left:-6px;top:-6px;cursor:nwse-resize}.axp-workflow-designer .axp-workflow-designer__resize--ne{right:-6px;top:-6px;cursor:nesw-resize}.axp-workflow-designer .axp-workflow-designer__resize--sw{left:-6px;bottom:-6px;cursor:nesw-resize}.axp-workflow-designer .axp-workflow-designer__resize--se{right:-6px;bottom:-6px;cursor:nwse-resize}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
4537
+ }], ctorParameters: () => [], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }], modelChange: [{ type: i0.Output, args: ["modelChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], highlightedNodeIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightedNodeIds", required: false }] }], gridSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridSize", required: false }] }], snapToGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToGrid", required: false }] }], connectionPreviewStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "connectionPreviewStyle", required: false }] }], backgroundPattern: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundPattern", required: false }] }], zoomChange: [{ type: i0.Output, args: ["zoomChange"] }], canConnect: [{ type: i0.Input, args: [{ isSignal: true, alias: "canConnect", required: false }] }], paletteItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "paletteItems", required: false }] }], showPortLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPortLabels", required: false }] }], nodeBodyTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeBodyTemplate", required: false }] }], designerPointerDown: [{ type: i0.Output, args: ["designerPointerDown"] }], designerClick: [{ type: i0.Output, args: ["designerClick"] }], designerDoubleClick: [{ type: i0.Output, args: ["designerDoubleClick"] }], nodePointerDown: [{ type: i0.Output, args: ["nodePointerDown"] }], nodeClick: [{ type: i0.Output, args: ["nodeClick"] }], nodeDoubleClick: [{ type: i0.Output, args: ["nodeDoubleClick"] }], nodeContextMenu: [{ type: i0.Output, args: ["nodeContextMenu"] }], nodeDragStart: [{ type: i0.Output, args: ["nodeDragStart"] }], nodeDragEnd: [{ type: i0.Output, args: ["nodeDragEnd"] }], portPointerDown: [{ type: i0.Output, args: ["portPointerDown"] }], linkDragStart: [{ type: i0.Output, args: ["linkDragStart"] }], linkDragMove: [{ type: i0.Output, args: ["linkDragMove"] }], linkDragEnd: [{ type: i0.Output, args: ["linkDragEnd"] }], connectorClick: [{ type: i0.Output, args: ["connectorClick"] }], connectorDoubleClick: [{ type: i0.Output, args: ["connectorDoubleClick"] }], connectionCreated: [{ type: i0.Output, args: ["connectionCreated"] }], connectionRejected: [{ type: i0.Output, args: ["connectionRejected"] }], activationChange: [{ type: i0.Output, args: ["activationChange"] }], surfaceRef: [{ type: i0.ViewChild, args: ['surface', { isSignal: true }] }], panViewRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPanViewDirective), { isSignal: true }] }] } });
4538
+
4539
+ /**
4540
+ * Makes any host element a draggable palette source. Set `[axpWorkflowPaletteItem]="item"` and optional outputs.
4541
+ * Drop target: `axp-workflow-designer` canvas (same MIME as drag payload).
4542
+ */
4543
+ class AXPWorkflowPaletteItemDragDirective {
4544
+ constructor() {
4545
+ /** Palette definition serialized into the drag payload. */
4546
+ this.axpWorkflowPaletteItem = input.required({ ...(ngDevMode ? { debugName: "axpWorkflowPaletteItem" } : /* istanbul ignore next */ {}), alias: 'axpWorkflowPaletteItem' });
4547
+ /** When true, drag is disabled. */
4548
+ this.axpWorkflowPaletteDragDisabled = input(false, { ...(ngDevMode ? { debugName: "axpWorkflowPaletteDragDisabled" } : /* istanbul ignore next */ {}), alias: 'axpWorkflowPaletteDragDisabled' });
4549
+ this.axpWorkflowPaletteDragStart = output({
4550
+ alias: 'axpWorkflowPaletteDragStart',
4551
+ });
4552
+ this.axpWorkflowPaletteDragEnd = output({
4553
+ alias: 'axpWorkflowPaletteDragEnd',
4554
+ });
4555
+ this.hostDraggable = computed(() => (this.axpWorkflowPaletteDragDisabled() ? null : true), /* @ts-ignore */
4556
+ ...(ngDevMode ? [{ debugName: "hostDraggable" }] : /* istanbul ignore next */ []));
4557
+ this.lastItem = null;
4558
+ }
4559
+ onDragStart(event) {
4560
+ if (this.axpWorkflowPaletteDragDisabled()) {
4561
+ event.preventDefault();
4562
+ return;
4563
+ }
4564
+ const item = this.axpWorkflowPaletteItem();
4565
+ this.lastItem = item;
4566
+ event.dataTransfer?.setData(AXP_WORKFLOW_PALETTE_MIME, JSON.stringify(item));
4567
+ event.dataTransfer?.setData('text/plain', item.title);
4568
+ if (event.dataTransfer)
4569
+ event.dataTransfer.effectAllowed = 'copy';
4570
+ this.axpWorkflowPaletteDragStart.emit({ item, event });
4571
+ }
4572
+ onDragEnd(event) {
4573
+ this.axpWorkflowPaletteDragEnd.emit({ item: this.lastItem, event });
4574
+ this.lastItem = null;
4575
+ }
4576
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPWorkflowPaletteItemDragDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4577
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: AXPWorkflowPaletteItemDragDirective, isStandalone: true, selector: "[axpWorkflowPaletteItem]", inputs: { axpWorkflowPaletteItem: { classPropertyName: "axpWorkflowPaletteItem", publicName: "axpWorkflowPaletteItem", isSignal: true, isRequired: true, transformFunction: null }, axpWorkflowPaletteDragDisabled: { classPropertyName: "axpWorkflowPaletteDragDisabled", publicName: "axpWorkflowPaletteDragDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { axpWorkflowPaletteDragStart: "axpWorkflowPaletteDragStart", axpWorkflowPaletteDragEnd: "axpWorkflowPaletteDragEnd" }, host: { listeners: { "dragstart": "onDragStart($event)", "dragend": "onDragEnd($event)" }, properties: { "attr.draggable": "hostDraggable()" } }, ngImport: i0 }); }
4578
+ }
4579
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPWorkflowPaletteItemDragDirective, decorators: [{
4580
+ type: Directive,
4581
+ args: [{
4582
+ selector: '[axpWorkflowPaletteItem]',
4583
+ host: {
4584
+ '[attr.draggable]': 'hostDraggable()',
4585
+ '(dragstart)': 'onDragStart($event)',
4586
+ '(dragend)': 'onDragEnd($event)',
4587
+ },
4588
+ }]
4589
+ }], propDecorators: { axpWorkflowPaletteItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "axpWorkflowPaletteItem", required: true }] }], axpWorkflowPaletteDragDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "axpWorkflowPaletteDragDisabled", required: false }] }], axpWorkflowPaletteDragStart: [{ type: i0.Output, args: ["axpWorkflowPaletteDragStart"] }], axpWorkflowPaletteDragEnd: [{ type: i0.Output, args: ["axpWorkflowPaletteDragEnd"] }] } });
4590
+
4591
+ //#region ---- Point & geometry ----
4592
+ //#endregion
4593
+
4594
+ /**
4595
+ * Generated bundle index. Do not edit.
4596
+ */
4597
+
4598
+ export { AXPMenuManagementService as A, snapPoint as B, snapScalar as C, REST_API_GENERATOR_WIDGET_NAME as R, REST_API_GENERATOR_IMPORT_DEBOUNCE_MS as a, AXMPlatformManagementModule as b, createEmptyRestApiOutput as c, AXPWorkflowDesignerComponent as d, AXPWorkflowPaletteItemDragDirective as e, formatRestApiSummary as f, AXP_WORKFLOW_PALETTE_MIME as g, hasParsedContent as h, arrangeWorkflowDiagram as i, cloneDiagram as j, connectorPath as k, createNodeFromPaletteItem as l, createPortsFromPalette as m, normalizeRestApiOutput as n, createWorkflowConnectorId as o, parseHttpRequest as p, createWorkflowNodeId as q, createWorkflowPortId as r, defaultCanConnectRequest as s, findConnector as t, findNode as u, findPort as v, parsePaletteItemDragPayload as w, portAnchorOnNode as x, portCanvasPosition as y, routeOrthogonalConnector as z };
4599
+ //# sourceMappingURL=acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-V_ao4WVM.mjs.map