@acorex-platform/module-platform-dev-tools-client 0.0.38 → 0.0.48

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.
@@ -1,45 +1,42 @@
1
- import { AXPModuleManifestRegistry, AXP_MODULE_MANIFEST_PROVIDER, provideLazyProvider } from '@acorex-platform/framework-client/core';
1
+ import { AXPModuleManifestRegistry, AXPDeviceService, provideLazyProvider } from '@acorex-platform/framework-client/core';
2
2
  import { AXP_SETTING_DEFINITION_PROVIDER, AXP_MENU_PROVIDER } from '@acorex-platform/framework-client/common';
3
- import { AXPPermissionDefinitionService, AXPAuthGuard, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
3
+ import { AXPAuthGuard, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
4
4
  import { AXPRootLayoutComponent } from '@acorex-platform/framework-client/themes/default';
5
5
  import * as i0 from '@angular/core';
6
- import { Injectable, inject, input, ChangeDetectionStrategy, ViewEncapsulation, Component, signal, effect, viewChild, output, NgModule } from '@angular/core';
7
- import { ROUTES } from '@angular/router';
8
- import { RootConfig, AXPApplicationsPlatformPermissionKeys, AXPPlatformDevToolsSettings, AXP_PLATFORM_DEV_TOOLS_COMMUNICATION_DEBUG_REDIRECT_SECTION } from '@acorex-platform/module-platform-dev-tools-client/contracts';
6
+ import { Injectable, inject, signal, input, computed, ChangeDetectionStrategy, ViewEncapsulation, Component, DestroyRef, linkedSignal, effect, viewChild, NgModule } from '@angular/core';
7
+ import { ActivatedRoute, ROUTES } from '@angular/router';
9
8
  import { AXPCommonPermissionGroupKeys } from '@acorex-platform/module-common-client/contracts';
10
- import { AXPWidgetsList } from '@acorex-platform/framework-shared/core';
11
- import { AXPEntityResolver } from '@acorex-platform/framework-client/layout/entity';
9
+ import { AXPApplicationsPlatformPermissionKeys, AXPPlatformDevToolsSettings, AXP_PLATFORM_DEV_TOOLS_COMMUNICATION_DEBUG_REDIRECT_SECTION } from '@acorex-platform/module-platform-dev-tools-client/contracts';
10
+ export { PlatformDevToolsManifest } from '@acorex-platform/module-platform-dev-tools-client/contracts';
11
+ import { AXPWidgetsList, runtimeQueryPayloadData, AXP_MODULE_TYPE, resolveModuleType, resolveModuleDomain } from '@acorex-platform/framework-shared/core';
12
+ import { AXPQueryService } from '@acorex-platform/framework-client/runtime';
13
+ import { AXMPlatformExplorerQueryKeys } from '@acorex-platform/module-platform-management-client/contracts';
12
14
  import * as i1 from '@angular/common';
13
- import { CommonModule } from '@angular/common';
15
+ import { CommonModule, NgTemplateOutlet } from '@angular/common';
16
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
17
+ import { AXResizableDirective } from '@acorex/cdk/resizable';
14
18
  import { AXTreeViewComponent } from '@acorex/components/tree-view';
15
- import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
16
- import * as i2 from '@acorex/core/translation';
19
+ import * as i4 from '@acorex/components/decorators';
20
+ import { AXDecoratorModule } from '@acorex/components/decorators';
21
+ import * as i2$1 from '@acorex/components/loading';
22
+ import { AXLoadingModule } from '@acorex/components/loading';
23
+ import * as i3$1 from '@acorex/components/search-box';
24
+ import { AXSearchBoxModule } from '@acorex/components/search-box';
25
+ import * as i5$1 from '@acorex/components/tabs';
26
+ import { AXTabsModule } from '@acorex/components/tabs';
27
+ import * as i3 from '@acorex/core/translation';
17
28
  import { AXTranslationModule } from '@acorex/core/translation';
18
- import { AXPThemeLayoutStartSideComponent, AXPThemeLayoutHeaderComponent, AXPThemeLayoutBlockComponent } from '@acorex-platform/framework-client/layout/components';
29
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
30
+ import { AXPStateMessageComponent, AXPThemeLayoutStartSideComponent, AXPThemeLayoutHeaderComponent, AXPThemeLayoutBlockComponent } from '@acorex-platform/framework-client/layout/components';
31
+ import * as i2 from '@acorex/components/badge';
32
+ import { AXBadgeModule } from '@acorex/components/badge';
33
+ import * as i5 from '@acorex/components/drawer';
34
+ import { AXDrawerModule } from '@acorex/components/drawer';
35
+ import { arrangeWorkflowDiagram, AXPWorkflowDesignerComponent } from '@acorex-platform/module-platform-management-client';
19
36
  import { AXDataSource } from '@acorex/cdk/common';
20
37
  import * as i1$1 from '@acorex/components/data-table';
21
38
  import { AXDataTableModule } from '@acorex/components/data-table';
22
39
 
23
- //#region ---- Imports ----
24
- //#endregion
25
- //#region ---- Manifest Definition ----
26
- /**
27
- * Platform Dev Tools Module Manifest.
28
- * Development-only module for exploring platform structure (modules, entities, permissions, features).
29
- */
30
- const PlatformDevToolsManifest = {
31
- name: RootConfig.module.name,
32
- version: '1.0.0',
33
- title: RootConfig.module.title,
34
- icon: RootConfig.module.icon,
35
- i18n: RootConfig.config.i18n,
36
- type: 'platform',
37
- domain: 'platform',
38
- description: '@platform-dev-tools:module.description',
39
- required: false,
40
- };
41
- //#endregion
42
-
43
40
  //#endregion
44
41
  //#region ---- Permission Definition Provider ----
45
42
  /**
@@ -100,147 +97,353 @@ class AXPPlatformDevToolsSettingProvider {
100
97
  }
101
98
  //#endregion
102
99
 
103
- //#region ---- Tree node types ----
100
+ //#region ----- Tree node types -----
104
101
  //#endregion
105
102
 
106
- //#region ---- Imports ----
107
103
  //#endregion
108
- //#region ---- Service ----
104
+ //#region ----- Constants -----
105
+ const EXPLORER_QUERY_MODULE = 'module';
106
+ const EXPLORER_QUERY_KIND = 'kind';
107
+ const EXPLORER_QUERY_NAME = 'name';
108
+ const EXPLORER_QUERY_SEARCH = 'q';
109
+ const FOLDER_KINDS = new Set(['entities', 'commands', 'queries', 'plugins', 'widgets']);
110
+ const PAGE_KINDS = new Set([
111
+ 'module',
112
+ 'diagram',
113
+ 'entity',
114
+ 'command',
115
+ 'query',
116
+ 'plugin',
117
+ 'widget',
118
+ ]);
119
+ //#endregion
120
+ //#region ----- Kind helpers -----
121
+ function isExplorerFolderKind(kind) {
122
+ return !!kind && FOLDER_KINDS.has(kind);
123
+ }
124
+ function isExplorerPageKind(kind) {
125
+ return !!kind && PAGE_KINDS.has(kind);
126
+ }
127
+ function catalogFolderKind(kind) {
128
+ switch (kind) {
129
+ case 'entity':
130
+ return 'entities';
131
+ case 'command':
132
+ return 'commands';
133
+ case 'query':
134
+ return 'queries';
135
+ case 'plugin':
136
+ return 'plugins';
137
+ case 'widget':
138
+ return 'widgets';
139
+ }
140
+ }
141
+ //#endregion
142
+ //#region ----- Query params -----
143
+ function param(map, key) {
144
+ const value = map.get(key)?.trim();
145
+ return value || undefined;
146
+ }
147
+ function explorerStateFromQuery(map) {
148
+ const module = param(map, EXPLORER_QUERY_MODULE);
149
+ const rawKind = param(map, EXPLORER_QUERY_KIND);
150
+ const kind = rawKind === 'entities' ? 'diagram' : rawKind;
151
+ return {
152
+ ...(module ? { module } : {}),
153
+ ...(isExplorerPageKind(kind) ? { kind } : {}),
154
+ ...(param(map, EXPLORER_QUERY_NAME) ? { name: param(map, EXPLORER_QUERY_NAME) } : {}),
155
+ ...(param(map, EXPLORER_QUERY_SEARCH) ? { search: param(map, EXPLORER_QUERY_SEARCH) } : {}),
156
+ };
157
+ }
158
+ function queryParamsFromExplorer(node, search) {
159
+ const q = search.trim();
160
+ const params = {
161
+ [EXPLORER_QUERY_MODULE]: null,
162
+ [EXPLORER_QUERY_KIND]: null,
163
+ [EXPLORER_QUERY_NAME]: null,
164
+ [EXPLORER_QUERY_SEARCH]: q || null,
165
+ };
166
+ if (!node || node.kind === 'type-group' || isExplorerFolderKind(node.kind)) {
167
+ return params;
168
+ }
169
+ if (node.moduleName) {
170
+ params[EXPLORER_QUERY_MODULE] = node.moduleName;
171
+ }
172
+ if (node.kind === 'module') {
173
+ return params;
174
+ }
175
+ if (node.kind === 'diagram') {
176
+ params[EXPLORER_QUERY_KIND] = 'diagram';
177
+ return params;
178
+ }
179
+ if (node.kind === 'entity' ||
180
+ node.kind === 'command' ||
181
+ node.kind === 'query' ||
182
+ node.kind === 'plugin' ||
183
+ node.kind === 'widget') {
184
+ params[EXPLORER_QUERY_KIND] = node.kind;
185
+ const payload = node.payload;
186
+ const name = String(payload?.name ?? '').trim() ||
187
+ String(payload?.fullName ?? '').trim() ||
188
+ node.label;
189
+ params[EXPLORER_QUERY_NAME] = name;
190
+ }
191
+ return params;
192
+ }
193
+ function explorerNodeId(moduleName, kind, name) {
194
+ if (kind === 'module') {
195
+ return `module-${moduleName}`;
196
+ }
197
+ if (kind === 'diagram') {
198
+ return `module-${moduleName}-diagram`;
199
+ }
200
+ return `${kind}-${name ?? ''}`;
201
+ }
202
+ function explorerRevealIds(node) {
203
+ const moduleName = node.moduleName;
204
+ if (!moduleName) {
205
+ return [node.id];
206
+ }
207
+ const ids = [`module-${moduleName}`];
208
+ if (node.kind === 'entity' ||
209
+ node.kind === 'command' ||
210
+ node.kind === 'query' ||
211
+ node.kind === 'plugin' ||
212
+ node.kind === 'widget') {
213
+ ids.push(`module-${moduleName}-${catalogFolderKind(node.kind)}`);
214
+ }
215
+ if (node.kind !== 'module') {
216
+ ids.push(node.id);
217
+ }
218
+ return ids;
219
+ }
220
+ function sameExplorerNode(left, right) {
221
+ return left?.id === right?.id;
222
+ }
223
+ //#endregion
224
+
225
+ //#region ----- Imports -----
226
+ //#endregion
227
+ //#region ----- Service -----
109
228
  class PlatformExplorerDataService {
110
229
  constructor() {
111
- this.manifestRegistry = inject(AXPModuleManifestRegistry);
112
- this.entityResolver = inject(AXPEntityResolver);
113
- this.permissionService = inject(AXPPermissionDefinitionService);
114
- }
115
- /**
116
- * Build only root-level module nodes (no children). Children are loaded on expand via getModuleChildren.
117
- * Includes all modules; use buildModulesOnlyFiltered() to exclude modules with no entities/permissions/features.
118
- */
119
- async buildModulesOnly() {
120
- await this.manifestRegistry.initialize();
121
- const modules = this.manifestRegistry.getAll();
122
- const tree = modules.map((mod) => ({
123
- id: `module-${mod.name}`,
124
- kind: 'module',
125
- label: mod.title,
126
- moduleName: mod.name,
127
- moduleTitle: mod.title,
128
- payload: mod,
129
- children: [],
130
- }));
131
- tree.sort((a, b) => {
132
- const nameA = a.payload?.name ?? a.moduleName ?? '';
133
- const nameB = b.payload?.name ?? b.moduleName ?? '';
134
- return nameA.localeCompare(nameB, undefined, { sensitivity: 'base' });
135
- });
136
- return tree;
137
- }
138
- /**
139
- * Build root-level module nodes, excluding modules that have no entities, permissions, or features.
140
- * Each module node gets itemCount = number of non-empty subcategories (for badge).
141
- */
142
- async buildModulesOnlyFiltered() {
143
- const all = await this.buildModulesOnly();
144
- const filtered = [];
145
- for (const modNode of all) {
146
- const children = await this.getModuleChildren(modNode.id);
147
- if (children.length > 0) {
148
- filtered.push({
149
- ...modNode,
150
- itemCount: children.length,
151
- });
152
- }
230
+ this.queryService = inject(AXPQueryService);
231
+ this.manifestRegistry = inject(AXPModuleManifestRegistry, { optional: true });
232
+ this.moduleCache = new Map();
233
+ this.entityTabs = signal([], /* @ts-ignore */
234
+ ...(ngDevMode ? [{ debugName: "entityTabs" }] : /* istanbul ignore next */ []));
235
+ this.activeEntitySection = signal('properties', /* @ts-ignore */
236
+ ...(ngDevMode ? [{ debugName: "activeEntitySection" }] : /* istanbul ignore next */ []));
237
+ }
238
+ clearCache() {
239
+ this.moduleCache.clear();
240
+ }
241
+ setEntityTabs(tabs) {
242
+ this.entityTabs.set(tabs);
243
+ }
244
+ selectEntitySection(section) {
245
+ this.activeEntitySection.set(section);
246
+ }
247
+ clearEntityTabs() {
248
+ this.entityTabs.set([]);
249
+ this.activeEntitySection.set('properties');
250
+ }
251
+ async listModules(search) {
252
+ const result = await this.queryService.fetch(AXMPlatformExplorerQueryKeys.ListModules, { search: search?.trim() || undefined });
253
+ return runtimeQueryPayloadData(result)?.items ?? [];
254
+ }
255
+ async getModule(moduleName) {
256
+ const name = moduleName.trim();
257
+ const cached = this.moduleCache.get(name);
258
+ if (cached) {
259
+ return cached;
260
+ }
261
+ const result = await this.queryService.fetch(AXMPlatformExplorerQueryKeys.GetModule, { module: name });
262
+ const catalog = runtimeQueryPayloadData(result) ?? null;
263
+ if (catalog) {
264
+ this.moduleCache.set(name, catalog);
153
265
  }
154
- return filtered;
266
+ return catalog;
155
267
  }
156
- /**
157
- * Load entities, permissions, and features for a module (called when module node is expanded or clicked).
158
- * Only includes subcategories that have at least one item. Returns empty array if module has no items.
159
- */
160
- async getModuleChildren(moduleId) {
161
- const moduleName = moduleId.startsWith('module-') ? moduleId.slice(7) : moduleId;
162
- const mod = this.manifestRegistry.get(moduleName);
163
- if (!mod)
164
- return [];
165
- const moduleTitle = mod.title;
166
- const [allEntries, permissions, features] = await Promise.all([
167
- this.entityResolver.listAll(),
168
- this.getPermissionsForModule(moduleName),
169
- Promise.resolve(this.getFeaturesForModule(moduleName)),
170
- ]);
171
- const moduleEntities = allEntries.filter((e) => e.module === moduleName);
172
- const permissionsCount = permissions.length;
173
- const featuresCount = features.length;
174
- const nodes = [];
175
- if (moduleEntities.length > 0) {
176
- nodes.push({
177
- id: `module-${moduleName}-entities`,
178
- kind: 'entities',
179
- label: '@platform-dev-tools:module.terms.entities',
268
+ async getEntity(moduleName, entityName) {
269
+ const result = await this.queryService.fetch(AXMPlatformExplorerQueryKeys.GetEntity, { module: moduleName, entity: entityName });
270
+ return runtimeQueryPayloadData(result) ?? null;
271
+ }
272
+ async getCatalogItem(kind, name) {
273
+ const result = await this.queryService.fetch(AXMPlatformExplorerQueryKeys.GetCatalogItem, { kind, name });
274
+ return runtimeQueryPayloadData(result) ?? null;
275
+ }
276
+ getManifest(moduleName) {
277
+ return this.manifestRegistry?.get(moduleName);
278
+ }
279
+ moduleTitle(moduleName) {
280
+ return this.getManifest(moduleName)?.title ?? moduleName;
281
+ }
282
+ moduleIcon(moduleName) {
283
+ return this.getManifest(moduleName)?.icon || 'fa-light fa-cube';
284
+ }
285
+ async resolveExplorerSelection(state, moduleNodes) {
286
+ const moduleName = state.module?.trim();
287
+ if (!moduleName) {
288
+ return null;
289
+ }
290
+ const moduleNode = moduleNodes.find((node) => node.kind === 'module' && node.moduleName === moduleName);
291
+ if (!moduleNode) {
292
+ return null;
293
+ }
294
+ const kind = state.kind;
295
+ if (!kind || kind === 'module') {
296
+ return moduleNode;
297
+ }
298
+ const title = this.moduleTitle(moduleName);
299
+ if (kind === 'diagram') {
300
+ const catalog = await this.getModule(moduleName);
301
+ if (!catalog?.entities.length) {
302
+ return moduleNode;
303
+ }
304
+ return {
305
+ id: `module-${moduleName}-diagram`,
306
+ kind: 'diagram',
307
+ label: '@platform-dev-tools:module.terms.diagram',
180
308
  moduleName,
181
- moduleTitle,
182
- itemCount: moduleEntities.length,
183
- children: moduleEntities.map((e) => ({
184
- id: `entity-${moduleName}-${e.name}`,
185
- kind: 'entity',
186
- label: e.name,
187
- moduleName,
188
- moduleTitle,
189
- payload: e,
190
- })),
309
+ moduleTitle: title,
310
+ };
311
+ }
312
+ const name = state.name?.trim();
313
+ if (!name) {
314
+ return moduleNode;
315
+ }
316
+ const children = await this.getCategoryChildren(moduleName, catalogFolderKind(kind));
317
+ return (children.find((node) => {
318
+ const payload = node.payload;
319
+ return (node.id === `${kind}-${name}` ||
320
+ String(payload?.name ?? '') === name ||
321
+ String(payload?.fullName ?? '') === name);
322
+ }) ?? moduleNode);
323
+ }
324
+ toModuleTreeNodes(modules) {
325
+ const buckets = new Map();
326
+ for (const type of Object.values(AXP_MODULE_TYPE)) {
327
+ buckets.set(type, []);
328
+ }
329
+ for (const mod of modules) {
330
+ const manifest = this.getManifest(mod.name);
331
+ const type = resolveModuleType({
332
+ name: mod.name,
333
+ type: manifest?.type,
334
+ required: manifest?.required,
191
335
  });
336
+ const list = buckets.get(type) ?? [];
337
+ list.push(mod);
338
+ buckets.set(type, list);
192
339
  }
193
- if (permissionsCount > 0) {
340
+ const nodes = [];
341
+ let firstGroup = true;
342
+ for (const type of Object.values(AXP_MODULE_TYPE)) {
343
+ const list = [...(buckets.get(type) ?? [])].sort((a, b) => this.moduleTitle(a.name).localeCompare(this.moduleTitle(b.name), undefined, { sensitivity: 'base' }));
344
+ if (list.length === 0) {
345
+ continue;
346
+ }
194
347
  nodes.push({
195
- id: `module-${moduleName}-permissions`,
196
- kind: 'permissions',
197
- label: '@platform-dev-tools:module.terms.permissions',
198
- moduleName,
199
- moduleTitle,
200
- itemCount: permissionsCount,
348
+ id: `type-group-${type}`,
349
+ kind: 'type-group',
350
+ label: `@application-management:editions.modules-editor.types.${type}`,
351
+ divided: !firstGroup,
201
352
  });
353
+ firstGroup = false;
354
+ for (const mod of list) {
355
+ nodes.push(this.toModuleNode(mod));
356
+ }
357
+ }
358
+ return nodes;
359
+ }
360
+ toModuleNode(mod) {
361
+ return {
362
+ id: `module-${mod.name}`,
363
+ kind: 'module',
364
+ label: this.moduleTitle(mod.name),
365
+ moduleName: mod.name,
366
+ moduleTitle: this.moduleTitle(mod.name),
367
+ icon: this.moduleIcon(mod.name),
368
+ itemCount: mod.categoryCount + (mod.entityCount > 0 ? 1 : 0),
369
+ payload: mod,
370
+ };
371
+ }
372
+ async getModuleChildren(moduleName) {
373
+ const catalog = await this.getModule(moduleName);
374
+ if (!catalog) {
375
+ return [];
202
376
  }
203
- if (featuresCount > 0) {
377
+ const title = this.moduleTitle(moduleName);
378
+ const nodes = [];
379
+ if (catalog.entities.length > 0) {
204
380
  nodes.push({
205
- id: `module-${moduleName}-features`,
206
- kind: 'features',
207
- label: '@platform-dev-tools:module.terms.features',
381
+ id: `module-${moduleName}-diagram`,
382
+ kind: 'diagram',
383
+ label: '@platform-dev-tools:module.terms.diagram',
208
384
  moduleName,
209
- moduleTitle,
210
- itemCount: featuresCount,
385
+ moduleTitle: title,
211
386
  });
212
387
  }
388
+ this.pushCategory(nodes, moduleName, title, 'entities', catalog.entities.length);
389
+ this.pushCategory(nodes, moduleName, title, 'commands', catalog.commands.length);
390
+ this.pushCategory(nodes, moduleName, title, 'queries', catalog.queries.length);
391
+ this.pushCategory(nodes, moduleName, title, 'plugins', catalog.plugins.length);
392
+ this.pushCategory(nodes, moduleName, title, 'widgets', catalog.widgets.length);
213
393
  return nodes;
214
394
  }
215
- /**
216
- * Get all permissions for a module (for display in module detail).
217
- */
218
- async getPermissionsForModule(moduleName) {
219
- const groups = await this.permissionService.getGroups();
220
- return this.collectPermissionsForModule(groups, moduleName);
221
- }
222
- /**
223
- * Get all features for a module (for display in module detail).
224
- */
225
- getFeaturesForModule(moduleName) {
226
- return this.manifestRegistry.getModuleFeatures(moduleName);
227
- }
228
- collectPermissionsForModule(groups, moduleName) {
229
- const prefix = `${moduleName}:`;
230
- const out = [];
231
- const collect = (items) => {
232
- for (const p of items) {
233
- if (p.name.startsWith(prefix))
234
- out.push(p);
235
- if (p.children?.length)
236
- collect(p.children);
237
- }
238
- };
239
- for (const g of groups) {
240
- if (g.name === moduleName)
241
- collect(g.permissions);
395
+ async getCategoryChildren(moduleName, kind) {
396
+ const catalog = await this.getModule(moduleName);
397
+ if (!catalog) {
398
+ return [];
399
+ }
400
+ const title = this.moduleTitle(moduleName);
401
+ switch (kind) {
402
+ case 'entities':
403
+ return catalog.entities.map((item) => ({
404
+ id: `entity-${item.fullName}`,
405
+ kind: 'entity',
406
+ label: item.name,
407
+ moduleName,
408
+ moduleTitle: title,
409
+ icon: item.icon,
410
+ payload: item,
411
+ }));
412
+ case 'commands':
413
+ return catalog.commands.map((item) => this.namedNode('command', item.name, item.title ?? item.name, moduleName, title, item, item.icon));
414
+ case 'queries':
415
+ return catalog.queries.map((item) => this.namedNode('query', item.name, item.title ?? item.name, moduleName, title, item, item.icon));
416
+ case 'plugins':
417
+ return catalog.plugins.map((item) => this.namedNode('plugin', item.name, item.title ?? item.name, moduleName, title, item, item.icon));
418
+ case 'widgets':
419
+ return catalog.widgets.map((item) => this.namedNode('widget', item.name, item.title ?? item.name, moduleName, title, item, item.icon));
420
+ default:
421
+ return [];
422
+ }
423
+ }
424
+ pushCategory(nodes, moduleName, moduleTitle, kind, count) {
425
+ if (count <= 0) {
426
+ return;
242
427
  }
243
- return out;
428
+ nodes.push({
429
+ id: `module-${moduleName}-${kind}`,
430
+ kind,
431
+ label: `@platform-dev-tools:module.terms.${kind}`,
432
+ moduleName,
433
+ moduleTitle,
434
+ itemCount: count,
435
+ });
436
+ }
437
+ namedNode(kind, name, label, moduleName, moduleTitle, payload, icon) {
438
+ return {
439
+ id: `${kind}-${name}`,
440
+ kind,
441
+ label,
442
+ moduleName,
443
+ moduleTitle,
444
+ icon,
445
+ payload,
446
+ };
244
447
  }
245
448
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PlatformExplorerDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
246
449
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PlatformExplorerDataService }); }
@@ -249,540 +452,1883 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
249
452
  type: Injectable
250
453
  }] });
251
454
 
252
- //#region ---- Imports ----
455
+ //#region ----- Imports -----
253
456
  //#endregion
457
+ //#region ----- Constants -----
458
+ const TYPE_I18N = '@application-management:editions.modules-editor.types';
459
+ const DOMAIN_I18N = '@application-management:editions.modules-editor.domains';
460
+ //#endregion
461
+ //#region ----- Component -----
254
462
  class AXPlatformExplorerModuleDetailComponent {
255
463
  constructor() {
256
464
  this.payload = input(/* @ts-ignore */
257
465
  ...(ngDevMode ? [undefined, { debugName: "payload" }] : /* istanbul ignore next */ []));
466
+ this.dataService = inject(PlatformExplorerDataService);
467
+ this.view = computed(() => {
468
+ const summary = this.payload();
469
+ if (!summary) {
470
+ return null;
471
+ }
472
+ const manifest = this.dataService.getManifest(summary.name);
473
+ if (!manifest) {
474
+ return null;
475
+ }
476
+ return this.toView(summary.name, manifest);
477
+ }, /* @ts-ignore */
478
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
258
479
  }
259
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerModuleDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
260
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerModuleDetailComponent, isStandalone: true, selector: "axp-platform-explorer-module-detail", inputs: { payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
261
- @if (payload()) {
262
- <section class="axp-module-detail">
263
- <h3 class="axp-module-detail__section-title">
264
- {{ '@platform-dev-tools:module.terms.module-info' | translate | async }}
265
- </h3>
266
- <dl class="axp-detail-dl">
267
- <dt>
268
- {{ '@platform-dev-tools:module.terms.name' | translate | async }}
269
- </dt>
270
- <dd>{{ payload()!.name }}</dd>
271
-
272
- <dt>
273
- {{ '@platform-dev-tools:module.terms.version' | translate | async }}
274
- </dt>
275
- <dd>{{ payload()!.version }}</dd>
276
-
277
- <dt>
278
- {{ '@platform-dev-tools:module.terms.title' | translate | async }}
279
- </dt>
280
- <dd>{{ payload()!.title }}</dd>
281
-
282
- @if (payload()!.description) {
283
- <dt>
284
- {{ '@platform-dev-tools:module.terms.description' | translate | async }}
285
- </dt>
286
- <dd>{{ payload()!.description }}</dd>
287
- }
288
-
289
- @if (payload()!.i18n) {
290
- <dt>i18n</dt>
291
- <dd>{{ payload()!.i18n }}</dd>
292
- }
293
-
294
- @if (payload()!.required !== undefined) {
295
- <dt>
296
- {{ '@platform-dev-tools:module.terms.required' | translate | async }}
297
- </dt>
298
- <dd>{{ payload()!.required }}</dd>
299
- }
300
- </dl>
301
- </section>
480
+ toView(name, manifest) {
481
+ const type = resolveModuleType(manifest);
482
+ const domain = resolveModuleDomain(manifest);
483
+ return {
484
+ name,
485
+ title: manifest.title || name,
486
+ description: manifest.description,
487
+ icon: manifest.icon || 'fa-light fa-cube',
488
+ version: manifest.version,
489
+ type,
490
+ typeKey: `${TYPE_I18N}.${type}`,
491
+ domain,
492
+ domainKey: `${DOMAIN_I18N}.${domain}`,
493
+ i18n: manifest.i18n,
494
+ required: !!manifest.required,
495
+ dependencies: (manifest.dependencies ?? []).map((dep) => ({
496
+ name: dep,
497
+ title: dep.includes('.') ? dep : this.dataService.moduleTitle(dep),
498
+ })),
499
+ };
302
500
  }
303
- `, isInline: true, styles: [".axp-module-detail{margin-bottom:24px}.axp-module-detail__section-title{margin:0 0 12px;font-size:15px;font-weight:600}.axp-detail-dl{display:grid;grid-template-columns:auto 1fr;gap:4px 16px;margin:0}.axp-detail-dl dt{font-weight:600;color:var(--ax-neutral-text-secondary, #666)}.axp-detail-dl dd{margin:0}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
501
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerModuleDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
502
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerModuleDetailComponent, isStandalone: true, selector: "axp-platform-explorer-module-detail", inputs: { payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: "<div class=\"flex flex-col flex-1 min-h-0 overflow-auto\">\n <!-- ----- Module identity from client manifest ----- -->\n @if (payload(); as mod) {\n <section class=\"flex flex-col gap-5 p-4\">\n <!--->\n <!-- ----- Manifest header when the module is registered on the client ----- -->\n @if (view(); as info) {\n <div class=\"flex items-start gap-3\">\n <i class=\"text-2xl text-primary shrink-0 mt-0.5\" [ngClass]=\"info.icon\" aria-hidden=\"true\"></i>\n <div class=\"flex flex-col gap-1 min-w-0\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <h2 class=\"m-0 text-lg font-semibold\">{{ info.title | translate | async }}</h2>\n <ax-badge color=\"secondary\" [text]=\"info.version\"></ax-badge>\n <ax-badge color=\"primary\" [text]=\"(info.typeKey | translate | async) ?? info.type\"></ax-badge>\n <ax-badge [text]=\"(info.domainKey | translate | async) ?? info.domain\"></ax-badge>\n <!--->\n <!-- ----- Required modules cannot be disabled in editions ----- -->\n @if (info.required) {\n <ax-badge\n color=\"success\"\n [text]=\"('@platform-dev-tools:module.terms.required' | translate | async) ?? ''\"\n ></ax-badge>\n }\n </div>\n <!--->\n <!-- ----- Optional manifest description ----- -->\n @if (info.description) {\n <p class=\"m-0 text-sm text-muted\">{{ info.description | translate | async }}</p>\n }\n </div>\n </div>\n\n <dl class=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 m-0\">\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd class=\"m-0\"><code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ info.name }}</code></dd>\n <!--->\n <!-- ----- i18n scope from the module manifest ----- -->\n @if (info.i18n) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.i18n' | translate | async }}</dt>\n <dd class=\"m-0\"><code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ info.i18n }}</code></dd>\n }\n </dl>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.dependencies' | translate | async }}</div>\n <!--->\n <!-- ----- Empty dependency list ----- -->\n @if (info.dependencies.length === 0) {\n <p class=\"m-0 text-sm text-muted\">{{ '@platform-dev-tools:module.messages.no-dependencies' | translate | async }}</p>\n }\n <!-- ----- Manifest dependency chips ----- -->\n @else {\n <div class=\"flex flex-wrap gap-2\">\n @for (dep of info.dependencies; track dep.name) {\n <ax-badge [text]=\"(dep.title | translate | async) ?? dep.name\"></ax-badge>\n }\n </div>\n }\n </div>\n }\n <!-- ----- Catalog-only module with no client manifest ----- -->\n @else {\n <dl class=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 m-0\">\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd class=\"m-0\">{{ mod.name }}</dd>\n </dl>\n <p class=\"m-0 text-sm text-muted\">{{ '@platform-dev-tools:module.messages.no-manifest' | translate | async }}</p>\n }\n\n <div class=\"flex flex-wrap gap-2\">\n <ax-badge\n color=\"primary\"\n [text]=\"('@platform-dev-tools:module.badges.entities' | translate: { params: { count: mod.entityCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n color=\"success\"\n [text]=\"('@platform-dev-tools:module.badges.commands' | translate: { params: { count: mod.commandCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n color=\"primary\"\n [text]=\"('@platform-dev-tools:module.badges.queries' | translate: { params: { count: mod.queryCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n color=\"warning\"\n [text]=\"('@platform-dev-tools:module.badges.plugins' | translate: { params: { count: mod.pluginCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n [text]=\"('@platform-dev-tools:module.badges.widgets' | translate: { params: { count: mod.widgetCount } } | async) ?? ''\"\n ></ax-badge>\n </div>\n </section>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
304
503
  }
305
504
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerModuleDetailComponent, decorators: [{
306
505
  type: Component,
307
- args: [{ selector: 'axp-platform-explorer-module-detail', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXTranslationModule], template: `
308
- @if (payload()) {
309
- <section class="axp-module-detail">
310
- <h3 class="axp-module-detail__section-title">
311
- {{ '@platform-dev-tools:module.terms.module-info' | translate | async }}
312
- </h3>
313
- <dl class="axp-detail-dl">
314
- <dt>
315
- {{ '@platform-dev-tools:module.terms.name' | translate | async }}
316
- </dt>
317
- <dd>{{ payload()!.name }}</dd>
318
-
319
- <dt>
320
- {{ '@platform-dev-tools:module.terms.version' | translate | async }}
321
- </dt>
322
- <dd>{{ payload()!.version }}</dd>
323
-
324
- <dt>
325
- {{ '@platform-dev-tools:module.terms.title' | translate | async }}
326
- </dt>
327
- <dd>{{ payload()!.title }}</dd>
506
+ args: [{ selector: 'axp-platform-explorer-module-detail', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'flex flex-col flex-1 min-h-0' }, imports: [CommonModule, AXTranslationModule, AXBadgeModule], template: "<div class=\"flex flex-col flex-1 min-h-0 overflow-auto\">\n <!-- ----- Module identity from client manifest ----- -->\n @if (payload(); as mod) {\n <section class=\"flex flex-col gap-5 p-4\">\n <!--->\n <!-- ----- Manifest header when the module is registered on the client ----- -->\n @if (view(); as info) {\n <div class=\"flex items-start gap-3\">\n <i class=\"text-2xl text-primary shrink-0 mt-0.5\" [ngClass]=\"info.icon\" aria-hidden=\"true\"></i>\n <div class=\"flex flex-col gap-1 min-w-0\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <h2 class=\"m-0 text-lg font-semibold\">{{ info.title | translate | async }}</h2>\n <ax-badge color=\"secondary\" [text]=\"info.version\"></ax-badge>\n <ax-badge color=\"primary\" [text]=\"(info.typeKey | translate | async) ?? info.type\"></ax-badge>\n <ax-badge [text]=\"(info.domainKey | translate | async) ?? info.domain\"></ax-badge>\n <!--->\n <!-- ----- Required modules cannot be disabled in editions ----- -->\n @if (info.required) {\n <ax-badge\n color=\"success\"\n [text]=\"('@platform-dev-tools:module.terms.required' | translate | async) ?? ''\"\n ></ax-badge>\n }\n </div>\n <!--->\n <!-- ----- Optional manifest description ----- -->\n @if (info.description) {\n <p class=\"m-0 text-sm text-muted\">{{ info.description | translate | async }}</p>\n }\n </div>\n </div>\n\n <dl class=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 m-0\">\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd class=\"m-0\"><code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ info.name }}</code></dd>\n <!--->\n <!-- ----- i18n scope from the module manifest ----- -->\n @if (info.i18n) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.i18n' | translate | async }}</dt>\n <dd class=\"m-0\"><code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ info.i18n }}</code></dd>\n }\n </dl>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.dependencies' | translate | async }}</div>\n <!--->\n <!-- ----- Empty dependency list ----- -->\n @if (info.dependencies.length === 0) {\n <p class=\"m-0 text-sm text-muted\">{{ '@platform-dev-tools:module.messages.no-dependencies' | translate | async }}</p>\n }\n <!-- ----- Manifest dependency chips ----- -->\n @else {\n <div class=\"flex flex-wrap gap-2\">\n @for (dep of info.dependencies; track dep.name) {\n <ax-badge [text]=\"(dep.title | translate | async) ?? dep.name\"></ax-badge>\n }\n </div>\n }\n </div>\n }\n <!-- ----- Catalog-only module with no client manifest ----- -->\n @else {\n <dl class=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 m-0\">\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd class=\"m-0\">{{ mod.name }}</dd>\n </dl>\n <p class=\"m-0 text-sm text-muted\">{{ '@platform-dev-tools:module.messages.no-manifest' | translate | async }}</p>\n }\n\n <div class=\"flex flex-wrap gap-2\">\n <ax-badge\n color=\"primary\"\n [text]=\"('@platform-dev-tools:module.badges.entities' | translate: { params: { count: mod.entityCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n color=\"success\"\n [text]=\"('@platform-dev-tools:module.badges.commands' | translate: { params: { count: mod.commandCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n color=\"primary\"\n [text]=\"('@platform-dev-tools:module.badges.queries' | translate: { params: { count: mod.queryCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n color=\"warning\"\n [text]=\"('@platform-dev-tools:module.badges.plugins' | translate: { params: { count: mod.pluginCount } } | async) ?? ''\"\n ></ax-badge>\n <ax-badge\n [text]=\"('@platform-dev-tools:module.badges.widgets' | translate: { params: { count: mod.widgetCount } } | async) ?? ''\"\n ></ax-badge>\n </div>\n </section>\n }\n</div>\n" }]
507
+ }], propDecorators: { payload: [{ type: i0.Input, args: [{ isSignal: true, alias: "payload", required: false }] }] } });
328
508
 
329
- @if (payload()!.description) {
330
- <dt>
331
- {{ '@platform-dev-tools:module.terms.description' | translate | async }}
332
- </dt>
333
- <dd>{{ payload()!.description }}</dd>
334
- }
509
+ //#endregion
510
+ //#region ----- Plugin contribution field names -----
511
+ function isPlaceholderName(name) {
512
+ const trimmed = name.trim();
513
+ return trimmed.startsWith('(from ') || trimmed.includes('options.fields[]');
514
+ }
515
+ function parseContributions(json) {
516
+ if (json == null) {
517
+ return null;
518
+ }
519
+ let parsed = json;
520
+ if (typeof json === 'string') {
521
+ const trimmed = json.trim();
522
+ if (!trimmed) {
523
+ return null;
524
+ }
525
+ try {
526
+ parsed = JSON.parse(trimmed);
527
+ }
528
+ catch {
529
+ return null;
530
+ }
531
+ }
532
+ if (parsed == null || typeof parsed !== 'object') {
533
+ return null;
534
+ }
535
+ return parsed;
536
+ }
537
+ function optionString(options, key) {
538
+ if (!key || options == null || typeof options !== 'object') {
539
+ return '';
540
+ }
541
+ const value = options[key];
542
+ return typeof value === 'string' ? value.trim() : '';
543
+ }
544
+ function contributedNames(plugin) {
545
+ const contributions = parseContributions(plugin.contributionsJson);
546
+ const names = new Set();
547
+ const add = (name, nameFromOption) => {
548
+ const fromOption = optionString(plugin.options, nameFromOption);
549
+ const fieldName = fromOption || name?.trim() || '';
550
+ if (fieldName && !isPlaceholderName(fieldName)) {
551
+ names.add(fieldName);
552
+ }
553
+ };
554
+ for (const property of contributions?.properties ?? []) {
555
+ add(property.name, property.nameFromOption);
556
+ }
557
+ for (const column of contributions?.columns ?? []) {
558
+ add(column.name, column.nameFromOption);
559
+ }
560
+ return [...names];
561
+ }
562
+ /**
563
+ * Maps host property/column names to the plugin that contributed them.
564
+ * First matching plugin wins when two plugins claim the same name.
565
+ */
566
+ function pluginSourceByFieldName(plugins) {
567
+ const map = new Map();
568
+ for (const plugin of plugins) {
569
+ const source = {
570
+ name: plugin.name,
571
+ title: plugin.title || plugin.name,
572
+ };
573
+ for (const fieldName of contributedNames(plugin)) {
574
+ if (!map.has(fieldName)) {
575
+ map.set(fieldName, source);
576
+ }
577
+ }
578
+ }
579
+ return map;
580
+ }
581
+ //#endregion
335
582
 
336
- @if (payload()!.i18n) {
337
- <dt>i18n</dt>
338
- <dd>{{ payload()!.i18n }}</dd>
339
- }
583
+ //#region ----- Types -----
584
+ //#endregion
585
+ //#region ----- Flatten -----
586
+ function formatLeaf(value) {
587
+ if (value === null) {
588
+ return 'null';
589
+ }
590
+ if (typeof value === 'string') {
591
+ return value;
592
+ }
593
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
594
+ return String(value);
595
+ }
596
+ try {
597
+ return JSON.stringify(value);
598
+ }
599
+ catch {
600
+ return String(value);
601
+ }
602
+ }
603
+ function joinPath(prefix, key) {
604
+ return prefix ? `${prefix}.${key}` : key;
605
+ }
606
+ function flattenToPairs(value, prefix = '') {
607
+ if (value === undefined || typeof value === 'function') {
608
+ return [];
609
+ }
610
+ if (value !== null && typeof value === 'object') {
611
+ if (Array.isArray(value)) {
612
+ if (value.length === 0) {
613
+ return prefix ? [{ path: prefix, value: '[]' }] : [];
614
+ }
615
+ return value.flatMap((item, index) => flattenToPairs(item, joinPath(prefix, String(index))));
616
+ }
617
+ const entries = Object.entries(value).filter(([, nested]) => nested !== undefined && typeof nested !== 'function');
618
+ if (entries.length === 0) {
619
+ return prefix ? [{ path: prefix, value: '{}' }] : [];
620
+ }
621
+ return entries.flatMap(([key, nested]) => flattenToPairs(nested, joinPath(prefix, key)));
622
+ }
623
+ return [{ path: prefix, value: formatLeaf(value) }];
624
+ }
625
+ function flattenJsonText(json) {
626
+ if (!json?.trim()) {
627
+ return [];
628
+ }
629
+ try {
630
+ return flattenToPairs(JSON.parse(json));
631
+ }
632
+ catch {
633
+ return [{ path: '', value: json }];
634
+ }
635
+ }
636
+ //#endregion
340
637
 
341
- @if (payload()!.required !== undefined) {
342
- <dt>
343
- {{ '@platform-dev-tools:module.terms.required' | translate | async }}
344
- </dt>
345
- <dd>{{ payload()!.required }}</dd>
346
- }
347
- </dl>
348
- </section>
349
- }
350
- `, styles: [".axp-module-detail{margin-bottom:24px}.axp-module-detail__section-title{margin:0 0 12px;font-size:15px;font-weight:600}.axp-detail-dl{display:grid;grid-template-columns:auto 1fr;gap:4px 16px;margin:0}.axp-detail-dl dt{font-weight:600;color:var(--ax-neutral-text-secondary, #666)}.axp-detail-dl dd{margin:0}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
351
- }], propDecorators: { payload: [{ type: i0.Input, args: [{ isSignal: true, alias: "payload", required: false }] }] } });
638
+ //#endregion
639
+ //#region ----- Constants -----
640
+ const SCOPE_ORDER = ['typeLevel', 'selected', 'individual', 'section'];
641
+ //#endregion
642
+ //#region ----- Serialize -----
643
+ function humanText(value) {
644
+ if (typeof value !== 'string') {
645
+ return undefined;
646
+ }
647
+ const trimmed = value.trim();
648
+ if (!trimmed || trimmed.includes('{{')) {
649
+ return undefined;
650
+ }
651
+ return trimmed;
652
+ }
653
+ function titleCaseType(type) {
654
+ return type
655
+ .split('-')
656
+ .filter(Boolean)
657
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
658
+ .join(' ');
659
+ }
660
+ function boolOrExpression(value) {
661
+ if (typeof value === 'boolean') {
662
+ return value ? 'yes' : 'no';
663
+ }
664
+ if (typeof value === 'string' && value.trim()) {
665
+ return value.trim();
666
+ }
667
+ return undefined;
668
+ }
669
+ function asActions(value) {
670
+ if (!Array.isArray(value)) {
671
+ return [];
672
+ }
673
+ return value.filter((item) => !!item && typeof item === 'object');
674
+ }
675
+ function layoutActions(layout) {
676
+ if (!layout || typeof layout !== 'object') {
677
+ return [];
678
+ }
679
+ return asActions(layout.actions);
680
+ }
681
+ function resolveCommand(command) {
682
+ if (typeof command === 'function') {
683
+ return { name: 'function', optionsPairs: [], metadataPairs: [] };
684
+ }
685
+ if (typeof command === 'string') {
686
+ return { name: command, optionsPairs: [], metadataPairs: [] };
687
+ }
688
+ if (command && typeof command === 'object') {
689
+ return {
690
+ name: typeof command.name === 'string' ? command.name : '',
691
+ optionsPairs: flattenToPairs(command.options),
692
+ metadataPairs: flattenToPairs(command.metadata),
693
+ };
694
+ }
695
+ return { name: '', optionsPairs: [], metadataPairs: [] };
696
+ }
697
+ function resolveItems(items) {
698
+ if (Array.isArray(items)) {
699
+ return { children: asActions(items) };
700
+ }
701
+ if (typeof items === 'string' && items.trim()) {
702
+ return { children: [], expression: items.trim() };
703
+ }
704
+ return { children: [] };
705
+ }
706
+ //#endregion
707
+ //#region ----- Actions -----
708
+ function toActionView(action, id, depth) {
709
+ const command = resolveCommand(action.command);
710
+ const items = resolveItems(action.items);
711
+ const hidden = boolOrExpression(action.hidden);
712
+ const disabled = boolOrExpression(action.disabled);
713
+ const type = typeof action.type === 'string' ? action.type : undefined;
714
+ const shortcuts = (action.shortcuts ?? []).filter((item) => typeof item === 'string' && item.trim());
715
+ const permissions = (action.permissions ?? []).filter((item) => typeof item === 'string' && item.trim());
716
+ return {
717
+ id,
718
+ name: typeof action.name === 'string' ? action.name : undefined,
719
+ title: typeof action.title === 'string' ? action.title : undefined,
720
+ commandName: command.name,
721
+ type,
722
+ typeLabel: type ? titleCaseType(type) : '',
723
+ scope: typeof action.scope === 'string' ? action.scope : 'individual',
724
+ priority: typeof action.priority === 'string' ? action.priority : 'secondary',
725
+ icon: typeof action.icon === 'string' ? action.icon : undefined,
726
+ color: typeof action.color === 'string' ? action.color : undefined,
727
+ shortcuts,
728
+ permissions,
729
+ hidden,
730
+ disabled,
731
+ isDefault: action.default === true,
732
+ separated: action.separated === true,
733
+ order: typeof action.order === 'number' ? action.order : undefined,
734
+ depth,
735
+ optionsPairs: command.optionsPairs,
736
+ metadataPairs: command.metadataPairs,
737
+ submenuExpression: items.expression,
738
+ };
739
+ }
740
+ function flattenActions(actions, prefix, depth = 0) {
741
+ const rows = [];
742
+ actions.forEach((action, index) => {
743
+ const id = `${prefix}.${index}`;
744
+ rows.push(toActionView(action, id, depth));
745
+ const items = resolveItems(action.items);
746
+ if (items.children.length) {
747
+ rows.push(...flattenActions(items.children, id, depth + 1));
748
+ }
749
+ });
750
+ return rows;
751
+ }
752
+ function groupByScope(actions) {
753
+ const map = new Map();
754
+ for (const action of actions) {
755
+ const scope = action.scope || 'individual';
756
+ const list = map.get(scope) ?? [];
757
+ list.push(action);
758
+ map.set(scope, list);
759
+ }
760
+ const extra = [...map.keys()].filter((scope) => !SCOPE_ORDER.includes(scope));
761
+ return [...SCOPE_ORDER, ...extra]
762
+ .filter((scope) => (map.get(scope)?.length ?? 0) > 0)
763
+ .map((scope) => ({ scope, actions: map.get(scope) ?? [] }));
764
+ }
765
+ function toSection(id, kind, actions, extra, allowEmpty = false) {
766
+ const flat = flattenActions(actions, id);
767
+ if (!flat.length && !allowEmpty) {
768
+ return null;
769
+ }
770
+ return {
771
+ id,
772
+ kind,
773
+ views: extra?.views ?? [],
774
+ relatedTitle: extra?.relatedTitle,
775
+ relatedEntity: extra?.relatedEntity,
776
+ relatedLayoutType: extra?.relatedLayoutType,
777
+ layoutTitle: extra?.layoutTitle,
778
+ layoutDescription: extra?.layoutDescription,
779
+ groups: groupByScope(flat),
780
+ actionCount: flat.length,
781
+ };
782
+ }
783
+ function queryViews(views) {
784
+ return (views ?? []).map((view) => ({
785
+ name: view.name,
786
+ title: humanText(view.title),
787
+ }));
788
+ }
789
+ function relatedLayoutType(related) {
790
+ const type = related.layout?.type;
791
+ return typeof type === 'string' ? type : undefined;
792
+ }
793
+ function buildLayoutSections(entity) {
794
+ const master = entity.interfaces?.master;
795
+ const sections = [];
796
+ const list = toSection('list', 'list', layoutActions(master?.list), {
797
+ layoutTitle: humanText(master?.list?.title),
798
+ layoutDescription: humanText(master?.list?.description),
799
+ views: queryViews(master?.list?.views),
800
+ }, true);
801
+ if (list) {
802
+ sections.push(list);
803
+ }
804
+ const record = toSection('record', 'record', layoutActions(master?.single), {
805
+ layoutTitle: humanText(master?.single?.title),
806
+ layoutDescription: humanText(master?.single?.description),
807
+ }, true);
808
+ if (record) {
809
+ sections.push(record);
810
+ }
811
+ const create = toSection('create', 'create', layoutActions(master?.create), {
812
+ layoutTitle: humanText(master?.create?.title),
813
+ }, true);
814
+ if (create) {
815
+ sections.push(create);
816
+ }
817
+ const update = toSection('update', 'update', layoutActions(master?.update), {
818
+ layoutTitle: humanText(master?.update?.title),
819
+ }, true);
820
+ if (update) {
821
+ sections.push(update);
822
+ }
823
+ const detailList = toSection('detail-list', 'detail-list', layoutActions(entity.interfaces?.detail?.list), {
824
+ layoutTitle: humanText(entity.interfaces?.detail?.list?.title),
825
+ layoutDescription: humanText(entity.interfaces?.detail?.list?.description),
826
+ });
827
+ if (detailList) {
828
+ sections.push(detailList);
829
+ }
830
+ (entity.relatedEntities ?? []).forEach((related, index) => {
831
+ const relatedActions = asActions(related.actions);
832
+ const section = toSection(`related:${index}`, 'related', relatedActions, {
833
+ relatedTitle: humanText(related.title),
834
+ relatedEntity: related.entity,
835
+ relatedLayoutType: relatedLayoutType(related),
836
+ });
837
+ if (section) {
838
+ sections.push(section);
839
+ }
840
+ });
841
+ return sections;
842
+ }
843
+ //#endregion
352
844
 
845
+ const PROPERTY_SCHEMA_FLAG_KEYS = [
846
+ 'nullable',
847
+ 'unique',
848
+ 'readonly',
849
+ 'visible',
850
+ 'searchable',
851
+ 'full-text',
852
+ 'filterable',
853
+ 'auditable',
854
+ ];
855
+ const PROPERTY_SCHEMA_PAIR_KEYS = ['data-type', 'group', 'default-value'];
856
+ const PROPERTY_PRESENTATION_FLAG_KEYS = [
857
+ 'inline-filter',
858
+ 'sortable',
859
+ 'advanced-filter',
860
+ 'preload-filter',
861
+ ];
862
+ const PROPERTY_PRESENTATION_PAIR_KEYS = [
863
+ 'editor',
864
+ 'widget-path',
865
+ 'widget-mode',
866
+ 'widget-id',
867
+ 'filter-widget',
868
+ ];
869
+ const PROPERTY_SCHEMA_ORDER = [
870
+ 'data-type',
871
+ 'nullable',
872
+ 'group',
873
+ 'unique',
874
+ 'readonly',
875
+ 'visible',
876
+ 'searchable',
877
+ 'full-text',
878
+ 'filterable',
879
+ 'auditable',
880
+ 'default-value',
881
+ ];
882
+ const PROPERTY_PRESENTATION_ORDER = [
883
+ 'editor',
884
+ 'inline-filter',
885
+ 'advanced-filter',
886
+ 'sortable',
887
+ 'widget-path',
888
+ 'widget-mode',
889
+ 'widget-id',
890
+ 'filter-widget',
891
+ 'preload-filter',
892
+ ];
353
893
  //#endregion
354
- function isListEntry(p) {
355
- return p != null && 'fullName' in p && !('properties' in p);
894
+ //#region ----- JSON / scalar -----
895
+ function formatScalar(value) {
896
+ if (value === undefined || typeof value === 'function') {
897
+ return undefined;
898
+ }
899
+ if (value === null) {
900
+ return 'null';
901
+ }
902
+ if (typeof value === 'string') {
903
+ const trimmed = value.trim();
904
+ return trimmed || undefined;
905
+ }
906
+ if (typeof value === 'number' || typeof value === 'boolean') {
907
+ return String(value);
908
+ }
909
+ return undefined;
910
+ }
911
+ function resolvePropertyDataType(schemaDataType, normalizedDataType, editor, widgetDataTypes) {
912
+ const widgetName = typeof editor === 'string' ? editor.trim() : '';
913
+ const fromWidget = widgetName ? widgetDataTypes[widgetName] : undefined;
914
+ if (fromWidget) {
915
+ return fromWidget;
916
+ }
917
+ return formatScalar(schemaDataType) ?? formatScalar(normalizedDataType);
918
+ }
919
+ function pushFlag(flags, labelKey, value, always = false) {
920
+ if (typeof value !== 'boolean') {
921
+ return;
922
+ }
923
+ if (!always && !value) {
924
+ return;
925
+ }
926
+ if (flags.some((flag) => flag.labelKey === labelKey)) {
927
+ return;
928
+ }
929
+ flags.push({ labelKey, on: value });
930
+ }
931
+ function pushPair(pairs, labelKey, value) {
932
+ const text = formatScalar(value);
933
+ if (!text || pairs.some((pair) => pair.labelKey === labelKey)) {
934
+ return;
935
+ }
936
+ pairs.push({ labelKey, value: text });
937
+ }
938
+ function pushJson(blocks, labelKey, value) {
939
+ const pairs = flattenToPairs(value);
940
+ if (!pairs.length || blocks.some((block) => block.labelKey === labelKey)) {
941
+ return;
942
+ }
943
+ blocks.push({ labelKey, pairs });
944
+ }
945
+ function formatAction(action) {
946
+ if (Array.isArray(action)) {
947
+ return action.filter((item) => typeof item === 'string' && item.trim()).join(', ');
948
+ }
949
+ return typeof action === 'string' ? action.trim() : '';
950
+ }
951
+ //#endregion
952
+ //#region ----- Build details -----
953
+ function hasPropertyDetails(details) {
954
+ return (details.flags.length > 0 ||
955
+ details.pairs.length > 0 ||
956
+ details.triggers.length > 0 ||
957
+ details.validations.length > 0 ||
958
+ details.jsonBlocks.length > 0);
959
+ }
960
+ function propertyFlag(details, key) {
961
+ return details.flags.find((flag) => flag.labelKey === key)?.on;
962
+ }
963
+ function propertyFlagsNamed(details, keys) {
964
+ return keys
965
+ .map((key) => details.flags.find((flag) => flag.labelKey === key))
966
+ .filter((flag) => !!flag);
967
+ }
968
+ function propertyPairsNamed(details, keys) {
969
+ return keys
970
+ .map((key) => details.pairs.find((pair) => pair.labelKey === key))
971
+ .filter((pair) => !!pair);
972
+ }
973
+ function propertyEntries(details, pairKeys, flagKeys, order) {
974
+ const byKey = new Map();
975
+ for (const pair of propertyPairsNamed(details, pairKeys)) {
976
+ byKey.set(pair.labelKey, { kind: 'pair', labelKey: pair.labelKey, value: pair.value });
977
+ }
978
+ for (const flag of propertyFlagsNamed(details, flagKeys)) {
979
+ byKey.set(flag.labelKey, { kind: 'flag', labelKey: flag.labelKey, on: flag.on });
980
+ }
981
+ return order
982
+ .map((key) => byKey.get(key))
983
+ .filter((entry) => !!entry);
984
+ }
985
+ function buildPropertyDetails(layout, normalized, widgetDataTypes = {}) {
986
+ const flags = [];
987
+ const pairs = [];
988
+ const triggers = [];
989
+ const validations = [];
990
+ const jsonBlocks = [];
991
+ const schema = layout?.schema;
992
+ const widget = schema?.interface;
993
+ const features = normalized?.features;
994
+ const list = layout?.options;
995
+ pushFlag(flags, 'nullable', schema?.nullable ?? normalized?.fieldFeatures?.nullable, true);
996
+ pushFlag(flags, 'unique', schema?.unique?.enabled);
997
+ const readonly = schema?.readonly ?? normalized?.fieldFeatures?.readOnly;
998
+ if (typeof readonly === 'boolean') {
999
+ pushFlag(flags, 'readonly', readonly);
1000
+ }
1001
+ else {
1002
+ pushPair(pairs, 'readonly', readonly);
1003
+ }
1004
+ const visible = schema?.visible;
1005
+ if (typeof visible === 'boolean') {
1006
+ if (!visible) {
1007
+ pushFlag(flags, 'visible', false, true);
1008
+ }
1009
+ }
1010
+ else {
1011
+ pushPair(pairs, 'visible', visible);
1012
+ }
1013
+ pushFlag(flags, 'searchable', features?.searchable?.enabled);
1014
+ pushFlag(flags, 'full-text', features?.searchable?.fullText);
1015
+ pushFlag(flags, 'filterable', features?.filterable?.enabled);
1016
+ pushFlag(flags, 'inline-filter', features?.filterable?.inline ?? list?.filter?.inline?.enabled, true);
1017
+ pushFlag(flags, 'sortable', features?.sortable?.enabled ?? list?.sort?.enabled, true);
1018
+ pushFlag(flags, 'auditable', features?.auditable?.enabled);
1019
+ pushFlag(flags, 'advanced-filter', list?.filter?.advance?.enabled, true);
1020
+ pushFlag(flags, 'preload-filter', list?.filter?.preload?.enabled);
1021
+ pushPair(pairs, 'group', layout?.groupId);
1022
+ const editor = widget?.type ?? normalized?.interface?.name;
1023
+ pushPair(pairs, 'data-type', resolvePropertyDataType(schema?.dataType, normalized?.dataType, editor, widgetDataTypes));
1024
+ pushPair(pairs, 'editor', editor);
1025
+ pushPair(pairs, 'widget-path', widget?.path);
1026
+ pushPair(pairs, 'widget-mode', widget?.mode);
1027
+ pushPair(pairs, 'widget-id', widget?.id);
1028
+ pushPair(pairs, 'filter-widget', list?.filter?.advance?.widgetType);
1029
+ const defaultValue = schema?.defaultValue ?? normalized?.defaultValue;
1030
+ const defaultText = formatScalar(defaultValue);
1031
+ if (defaultText) {
1032
+ pushPair(pairs, 'default-value', defaultText);
1033
+ }
1034
+ else {
1035
+ pushJson(jsonBlocks, 'default-value', defaultValue);
1036
+ }
1037
+ for (const trigger of widget?.triggers ?? []) {
1038
+ const event = typeof trigger.event === 'string' ? trigger.event.trim() : '';
1039
+ const action = formatAction(trigger.action);
1040
+ if (!event && !action) {
1041
+ continue;
1042
+ }
1043
+ triggers.push({
1044
+ event,
1045
+ action,
1046
+ disabled: !!trigger.disabled,
1047
+ });
1048
+ }
1049
+ const rules = layout?.validations ?? normalized?.validations ?? [];
1050
+ for (const rule of rules) {
1051
+ const name = typeof rule.rule === 'string' ? rule.rule.trim() : '';
1052
+ if (!name) {
1053
+ continue;
1054
+ }
1055
+ validations.push({
1056
+ rule: name,
1057
+ message: typeof rule.message === 'string' ? rule.message.trim() || undefined : undefined,
1058
+ optionsPairs: flattenToPairs(rule.options),
1059
+ disabled: 'disabled' in rule && rule.disabled === true,
1060
+ });
1061
+ }
1062
+ pushJson(jsonBlocks, 'widget-options', widget?.options ?? normalized?.interface?.options);
1063
+ pushJson(jsonBlocks, 'value-transforms', widget?.valueTransforms);
1064
+ pushJson(jsonBlocks, 'widget-children', widget?.children);
1065
+ return { flags, pairs, triggers, validations, jsonBlocks };
356
1066
  }
1067
+ //#endregion
1068
+
1069
+ //#region ----- Imports -----
1070
+ //#endregion
1071
+ //#region ----- Constants -----
1072
+ const PRIMARY_ACTION_TABS = ['list', 'record', 'create', 'update'];
1073
+ const CODE_PAIR_KEYS = new Set([
1074
+ 'editor',
1075
+ 'widget-path',
1076
+ 'widget-mode',
1077
+ 'widget-id',
1078
+ 'filter-widget',
1079
+ ]);
1080
+ //#endregion
1081
+ //#region ----- Component -----
357
1082
  class AXPlatformExplorerEntityDetailComponent {
1083
+ //#endregion
358
1084
  constructor() {
359
1085
  this.payload = input(/* @ts-ignore */
360
1086
  ...(ngDevMode ? [undefined, { debugName: "payload" }] : /* istanbul ignore next */ []));
361
- this.entity = signal(null, /* @ts-ignore */
362
- ...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
1087
+ //#region ----- Services & Dependencies -----
1088
+ this.dataService = inject(PlatformExplorerDataService);
1089
+ this.destroyRef = inject(DestroyRef);
1090
+ //#endregion
1091
+ //#region ----- State Signals -----
1092
+ this.detail = signal(null, /* @ts-ignore */
1093
+ ...(ngDevMode ? [{ debugName: "detail" }] : /* istanbul ignore next */ []));
363
1094
  this.loading = signal(false, /* @ts-ignore */
364
1095
  ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
365
- this.entityResolver = inject(AXPEntityResolver);
1096
+ this.loadError = signal(false, /* @ts-ignore */
1097
+ ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
1098
+ this.activeSection = this.dataService.activeEntitySection;
1099
+ this.selectedName = signal(null, /* @ts-ignore */
1100
+ ...(ngDevMode ? [{ debugName: "selectedName" }] : /* istanbul ignore next */ []));
1101
+ this.selectedActionId = linkedSignal({ ...(ngDevMode ? { debugName: "selectedActionId" } : /* istanbul ignore next */ {}), source: () => this.activeSection(),
1102
+ computation: () => null });
1103
+ this.actionQuery = linkedSignal({ ...(ngDevMode ? { debugName: "actionQuery" } : /* istanbul ignore next */ {}), source: () => this.activeSection(),
1104
+ computation: () => '' });
1105
+ //#endregion
1106
+ //#region ----- Computed Properties -----
1107
+ this.properties = computed(() => this.mergeProperties(this.detail()), /* @ts-ignore */
1108
+ ...(ngDevMode ? [{ debugName: "properties" }] : /* istanbul ignore next */ []));
1109
+ this.layoutSections = computed(() => {
1110
+ const detail = this.detail();
1111
+ return detail ? buildLayoutSections(detail.layout) : [];
1112
+ }, /* @ts-ignore */
1113
+ ...(ngDevMode ? [{ debugName: "layoutSections" }] : /* istanbul ignore next */ []));
1114
+ this.selectedProperty = computed(() => {
1115
+ const name = this.selectedName();
1116
+ if (!name) {
1117
+ return null;
1118
+ }
1119
+ return this.properties().find((row) => row.name === name) ?? null;
1120
+ }, /* @ts-ignore */
1121
+ ...(ngDevMode ? [{ debugName: "selectedProperty" }] : /* istanbul ignore next */ []));
1122
+ this.isFieldSection = computed(() => this.activeSection() === 'properties', /* @ts-ignore */
1123
+ ...(ngDevMode ? [{ debugName: "isFieldSection" }] : /* istanbul ignore next */ []));
1124
+ this.inspectorOpen = computed(() => this.isFieldSection() ? this.selectedProperty() != null : this.selectedAction() != null, /* @ts-ignore */
1125
+ ...(ngDevMode ? [{ debugName: "inspectorOpen" }] : /* istanbul ignore next */ []));
1126
+ this.inspectorAriaKey = computed(() => this.isFieldSection()
1127
+ ? '@platform-dev-tools:module.property-details.field-definition'
1128
+ : '@platform-dev-tools:module.action-details.action-definition', /* @ts-ignore */
1129
+ ...(ngDevMode ? [{ debugName: "inspectorAriaKey" }] : /* istanbul ignore next */ []));
1130
+ this.activeLayout = computed(() => {
1131
+ const section = this.activeSection();
1132
+ if (section === 'properties') {
1133
+ return null;
1134
+ }
1135
+ return this.layoutSections().find((item) => item.id === section || item.kind === section) ?? null;
1136
+ }, /* @ts-ignore */
1137
+ ...(ngDevMode ? [{ debugName: "activeLayout" }] : /* istanbul ignore next */ []));
1138
+ this.activeActions = computed(() => {
1139
+ const layout = this.activeLayout();
1140
+ if (!layout) {
1141
+ return [];
1142
+ }
1143
+ return layout.groups.flatMap((group) => group.actions);
1144
+ }, /* @ts-ignore */
1145
+ ...(ngDevMode ? [{ debugName: "activeActions" }] : /* istanbul ignore next */ []));
1146
+ this.filteredActions = computed(() => {
1147
+ const query = this.actionQuery().trim().toLowerCase();
1148
+ const rows = this.activeActions();
1149
+ if (!query) {
1150
+ return rows;
1151
+ }
1152
+ return rows.filter((row) => this.matchesActionQuery(row, query));
1153
+ }, /* @ts-ignore */
1154
+ ...(ngDevMode ? [{ debugName: "filteredActions" }] : /* istanbul ignore next */ []));
1155
+ this.selectedAction = computed(() => {
1156
+ const id = this.selectedActionId();
1157
+ const rows = this.activeActions();
1158
+ if (id) {
1159
+ const match = rows.find((row) => row.id === id);
1160
+ if (match) {
1161
+ return match;
1162
+ }
1163
+ }
1164
+ return rows[0] ?? null;
1165
+ }, /* @ts-ignore */
1166
+ ...(ngDevMode ? [{ debugName: "selectedAction" }] : /* istanbul ignore next */ []));
1167
+ this.selectedSchemaEntries = computed(() => {
1168
+ const field = this.selectedProperty();
1169
+ return field ? this.schemaEntries(field.details) : [];
1170
+ }, /* @ts-ignore */
1171
+ ...(ngDevMode ? [{ debugName: "selectedSchemaEntries" }] : /* istanbul ignore next */ []));
1172
+ this.selectedPresentationEntries = computed(() => {
1173
+ const field = this.selectedProperty();
1174
+ return field ? this.presentationEntries(field.details) : [];
1175
+ }, /* @ts-ignore */
1176
+ ...(ngDevMode ? [{ debugName: "selectedPresentationEntries" }] : /* istanbul ignore next */ []));
1177
+ this.destroyRef.onDestroy(() => this.dataService.clearEntityTabs());
366
1178
  effect(() => {
367
- const p = this.payload();
368
- if (!p) {
369
- this.entity.set(null);
370
- this.loading.set(false);
371
- return;
1179
+ const summary = this.payload();
1180
+ void this.loadDetail(summary);
1181
+ });
1182
+ }
1183
+ //#region ----- Event Handlers -----
1184
+ selectProperty(name) {
1185
+ this.selectedName.set(name);
1186
+ }
1187
+ selectAction(id) {
1188
+ this.selectedActionId.set(id);
1189
+ }
1190
+ isSelected(name) {
1191
+ return this.selectedName() === name;
1192
+ }
1193
+ isActionSelected(id) {
1194
+ return this.selectedAction()?.id === id;
1195
+ }
1196
+ onActionSearchChanged(event) {
1197
+ this.actionQuery.set(event.value ?? '');
1198
+ }
1199
+ flagValue(details, key) {
1200
+ return propertyFlag(details, key);
1201
+ }
1202
+ isCodePair(key) {
1203
+ return CODE_PAIR_KEYS.has(key);
1204
+ }
1205
+ isTypePair(key) {
1206
+ return key === 'data-type';
1207
+ }
1208
+ hiddenFlag(action) {
1209
+ return this.yesNoFlag(action.hidden);
1210
+ }
1211
+ disabledFlag(action) {
1212
+ return this.yesNoFlag(action.disabled);
1213
+ }
1214
+ hiddenExpression(action) {
1215
+ return this.expressionValue(action.hidden);
1216
+ }
1217
+ disabledExpression(action) {
1218
+ return this.expressionValue(action.disabled);
1219
+ }
1220
+ //#endregion
1221
+ //#region ----- Property Details -----
1222
+ schemaEntries(details) {
1223
+ return propertyEntries(details, PROPERTY_SCHEMA_PAIR_KEYS, PROPERTY_SCHEMA_FLAG_KEYS, PROPERTY_SCHEMA_ORDER);
1224
+ }
1225
+ presentationEntries(details) {
1226
+ return propertyEntries(details, PROPERTY_PRESENTATION_PAIR_KEYS, PROPERTY_PRESENTATION_FLAG_KEYS, PROPERTY_PRESENTATION_ORDER);
1227
+ }
1228
+ //#endregion
1229
+ //#region ----- Data Loading -----
1230
+ async loadDetail(summary) {
1231
+ this.actionQuery.set('');
1232
+ this.selectedName.set(null);
1233
+ this.selectedActionId.set(null);
1234
+ if (!summary) {
1235
+ this.detail.set(null);
1236
+ this.loading.set(false);
1237
+ this.loadError.set(false);
1238
+ this.dataService.clearEntityTabs();
1239
+ return;
1240
+ }
1241
+ this.loading.set(true);
1242
+ this.loadError.set(false);
1243
+ this.detail.set(null);
1244
+ try {
1245
+ const detail = await this.dataService.getEntity(summary.module, summary.name);
1246
+ if (this.payload() === summary) {
1247
+ this.detail.set(detail);
1248
+ this.selectedName.set(this.mergeProperties(detail)[0]?.name ?? null);
1249
+ this.dataService.setEntityTabs(this.buildTabs(detail));
372
1250
  }
373
- if (isListEntry(p)) {
374
- this.loading.set(true);
375
- this.entity.set(null);
376
- this.entityResolver.get(p.module, p.name).then((e) => {
377
- if (this.payload() === p) {
378
- this.entity.set(e ?? null);
379
- }
380
- this.loading.set(false);
381
- });
1251
+ }
1252
+ catch {
1253
+ if (this.payload() === summary) {
1254
+ this.loadError.set(true);
382
1255
  }
383
- else {
384
- this.entity.set(p);
1256
+ }
1257
+ finally {
1258
+ if (this.payload() === summary) {
385
1259
  this.loading.set(false);
386
1260
  }
387
- });
1261
+ }
388
1262
  }
389
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerEntityDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
390
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerEntityDetailComponent, isStandalone: true, selector: "axp-platform-explorer-entity-detail", inputs: { payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: `
391
- <div class="flex flex-col flex-1 min-h-0 pt-0 overflow-auto">
392
- @if (entity(); as e) {
393
- <dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 mb-3 m-0">
394
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.module' | translate | async }}</dt><dd class="m-0">{{ e.module }}</dd>
395
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt><dd class="m-0">{{ e.name }}</dd>
396
- @if (e.title) {
397
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.title' | translate | async }}</dt><dd class="m-0">{{ e.title | translate | async}}</dd>
398
- }
399
- @if (e.formats.plural) {
400
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.plural' | translate | async }}</dt><dd class="m-0">{{ e.formats.plural | translate | async}}</dd>
401
- }
402
- </dl>
403
- @if (e.properties && e.properties.length > 0) {
404
- <p class="mb-2 mt-3 font-bold">{{ '@platform-dev-tools:module.terms.properties' | translate | async }} ({{ e.properties.length }})</p>
405
- <div class="flex-1 min-h-0 overflow-auto border border-lightest rounded-md">
406
- <table class="w-full border-collapse text-[13px]">
407
- <thead>
408
- <tr>
409
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</th>
410
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.title' | translate | async }}</th>
411
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.type' | translate | async }}</th>
412
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.description' | translate | async }}</th>
413
- </tr>
414
- </thead>
415
- <tbody class="axp-entity-detail-tbody">
416
- @for (p of e.properties; track p.name) {
417
- <tr>
418
- <td class="p-2 px-3 border-b border-lightest"><code class="text-xs bg-lighter px-1.5 py-0.5 rounded">{{ p.name }}</code></td>
419
- <td class="p-2 px-3 border-b border-lightest">{{ p.title | translate | async }}</td>
420
- <td class="p-2 px-3 border-b border-lightest">{{ p.schema.dataType }}</td>
421
- <td class="p-2 px-3 border-b border-lightest">@if (p.description) { {{ p.description | translate | async }} } @else { – }</td>
422
- </tr>
423
- }
424
- </tbody>
425
- </table>
426
- </div>
1263
+ buildTabs(detail) {
1264
+ if (!detail) {
1265
+ return [];
427
1266
  }
428
- } @else if (loading()) {
429
- <p class="m-0 text-muted">{{ '@platform-dev-tools:module.terms.loading-entity' | translate | async }}</p>
430
- }
431
- </div>
432
- `, isInline: true, styles: [".axp-entity-detail-tbody tr:last-child td{border-bottom:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1267
+ const extra = buildLayoutSections(detail.layout).filter((section) => !PRIMARY_ACTION_TABS.includes(section.kind));
1268
+ const tabs = [
1269
+ { id: 'properties', textKey: '@platform-dev-tools:module.terms.properties' },
1270
+ ];
1271
+ for (const kind of PRIMARY_ACTION_TABS) {
1272
+ tabs.push({ id: kind, textKey: `@platform-dev-tools:module.layouts.${kind}` });
1273
+ }
1274
+ for (const layout of extra) {
1275
+ tabs.push({
1276
+ id: layout.id,
1277
+ textKey: layout.kind === 'related'
1278
+ ? '@platform-dev-tools:module.layouts.related'
1279
+ : `@platform-dev-tools:module.layouts.${layout.kind}`,
1280
+ extraText: layout.kind === 'related' ? layout.relatedTitle || layout.relatedEntity : undefined,
1281
+ });
1282
+ }
1283
+ return tabs;
1284
+ }
1285
+ mergeProperties(detail) {
1286
+ if (!detail) {
1287
+ return [];
1288
+ }
1289
+ const pluginByField = pluginSourceByFieldName(detail.plugins);
1290
+ const widgetDataTypes = detail.widgetDataTypes ?? {};
1291
+ const normalizedByName = new Map((detail.normalized?.fields ?? []).map((field) => [field.name, field]));
1292
+ const rows = [];
1293
+ const seen = new Set();
1294
+ for (const property of detail.layout.properties ?? []) {
1295
+ seen.add(property.name);
1296
+ rows.push(this.toRow(property.name, pluginByField, widgetDataTypes, property, normalizedByName.get(property.name)));
1297
+ }
1298
+ for (const field of detail.normalized?.fields ?? []) {
1299
+ if (seen.has(field.name)) {
1300
+ continue;
1301
+ }
1302
+ seen.add(field.name);
1303
+ rows.push(this.toRow(field.name, pluginByField, widgetDataTypes, undefined, field));
1304
+ }
1305
+ return rows;
1306
+ }
1307
+ toRow(name, pluginByField, widgetDataTypes, layout, normalized) {
1308
+ const plugin = pluginByField.get(name);
1309
+ const details = buildPropertyDetails(layout, normalized, widgetDataTypes);
1310
+ const title = this.asText(layout?.title) ?? this.asText(normalized?.title);
1311
+ const description = this.asText(layout?.description) ?? this.asText(normalized?.description);
1312
+ return {
1313
+ name,
1314
+ title,
1315
+ dataType: propertyPairsNamed(details, ['data-type'])[0]?.value ?? '',
1316
+ editor: propertyPairsNamed(details, ['editor'])[0]?.value ?? '',
1317
+ description,
1318
+ pluginName: plugin?.name,
1319
+ pluginTitle: plugin?.title,
1320
+ details,
1321
+ };
1322
+ }
1323
+ asText(value) {
1324
+ return typeof value === 'string' && value.trim() ? value : undefined;
1325
+ }
1326
+ matchesActionQuery(row, query) {
1327
+ return (row.commandName.toLowerCase().includes(query) ||
1328
+ (row.name?.toLowerCase().includes(query) ?? false) ||
1329
+ (row.title?.toLowerCase().includes(query) ?? false) ||
1330
+ (row.typeLabel?.toLowerCase().includes(query) ?? false) ||
1331
+ row.scope.toLowerCase().includes(query));
1332
+ }
1333
+ yesNoFlag(value) {
1334
+ if (value === 'yes') {
1335
+ return true;
1336
+ }
1337
+ if (value === 'no') {
1338
+ return false;
1339
+ }
1340
+ return undefined;
1341
+ }
1342
+ expressionValue(value) {
1343
+ if (!value || value === 'yes' || value === 'no') {
1344
+ return undefined;
1345
+ }
1346
+ return value;
1347
+ }
1348
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerEntityDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1349
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerEntityDetailComponent, isStandalone: true, selector: "axp-platform-explorer-entity-detail", inputs: { payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: "<div class=\"flex flex-col flex-1 min-h-0\">\n <!-- ----- Loading entity layout ----- -->\n @if (loading()) {\n <div class=\"flex flex-1 items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Entity catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- Entity documentation with field or action inspector ----- -->\n @else if (detail(); as d) {\n <ax-drawer-container class=\"axp-platform-explorer__docs\">\n <ax-content class=\"axp-platform-explorer__docs-main\">\n <div class=\"axp-platform-explorer__docs-shell\">\n <div class=\"axp-platform-explorer__docs-chrome\">\n <!--->\n <!-- ----- Attached plugins as chips ----- -->\n @if (d.plugins.length) {\n <div class=\"axp-platform-explorer__docs-plugins\">\n @for (plugin of d.plugins; track plugin.name) {\n <span class=\"axp-platform-explorer__docs-chip\">\n <i class=\"fa-light fa-plug\" aria-hidden=\"true\"></i>\n {{ (plugin.title || plugin.name) | translate | async }}\n </span>\n }\n </div>\n }\n <!--->\n <!-- ----- Field column headers stay outside the scrolling rows ----- -->\n @if (isFieldSection() && properties().length) {\n <div class=\"axp-platform-explorer__docs-table-head\" role=\"row\">\n <span role=\"columnheader\">{{ '@platform-dev-tools:module.terms.field' | translate | async }}</span>\n <span role=\"columnheader\">\n {{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}\n </span>\n <span role=\"columnheader\">\n {{ '@platform-dev-tools:module.property-details.nullable' | translate | async }}\n </span>\n </div>\n }\n </div>\n\n <!--->\n <!-- ----- Field table ----- -->\n @if (isFieldSection()) {\n <div class=\"axp-platform-explorer__docs-list\">\n <!--->\n <!-- ----- Empty property list ----- -->\n @if (properties().length === 0) {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.messages.no-properties' | translate | async }}\n </p>\n }\n <!-- ----- Field rows ----- -->\n @else {\n <div class=\"axp-platform-explorer__table-wrap axp-platform-explorer__table-wrap--fields\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead class=\"sr-only\">\n <tr>\n <th>{{ '@platform-dev-tools:module.terms.field' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.property-details.nullable' | translate | async }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of properties(); track row.name) {\n <tr\n class=\"axp-platform-explorer__table-row\"\n [class.axp-platform-explorer__table-row--selected]=\"isSelected(row.name)\"\n (click)=\"selectProperty(row.name)\"\n >\n <td>\n <button\n type=\"button\"\n class=\"axp-platform-explorer__field-button\"\n [attr.aria-pressed]=\"isSelected(row.name)\"\n (click)=\"selectProperty(row.name); $event.stopPropagation()\"\n >\n <code dir=\"ltr\">{{ row.name }}</code>\n </button>\n <!--->\n <!-- ----- Translated property title under the field name ----- -->\n @if (row.title) {\n <div class=\"axp-platform-explorer__field-title\">\n {{ row.title | translate | async }}\n </div>\n }\n </td>\n <td>\n <!--->\n <!-- ----- Stored data type chip ----- -->\n @if (row.dataType) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ row.dataType }}</span>\n }\n <!-- ----- Missing data type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n <td>\n <ng-container\n *ngTemplateOutlet=\"boolCell; context: { value: flagValue(row.details, 'nullable') }\"\n />\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <p class=\"axp-platform-explorer__docs-footnote\">\n {{ '@platform-dev-tools:module.property-details.select-field' | translate | async }}\n </p>\n </div>\n }\n <!-- ----- Action table ----- -->\n @else if (activeLayout(); as layout) {\n <div class=\"axp-platform-explorer__docs-list axp-platform-explorer__docs-list--padded\">\n <div class=\"axp-platform-explorer__docs-toolbar\">\n <div>\n <h2 class=\"axp-platform-explorer__docs-heading\">\n {{ ('@platform-dev-tools:module.layouts.' + layout.kind) | translate | async }}\n </h2>\n <p class=\"axp-platform-explorer__docs-count\">\n {{ ('@platform-dev-tools:module.layouts.' + layout.kind + '-hint') | translate | async }}\n </p>\n <p class=\"axp-platform-explorer__docs-count\">\n <!--->\n <!-- ----- Filtered action count ----- -->\n @if (actionQuery().trim()) {\n {{\n '@platform-dev-tools:module.action-details.action-count-filtered'\n | translate: { params: { shown: filteredActions().length, count: layout.actionCount } }\n | async\n }}\n }\n <!-- ----- Full action count ----- -->\n @else {\n {{\n '@platform-dev-tools:module.layouts.action-count'\n | translate: { params: { count: layout.actionCount } }\n | async\n }}\n }\n </p>\n </div>\n <ax-search-box\n class=\"axp-platform-explorer__docs-search\"\n look=\"fill\"\n [delayTime]=\"200\"\n [value]=\"actionQuery()\"\n [placeholder]=\"\n ('@platform-dev-tools:module.action-details.search-actions' | translate | async) ?? ''\n \"\n (onValueChanged)=\"onActionSearchChanged($event)\"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n </div>\n <div class=\"flex flex-wrap items-center gap-1.5 mb-4\">\n <!--->\n <!-- ----- Related entity identity ----- -->\n @if (layout.relatedEntity) {\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ layout.relatedEntity }}</code>\n }\n <!--->\n <!-- ----- Related layout type ----- -->\n @if (layout.relatedLayoutType) {\n <span class=\"inline-flex items-center rounded-full bg-lighter px-2 py-0.5 text-xs text-muted\">\n {{\n ('@platform-dev-tools:module.layouts.related-types.' + layout.relatedLayoutType)\n | translate | async\n }}\n </span>\n }\n <!--->\n <!-- ----- List query views ----- -->\n @for (view of layout.views; track view.name) {\n <span class=\"inline-flex items-center rounded-full bg-lighter px-2 py-0.5 text-xs text-muted\">\n {{ (view.title || view.name) | translate | async }}\n </span>\n }\n </div>\n <!--->\n <!-- ----- Optional stored layout title ----- -->\n @if (layout.layoutTitle) {\n <p class=\"m-0 mb-2 text-sm\">{{ layout.layoutTitle | translate | async }}</p>\n }\n <!--->\n <!-- ----- Optional stored layout description ----- -->\n @if (layout.layoutDescription) {\n <p class=\"m-0 mb-3 text-sm text-muted\">{{ layout.layoutDescription | translate | async }}</p>\n }\n\n <!--->\n <!-- ----- Empty action list ----- -->\n @if (layout.actionCount === 0) {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.messages.no-actions' | translate | async }}\n </p>\n }\n <!-- ----- Search matched no actions ----- -->\n @else if (filteredActions().length === 0) {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.action-details.no-matching-actions' | translate | async }}\n </p>\n }\n <!-- ----- Action rows ----- -->\n @else {\n <div class=\"axp-platform-explorer__table-wrap axp-platform-explorer__table-wrap--fields\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead>\n <tr>\n <th>{{ '@platform-dev-tools:module.action-details.item' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.action-details.type' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.action-details.priority' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.action-details.scope' | translate | async }}</th>\n </tr>\n </thead>\n <tbody>\n @for (action of filteredActions(); track action.id) {\n <tr\n class=\"axp-platform-explorer__table-row\"\n [class.axp-platform-explorer__table-row--selected]=\"isActionSelected(action.id)\"\n (click)=\"selectAction(action.id)\"\n >\n <td>\n <div [style.padding-inline-start.rem]=\"action.depth\">\n <button\n type=\"button\"\n class=\"axp-platform-explorer__field-button\"\n [attr.aria-pressed]=\"isActionSelected(action.id)\"\n (click)=\"selectAction(action.id); $event.stopPropagation()\"\n >\n <!--->\n <!-- ----- Action icon from the layout ----- -->\n @if (action.icon) {\n <i class=\"text-muted me-1\" [ngClass]=\"action.icon\" aria-hidden=\"true\"></i>\n }\n <code dir=\"ltr\">{{ action.commandName || action.name || '\u2013' }}</code>\n </button>\n <!--->\n <!-- ----- Translated action title ----- -->\n @if (action.title) {\n <div class=\"axp-platform-explorer__field-title\">\n {{ action.title | translate | async }}\n </div>\n }\n </div>\n </td>\n <td>\n <!--->\n <!-- ----- Action type chip ----- -->\n @if (action.typeLabel) {\n <span class=\"axp-platform-explorer__type-chip\">{{ action.typeLabel }}</span>\n }\n <!-- ----- Missing action type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n <td>\n <span\n class=\"axp-platform-explorer__priority\"\n [class.is-primary]=\"action.priority === 'primary'\"\n >\n {{\n ('@platform-dev-tools:module.action-details.priorities.' + action.priority)\n | translate | async\n }}\n </span>\n </td>\n <td class=\"text-muted\">\n {{\n ('@platform-dev-tools:module.action-details.scopes.' + action.scope) | translate | async\n }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <p class=\"axp-platform-explorer__docs-footnote\">\n {{ '@platform-dev-tools:module.action-details.select-action' | translate | async }}\n </p>\n </div>\n }\n </div>\n </ax-content>\n <ax-drawer\n class=\"axp-platform-explorer__docs-side\"\n [location]=\"'end'\"\n [mode]=\"'push'\"\n [collapsed]=\"!inspectorOpen()\"\n >\n <ax-content\n class=\"axp-platform-explorer__docs-detail\"\n [attr.aria-label]=\"inspectorAriaKey() | translate | async\"\n >\n <!--->\n <!-- ----- Selected field definition ----- -->\n @if (isFieldSection()) {\n <ng-container *ngTemplateOutlet=\"fieldDetail\" />\n }\n <!-- ----- Selected action definition ----- -->\n @else {\n <ng-container *ngTemplateOutlet=\"actionDetail\" />\n }\n </ax-content>\n </ax-drawer>\n </ax-drawer-container>\n }\n</div>\n\n<ng-template #boolCell let-value=\"value\">\n <!-- ----- Affirmative flag ----- -->\n @if (value === true) {\n <span class=\"axp-platform-explorer__yes\">\n <i class=\"fa-light fa-check\" aria-hidden=\"true\"></i>\n {{ '@platform-dev-tools:module.property-details.yes' | translate | async }}\n </span>\n }\n <!-- ----- Negative flag ----- -->\n @else if (value === false) {\n {{ '@platform-dev-tools:module.property-details.no' | translate | async }}\n }\n <!-- ----- Unknown flag ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n</ng-template>\n\n<ng-template #kvList let-pairs=\"pairs\">\n <dl class=\"axp-platform-explorer__docs-dl axp-platform-explorer__docs-dl--json\">\n @for (pair of pairs; track pair.path) {\n <dt>\n <code dir=\"ltr\">{{ pair.path || '\u2013' }}</code>\n </dt>\n <dd>\n <code dir=\"ltr\">{{ pair.value }}</code>\n </dd>\n }\n </dl>\n</ng-template>\n\n<ng-template #fieldDetail>\n <!-- ----- Selected field definition ----- -->\n @if (selectedProperty(); as field) {\n <div class=\"axp-platform-explorer__docs-detail-header\">\n <div class=\"axp-platform-explorer__docs-eyebrow\">\n {{ '@platform-dev-tools:module.property-details.field-definition' | translate | async }}\n </div>\n <h2 class=\"axp-platform-explorer__docs-detail-title\">\n {{ (field.title || field.name) | translate | async }}\n </h2>\n <code class=\"axp-platform-explorer__docs-key\" dir=\"ltr\">{{ field.name }}</code>\n <!--->\n <!-- ----- Plugin source of the selected field ----- -->\n @if (field.pluginTitle) {\n <span class=\"axp-platform-explorer__docs-chip axp-platform-explorer__docs-chip--field\">\n <i class=\"fa-light fa-plug\" aria-hidden=\"true\"></i>\n {{ field.pluginTitle | translate | async }}\n </span>\n }\n </div>\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.terms.description' | translate | async }}</h3>\n <p class=\"axp-platform-explorer__docs-description\">\n <!--->\n <!-- ----- Translated field description ----- -->\n @if (field.description) {\n {{ field.description | translate | async }}\n }\n <!-- ----- Missing field description ----- -->\n @else {\n {{ '@platform-dev-tools:module.property-details.no-description' | translate | async }}\n }\n </p>\n </section>\n <!--->\n <!-- ----- Schema flags and scalar pairs ----- -->\n @if (selectedSchemaEntries(); as schema) {\n <!--->\n <!-- ----- Render schema when at least one entry exists ----- -->\n @if (schema.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.schema' | translate | async }}</h3>\n <dl class=\"axp-platform-explorer__docs-dl\">\n @for (entry of schema; track entry.labelKey) {\n <dt>{{ ('@platform-dev-tools:module.property-details.' + entry.labelKey) | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Boolean schema flags ----- -->\n @if (entry.kind === 'flag') {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: entry.on }\" />\n }\n <!-- ----- Schema scalar values ----- -->\n @else {\n <!--->\n <!-- ----- Data type shown as a chip ----- -->\n @if (isTypePair(entry.labelKey)) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ entry.value }}</span>\n }\n <!-- ----- Code-like schema values ----- -->\n @else if (isCodePair(entry.labelKey)) {\n <code dir=\"ltr\">{{ entry.value }}</code>\n }\n <!-- ----- Plain schema values ----- -->\n @else {\n {{ entry.value }}\n }\n }\n </dd>\n }\n </dl>\n </section>\n }\n }\n <!--->\n <!-- ----- Presentation flags and widget pairs ----- -->\n @if (selectedPresentationEntries(); as presentation) {\n <!--->\n <!-- ----- Render presentation when at least one entry exists ----- -->\n @if (presentation.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.presentation' | translate | async }}</h3>\n <dl class=\"axp-platform-explorer__docs-dl\">\n @for (entry of presentation; track entry.labelKey) {\n <dt>{{ ('@platform-dev-tools:module.property-details.' + entry.labelKey) | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Boolean presentation flags ----- -->\n @if (entry.kind === 'flag') {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: entry.on }\" />\n }\n <!-- ----- Presentation scalar values ----- -->\n @else {\n <!--->\n <!-- ----- Widget identifiers as code ----- -->\n @if (isCodePair(entry.labelKey) || isTypePair(entry.labelKey)) {\n <code dir=\"ltr\">{{ entry.value }}</code>\n }\n <!-- ----- Plain presentation values ----- -->\n @else {\n {{ entry.value }}\n }\n }\n </dd>\n }\n </dl>\n </section>\n }\n }\n <!--->\n <!-- ----- Widget triggers ----- -->\n @if (field.details.triggers.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.triggers' | translate | async }}</h3>\n <div class=\"axp-platform-explorer__prop-stack\">\n @for (trigger of field.details.triggers; track $index) {\n <div class=\"axp-platform-explorer__prop-card\">\n <div class=\"axp-platform-explorer__prop-trigger\">\n <span class=\"text-muted text-xs\">\n {{ '@platform-dev-tools:module.property-details.event' | translate | async }}\n </span>\n <code>{{ trigger.event || '\u2013' }}</code>\n <i class=\"fa-light fa-arrow-right text-muted\" aria-hidden=\"true\"></i>\n <span class=\"text-muted text-xs\">\n {{ '@platform-dev-tools:module.property-details.action' | translate | async }}\n </span>\n <code>{{ trigger.action || '\u2013' }}</code>\n </div>\n <!--->\n <!-- ----- Disabled trigger badge ----- -->\n @if (trigger.disabled) {\n <span class=\"axp-platform-explorer__prop-flag\">\n {{ '@platform-dev-tools:module.property-details.disabled' | translate | async }}\n </span>\n }\n </div>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Validation rules ----- -->\n @if (field.details.validations.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.validations' | translate | async }}</h3>\n <div class=\"axp-platform-explorer__prop-stack\">\n @for (rule of field.details.validations; track $index) {\n <div class=\"axp-platform-explorer__prop-card\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <code>{{ rule.rule }}</code>\n <!--->\n <!-- ----- Disabled validation badge ----- -->\n @if (rule.disabled) {\n <span class=\"axp-platform-explorer__prop-flag\">\n {{ '@platform-dev-tools:module.property-details.disabled' | translate | async }}\n </span>\n }\n </div>\n <!--->\n <!-- ----- Validation message ----- -->\n @if (rule.message) {\n <p class=\"m-0 text-xs text-muted\">{{ rule.message | translate | async }}</p>\n }\n <!--->\n <!-- ----- Flattened validation options ----- -->\n @if (rule.optionsPairs.length) {\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: rule.optionsPairs }\" />\n }\n </div>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Flattened nested JSON (widget options, transforms, children) ----- -->\n @if (field.details.jsonBlocks.length) {\n @for (block of field.details.jsonBlocks; track block.labelKey) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ ('@platform-dev-tools:module.property-details.' + block.labelKey) | translate | async }}</h3>\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: block.pairs }\" />\n </section>\n }\n }\n }\n <!-- ----- No field selected ----- -->\n @else {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.property-details.select-field' | translate | async }}\n </p>\n }\n</ng-template>\n\n<ng-template #actionDetail>\n <!-- ----- Selected action definition ----- -->\n @if (selectedAction(); as action) {\n <div class=\"axp-platform-explorer__docs-detail-header\">\n <div class=\"axp-platform-explorer__docs-eyebrow\">\n {{ '@platform-dev-tools:module.action-details.action-definition' | translate | async }}\n </div>\n <h2 class=\"axp-platform-explorer__docs-detail-title\">\n {{ (action.title || action.commandName || action.name || '\u2013') | translate | async }}\n </h2>\n <code class=\"axp-platform-explorer__docs-key\" dir=\"ltr\">{{ action.commandName || action.name || '\u2013' }}</code>\n </div>\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.behavior' | translate | async }}</h3>\n <dl class=\"axp-platform-explorer__docs-dl\">\n <dt>{{ '@platform-dev-tools:module.action-details.type' | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Action type ----- -->\n @if (action.typeLabel) {\n <span class=\"axp-platform-explorer__type-chip\">{{ action.typeLabel }}</span>\n }\n <!-- ----- Missing action type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.priority' | translate | async }}</dt>\n <dd>\n {{ ('@platform-dev-tools:module.action-details.priorities.' + action.priority) | translate | async }}\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.scope' | translate | async }}</dt>\n <dd>{{ ('@platform-dev-tools:module.action-details.scopes.' + action.scope) | translate | async }}</dd>\n <dt>{{ '@platform-dev-tools:module.action-details.default' | translate | async }}</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: action.isDefault }\" />\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.separated' | translate | async }}</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: action.separated }\" />\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.hidden' | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Hidden yes/no ----- -->\n @if (hiddenFlag(action) !== undefined) {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: hiddenFlag(action) }\" />\n }\n <!-- ----- Hidden expression ----- -->\n @else if (hiddenExpression(action); as expression) {\n <code dir=\"ltr\">{{ expression }}</code>\n }\n <!-- ----- Hidden unknown ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.disabled' | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Disabled yes/no ----- -->\n @if (disabledFlag(action) !== undefined) {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: disabledFlag(action) }\" />\n }\n <!-- ----- Disabled expression ----- -->\n @else if (disabledExpression(action); as expression) {\n <code dir=\"ltr\">{{ expression }}</code>\n }\n <!-- ----- Disabled unknown ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </dd>\n <!--->\n <!-- ----- Named action key ----- -->\n @if (action.name) {\n <dt>{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd>\n <code dir=\"ltr\">{{ action.name }}</code>\n </dd>\n }\n <!--->\n <!-- ----- Color token ----- -->\n @if (action.color) {\n <dt>{{ '@platform-dev-tools:module.action-details.color' | translate | async }}</dt>\n <dd>\n <code dir=\"ltr\">{{ action.color }}</code>\n </dd>\n }\n <!--->\n <!-- ----- Sort order ----- -->\n @if (action.order != null) {\n <dt>{{ '@platform-dev-tools:module.action-details.order' | translate | async }}</dt>\n <dd>{{ action.order }}</dd>\n }\n </dl>\n </section>\n <!--->\n <!-- ----- Keyboard shortcuts ----- -->\n @if (action.shortcuts.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.shortcuts' | translate | async }}</h3>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (shortcut of action.shortcuts; track shortcut) {\n <code>{{ shortcut }}</code>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Permission keys ----- -->\n @if (action.permissions.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.terms.permissions' | translate | async }}</h3>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (permission of action.permissions; track permission) {\n <code>{{ permission }}</code>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Dynamic submenu expression ----- -->\n @if (action.submenuExpression) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.submenu' | translate | async }}</h3>\n <code dir=\"ltr\">{{ action.submenuExpression }}</code>\n </section>\n }\n <!--->\n <!-- ----- Flattened command options ----- -->\n @if (action.optionsPairs.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.options' | translate | async }}</h3>\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: action.optionsPairs }\" />\n </section>\n }\n <!--->\n <!-- ----- Flattened command metadata ----- -->\n @if (action.metadataPairs.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.metadata' | translate | async }}</h3>\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: action.metadataPairs }\" />\n </section>\n }\n }\n <!-- ----- No action selected ----- -->\n @else {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.action-details.select-action' | translate | async }}\n </p>\n }\n</ng-template>\n", styles: ["@layer properties;ax-drawer-container.axp-platform-explorer__docs,ax-content.axp-platform-explorer__docs-main{height:100%;min-height:0}.axp-platform-explorer__docs-main{display:flex;height:100%;min-height:0px;min-width:0px;flex:1;flex-direction:column;overflow:hidden;display:flex!important;overflow:hidden!important}.axp-platform-explorer__docs-shell{display:grid;grid-template-rows:auto minmax(0,1fr);height:100%;min-height:0;overflow:hidden}.axp-platform-explorer__docs-chrome{z-index:20;flex-shrink:0;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);scrollbar-gutter:stable}.axp-platform-explorer__docs-chrome:where(.ax-dark,.ax-dark *):not(:where(.ax-light,.ax-light *)){background-color:var(--color-dark);color:var(--color-on-dark);border-color:var(--color-border-dark)}.axp-platform-explorer__docs-plugins{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 1.5);padding-inline:calc(var(--spacing, .25rem) * 6);padding-block:calc(var(--spacing, .25rem) * 3)}.axp-platform-explorer__docs-table-head{display:grid;grid-template-columns:46% minmax(0,1fr) minmax(0,1fr);margin-inline:calc(var(--spacing, .25rem) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));background-color:rgba(var(--ax-sys-color-lighter-surface));text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-table-head{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-table-head>*{padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}.axp-platform-explorer__docs-chip{display:inline-flex;align-items:center;gap:var(--spacing, .25rem);border-radius:calc(infinity * 1px);padding-inline:calc(var(--spacing, .25rem) * 2);padding-block:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface));border:1px solid rgb(var(--ax-sys-color-border-warning-lightest-surface))}.axp-platform-explorer__docs-chip--field{margin-top:calc(var(--spacing, .25rem) * 1.5)}.axp-platform-explorer__docs-list{min-height:0px;min-width:0px;overflow:auto;padding-inline:calc(var(--spacing, .25rem) * 6);padding-top:0;padding-bottom:calc(var(--spacing, .25rem) * 5);scrollbar-gutter:stable}.axp-platform-explorer__docs-list--padded{padding-top:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-toolbar{margin-bottom:calc(var(--spacing, .25rem) * 5);display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}.axp-platform-explorer__docs-heading{margin:0;font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-platform-explorer__docs-count{margin:0;margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-count{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-search{width:calc(var(--spacing, .25rem) * 52);max-width:100%}.axp-platform-explorer__docs-empty,.axp-platform-explorer__docs-footnote{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-empty,.axp-platform-explorer__docs-footnote{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-empty{padding-block:calc(var(--spacing, .25rem) * 8);text-align:center}.axp-platform-explorer__docs-footnote{margin:0;padding-top:calc(var(--spacing, .25rem) * 4)}.axp-platform-explorer__docs-side{width:calc(var(--spacing, .25rem) * 80);border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}.axp-platform-explorer__docs-detail{display:flex;height:100%;min-width:0px;flex-direction:column;overflow:auto;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 5);padding-block:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-eyebrow{margin-bottom:calc(var(--spacing, .25rem) * 2.5);font-size:11px;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-widest, .1em);letter-spacing:var(--tracking-widest, .1em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-eyebrow{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-detail-title{margin:0;margin-bottom:var(--spacing, .25rem);font-size:var(--text-lg, 1.125rem);line-height:var(--tw-leading, var(--text-lg--line-height, calc(1.75 / 1.125)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);overflow-wrap:break-word}.axp-platform-explorer__docs-key{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__docs-detail-block{margin-top:calc(var(--spacing, .25rem) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-top:calc(var(--spacing, .25rem) * 4)}.axp-platform-explorer__docs-detail-block h3{margin:0;margin-bottom:calc(var(--spacing, .25rem) * 2.5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-platform-explorer__docs-description{margin:0;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-description{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-dl{margin:0;display:grid;column-gap:calc(var(--spacing, .25rem) * 3);row-gap:calc(var(--spacing, .25rem) * 2.5);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));grid-template-columns:minmax(0,1fr) auto}.axp-platform-explorer__docs-dl dt{color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-dl dt{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-dl dd{margin:0;max-width:calc(var(--spacing, .25rem) * 40);text-align:end;overflow-wrap:break-word}.axp-platform-explorer__docs-actions{display:flex;min-height:0px;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);overflow:auto;padding-inline:calc(var(--spacing, .25rem) * 6);padding-block:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-layout{margin-top:calc(var(--spacing, .25rem) * 6);display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);padding-inline:0px}.axp-platform-explorer__docs-actions .axp-platform-explorer__docs-layout{margin-top:0}.axp-platform-explorer__table-wrap{overflow-x:auto;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}.axp-platform-explorer__table-wrap--fields{overflow:visible;border-radius:0;border-style:var(--tw-border-style);border-width:0px}.axp-platform-explorer__table{width:100%;min-width:36rem;border-collapse:collapse;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.axp-platform-explorer__table--fields{min-width:0px;table-layout:fixed}.axp-platform-explorer__table--fields th:first-child{width:46%}.axp-platform-explorer__table th{background-color:rgba(var(--ax-sys-color-lighter-surface));text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgba(var(--ax-sys-color-text-default));border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__table th{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__table--fields thead.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.axp-platform-explorer__table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 4);vertical-align:middle;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__table--fields tbody tr{cursor:pointer}.axp-platform-explorer__table-row--alt td{background-color:rgba(var(--ax-sys-color-lighter-surface))}.axp-platform-explorer__table-row:hover td,.axp-platform-explorer__table-row--selected td{background-color:rgba(var(--ax-sys-color-primary-lightest-surface))}.axp-platform-explorer__table-row--selected td:first-child{box-shadow:inset 3px 0 0 var(--ax-color-primary, currentColor)}:dir(rtl) .axp-platform-explorer__table-row--selected td:first-child{box-shadow:inset -3px 0 0 var(--ax-color-primary, currentColor)}.axp-platform-explorer__table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0px}.axp-platform-explorer__table-detail td{padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 3)}.axp-platform-explorer__docs-dl--json{grid-template-columns:minmax(0,1.2fr) minmax(0,1fr)}.axp-platform-explorer__docs-dl--json dd{max-width:none}.axp-platform-explorer__docs-dl--json dt code,.axp-platform-explorer__docs-dl--json dd code{background-color:transparent;padding:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));word-break:break-all}.axp-platform-explorer__field-button{margin:0;display:inline-flex;appearance:none;align-items:center;border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding:0;text-align:start;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-medium, 500);font-weight:var(--font-weight-medium, 500)}.axp-platform-explorer__field-button code{background-color:transparent;padding:0;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.axp-platform-explorer__table-row--selected .axp-platform-explorer__field-button{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__field-button:hover{text-decoration:underline;text-underline-offset:3px}.axp-platform-explorer__field-title{margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__field-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__type-chip{display:inline-block;border-radius:.25rem;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 1.5);padding-block:calc(var(--spacing, .25rem) * .5);font-family:var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__unknown{color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__unknown{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__yes{display:inline-flex;align-items:center;gap:var(--spacing, .25rem);color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__yes i{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__prop-toggle,.axp-platform-explorer__prop-toggle-spacer{display:inline-flex;height:calc(var(--spacing, .25rem) * 7);width:calc(var(--spacing, .25rem) * 7);flex-shrink:0;align-items:center;justify-content:center;border-radius:var(--radius-md, .375rem)}.axp-platform-explorer__prop-toggle{cursor:pointer;appearance:none;border-style:var(--tw-border-style);border-width:0px;background-color:transparent;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__prop-toggle{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__prop-toggle:hover{background-color:rgba(var(--ax-sys-color-lighter-surface));color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__prop-toggle i{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-duration: .15s;transition-duration:.15s}.axp-platform-explorer__prop-toggle.is-open i{rotate:90deg}.axp-platform-explorer__prop-details{margin-inline-start:calc(var(--spacing, .25rem) * 9);display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding:calc(var(--spacing, .25rem) * 4)}.axp-platform-explorer__prop-flags{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-flag{display:inline-flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 2.5);padding-block:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__prop-pairs{margin:0;display:grid;column-gap:calc(var(--spacing, .25rem) * 6);row-gap:calc(var(--spacing, .25rem) * 2);grid-template-columns:minmax(7rem,11rem) minmax(0,1fr)}.axp-platform-explorer__prop-pairs dt{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__prop-pairs dt{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__prop-pairs dd{margin:0;min-width:0px}.axp-platform-explorer__prop-pairs code,.axp-platform-explorer__prop-card code,.axp-platform-explorer__prop-trigger code{border-radius:.25rem;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 1.5);padding-block:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));word-break:break-all}.axp-platform-explorer__prop-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-section-title{margin:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__prop-section-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__prop-stack{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-card{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}.axp-platform-explorer__prop-trigger{display:flex;flex-wrap:wrap;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-json{margin:0;max-height:calc(var(--spacing, .25rem) * 48);overflow:auto;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding:calc(var(--spacing, .25rem) * 3);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-relaxed, 1.625);line-height:var(--leading-relaxed, 1.625);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.axp-platform-explorer__table--actions{min-width:36rem}.axp-platform-explorer__priority{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__priority{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__priority.is-primary{--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);color:rgba(var(--ax-sys-color-primary-surface))}@media(max-width:760px){.axp-platform-explorer__docs-side{width:calc(var(--spacing, .25rem) * 72)}.axp-platform-explorer__docs-plugins,.axp-platform-explorer__docs-list,.axp-platform-explorer__docs-actions{padding-inline:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-table-head{margin-inline:calc(var(--spacing, .25rem) * 5)}}@media(max-width:440px){.axp-platform-explorer__docs-toolbar{display:block}.axp-platform-explorer__docs-search{margin-top:calc(var(--spacing, .25rem) * 3);width:100%}}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@property --tw-duration{syntax: \"*\"; inherits: false;}@property --tw-leading{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-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial;--tw-duration: initial;--tw-leading: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i2$1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i3$1.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i4.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "component", type: i4.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXDrawerModule }, { kind: "component", type: i5.AXDrawerComponent, selector: "ax-drawer", inputs: ["location", "showBackdrop", "mode", "transition", "closeOnBackdropClick", "backdropClass", "collapsed", "singleOpenMode"], outputs: ["onBackdropClick", "collapsedStateChanged"] }, { kind: "component", type: i5.AXDrawerContainerComponent, selector: "ax-drawer-container" }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
433
1350
  }
434
1351
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerEntityDetailComponent, decorators: [{
435
1352
  type: Component,
436
- args: [{ selector: 'axp-platform-explorer-entity-detail', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXTranslationModule], host: { class: 'flex flex-col flex-1 min-h-0' }, template: `
437
- <div class="flex flex-col flex-1 min-h-0 pt-0 overflow-auto">
438
- @if (entity(); as e) {
439
- <dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 mb-3 m-0">
440
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.module' | translate | async }}</dt><dd class="m-0">{{ e.module }}</dd>
441
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt><dd class="m-0">{{ e.name }}</dd>
442
- @if (e.title) {
443
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.title' | translate | async }}</dt><dd class="m-0">{{ e.title | translate | async}}</dd>
444
- }
445
- @if (e.formats.plural) {
446
- <dt class="font-semibold text-muted">{{ '@platform-dev-tools:module.terms.plural' | translate | async }}</dt><dd class="m-0">{{ e.formats.plural | translate | async}}</dd>
447
- }
448
- </dl>
449
- @if (e.properties && e.properties.length > 0) {
450
- <p class="mb-2 mt-3 font-bold">{{ '@platform-dev-tools:module.terms.properties' | translate | async }} ({{ e.properties.length }})</p>
451
- <div class="flex-1 min-h-0 overflow-auto border border-lightest rounded-md">
452
- <table class="w-full border-collapse text-[13px]">
453
- <thead>
454
- <tr>
455
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</th>
456
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.title' | translate | async }}</th>
457
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.type' | translate | async }}</th>
458
- <th class="text-start p-2 px-3 bg-lighter font-semibold border-b border-lightest">{{ '@platform-dev-tools:module.terms.description' | translate | async }}</th>
459
- </tr>
460
- </thead>
461
- <tbody class="axp-entity-detail-tbody">
462
- @for (p of e.properties; track p.name) {
463
- <tr>
464
- <td class="p-2 px-3 border-b border-lightest"><code class="text-xs bg-lighter px-1.5 py-0.5 rounded">{{ p.name }}</code></td>
465
- <td class="p-2 px-3 border-b border-lightest">{{ p.title | translate | async }}</td>
466
- <td class="p-2 px-3 border-b border-lightest">{{ p.schema.dataType }}</td>
467
- <td class="p-2 px-3 border-b border-lightest">@if (p.description) { {{ p.description | translate | async }} } @else { – }</td>
468
- </tr>
469
- }
470
- </tbody>
471
- </table>
472
- </div>
473
- }
474
- } @else if (loading()) {
475
- <p class="m-0 text-muted">{{ '@platform-dev-tools:module.terms.loading-entity' | translate | async }}</p>
476
- }
477
- </div>
478
- `, styles: [".axp-entity-detail-tbody tr:last-child td{border-bottom:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1353
+ args: [{ selector: 'axp-platform-explorer-entity-detail', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'flex flex-col flex-1 min-h-0' }, imports: [
1354
+ CommonModule,
1355
+ NgTemplateOutlet,
1356
+ AXTranslationModule,
1357
+ AXLoadingModule,
1358
+ AXSearchBoxModule,
1359
+ AXDecoratorModule,
1360
+ AXDrawerModule,
1361
+ AXPStateMessageComponent,
1362
+ ], template: "<div class=\"flex flex-col flex-1 min-h-0\">\n <!-- ----- Loading entity layout ----- -->\n @if (loading()) {\n <div class=\"flex flex-1 items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Entity catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- Entity documentation with field or action inspector ----- -->\n @else if (detail(); as d) {\n <ax-drawer-container class=\"axp-platform-explorer__docs\">\n <ax-content class=\"axp-platform-explorer__docs-main\">\n <div class=\"axp-platform-explorer__docs-shell\">\n <div class=\"axp-platform-explorer__docs-chrome\">\n <!--->\n <!-- ----- Attached plugins as chips ----- -->\n @if (d.plugins.length) {\n <div class=\"axp-platform-explorer__docs-plugins\">\n @for (plugin of d.plugins; track plugin.name) {\n <span class=\"axp-platform-explorer__docs-chip\">\n <i class=\"fa-light fa-plug\" aria-hidden=\"true\"></i>\n {{ (plugin.title || plugin.name) | translate | async }}\n </span>\n }\n </div>\n }\n <!--->\n <!-- ----- Field column headers stay outside the scrolling rows ----- -->\n @if (isFieldSection() && properties().length) {\n <div class=\"axp-platform-explorer__docs-table-head\" role=\"row\">\n <span role=\"columnheader\">{{ '@platform-dev-tools:module.terms.field' | translate | async }}</span>\n <span role=\"columnheader\">\n {{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}\n </span>\n <span role=\"columnheader\">\n {{ '@platform-dev-tools:module.property-details.nullable' | translate | async }}\n </span>\n </div>\n }\n </div>\n\n <!--->\n <!-- ----- Field table ----- -->\n @if (isFieldSection()) {\n <div class=\"axp-platform-explorer__docs-list\">\n <!--->\n <!-- ----- Empty property list ----- -->\n @if (properties().length === 0) {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.messages.no-properties' | translate | async }}\n </p>\n }\n <!-- ----- Field rows ----- -->\n @else {\n <div class=\"axp-platform-explorer__table-wrap axp-platform-explorer__table-wrap--fields\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead class=\"sr-only\">\n <tr>\n <th>{{ '@platform-dev-tools:module.terms.field' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.property-details.nullable' | translate | async }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of properties(); track row.name) {\n <tr\n class=\"axp-platform-explorer__table-row\"\n [class.axp-platform-explorer__table-row--selected]=\"isSelected(row.name)\"\n (click)=\"selectProperty(row.name)\"\n >\n <td>\n <button\n type=\"button\"\n class=\"axp-platform-explorer__field-button\"\n [attr.aria-pressed]=\"isSelected(row.name)\"\n (click)=\"selectProperty(row.name); $event.stopPropagation()\"\n >\n <code dir=\"ltr\">{{ row.name }}</code>\n </button>\n <!--->\n <!-- ----- Translated property title under the field name ----- -->\n @if (row.title) {\n <div class=\"axp-platform-explorer__field-title\">\n {{ row.title | translate | async }}\n </div>\n }\n </td>\n <td>\n <!--->\n <!-- ----- Stored data type chip ----- -->\n @if (row.dataType) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ row.dataType }}</span>\n }\n <!-- ----- Missing data type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n <td>\n <ng-container\n *ngTemplateOutlet=\"boolCell; context: { value: flagValue(row.details, 'nullable') }\"\n />\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <p class=\"axp-platform-explorer__docs-footnote\">\n {{ '@platform-dev-tools:module.property-details.select-field' | translate | async }}\n </p>\n </div>\n }\n <!-- ----- Action table ----- -->\n @else if (activeLayout(); as layout) {\n <div class=\"axp-platform-explorer__docs-list axp-platform-explorer__docs-list--padded\">\n <div class=\"axp-platform-explorer__docs-toolbar\">\n <div>\n <h2 class=\"axp-platform-explorer__docs-heading\">\n {{ ('@platform-dev-tools:module.layouts.' + layout.kind) | translate | async }}\n </h2>\n <p class=\"axp-platform-explorer__docs-count\">\n {{ ('@platform-dev-tools:module.layouts.' + layout.kind + '-hint') | translate | async }}\n </p>\n <p class=\"axp-platform-explorer__docs-count\">\n <!--->\n <!-- ----- Filtered action count ----- -->\n @if (actionQuery().trim()) {\n {{\n '@platform-dev-tools:module.action-details.action-count-filtered'\n | translate: { params: { shown: filteredActions().length, count: layout.actionCount } }\n | async\n }}\n }\n <!-- ----- Full action count ----- -->\n @else {\n {{\n '@platform-dev-tools:module.layouts.action-count'\n | translate: { params: { count: layout.actionCount } }\n | async\n }}\n }\n </p>\n </div>\n <ax-search-box\n class=\"axp-platform-explorer__docs-search\"\n look=\"fill\"\n [delayTime]=\"200\"\n [value]=\"actionQuery()\"\n [placeholder]=\"\n ('@platform-dev-tools:module.action-details.search-actions' | translate | async) ?? ''\n \"\n (onValueChanged)=\"onActionSearchChanged($event)\"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n </div>\n <div class=\"flex flex-wrap items-center gap-1.5 mb-4\">\n <!--->\n <!-- ----- Related entity identity ----- -->\n @if (layout.relatedEntity) {\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ layout.relatedEntity }}</code>\n }\n <!--->\n <!-- ----- Related layout type ----- -->\n @if (layout.relatedLayoutType) {\n <span class=\"inline-flex items-center rounded-full bg-lighter px-2 py-0.5 text-xs text-muted\">\n {{\n ('@platform-dev-tools:module.layouts.related-types.' + layout.relatedLayoutType)\n | translate | async\n }}\n </span>\n }\n <!--->\n <!-- ----- List query views ----- -->\n @for (view of layout.views; track view.name) {\n <span class=\"inline-flex items-center rounded-full bg-lighter px-2 py-0.5 text-xs text-muted\">\n {{ (view.title || view.name) | translate | async }}\n </span>\n }\n </div>\n <!--->\n <!-- ----- Optional stored layout title ----- -->\n @if (layout.layoutTitle) {\n <p class=\"m-0 mb-2 text-sm\">{{ layout.layoutTitle | translate | async }}</p>\n }\n <!--->\n <!-- ----- Optional stored layout description ----- -->\n @if (layout.layoutDescription) {\n <p class=\"m-0 mb-3 text-sm text-muted\">{{ layout.layoutDescription | translate | async }}</p>\n }\n\n <!--->\n <!-- ----- Empty action list ----- -->\n @if (layout.actionCount === 0) {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.messages.no-actions' | translate | async }}\n </p>\n }\n <!-- ----- Search matched no actions ----- -->\n @else if (filteredActions().length === 0) {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.action-details.no-matching-actions' | translate | async }}\n </p>\n }\n <!-- ----- Action rows ----- -->\n @else {\n <div class=\"axp-platform-explorer__table-wrap axp-platform-explorer__table-wrap--fields\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead>\n <tr>\n <th>{{ '@platform-dev-tools:module.action-details.item' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.action-details.type' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.action-details.priority' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.action-details.scope' | translate | async }}</th>\n </tr>\n </thead>\n <tbody>\n @for (action of filteredActions(); track action.id) {\n <tr\n class=\"axp-platform-explorer__table-row\"\n [class.axp-platform-explorer__table-row--selected]=\"isActionSelected(action.id)\"\n (click)=\"selectAction(action.id)\"\n >\n <td>\n <div [style.padding-inline-start.rem]=\"action.depth\">\n <button\n type=\"button\"\n class=\"axp-platform-explorer__field-button\"\n [attr.aria-pressed]=\"isActionSelected(action.id)\"\n (click)=\"selectAction(action.id); $event.stopPropagation()\"\n >\n <!--->\n <!-- ----- Action icon from the layout ----- -->\n @if (action.icon) {\n <i class=\"text-muted me-1\" [ngClass]=\"action.icon\" aria-hidden=\"true\"></i>\n }\n <code dir=\"ltr\">{{ action.commandName || action.name || '\u2013' }}</code>\n </button>\n <!--->\n <!-- ----- Translated action title ----- -->\n @if (action.title) {\n <div class=\"axp-platform-explorer__field-title\">\n {{ action.title | translate | async }}\n </div>\n }\n </div>\n </td>\n <td>\n <!--->\n <!-- ----- Action type chip ----- -->\n @if (action.typeLabel) {\n <span class=\"axp-platform-explorer__type-chip\">{{ action.typeLabel }}</span>\n }\n <!-- ----- Missing action type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n <td>\n <span\n class=\"axp-platform-explorer__priority\"\n [class.is-primary]=\"action.priority === 'primary'\"\n >\n {{\n ('@platform-dev-tools:module.action-details.priorities.' + action.priority)\n | translate | async\n }}\n </span>\n </td>\n <td class=\"text-muted\">\n {{\n ('@platform-dev-tools:module.action-details.scopes.' + action.scope) | translate | async\n }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <p class=\"axp-platform-explorer__docs-footnote\">\n {{ '@platform-dev-tools:module.action-details.select-action' | translate | async }}\n </p>\n </div>\n }\n </div>\n </ax-content>\n <ax-drawer\n class=\"axp-platform-explorer__docs-side\"\n [location]=\"'end'\"\n [mode]=\"'push'\"\n [collapsed]=\"!inspectorOpen()\"\n >\n <ax-content\n class=\"axp-platform-explorer__docs-detail\"\n [attr.aria-label]=\"inspectorAriaKey() | translate | async\"\n >\n <!--->\n <!-- ----- Selected field definition ----- -->\n @if (isFieldSection()) {\n <ng-container *ngTemplateOutlet=\"fieldDetail\" />\n }\n <!-- ----- Selected action definition ----- -->\n @else {\n <ng-container *ngTemplateOutlet=\"actionDetail\" />\n }\n </ax-content>\n </ax-drawer>\n </ax-drawer-container>\n }\n</div>\n\n<ng-template #boolCell let-value=\"value\">\n <!-- ----- Affirmative flag ----- -->\n @if (value === true) {\n <span class=\"axp-platform-explorer__yes\">\n <i class=\"fa-light fa-check\" aria-hidden=\"true\"></i>\n {{ '@platform-dev-tools:module.property-details.yes' | translate | async }}\n </span>\n }\n <!-- ----- Negative flag ----- -->\n @else if (value === false) {\n {{ '@platform-dev-tools:module.property-details.no' | translate | async }}\n }\n <!-- ----- Unknown flag ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n</ng-template>\n\n<ng-template #kvList let-pairs=\"pairs\">\n <dl class=\"axp-platform-explorer__docs-dl axp-platform-explorer__docs-dl--json\">\n @for (pair of pairs; track pair.path) {\n <dt>\n <code dir=\"ltr\">{{ pair.path || '\u2013' }}</code>\n </dt>\n <dd>\n <code dir=\"ltr\">{{ pair.value }}</code>\n </dd>\n }\n </dl>\n</ng-template>\n\n<ng-template #fieldDetail>\n <!-- ----- Selected field definition ----- -->\n @if (selectedProperty(); as field) {\n <div class=\"axp-platform-explorer__docs-detail-header\">\n <div class=\"axp-platform-explorer__docs-eyebrow\">\n {{ '@platform-dev-tools:module.property-details.field-definition' | translate | async }}\n </div>\n <h2 class=\"axp-platform-explorer__docs-detail-title\">\n {{ (field.title || field.name) | translate | async }}\n </h2>\n <code class=\"axp-platform-explorer__docs-key\" dir=\"ltr\">{{ field.name }}</code>\n <!--->\n <!-- ----- Plugin source of the selected field ----- -->\n @if (field.pluginTitle) {\n <span class=\"axp-platform-explorer__docs-chip axp-platform-explorer__docs-chip--field\">\n <i class=\"fa-light fa-plug\" aria-hidden=\"true\"></i>\n {{ field.pluginTitle | translate | async }}\n </span>\n }\n </div>\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.terms.description' | translate | async }}</h3>\n <p class=\"axp-platform-explorer__docs-description\">\n <!--->\n <!-- ----- Translated field description ----- -->\n @if (field.description) {\n {{ field.description | translate | async }}\n }\n <!-- ----- Missing field description ----- -->\n @else {\n {{ '@platform-dev-tools:module.property-details.no-description' | translate | async }}\n }\n </p>\n </section>\n <!--->\n <!-- ----- Schema flags and scalar pairs ----- -->\n @if (selectedSchemaEntries(); as schema) {\n <!--->\n <!-- ----- Render schema when at least one entry exists ----- -->\n @if (schema.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.schema' | translate | async }}</h3>\n <dl class=\"axp-platform-explorer__docs-dl\">\n @for (entry of schema; track entry.labelKey) {\n <dt>{{ ('@platform-dev-tools:module.property-details.' + entry.labelKey) | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Boolean schema flags ----- -->\n @if (entry.kind === 'flag') {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: entry.on }\" />\n }\n <!-- ----- Schema scalar values ----- -->\n @else {\n <!--->\n <!-- ----- Data type shown as a chip ----- -->\n @if (isTypePair(entry.labelKey)) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ entry.value }}</span>\n }\n <!-- ----- Code-like schema values ----- -->\n @else if (isCodePair(entry.labelKey)) {\n <code dir=\"ltr\">{{ entry.value }}</code>\n }\n <!-- ----- Plain schema values ----- -->\n @else {\n {{ entry.value }}\n }\n }\n </dd>\n }\n </dl>\n </section>\n }\n }\n <!--->\n <!-- ----- Presentation flags and widget pairs ----- -->\n @if (selectedPresentationEntries(); as presentation) {\n <!--->\n <!-- ----- Render presentation when at least one entry exists ----- -->\n @if (presentation.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.presentation' | translate | async }}</h3>\n <dl class=\"axp-platform-explorer__docs-dl\">\n @for (entry of presentation; track entry.labelKey) {\n <dt>{{ ('@platform-dev-tools:module.property-details.' + entry.labelKey) | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Boolean presentation flags ----- -->\n @if (entry.kind === 'flag') {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: entry.on }\" />\n }\n <!-- ----- Presentation scalar values ----- -->\n @else {\n <!--->\n <!-- ----- Widget identifiers as code ----- -->\n @if (isCodePair(entry.labelKey) || isTypePair(entry.labelKey)) {\n <code dir=\"ltr\">{{ entry.value }}</code>\n }\n <!-- ----- Plain presentation values ----- -->\n @else {\n {{ entry.value }}\n }\n }\n </dd>\n }\n </dl>\n </section>\n }\n }\n <!--->\n <!-- ----- Widget triggers ----- -->\n @if (field.details.triggers.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.triggers' | translate | async }}</h3>\n <div class=\"axp-platform-explorer__prop-stack\">\n @for (trigger of field.details.triggers; track $index) {\n <div class=\"axp-platform-explorer__prop-card\">\n <div class=\"axp-platform-explorer__prop-trigger\">\n <span class=\"text-muted text-xs\">\n {{ '@platform-dev-tools:module.property-details.event' | translate | async }}\n </span>\n <code>{{ trigger.event || '\u2013' }}</code>\n <i class=\"fa-light fa-arrow-right text-muted\" aria-hidden=\"true\"></i>\n <span class=\"text-muted text-xs\">\n {{ '@platform-dev-tools:module.property-details.action' | translate | async }}\n </span>\n <code>{{ trigger.action || '\u2013' }}</code>\n </div>\n <!--->\n <!-- ----- Disabled trigger badge ----- -->\n @if (trigger.disabled) {\n <span class=\"axp-platform-explorer__prop-flag\">\n {{ '@platform-dev-tools:module.property-details.disabled' | translate | async }}\n </span>\n }\n </div>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Validation rules ----- -->\n @if (field.details.validations.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.property-details.validations' | translate | async }}</h3>\n <div class=\"axp-platform-explorer__prop-stack\">\n @for (rule of field.details.validations; track $index) {\n <div class=\"axp-platform-explorer__prop-card\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <code>{{ rule.rule }}</code>\n <!--->\n <!-- ----- Disabled validation badge ----- -->\n @if (rule.disabled) {\n <span class=\"axp-platform-explorer__prop-flag\">\n {{ '@platform-dev-tools:module.property-details.disabled' | translate | async }}\n </span>\n }\n </div>\n <!--->\n <!-- ----- Validation message ----- -->\n @if (rule.message) {\n <p class=\"m-0 text-xs text-muted\">{{ rule.message | translate | async }}</p>\n }\n <!--->\n <!-- ----- Flattened validation options ----- -->\n @if (rule.optionsPairs.length) {\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: rule.optionsPairs }\" />\n }\n </div>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Flattened nested JSON (widget options, transforms, children) ----- -->\n @if (field.details.jsonBlocks.length) {\n @for (block of field.details.jsonBlocks; track block.labelKey) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ ('@platform-dev-tools:module.property-details.' + block.labelKey) | translate | async }}</h3>\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: block.pairs }\" />\n </section>\n }\n }\n }\n <!-- ----- No field selected ----- -->\n @else {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.property-details.select-field' | translate | async }}\n </p>\n }\n</ng-template>\n\n<ng-template #actionDetail>\n <!-- ----- Selected action definition ----- -->\n @if (selectedAction(); as action) {\n <div class=\"axp-platform-explorer__docs-detail-header\">\n <div class=\"axp-platform-explorer__docs-eyebrow\">\n {{ '@platform-dev-tools:module.action-details.action-definition' | translate | async }}\n </div>\n <h2 class=\"axp-platform-explorer__docs-detail-title\">\n {{ (action.title || action.commandName || action.name || '\u2013') | translate | async }}\n </h2>\n <code class=\"axp-platform-explorer__docs-key\" dir=\"ltr\">{{ action.commandName || action.name || '\u2013' }}</code>\n </div>\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.behavior' | translate | async }}</h3>\n <dl class=\"axp-platform-explorer__docs-dl\">\n <dt>{{ '@platform-dev-tools:module.action-details.type' | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Action type ----- -->\n @if (action.typeLabel) {\n <span class=\"axp-platform-explorer__type-chip\">{{ action.typeLabel }}</span>\n }\n <!-- ----- Missing action type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.priority' | translate | async }}</dt>\n <dd>\n {{ ('@platform-dev-tools:module.action-details.priorities.' + action.priority) | translate | async }}\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.scope' | translate | async }}</dt>\n <dd>{{ ('@platform-dev-tools:module.action-details.scopes.' + action.scope) | translate | async }}</dd>\n <dt>{{ '@platform-dev-tools:module.action-details.default' | translate | async }}</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: action.isDefault }\" />\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.separated' | translate | async }}</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: action.separated }\" />\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.hidden' | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Hidden yes/no ----- -->\n @if (hiddenFlag(action) !== undefined) {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: hiddenFlag(action) }\" />\n }\n <!-- ----- Hidden expression ----- -->\n @else if (hiddenExpression(action); as expression) {\n <code dir=\"ltr\">{{ expression }}</code>\n }\n <!-- ----- Hidden unknown ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </dd>\n <dt>{{ '@platform-dev-tools:module.action-details.disabled' | translate | async }}</dt>\n <dd>\n <!--->\n <!-- ----- Disabled yes/no ----- -->\n @if (disabledFlag(action) !== undefined) {\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: disabledFlag(action) }\" />\n }\n <!-- ----- Disabled expression ----- -->\n @else if (disabledExpression(action); as expression) {\n <code dir=\"ltr\">{{ expression }}</code>\n }\n <!-- ----- Disabled unknown ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </dd>\n <!--->\n <!-- ----- Named action key ----- -->\n @if (action.name) {\n <dt>{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd>\n <code dir=\"ltr\">{{ action.name }}</code>\n </dd>\n }\n <!--->\n <!-- ----- Color token ----- -->\n @if (action.color) {\n <dt>{{ '@platform-dev-tools:module.action-details.color' | translate | async }}</dt>\n <dd>\n <code dir=\"ltr\">{{ action.color }}</code>\n </dd>\n }\n <!--->\n <!-- ----- Sort order ----- -->\n @if (action.order != null) {\n <dt>{{ '@platform-dev-tools:module.action-details.order' | translate | async }}</dt>\n <dd>{{ action.order }}</dd>\n }\n </dl>\n </section>\n <!--->\n <!-- ----- Keyboard shortcuts ----- -->\n @if (action.shortcuts.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.shortcuts' | translate | async }}</h3>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (shortcut of action.shortcuts; track shortcut) {\n <code>{{ shortcut }}</code>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Permission keys ----- -->\n @if (action.permissions.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.terms.permissions' | translate | async }}</h3>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (permission of action.permissions; track permission) {\n <code>{{ permission }}</code>\n }\n </div>\n </section>\n }\n <!--->\n <!-- ----- Dynamic submenu expression ----- -->\n @if (action.submenuExpression) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.submenu' | translate | async }}</h3>\n <code dir=\"ltr\">{{ action.submenuExpression }}</code>\n </section>\n }\n <!--->\n <!-- ----- Flattened command options ----- -->\n @if (action.optionsPairs.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.options' | translate | async }}</h3>\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: action.optionsPairs }\" />\n </section>\n }\n <!--->\n <!-- ----- Flattened command metadata ----- -->\n @if (action.metadataPairs.length) {\n <section class=\"axp-platform-explorer__docs-detail-block\">\n <h3>{{ '@platform-dev-tools:module.action-details.metadata' | translate | async }}</h3>\n <ng-container *ngTemplateOutlet=\"kvList; context: { pairs: action.metadataPairs }\" />\n </section>\n }\n }\n <!-- ----- No action selected ----- -->\n @else {\n <p class=\"axp-platform-explorer__docs-empty\">\n {{ '@platform-dev-tools:module.action-details.select-action' | translate | async }}\n </p>\n }\n</ng-template>\n", styles: ["@layer properties;ax-drawer-container.axp-platform-explorer__docs,ax-content.axp-platform-explorer__docs-main{height:100%;min-height:0}.axp-platform-explorer__docs-main{display:flex;height:100%;min-height:0px;min-width:0px;flex:1;flex-direction:column;overflow:hidden;display:flex!important;overflow:hidden!important}.axp-platform-explorer__docs-shell{display:grid;grid-template-rows:auto minmax(0,1fr);height:100%;min-height:0;overflow:hidden}.axp-platform-explorer__docs-chrome{z-index:20;flex-shrink:0;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);scrollbar-gutter:stable}.axp-platform-explorer__docs-chrome:where(.ax-dark,.ax-dark *):not(:where(.ax-light,.ax-light *)){background-color:var(--color-dark);color:var(--color-on-dark);border-color:var(--color-border-dark)}.axp-platform-explorer__docs-plugins{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 1.5);padding-inline:calc(var(--spacing, .25rem) * 6);padding-block:calc(var(--spacing, .25rem) * 3)}.axp-platform-explorer__docs-table-head{display:grid;grid-template-columns:46% minmax(0,1fr) minmax(0,1fr);margin-inline:calc(var(--spacing, .25rem) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));background-color:rgba(var(--ax-sys-color-lighter-surface));text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-table-head{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-table-head>*{padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}.axp-platform-explorer__docs-chip{display:inline-flex;align-items:center;gap:var(--spacing, .25rem);border-radius:calc(infinity * 1px);padding-inline:calc(var(--spacing, .25rem) * 2);padding-block:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface));border:1px solid rgb(var(--ax-sys-color-border-warning-lightest-surface))}.axp-platform-explorer__docs-chip--field{margin-top:calc(var(--spacing, .25rem) * 1.5)}.axp-platform-explorer__docs-list{min-height:0px;min-width:0px;overflow:auto;padding-inline:calc(var(--spacing, .25rem) * 6);padding-top:0;padding-bottom:calc(var(--spacing, .25rem) * 5);scrollbar-gutter:stable}.axp-platform-explorer__docs-list--padded{padding-top:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-toolbar{margin-bottom:calc(var(--spacing, .25rem) * 5);display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}.axp-platform-explorer__docs-heading{margin:0;font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-platform-explorer__docs-count{margin:0;margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-count{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-search{width:calc(var(--spacing, .25rem) * 52);max-width:100%}.axp-platform-explorer__docs-empty,.axp-platform-explorer__docs-footnote{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-empty,.axp-platform-explorer__docs-footnote{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-empty{padding-block:calc(var(--spacing, .25rem) * 8);text-align:center}.axp-platform-explorer__docs-footnote{margin:0;padding-top:calc(var(--spacing, .25rem) * 4)}.axp-platform-explorer__docs-side{width:calc(var(--spacing, .25rem) * 80);border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}.axp-platform-explorer__docs-detail{display:flex;height:100%;min-width:0px;flex-direction:column;overflow:auto;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 5);padding-block:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-eyebrow{margin-bottom:calc(var(--spacing, .25rem) * 2.5);font-size:11px;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-widest, .1em);letter-spacing:var(--tracking-widest, .1em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-eyebrow{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-detail-title{margin:0;margin-bottom:var(--spacing, .25rem);font-size:var(--text-lg, 1.125rem);line-height:var(--tw-leading, var(--text-lg--line-height, calc(1.75 / 1.125)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);overflow-wrap:break-word}.axp-platform-explorer__docs-key{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__docs-detail-block{margin-top:calc(var(--spacing, .25rem) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-top:calc(var(--spacing, .25rem) * 4)}.axp-platform-explorer__docs-detail-block h3{margin:0;margin-bottom:calc(var(--spacing, .25rem) * 2.5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-platform-explorer__docs-description{margin:0;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-description{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-dl{margin:0;display:grid;column-gap:calc(var(--spacing, .25rem) * 3);row-gap:calc(var(--spacing, .25rem) * 2.5);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));grid-template-columns:minmax(0,1fr) auto}.axp-platform-explorer__docs-dl dt{color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-dl dt{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__docs-dl dd{margin:0;max-width:calc(var(--spacing, .25rem) * 40);text-align:end;overflow-wrap:break-word}.axp-platform-explorer__docs-actions{display:flex;min-height:0px;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);overflow:auto;padding-inline:calc(var(--spacing, .25rem) * 6);padding-block:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-layout{margin-top:calc(var(--spacing, .25rem) * 6);display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);padding-inline:0px}.axp-platform-explorer__docs-actions .axp-platform-explorer__docs-layout{margin-top:0}.axp-platform-explorer__table-wrap{overflow-x:auto;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}.axp-platform-explorer__table-wrap--fields{overflow:visible;border-radius:0;border-style:var(--tw-border-style);border-width:0px}.axp-platform-explorer__table{width:100%;min-width:36rem;border-collapse:collapse;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.axp-platform-explorer__table--fields{min-width:0px;table-layout:fixed}.axp-platform-explorer__table--fields th:first-child{width:46%}.axp-platform-explorer__table th{background-color:rgba(var(--ax-sys-color-lighter-surface));text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgba(var(--ax-sys-color-text-default));border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__table th{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__table--fields thead.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.axp-platform-explorer__table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 4);vertical-align:middle;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__table--fields tbody tr{cursor:pointer}.axp-platform-explorer__table-row--alt td{background-color:rgba(var(--ax-sys-color-lighter-surface))}.axp-platform-explorer__table-row:hover td,.axp-platform-explorer__table-row--selected td{background-color:rgba(var(--ax-sys-color-primary-lightest-surface))}.axp-platform-explorer__table-row--selected td:first-child{box-shadow:inset 3px 0 0 var(--ax-color-primary, currentColor)}:dir(rtl) .axp-platform-explorer__table-row--selected td:first-child{box-shadow:inset -3px 0 0 var(--ax-color-primary, currentColor)}.axp-platform-explorer__table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0px}.axp-platform-explorer__table-detail td{padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 3)}.axp-platform-explorer__docs-dl--json{grid-template-columns:minmax(0,1.2fr) minmax(0,1fr)}.axp-platform-explorer__docs-dl--json dd{max-width:none}.axp-platform-explorer__docs-dl--json dt code,.axp-platform-explorer__docs-dl--json dd code{background-color:transparent;padding:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));word-break:break-all}.axp-platform-explorer__field-button{margin:0;display:inline-flex;appearance:none;align-items:center;border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding:0;text-align:start;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-medium, 500);font-weight:var(--font-weight-medium, 500)}.axp-platform-explorer__field-button code{background-color:transparent;padding:0;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.axp-platform-explorer__table-row--selected .axp-platform-explorer__field-button{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__field-button:hover{text-decoration:underline;text-underline-offset:3px}.axp-platform-explorer__field-title{margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__field-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__type-chip{display:inline-block;border-radius:.25rem;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 1.5);padding-block:calc(var(--spacing, .25rem) * .5);font-family:var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__unknown{color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__unknown{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__yes{display:inline-flex;align-items:center;gap:var(--spacing, .25rem);color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__yes i{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__prop-toggle,.axp-platform-explorer__prop-toggle-spacer{display:inline-flex;height:calc(var(--spacing, .25rem) * 7);width:calc(var(--spacing, .25rem) * 7);flex-shrink:0;align-items:center;justify-content:center;border-radius:var(--radius-md, .375rem)}.axp-platform-explorer__prop-toggle{cursor:pointer;appearance:none;border-style:var(--tw-border-style);border-width:0px;background-color:transparent;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__prop-toggle{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__prop-toggle:hover{background-color:rgba(var(--ax-sys-color-lighter-surface));color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__prop-toggle i{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-duration: .15s;transition-duration:.15s}.axp-platform-explorer__prop-toggle.is-open i{rotate:90deg}.axp-platform-explorer__prop-details{margin-inline-start:calc(var(--spacing, .25rem) * 9);display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding:calc(var(--spacing, .25rem) * 4)}.axp-platform-explorer__prop-flags{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-flag{display:inline-flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 2.5);padding-block:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__prop-pairs{margin:0;display:grid;column-gap:calc(var(--spacing, .25rem) * 6);row-gap:calc(var(--spacing, .25rem) * 2);grid-template-columns:minmax(7rem,11rem) minmax(0,1fr)}.axp-platform-explorer__prop-pairs dt{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__prop-pairs dt{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__prop-pairs dd{margin:0;min-width:0px}.axp-platform-explorer__prop-pairs code,.axp-platform-explorer__prop-card code,.axp-platform-explorer__prop-trigger code{border-radius:.25rem;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 1.5);padding-block:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));word-break:break-all}.axp-platform-explorer__prop-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-section-title{margin:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__prop-section-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__prop-stack{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-card{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}.axp-platform-explorer__prop-trigger{display:flex;flex-wrap:wrap;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__prop-json{margin:0;max-height:calc(var(--spacing, .25rem) * 48);overflow:auto;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding:calc(var(--spacing, .25rem) * 3);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-relaxed, 1.625);line-height:var(--leading-relaxed, 1.625);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.axp-platform-explorer__table--actions{min-width:36rem}.axp-platform-explorer__priority{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__priority{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__priority.is-primary{--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);color:rgba(var(--ax-sys-color-primary-surface))}@media(max-width:760px){.axp-platform-explorer__docs-side{width:calc(var(--spacing, .25rem) * 72)}.axp-platform-explorer__docs-plugins,.axp-platform-explorer__docs-list,.axp-platform-explorer__docs-actions{padding-inline:calc(var(--spacing, .25rem) * 5)}.axp-platform-explorer__docs-table-head{margin-inline:calc(var(--spacing, .25rem) * 5)}}@media(max-width:440px){.axp-platform-explorer__docs-toolbar{display:block}.axp-platform-explorer__docs-search{margin-top:calc(var(--spacing, .25rem) * 3);width:100%}}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@property --tw-duration{syntax: \"*\"; inherits: false;}@property --tw-leading{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-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial;--tw-duration: initial;--tw-leading: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
479
1363
  }], ctorParameters: () => [], propDecorators: { payload: [{ type: i0.Input, args: [{ isSignal: true, alias: "payload", required: false }] }] } });
480
1364
 
481
- //#region ---- Imports ----
482
1365
  //#endregion
483
- class AXPlatformExplorerPermissionsListDetailComponent {
1366
+ //#region ----- Property rows -----
1367
+ function asRecord(value) {
1368
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1369
+ return null;
1370
+ }
1371
+ return value;
1372
+ }
1373
+ function asText(value) {
1374
+ return typeof value === 'string' ? value.trim() : '';
1375
+ }
1376
+ function hasRequired(def) {
1377
+ if (def['required'] === true) {
1378
+ return true;
1379
+ }
1380
+ const validations = def['validations'];
1381
+ if (!Array.isArray(validations)) {
1382
+ return false;
1383
+ }
1384
+ return validations.some((rule) => asRecord(rule)?.['rule'] === 'required');
1385
+ }
1386
+ function dataTypeOf(def) {
1387
+ const dataType = def['dataType'];
1388
+ if (typeof dataType === 'string' && dataType.trim()) {
1389
+ return dataType.trim();
1390
+ }
1391
+ const iface = asRecord(def['interface']);
1392
+ const name = iface?.['name'];
1393
+ if (typeof name === 'string' && name.trim()) {
1394
+ return name.trim();
1395
+ }
1396
+ return undefined;
1397
+ }
1398
+ function joinNotes(def, extra, omitCondition = false) {
1399
+ const parts = [
1400
+ ...extra,
1401
+ omitCondition ? '' : asText(def['condition']),
1402
+ asText(def['notes']),
1403
+ ].filter(Boolean);
1404
+ return parts.length ? parts.join(' · ') : undefined;
1405
+ }
1406
+ function toIoRows(defs) {
1407
+ if (!Array.isArray(defs)) {
1408
+ return [];
1409
+ }
1410
+ const rows = [];
1411
+ const seen = new Set();
1412
+ for (const item of defs) {
1413
+ const def = asRecord(item);
1414
+ if (!def) {
1415
+ continue;
1416
+ }
1417
+ const name = asText(def['name']);
1418
+ if (!name || seen.has(name)) {
1419
+ continue;
1420
+ }
1421
+ seen.add(name);
1422
+ const title = asText(def['title']) || undefined;
1423
+ const dataType = dataTypeOf(def);
1424
+ const note = joinNotes(def, []);
1425
+ rows.push({
1426
+ name,
1427
+ ...(title ? { title } : {}),
1428
+ ...(dataType ? { dataType } : {}),
1429
+ required: hasRequired(def),
1430
+ ...(note ? { note } : {}),
1431
+ });
1432
+ }
1433
+ return rows;
1434
+ }
1435
+ function capabilityProperties(section) {
1436
+ const row = asRecord(section);
1437
+ if (!row || row['enabled'] !== true) {
1438
+ return [];
1439
+ }
1440
+ return toIoRows(row['definition']);
1441
+ }
1442
+ function queryOutputRows(query) {
1443
+ const columns = toIoRows(query['columns']);
1444
+ if (columns.length) {
1445
+ return columns;
1446
+ }
1447
+ const valueType = asText(query['valueType']);
1448
+ if (valueType) {
1449
+ return [{ name: 'value', dataType: valueType, required: false }];
1450
+ }
1451
+ return [];
1452
+ }
1453
+ function queryInputRows(query) {
1454
+ const args = capabilityProperties(query['args']);
1455
+ const filter = capabilityProperties(query['filter']);
1456
+ if (!args.length) {
1457
+ return filter;
1458
+ }
1459
+ if (!filter.length) {
1460
+ return args;
1461
+ }
1462
+ const seen = new Set(args.map((row) => row.name));
1463
+ return [...args, ...filter.filter((row) => !seen.has(row.name))];
1464
+ }
1465
+ function emptyTables(showOutput) {
1466
+ return {
1467
+ input: [],
1468
+ output: [],
1469
+ showOutput,
1470
+ contributions: [],
1471
+ extensionKeys: [],
1472
+ };
1473
+ }
1474
+ //#endregion
1475
+ //#region ----- Plugin contributions -----
1476
+ function parseJsonObject(value) {
1477
+ if (typeof value === 'string') {
1478
+ const trimmed = value.trim();
1479
+ if (!trimmed) {
1480
+ return null;
1481
+ }
1482
+ try {
1483
+ return asRecord(JSON.parse(trimmed));
1484
+ }
1485
+ catch {
1486
+ return null;
1487
+ }
1488
+ }
1489
+ return asRecord(value);
1490
+ }
1491
+ function namedRows(list, nameKeys, extraKind) {
1492
+ if (!Array.isArray(list)) {
1493
+ return [];
1494
+ }
1495
+ const rows = [];
1496
+ for (const item of list) {
1497
+ const def = asRecord(item);
1498
+ if (!def) {
1499
+ continue;
1500
+ }
1501
+ let name = '';
1502
+ for (const key of nameKeys) {
1503
+ name = asText(def[key]);
1504
+ if (name) {
1505
+ break;
1506
+ }
1507
+ }
1508
+ if (!name) {
1509
+ continue;
1510
+ }
1511
+ const title = asText(def['title']) || undefined;
1512
+ const fromOption = asText(def['nameFromOption'] || def['idFromOption']);
1513
+ const extra = extraKind === 'scope'
1514
+ ? asText(def['scope']) || undefined
1515
+ : extraKind === 'condition'
1516
+ ? asText(def['condition']) || undefined
1517
+ : undefined;
1518
+ const note = joinNotes(def, fromOption ? [`options.${fromOption}`] : [], extraKind === 'condition');
1519
+ const dataType = dataTypeOf(def);
1520
+ rows.push({
1521
+ name,
1522
+ ...(title ? { title } : {}),
1523
+ ...(dataType ? { dataType } : {}),
1524
+ required: hasRequired(def),
1525
+ ...(extra && extraKind ? { extra, extraKind } : {}),
1526
+ ...(note ? { note } : {}),
1527
+ });
1528
+ }
1529
+ return rows;
1530
+ }
1531
+ function contributionSections(json) {
1532
+ const parsed = parseJsonObject(json);
1533
+ if (!parsed) {
1534
+ return { contributions: [], extensionKeys: [] };
1535
+ }
1536
+ const contributions = [];
1537
+ const add = (kind, rows, extraKey) => {
1538
+ if (!rows.length) {
1539
+ return;
1540
+ }
1541
+ contributions.push({ kind, rows, ...(extraKey ? { extraKey } : {}) });
1542
+ };
1543
+ add('properties', namedRows(parsed['properties'], ['name']));
1544
+ add('columns', namedRows(parsed['columns'], ['name'], 'condition'), 'condition');
1545
+ add('actions', namedRows(parsed['actions'], ['name'], 'scope'), 'scope');
1546
+ add('groups', namedRows(parsed['groups'], ['id', 'name']));
1547
+ add('pages', namedRows(parsed['pages'], ['name'], 'condition'), 'condition');
1548
+ add('related-entities', namedRows(parsed['relatedEntities'], ['entity', 'name']));
1549
+ const extensionKeys = Array.isArray(parsed['extensionKeys'])
1550
+ ? parsed['extensionKeys'].filter((key) => typeof key === 'string' && key.trim().length > 0)
1551
+ : [];
1552
+ return { contributions, extensionKeys };
1553
+ }
1554
+ //#endregion
1555
+ //#region ----- Catalog I/O -----
1556
+ function catalogIoFromDetail(kind, detail) {
1557
+ if (!detail) {
1558
+ return null;
1559
+ }
1560
+ if (kind === 'command' && detail.command) {
1561
+ return {
1562
+ ...emptyTables(true),
1563
+ input: toIoRows(detail.command.inputOptionDefinitions),
1564
+ output: toIoRows(detail.command.outputOptionDefinitions),
1565
+ };
1566
+ }
1567
+ if (kind === 'query' && detail.query) {
1568
+ const query = detail.query;
1569
+ return {
1570
+ ...emptyTables(true),
1571
+ input: queryInputRows(query),
1572
+ output: queryOutputRows(query),
1573
+ };
1574
+ }
1575
+ if (kind === 'widget' && detail.widget) {
1576
+ return {
1577
+ ...emptyTables(true),
1578
+ input: toIoRows(detail.widget.inputOptionDefinitions),
1579
+ output: toIoRows(detail.widget.outputOptionDefinitions),
1580
+ };
1581
+ }
1582
+ if (kind === 'plugin' && detail.plugin) {
1583
+ const contributed = contributionSections(detail.plugin.contributionsJson);
1584
+ return {
1585
+ ...emptyTables(false),
1586
+ input: toIoRows(detail.plugin.optionDefinitions),
1587
+ contributions: contributed.contributions,
1588
+ extensionKeys: contributed.extensionKeys,
1589
+ };
1590
+ }
1591
+ return null;
1592
+ }
1593
+ function stringList(value) {
1594
+ if (!Array.isArray(value)) {
1595
+ return [];
1596
+ }
1597
+ const items = [];
1598
+ const seen = new Set();
1599
+ for (const item of value) {
1600
+ const text = asText(item);
1601
+ if (!text || seen.has(text)) {
1602
+ continue;
1603
+ }
1604
+ seen.add(text);
1605
+ items.push(text);
1606
+ }
1607
+ return items;
1608
+ }
1609
+ function categoryLabels(value) {
1610
+ if (!Array.isArray(value)) {
1611
+ return [];
1612
+ }
1613
+ const items = [];
1614
+ const seen = new Set();
1615
+ for (const item of value) {
1616
+ const text = asText(item) || asText(asRecord(item)?.['title']) || asText(asRecord(item)?.['name']);
1617
+ if (!text || seen.has(text)) {
1618
+ continue;
1619
+ }
1620
+ seen.add(text);
1621
+ items.push(text);
1622
+ }
1623
+ return items;
1624
+ }
1625
+ //#endregion
1626
+
1627
+ //#region ----- Imports -----
1628
+ //#endregion
1629
+ //#region ----- Component -----
1630
+ class AXPlatformExplorerCatalogDetailComponent {
1631
+ //#endregion
484
1632
  constructor() {
485
- this.moduleName = input.required(/* @ts-ignore */
486
- ...(ngDevMode ? [{ debugName: "moduleName" }] : /* istanbul ignore next */ []));
1633
+ this.kind = input.required(/* @ts-ignore */
1634
+ ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
1635
+ this.item = input(/* @ts-ignore */
1636
+ ...(ngDevMode ? [undefined, { debugName: "item" }] : /* istanbul ignore next */ []));
1637
+ //#region ----- Services & Dependencies -----
487
1638
  this.dataService = inject(PlatformExplorerDataService);
488
- this.dataSource = new AXDataSource({
489
- key: 'name',
490
- pageSize: 20,
491
- load: async (e) => {
492
- const name = this.moduleName();
493
- if (!name)
494
- return { items: [], total: 0 };
495
- const list = await this.dataService.getPermissionsForModule(name);
496
- const skip = e?.skip ?? 0;
497
- const take = e?.take ?? 20;
498
- const items = list.slice(skip, skip + take);
499
- return { items, total: list.length };
500
- },
501
- });
1639
+ //#endregion
1640
+ //#region ----- State Signals -----
1641
+ this.detail = signal(null, /* @ts-ignore */
1642
+ ...(ngDevMode ? [{ debugName: "detail" }] : /* istanbul ignore next */ []));
1643
+ this.loading = signal(false, /* @ts-ignore */
1644
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
1645
+ this.loadError = signal(false, /* @ts-ignore */
1646
+ ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
1647
+ //#endregion
1648
+ //#region ----- Computed Properties -----
1649
+ this.ioTables = computed(() => catalogIoFromDetail(this.kind(), this.detail()), /* @ts-ignore */
1650
+ ...(ngDevMode ? [{ debugName: "ioTables" }] : /* istanbul ignore next */ []));
502
1651
  effect(() => {
503
- this.moduleName();
504
- this.dataSource.refresh();
1652
+ const kind = this.kind();
1653
+ const item = this.item();
1654
+ void this.load(kind, item?.name);
505
1655
  });
506
1656
  }
507
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerPermissionsListDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
508
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerPermissionsListDetailComponent, isStandalone: true, selector: "axp-platform-explorer-permissions-list-detail", inputs: { moduleName: { classPropertyName: "moduleName", publicName: "moduleName", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: `
509
- <div class="flex flex-1 min-h-0 pt-0 overflow-auto">
510
- <ax-data-table
511
- class="flex-1 border border-lightest rounded-md overflow-hidden"
512
- [paging]="true"
513
- [allowReordering]="true"
514
- [dataSource]="dataSource"
515
- >
516
- <ax-text-column [dataField]="'name'" [caption]="'@platform-dev-tools:module.terms.name' | translate | async" [width]="'420px'" [allowResizing]="true"></ax-text-column>
517
- <ax-text-column [dataField]="'title'" [caption]="'@platform-dev-tools:module.terms.title' | translate | async" [cellTemplate]="titleTpl" [width]="'260px'" [allowResizing]="true"></ax-text-column>
518
- <ax-text-column [dataField]="'description'" [caption]="'@platform-dev-tools:module.terms.description' | translate | async" [cellTemplate]="descTpl" [width]="'380px'" [allowResizing]="true"></ax-text-column>
519
- </ax-data-table>
520
- </div>
521
- <ng-template #titleTpl let-item>
522
- {{ $safeNavigationMigration(item.data?.title) | translate | async }}</ng-template>
523
- <ng-template #descTpl let-item>
524
- @if (item.data?.description) { {{ item.data.description | translate | async }} } @else { – }
525
- </ng-template>
526
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i1$1.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i1$1.AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "translate", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1657
+ record() {
1658
+ const detail = this.detail();
1659
+ if (!detail) {
1660
+ return null;
1661
+ }
1662
+ return (detail.command ?? detail.query ?? detail.plugin ?? detail.widget ?? null);
1663
+ }
1664
+ displayTitle(record) {
1665
+ return String(record['catalogTitle'] ?? record['title'] ?? record['name'] ?? '');
1666
+ }
1667
+ //#region ----- Helpers -----
1668
+ asText(value) {
1669
+ return typeof value === 'string' ? value : '';
1670
+ }
1671
+ asStringList(value) {
1672
+ return stringList(value);
1673
+ }
1674
+ asCategoryLabels(value) {
1675
+ return categoryLabels(value);
1676
+ }
1677
+ isNumber(value) {
1678
+ return typeof value === 'number' && Number.isFinite(value);
1679
+ }
1680
+ //#endregion
1681
+ //#region ----- Data Loading -----
1682
+ async load(kind, name) {
1683
+ if (!name) {
1684
+ this.detail.set(null);
1685
+ this.loading.set(false);
1686
+ this.loadError.set(false);
1687
+ return;
1688
+ }
1689
+ this.loading.set(true);
1690
+ this.loadError.set(false);
1691
+ this.detail.set(null);
1692
+ try {
1693
+ const detail = await this.dataService.getCatalogItem(kind, name);
1694
+ if (this.item()?.name === name && this.kind() === kind) {
1695
+ this.detail.set(detail);
1696
+ }
1697
+ }
1698
+ catch {
1699
+ if (this.item()?.name === name && this.kind() === kind) {
1700
+ this.loadError.set(true);
1701
+ }
1702
+ }
1703
+ finally {
1704
+ if (this.item()?.name === name && this.kind() === kind) {
1705
+ this.loading.set(false);
1706
+ }
1707
+ }
1708
+ }
1709
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerCatalogDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1710
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerCatalogDetailComponent, isStandalone: true, selector: "axp-platform-explorer-catalog-detail", inputs: { kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: true, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: "<div class=\"flex flex-col flex-1 min-h-0\">\n <!-- ----- Loading catalog row ----- -->\n @if (loading()) {\n <div class=\"flex flex-1 items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- Catalog row fields ----- -->\n @else if (record(); as row) {\n <section class=\"flex flex-col gap-6 p-4 overflow-auto\">\n <dl class=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 m-0\">\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd class=\"m-0\"><code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ row['name'] }}</code></dd>\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.title' | translate | async }}</dt>\n <dd class=\"m-0\">{{ displayTitle(row) | translate | async }}</dd>\n @if (row['description']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.description' | translate | async }}</dt>\n <dd class=\"m-0\">{{ asText(row['description']) | translate | async }}</dd>\n }\n @if (row['executionMode']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.execution-mode' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['executionMode'] }}</dd>\n }\n @if (row['sourceModule']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.source-module' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['sourceModule'] }}</dd>\n }\n @if (row['type']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.type' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['type'] }}</dd>\n }\n @if (row['dataType']) {\n <dt class=\"font-semibold text-muted\">\n {{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}\n </dt>\n <dd class=\"m-0\">\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ row['dataType'] }}</span>\n </dd>\n }\n @if (asText(row['icon']); as icon) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.icon' | translate | async }}</dt>\n <dd class=\"m-0 flex items-center gap-2\">\n <i [ngClass]=\"icon\" aria-hidden=\"true\"></i>\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\" dir=\"ltr\">{{ icon }}</code>\n </dd>\n }\n @if (isNumber(row['order'])) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.order' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['order'] }}</dd>\n }\n @if (row['defaultFilterWidgetName']) {\n <dt class=\"font-semibold text-muted\">\n {{ '@platform-dev-tools:module.terms.default-filter-widget' | translate | async }}\n </dt>\n <dd class=\"m-0\">\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\" dir=\"ltr\">{{ row['defaultFilterWidgetName'] }}</code>\n </dd>\n }\n @if (asStringList(row['groups']); as groups) {\n <!--->\n <!-- ----- Widget group tags ----- -->\n @if (groups.length) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.groups' | translate | async }}</dt>\n <dd class=\"m-0\">\n <div class=\"axp-platform-explorer__catalog-chips\">\n @for (group of groups; track group) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ group }}</span>\n }\n </div>\n </dd>\n }\n }\n @if (asCategoryLabels(row['categories']); as categories) {\n <!--->\n <!-- ----- Catalog categories ----- -->\n @if (categories.length) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.categories' | translate | async }}</dt>\n <dd class=\"m-0\">\n <div class=\"axp-platform-explorer__catalog-chips\">\n @for (category of categories; track category) {\n <span class=\"axp-platform-explorer__type-chip\">{{ category | translate | async }}</span>\n }\n </div>\n </dd>\n }\n }\n </dl>\n\n <!--->\n <!-- ----- Input, output, and plugin contribution tables ----- -->\n @if (ioTables(); as io) {\n <div class=\"axp-platform-explorer__catalog-io\">\n <section>\n <h2>{{ '@platform-dev-tools:module.terms.input' | translate | async }}</h2>\n <!--->\n <!-- ----- Empty input list ----- -->\n @if (io.input.length === 0) {\n <p class=\"axp-platform-explorer__docs-empty axp-platform-explorer__catalog-io-empty\">\n {{ '@platform-dev-tools:module.messages.no-input' | translate | async }}\n </p>\n }\n <!-- ----- Input rows ----- -->\n @else {\n <ng-container *ngTemplateOutlet=\"schemaTable; context: { rows: io.input }\" />\n }\n </section>\n <!--->\n <!-- ----- Output contract for commands, queries, and widgets ----- -->\n @if (io.showOutput) {\n <section>\n <h2>{{ '@platform-dev-tools:module.terms.output' | translate | async }}</h2>\n <!--->\n <!-- ----- Empty output list ----- -->\n @if (io.output.length === 0) {\n <p class=\"axp-platform-explorer__docs-empty axp-platform-explorer__catalog-io-empty\">\n {{ '@platform-dev-tools:module.messages.no-output' | translate | async }}\n </p>\n }\n <!-- ----- Output rows ----- -->\n @else {\n <ng-container *ngTemplateOutlet=\"schemaTable; context: { rows: io.output }\" />\n }\n </section>\n }\n <!--->\n <!-- ----- Plugin host contributions from the catalog definition ----- -->\n @if (io.contributions.length) {\n @for (section of io.contributions; track section.kind) {\n <section>\n <h2>{{ ('@platform-dev-tools:module.contributions.' + section.kind) | translate | async }}</h2>\n <!--->\n <!-- ----- Contributed properties use the schema table ----- -->\n @if (section.kind === 'properties') {\n <ng-container *ngTemplateOutlet=\"schemaTable; context: { rows: section.rows }\" />\n }\n <!-- ----- Columns, actions, groups, pages, related entities ----- -->\n @else {\n <ng-container\n *ngTemplateOutlet=\"itemTable; context: { rows: section.rows, extraKey: section.extraKey }\"\n />\n }\n </section>\n }\n }\n <!-- ----- Plugin declared no host contributions ----- -->\n @else if (!io.showOutput) {\n <p class=\"axp-platform-explorer__docs-empty axp-platform-explorer__catalog-io-empty\">\n {{ '@platform-dev-tools:module.messages.no-contributions' | translate | async }}\n </p>\n }\n <!--->\n <!-- ----- Middleware extension keys ----- -->\n @if (io.extensionKeys.length) {\n <section>\n <h2>{{ '@platform-dev-tools:module.contributions.extension-keys' | translate | async }}</h2>\n <div class=\"axp-platform-explorer__catalog-chips\">\n @for (key of io.extensionKeys; track key) {\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\" dir=\"ltr\">{{ key }}</code>\n }\n </div>\n </section>\n }\n </div>\n }\n </section>\n }\n</div>\n\n<ng-template #schemaTable let-rows=\"rows\">\n <div class=\"axp-platform-explorer__table-wrap\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead>\n <tr>\n <th>{{ '@platform-dev-tools:module.terms.field' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.terms.required' | translate | async }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows; track row.name) {\n <tr>\n <td>\n <ng-container *ngTemplateOutlet=\"nameCell; context: { row: row }\" />\n </td>\n <td>\n <!--->\n <!-- ----- Stored data type chip ----- -->\n @if (row.dataType) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ row.dataType }}</span>\n }\n <!-- ----- Missing data type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n <td>\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: row.required }\" />\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n</ng-template>\n\n<ng-template #itemTable let-rows=\"rows\" let-extraKey=\"extraKey\">\n <div class=\"axp-platform-explorer__table-wrap\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead>\n <tr>\n <th>{{ '@platform-dev-tools:module.terms.field' | translate | async }}</th>\n <!--->\n <!-- ----- Extra column for scope or condition ----- -->\n @if (extraKey) {\n <th>{{ ('@platform-dev-tools:module.contributions.' + extraKey) | translate | async }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rows; track row.name) {\n <tr>\n <td>\n <ng-container *ngTemplateOutlet=\"nameCell; context: { row: row }\" />\n </td>\n <!--->\n <!-- ----- Extra cell when the section declares one ----- -->\n @if (extraKey) {\n <td>\n <!--->\n <!-- ----- Action scope ----- -->\n @if (row.extraKind === 'scope' && row.extra) {\n {{ ('@platform-dev-tools:module.contributions.scopes.' + row.extra) | translate | async }}\n }\n <!-- ----- Condition expression ----- -->\n @else if (row.extra) {\n <code dir=\"ltr\">{{ row.extra }}</code>\n }\n <!-- ----- Missing extra ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n</ng-template>\n\n<ng-template #nameCell let-row=\"row\">\n <code dir=\"ltr\">{{ row.name }}</code>\n <!--->\n <!-- ----- Translated title under the field name ----- -->\n @if (row.title) {\n <div class=\"axp-platform-explorer__field-title\">\n {{ row.title | translate | async }}\n </div>\n }\n <!--->\n <!-- ----- Definition notes and option-sourced names ----- -->\n @if (row.note) {\n <div class=\"axp-platform-explorer__field-note\" dir=\"ltr\">{{ row.note }}</div>\n }\n</ng-template>\n\n<ng-template #boolCell let-value=\"value\">\n <!-- ----- Affirmative flag ----- -->\n @if (value === true) {\n <span class=\"axp-platform-explorer__yes\">\n <i class=\"fa-light fa-check\" aria-hidden=\"true\"></i>\n {{ '@platform-dev-tools:module.property-details.yes' | translate | async }}\n </span>\n }\n <!-- ----- Negative flag ----- -->\n @else {\n {{ '@platform-dev-tools:module.property-details.no' | translate | async }}\n }\n</ng-template>\n", styles: ["@layer properties;.axp-platform-explorer__catalog-io{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 6)}.axp-platform-explorer__catalog-io h2{margin:0;margin-bottom:calc(var(--spacing, .25rem) * 2);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-platform-explorer__catalog-io-empty{padding-block:calc(var(--spacing, .25rem) * 4);text-align:start}.axp-platform-explorer__table-wrap{overflow-x:auto;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}.axp-platform-explorer__table{width:100%;min-width:36rem;border-collapse:collapse;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.axp-platform-explorer__table--fields{min-width:0px;table-layout:fixed}.axp-platform-explorer__table--fields th:first-child{width:46%}.axp-platform-explorer__table th{background-color:rgba(var(--ax-sys-color-lighter-surface));text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgba(var(--ax-sys-color-text-default));border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__table th{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 4);vertical-align:middle;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0px}.axp-platform-explorer__field-title{margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__field-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__field-note{margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));word-break:break-all;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__field-note{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__catalog-chips{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 1.5)}.axp-platform-explorer__type-chip{display:inline-block;border-radius:.25rem;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 1.5);padding-block:calc(var(--spacing, .25rem) * .5);font-family:var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__unknown{color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__unknown{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__yes{display:inline-flex;align-items:center;gap:var(--spacing, .25rem);color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__yes i{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__docs-empty{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-empty{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@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;--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i2$1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1711
+ }
1712
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerCatalogDetailComponent, decorators: [{
1713
+ type: Component,
1714
+ args: [{ selector: 'axp-platform-explorer-catalog-detail', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'flex flex-col flex-1 min-h-0' }, imports: [CommonModule, AXTranslationModule, AXLoadingModule, AXPStateMessageComponent], template: "<div class=\"flex flex-col flex-1 min-h-0\">\n <!-- ----- Loading catalog row ----- -->\n @if (loading()) {\n <div class=\"flex flex-1 items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- Catalog row fields ----- -->\n @else if (record(); as row) {\n <section class=\"flex flex-col gap-6 p-4 overflow-auto\">\n <dl class=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 m-0\">\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.name' | translate | async }}</dt>\n <dd class=\"m-0\"><code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\">{{ row['name'] }}</code></dd>\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.title' | translate | async }}</dt>\n <dd class=\"m-0\">{{ displayTitle(row) | translate | async }}</dd>\n @if (row['description']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.description' | translate | async }}</dt>\n <dd class=\"m-0\">{{ asText(row['description']) | translate | async }}</dd>\n }\n @if (row['executionMode']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.execution-mode' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['executionMode'] }}</dd>\n }\n @if (row['sourceModule']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.source-module' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['sourceModule'] }}</dd>\n }\n @if (row['type']) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.type' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['type'] }}</dd>\n }\n @if (row['dataType']) {\n <dt class=\"font-semibold text-muted\">\n {{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}\n </dt>\n <dd class=\"m-0\">\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ row['dataType'] }}</span>\n </dd>\n }\n @if (asText(row['icon']); as icon) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.icon' | translate | async }}</dt>\n <dd class=\"m-0 flex items-center gap-2\">\n <i [ngClass]=\"icon\" aria-hidden=\"true\"></i>\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\" dir=\"ltr\">{{ icon }}</code>\n </dd>\n }\n @if (isNumber(row['order'])) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.order' | translate | async }}</dt>\n <dd class=\"m-0\">{{ row['order'] }}</dd>\n }\n @if (row['defaultFilterWidgetName']) {\n <dt class=\"font-semibold text-muted\">\n {{ '@platform-dev-tools:module.terms.default-filter-widget' | translate | async }}\n </dt>\n <dd class=\"m-0\">\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\" dir=\"ltr\">{{ row['defaultFilterWidgetName'] }}</code>\n </dd>\n }\n @if (asStringList(row['groups']); as groups) {\n <!--->\n <!-- ----- Widget group tags ----- -->\n @if (groups.length) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.groups' | translate | async }}</dt>\n <dd class=\"m-0\">\n <div class=\"axp-platform-explorer__catalog-chips\">\n @for (group of groups; track group) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ group }}</span>\n }\n </div>\n </dd>\n }\n }\n @if (asCategoryLabels(row['categories']); as categories) {\n <!--->\n <!-- ----- Catalog categories ----- -->\n @if (categories.length) {\n <dt class=\"font-semibold text-muted\">{{ '@platform-dev-tools:module.terms.categories' | translate | async }}</dt>\n <dd class=\"m-0\">\n <div class=\"axp-platform-explorer__catalog-chips\">\n @for (category of categories; track category) {\n <span class=\"axp-platform-explorer__type-chip\">{{ category | translate | async }}</span>\n }\n </div>\n </dd>\n }\n }\n </dl>\n\n <!--->\n <!-- ----- Input, output, and plugin contribution tables ----- -->\n @if (ioTables(); as io) {\n <div class=\"axp-platform-explorer__catalog-io\">\n <section>\n <h2>{{ '@platform-dev-tools:module.terms.input' | translate | async }}</h2>\n <!--->\n <!-- ----- Empty input list ----- -->\n @if (io.input.length === 0) {\n <p class=\"axp-platform-explorer__docs-empty axp-platform-explorer__catalog-io-empty\">\n {{ '@platform-dev-tools:module.messages.no-input' | translate | async }}\n </p>\n }\n <!-- ----- Input rows ----- -->\n @else {\n <ng-container *ngTemplateOutlet=\"schemaTable; context: { rows: io.input }\" />\n }\n </section>\n <!--->\n <!-- ----- Output contract for commands, queries, and widgets ----- -->\n @if (io.showOutput) {\n <section>\n <h2>{{ '@platform-dev-tools:module.terms.output' | translate | async }}</h2>\n <!--->\n <!-- ----- Empty output list ----- -->\n @if (io.output.length === 0) {\n <p class=\"axp-platform-explorer__docs-empty axp-platform-explorer__catalog-io-empty\">\n {{ '@platform-dev-tools:module.messages.no-output' | translate | async }}\n </p>\n }\n <!-- ----- Output rows ----- -->\n @else {\n <ng-container *ngTemplateOutlet=\"schemaTable; context: { rows: io.output }\" />\n }\n </section>\n }\n <!--->\n <!-- ----- Plugin host contributions from the catalog definition ----- -->\n @if (io.contributions.length) {\n @for (section of io.contributions; track section.kind) {\n <section>\n <h2>{{ ('@platform-dev-tools:module.contributions.' + section.kind) | translate | async }}</h2>\n <!--->\n <!-- ----- Contributed properties use the schema table ----- -->\n @if (section.kind === 'properties') {\n <ng-container *ngTemplateOutlet=\"schemaTable; context: { rows: section.rows }\" />\n }\n <!-- ----- Columns, actions, groups, pages, related entities ----- -->\n @else {\n <ng-container\n *ngTemplateOutlet=\"itemTable; context: { rows: section.rows, extraKey: section.extraKey }\"\n />\n }\n </section>\n }\n }\n <!-- ----- Plugin declared no host contributions ----- -->\n @else if (!io.showOutput) {\n <p class=\"axp-platform-explorer__docs-empty axp-platform-explorer__catalog-io-empty\">\n {{ '@platform-dev-tools:module.messages.no-contributions' | translate | async }}\n </p>\n }\n <!--->\n <!-- ----- Middleware extension keys ----- -->\n @if (io.extensionKeys.length) {\n <section>\n <h2>{{ '@platform-dev-tools:module.contributions.extension-keys' | translate | async }}</h2>\n <div class=\"axp-platform-explorer__catalog-chips\">\n @for (key of io.extensionKeys; track key) {\n <code class=\"text-xs bg-lighter px-1.5 py-0.5 rounded\" dir=\"ltr\">{{ key }}</code>\n }\n </div>\n </section>\n }\n </div>\n }\n </section>\n }\n</div>\n\n<ng-template #schemaTable let-rows=\"rows\">\n <div class=\"axp-platform-explorer__table-wrap\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead>\n <tr>\n <th>{{ '@platform-dev-tools:module.terms.field' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.property-details.data-type' | translate | async }}</th>\n <th>{{ '@platform-dev-tools:module.terms.required' | translate | async }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows; track row.name) {\n <tr>\n <td>\n <ng-container *ngTemplateOutlet=\"nameCell; context: { row: row }\" />\n </td>\n <td>\n <!--->\n <!-- ----- Stored data type chip ----- -->\n @if (row.dataType) {\n <span class=\"axp-platform-explorer__type-chip\" dir=\"ltr\">{{ row.dataType }}</span>\n }\n <!-- ----- Missing data type ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n <td>\n <ng-container *ngTemplateOutlet=\"boolCell; context: { value: row.required }\" />\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n</ng-template>\n\n<ng-template #itemTable let-rows=\"rows\" let-extraKey=\"extraKey\">\n <div class=\"axp-platform-explorer__table-wrap\">\n <table class=\"axp-platform-explorer__table axp-platform-explorer__table--fields\">\n <thead>\n <tr>\n <th>{{ '@platform-dev-tools:module.terms.field' | translate | async }}</th>\n <!--->\n <!-- ----- Extra column for scope or condition ----- -->\n @if (extraKey) {\n <th>{{ ('@platform-dev-tools:module.contributions.' + extraKey) | translate | async }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rows; track row.name) {\n <tr>\n <td>\n <ng-container *ngTemplateOutlet=\"nameCell; context: { row: row }\" />\n </td>\n <!--->\n <!-- ----- Extra cell when the section declares one ----- -->\n @if (extraKey) {\n <td>\n <!--->\n <!-- ----- Action scope ----- -->\n @if (row.extraKind === 'scope' && row.extra) {\n {{ ('@platform-dev-tools:module.contributions.scopes.' + row.extra) | translate | async }}\n }\n <!-- ----- Condition expression ----- -->\n @else if (row.extra) {\n <code dir=\"ltr\">{{ row.extra }}</code>\n }\n <!-- ----- Missing extra ----- -->\n @else {\n <span class=\"axp-platform-explorer__unknown\">\u2013</span>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n</ng-template>\n\n<ng-template #nameCell let-row=\"row\">\n <code dir=\"ltr\">{{ row.name }}</code>\n <!--->\n <!-- ----- Translated title under the field name ----- -->\n @if (row.title) {\n <div class=\"axp-platform-explorer__field-title\">\n {{ row.title | translate | async }}\n </div>\n }\n <!--->\n <!-- ----- Definition notes and option-sourced names ----- -->\n @if (row.note) {\n <div class=\"axp-platform-explorer__field-note\" dir=\"ltr\">{{ row.note }}</div>\n }\n</ng-template>\n\n<ng-template #boolCell let-value=\"value\">\n <!-- ----- Affirmative flag ----- -->\n @if (value === true) {\n <span class=\"axp-platform-explorer__yes\">\n <i class=\"fa-light fa-check\" aria-hidden=\"true\"></i>\n {{ '@platform-dev-tools:module.property-details.yes' | translate | async }}\n </span>\n }\n <!-- ----- Negative flag ----- -->\n @else {\n {{ '@platform-dev-tools:module.property-details.no' | translate | async }}\n }\n</ng-template>\n", styles: ["@layer properties;.axp-platform-explorer__catalog-io{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 6)}.axp-platform-explorer__catalog-io h2{margin:0;margin-bottom:calc(var(--spacing, .25rem) * 2);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-platform-explorer__catalog-io-empty{padding-block:calc(var(--spacing, .25rem) * 4);text-align:start}.axp-platform-explorer__table-wrap{overflow-x:auto;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}.axp-platform-explorer__table{width:100%;min-width:36rem;border-collapse:collapse;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.axp-platform-explorer__table--fields{min-width:0px;table-layout:fixed}.axp-platform-explorer__table--fields th:first-child{width:46%}.axp-platform-explorer__table th{background-color:rgba(var(--ax-sys-color-lighter-surface));text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgba(var(--ax-sys-color-text-default));border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__table th{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 4);vertical-align:middle;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0px}.axp-platform-explorer__field-title{margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__field-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__field-note{margin-top:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));word-break:break-all;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__field-note{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__catalog-chips{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 1.5)}.axp-platform-explorer__type-chip{display:inline-block;border-radius:.25rem;background-color:rgba(var(--ax-sys-color-lighter-surface));padding-inline:calc(var(--spacing, .25rem) * 1.5);padding-block:calc(var(--spacing, .25rem) * .5);font-family:var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__unknown{color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__unknown{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__yes{display:inline-flex;align-items:center;gap:var(--spacing, .25rem);color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__yes i{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}.axp-platform-explorer__docs-empty{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__docs-empty{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@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;--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1715
+ }], ctorParameters: () => [], propDecorators: { kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: true }] }], item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }] } });
1716
+
1717
+ //#region ----- Constants -----
1718
+ const EXPLORER_ENTITY_NODE_TYPE = 'explorer-entity';
1719
+ /** Crow's-foot / UML multiplicity shown on ERD ports (not i18n). */
1720
+ const ERD_CARDINALITY_ONE = '1..1';
1721
+ const ERD_CARDINALITY_MANY = '0..*';
1722
+ const NODE_W = 280;
1723
+ const NODE_HEAD_H = 52;
1724
+ /** Room per connector so relation names on parallel edges do not touch. */
1725
+ const PORT_SLOT_H = 24;
1726
+ const PORT_BAND_PAD = 28;
1727
+ const NODE_GAP_Y = 40;
1728
+ const EXTERNAL_GAP_X = 160;
1729
+ /** Grid spacing for entities that have no relations. */
1730
+ const ISOLATED_GAP_X = 48;
1731
+ const ISOLATED_GAP_Y = 88;
1732
+ const INTERNAL_ORIGIN = { x: 48, y: 48 };
1733
+ //#endregion
1734
+ //#region ----- Node helpers -----
1735
+ function isExplorerEntityNode(node) {
1736
+ return node.type === EXPLORER_ENTITY_NODE_TYPE;
1737
+ }
1738
+ function explorerEntityData(node) {
1739
+ const data = node.data;
1740
+ return data?.kind === 'entity' ? data : null;
1741
+ }
1742
+ //#endregion
1743
+ //#region ----- Build diagram -----
1744
+ function relationName(edge) {
1745
+ const labeled = edge.label?.trim();
1746
+ if (labeled) {
1747
+ return labeled;
1748
+ }
1749
+ const dot = edge.to.lastIndexOf('.');
1750
+ return dot > 0 ? edge.to.slice(dot + 1) : edge.to;
1751
+ }
1752
+ function sourceCardinality(kind) {
1753
+ return kind === 'lookup' ? ERD_CARDINALITY_MANY : ERD_CARDINALITY_ONE;
1754
+ }
1755
+ function targetCardinality(kind) {
1756
+ return kind === 'lookup' ? ERD_CARDINALITY_ONE : ERD_CARDINALITY_MANY;
1757
+ }
1758
+ function outboundPortId(edge) {
1759
+ return `${edge.from}:out:${edge.kind}:${edge.to}:${edge.label ?? ''}`;
1760
+ }
1761
+ function inboundPortId(edge) {
1762
+ return `${edge.to}:in:${edge.kind}:${edge.from}:${edge.label ?? ''}`;
1763
+ }
1764
+ function portRatio(index, count) {
1765
+ if (count <= 1) {
1766
+ return 0.5;
1767
+ }
1768
+ return (index + 1) / (count + 1);
1769
+ }
1770
+ function inboundPorts(incoming) {
1771
+ return incoming.map((edge, index) => ({
1772
+ id: inboundPortId(edge),
1773
+ kind: 'inbound',
1774
+ side: 'left',
1775
+ ratio: portRatio(index, incoming.length),
1776
+ label: targetCardinality(edge.kind),
1777
+ allowMultiple: false,
1778
+ readonly: true,
1779
+ }));
1780
+ }
1781
+ function outboundPorts(outgoing) {
1782
+ return outgoing.map((edge, index) => ({
1783
+ id: outboundPortId(edge),
1784
+ kind: 'outbound',
1785
+ side: 'right',
1786
+ ratio: portRatio(index, outgoing.length),
1787
+ label: sourceCardinality(edge.kind),
1788
+ readonly: true,
1789
+ }));
1790
+ }
1791
+ function lookupFieldKeys(edges) {
1792
+ const keys = new Set();
1793
+ for (const edge of edges) {
1794
+ if (edge.kind === 'lookup' && edge.label) {
1795
+ keys.add(`${edge.from}\0${edge.label}`);
1796
+ }
1797
+ }
1798
+ return keys;
1799
+ }
1800
+ function cardHeight(incoming, outgoing) {
1801
+ const slots = Math.max(incoming, outgoing);
1802
+ return slots <= 1 ? NODE_HEAD_H : Math.max(NODE_HEAD_H, PORT_BAND_PAD + slots * PORT_SLOT_H);
1803
+ }
1804
+ function toEntityData(item, moduleTitle, skipLookupFields) {
1805
+ return {
1806
+ kind: 'entity',
1807
+ fullName: item.fullName,
1808
+ name: item.name,
1809
+ title: item.title,
1810
+ module: item.module,
1811
+ moduleTitle,
1812
+ external: item.external,
1813
+ icon: item.icon || 'fa-light fa-table',
1814
+ properties: (item.properties ?? []).filter((property) => !skipLookupFields.has(`${item.fullName}\0${property.name}`)),
1815
+ };
1816
+ }
1817
+ function entityNode(data, outgoing, incoming) {
1818
+ const color = data.external ? 'warning' : 'primary';
1819
+ return {
1820
+ id: data.fullName,
1821
+ type: EXPLORER_ENTITY_NODE_TYPE,
1822
+ title: data.fullName,
1823
+ icon: data.icon,
1824
+ shape: 'rectangle',
1825
+ position: { x: 0, y: 0 },
1826
+ size: { width: NODE_W, height: cardHeight(incoming.length, outgoing.length) },
1827
+ inboundPorts: inboundPorts(incoming),
1828
+ outboundPorts: outboundPorts(outgoing),
1829
+ readonly: true,
1830
+ color,
1831
+ borderColor: color,
1832
+ data: { ...data },
1833
+ };
1834
+ }
1835
+ function connectors(edges) {
1836
+ return edges.map((edge) => ({
1837
+ id: outboundPortId(edge),
1838
+ sourceNodeId: edge.from,
1839
+ sourcePortId: outboundPortId(edge),
1840
+ targetNodeId: edge.to,
1841
+ targetPortId: inboundPortId(edge),
1842
+ readonly: true,
1843
+ color: (edge.kind === 'related' ? 'success' : 'primary'),
1844
+ borderColor: (edge.kind === 'related' ? 'success' : 'primary'),
1845
+ label: relationName(edge),
1846
+ data: { kind: edge.kind },
1847
+ }));
1848
+ }
1849
+ function nodesBBox(nodes) {
1850
+ if (nodes.length === 0) {
1851
+ return { minX: INTERNAL_ORIGIN.x, minY: INTERNAL_ORIGIN.y, maxX: INTERNAL_ORIGIN.x, maxY: INTERNAL_ORIGIN.y };
1852
+ }
1853
+ let minX = Number.POSITIVE_INFINITY;
1854
+ let minY = Number.POSITIVE_INFINITY;
1855
+ let maxX = Number.NEGATIVE_INFINITY;
1856
+ let maxY = Number.NEGATIVE_INFINITY;
1857
+ for (const node of nodes) {
1858
+ minX = Math.min(minX, node.position.x);
1859
+ minY = Math.min(minY, node.position.y);
1860
+ maxX = Math.max(maxX, node.position.x + node.size.width);
1861
+ maxY = Math.max(maxY, node.position.y + node.size.height);
1862
+ }
1863
+ return { minX, minY, maxX, maxY };
1864
+ }
1865
+ function groupEdgesBy(edges, keyOf) {
1866
+ const map = new Map();
1867
+ for (const edge of edges) {
1868
+ const key = keyOf(edge);
1869
+ const list = map.get(key);
1870
+ if (list) {
1871
+ list.push(edge);
1872
+ }
1873
+ else {
1874
+ map.set(key, [edge]);
1875
+ }
1876
+ }
1877
+ for (const list of map.values()) {
1878
+ list.sort((a, b) => relationName(a).localeCompare(relationName(b)) || a.from.localeCompare(b.from));
1879
+ }
1880
+ return map;
1881
+ }
1882
+ /**
1883
+ * Tiles unrelated entities in rows under the related cluster. A single column
1884
+ * would make the canvas very tall, which forces fit-to-content to a tiny zoom.
1885
+ */
1886
+ function tileIsolatedNodes(nodes, startY, availableWidth) {
1887
+ const placed = new Map();
1888
+ const columns = Math.max(1, Math.floor((availableWidth + ISOLATED_GAP_X) / (NODE_W + ISOLATED_GAP_X)));
1889
+ let rowY = startY;
1890
+ let rowHeight = 0;
1891
+ nodes.forEach((node, index) => {
1892
+ const column = index % columns;
1893
+ if (column === 0 && index > 0) {
1894
+ rowY += rowHeight + NODE_GAP_Y;
1895
+ rowHeight = 0;
1896
+ }
1897
+ placed.set(node.id, {
1898
+ ...node,
1899
+ position: { x: INTERNAL_ORIGIN.x + column * (NODE_W + ISOLATED_GAP_X), y: rowY },
1900
+ });
1901
+ rowHeight = Math.max(rowHeight, node.size.height);
1902
+ });
1903
+ return placed;
1904
+ }
1905
+ /**
1906
+ * Converts the explorer entity graph into a readonly ERD. Related entities are
1907
+ * arranged left to right, external targets cluster on the right with a yellow
1908
+ * header, and unrelated entities tile in a grid underneath. Cards stay
1909
+ * header-sized; the property list is shown/hidden without changing layout.
1910
+ */
1911
+ function buildExplorerEntityDiagram(graph, resolveModuleTitle) {
1912
+ if (!graph?.nodes.length) {
1913
+ return null;
1914
+ }
1915
+ const nodeIds = new Set(graph.nodes.map((item) => item.fullName));
1916
+ const validEdges = graph.edges.filter((edge) => nodeIds.has(edge.from) && nodeIds.has(edge.to));
1917
+ const outgoing = groupEdgesBy(validEdges, (edge) => edge.from);
1918
+ const incoming = groupEdgesBy(validEdges, (edge) => edge.to);
1919
+ const skipLookupFields = lookupFieldKeys(validEdges);
1920
+ const relatedIds = new Set(validEdges.flatMap((edge) => [edge.from, edge.to]));
1921
+ const entityNodes = graph.nodes.map((item) => {
1922
+ const data = toEntityData(item, resolveModuleTitle(item.module), skipLookupFields);
1923
+ return entityNode(data, outgoing.get(item.fullName) ?? [], incoming.get(item.fullName) ?? []);
1924
+ });
1925
+ const internalRelatedIds = entityNodes
1926
+ .filter((node) => !explorerEntityData(node)?.external && relatedIds.has(node.id))
1927
+ .map((node) => node.id);
1928
+ const arranged = arrangeWorkflowDiagram({
1929
+ nodes: entityNodes,
1930
+ connectors: connectors(validEdges),
1931
+ }, {
1932
+ nodeIds: internalRelatedIds,
1933
+ origin: INTERNAL_ORIGIN,
1934
+ direction: 'horizontal',
1935
+ horizontalGap: 220,
1936
+ verticalGap: 72,
1937
+ margin: 48,
1938
+ groupDisconnected: true,
1939
+ });
1940
+ const internals = arranged.nodes.filter((node) => !explorerEntityData(node)?.external && relatedIds.has(node.id));
1941
+ const box = nodesBBox(internals);
1942
+ const externals = arranged.nodes
1943
+ .filter((node) => explorerEntityData(node)?.external)
1944
+ .sort((a, b) => {
1945
+ const moduleCmp = (explorerEntityData(a)?.module ?? '').localeCompare(explorerEntityData(b)?.module ?? '');
1946
+ return moduleCmp !== 0 ? moduleCmp : a.title.localeCompare(b.title);
1947
+ });
1948
+ const externalX = box.maxX + EXTERNAL_GAP_X;
1949
+ let externalY = INTERNAL_ORIGIN.y;
1950
+ const positioned = new Map();
1951
+ for (const node of externals) {
1952
+ positioned.set(node.id, {
1953
+ ...node,
1954
+ position: { x: externalX, y: externalY },
1955
+ });
1956
+ externalY += node.size.height + NODE_GAP_Y;
1957
+ }
1958
+ const isolated = arranged.nodes
1959
+ .filter((node) => !explorerEntityData(node)?.external && !relatedIds.has(node.id))
1960
+ .sort((a, b) => a.id.localeCompare(b.id));
1961
+ if (isolated.length) {
1962
+ const clusterBottom = Math.max(box.maxY, externalY - NODE_GAP_Y, INTERNAL_ORIGIN.y);
1963
+ const clusterWidth = Math.max(NODE_W, (externals.length ? externalX + NODE_W : box.maxX) - INTERNAL_ORIGIN.x);
1964
+ const startY = internals.length || externals.length ? clusterBottom + ISOLATED_GAP_Y : INTERNAL_ORIGIN.y;
1965
+ for (const [id, node] of tileIsolatedNodes(isolated, startY, clusterWidth)) {
1966
+ positioned.set(id, node);
1967
+ }
1968
+ }
1969
+ return {
1970
+ nodes: arranged.nodes.map((node) => positioned.get(node.id) ?? node),
1971
+ connectors: arranged.connectors,
1972
+ };
1973
+ }
1974
+ function connectedNodeIds(model, nodeId) {
1975
+ const ids = new Set([nodeId]);
1976
+ for (const connector of model.connectors) {
1977
+ if (connector.sourceNodeId === nodeId) {
1978
+ ids.add(connector.targetNodeId);
1979
+ }
1980
+ if (connector.targetNodeId === nodeId) {
1981
+ ids.add(connector.sourceNodeId);
1982
+ }
1983
+ }
1984
+ return [...ids];
527
1985
  }
528
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerPermissionsListDetailComponent, decorators: [{
529
- type: Component,
530
- args: [{
531
- selector: 'axp-platform-explorer-permissions-list-detail',
532
- standalone: true,
533
- encapsulation: ViewEncapsulation.None,
534
- changeDetection: ChangeDetectionStrategy.OnPush,
535
- imports: [CommonModule, AXDataTableModule, AXTranslationModule],
536
- host: { class: 'flex flex-col flex-1 min-h-0' },
537
- template: `
538
- <div class="flex flex-1 min-h-0 pt-0 overflow-auto">
539
- <ax-data-table
540
- class="flex-1 border border-lightest rounded-md overflow-hidden"
541
- [paging]="true"
542
- [allowReordering]="true"
543
- [dataSource]="dataSource"
544
- >
545
- <ax-text-column [dataField]="'name'" [caption]="'@platform-dev-tools:module.terms.name' | translate | async" [width]="'420px'" [allowResizing]="true"></ax-text-column>
546
- <ax-text-column [dataField]="'title'" [caption]="'@platform-dev-tools:module.terms.title' | translate | async" [cellTemplate]="titleTpl" [width]="'260px'" [allowResizing]="true"></ax-text-column>
547
- <ax-text-column [dataField]="'description'" [caption]="'@platform-dev-tools:module.terms.description' | translate | async" [cellTemplate]="descTpl" [width]="'380px'" [allowResizing]="true"></ax-text-column>
548
- </ax-data-table>
549
- </div>
550
- <ng-template #titleTpl let-item>
551
- {{ $safeNavigationMigration(item.data?.title) | translate | async }}</ng-template>
552
- <ng-template #descTpl let-item>
553
- @if (item.data?.description) { {{ item.data.description | translate | async }} } @else { – }
554
- </ng-template>
555
- `,
556
- }]
557
- }], ctorParameters: () => [], propDecorators: { moduleName: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleName", required: true }] }] } });
1986
+ //#endregion
558
1987
 
559
- //#region ---- Imports ----
1988
+ //#region ----- Imports -----
560
1989
  //#endregion
561
- class AXPlatformExplorerFeaturesListDetailComponent {
1990
+ //#region ----- Component -----
1991
+ class AXPlatformExplorerEntitiesGraphComponent {
1992
+ //#endregion
562
1993
  constructor() {
563
1994
  this.moduleName = input.required(/* @ts-ignore */
564
1995
  ...(ngDevMode ? [{ debugName: "moduleName" }] : /* istanbul ignore next */ []));
1996
+ //#region ----- Services & Dependencies -----
565
1997
  this.dataService = inject(PlatformExplorerDataService);
566
- this.dataSource = new AXDataSource({
567
- key: 'name',
568
- pageSize: 20,
569
- load: (e) => {
570
- const name = this.moduleName();
571
- if (!name)
572
- return Promise.resolve({ items: [], total: 0 });
573
- const list = this.dataService.getFeaturesForModule(name);
574
- const skip = e?.skip ?? 0;
575
- const take = e?.take ?? 20;
576
- const items = list.slice(skip, skip + take);
577
- return Promise.resolve({ items, total: list.length });
578
- },
579
- });
1998
+ //#endregion
1999
+ //#region ----- Constants -----
2000
+ /** Readonly diagram: no spare drawing area, so fit-to-content keeps cards readable. */
2001
+ this.canvasMinSize = { width: 0, height: 0 };
2002
+ //#endregion
2003
+ //#region ----- State Signals -----
2004
+ this.loading = signal(false, /* @ts-ignore */
2005
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2006
+ this.loadError = signal(false, /* @ts-ignore */
2007
+ ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
2008
+ this.diagram = signal(null, /* @ts-ignore */
2009
+ ...(ngDevMode ? [{ debugName: "diagram" }] : /* istanbul ignore next */ []));
2010
+ this.highlightedNodeIds = signal([], /* @ts-ignore */
2011
+ ...(ngDevMode ? [{ debugName: "highlightedNodeIds" }] : /* istanbul ignore next */ []));
2012
+ this.expandedNodeIds = signal(new Set(), /* @ts-ignore */
2013
+ ...(ngDevMode ? [{ debugName: "expandedNodeIds" }] : /* istanbul ignore next */ []));
580
2014
  effect(() => {
581
- this.moduleName();
582
- this.dataSource.refresh();
2015
+ const moduleName = this.moduleName();
2016
+ void this.load(moduleName);
583
2017
  });
584
2018
  }
585
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerFeaturesListDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
586
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerFeaturesListDetailComponent, isStandalone: true, selector: "axp-platform-explorer-features-list-detail", inputs: { moduleName: { classPropertyName: "moduleName", publicName: "moduleName", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: `
587
- <div class="flex flex-1 min-h-0 pt-0 overflow-auto">
588
- <ax-data-table
589
- class="flex-1 border border-lightest rounded-md overflow-hidden"
590
- [paging]="true"
591
- [allowReordering]="true"
592
- [dataSource]="dataSource"
593
- >
594
- <ax-text-column dataField="key" [caption]="'@platform-dev-tools:module.terms.key' | translate | async" [width]="'240px'" [allowResizing]="true"></ax-text-column>
595
- <ax-text-column dataField="title" [caption]="'@platform-dev-tools:module.terms.title' | translate | async" [cellTemplate]="titleTpl" [width]="'280px'" [allowResizing]="true"></ax-text-column>
596
- <ax-text-column dataField="type" caption="Type" [width]="'100px'" [cellTemplate]="typeTpl" [allowResizing]="true"></ax-text-column>
597
- <ax-text-column dataField="description" [caption]="'@platform-dev-tools:module.terms.description' | translate | async" [cellTemplate]="descTpl" [width]="'340px'" [allowResizing]="true"></ax-text-column>
598
- <ax-text-column dataField="defaultValue" [caption]="'@platform-dev-tools:module.terms.default-value' | translate | async" [width]="'140px'" [allowResizing]="true"></ax-text-column>
599
- </ax-data-table>
600
- </div>
601
- <ng-template #titleTpl let-item>{{ $safeNavigationMigration(item.data?.title) | translate | async }}</ng-template>
602
- <ng-template #typeTpl let-item>{{ item.data?.type ?? '–' }}</ng-template>
603
- <ng-template #descTpl let-item>@if (item.data?.description) { {{ item.data.description | translate | async }} } @else { – }</ng-template>
604
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i1$1.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i1$1.AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "translate", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2019
+ //#region ----- Event Handlers -----
2020
+ entityOf(node) {
2021
+ return explorerEntityData(node);
2022
+ }
2023
+ entityLabel(entity) {
2024
+ return entity.fullName || `${entity.module}.${entity.name}`;
2025
+ }
2026
+ isExpanded(id) {
2027
+ return this.expandedNodeIds().has(id);
2028
+ }
2029
+ propertyToggleLabel(expanded) {
2030
+ return expanded
2031
+ ? '@platform-dev-tools:module.graph.hide-properties'
2032
+ : '@platform-dev-tools:module.graph.show-properties';
2033
+ }
2034
+ onToggleProperties(event, id) {
2035
+ event.preventDefault();
2036
+ event.stopPropagation();
2037
+ const next = new Set(this.expandedNodeIds());
2038
+ if (next.has(id)) {
2039
+ next.delete(id);
2040
+ }
2041
+ else {
2042
+ next.add(id);
2043
+ }
2044
+ this.expandedNodeIds.set(next);
2045
+ }
2046
+ onNodeClick(event) {
2047
+ const model = this.diagram();
2048
+ if (!model) {
2049
+ return;
2050
+ }
2051
+ this.highlightedNodeIds.set(connectedNodeIds(model, event.node.id));
2052
+ }
2053
+ onCanvasClick() {
2054
+ this.highlightedNodeIds.set([]);
2055
+ this.expandedNodeIds.set(new Set());
2056
+ }
2057
+ //#endregion
2058
+ //#region ----- Data Loading -----
2059
+ async load(moduleName) {
2060
+ this.loading.set(true);
2061
+ this.loadError.set(false);
2062
+ this.diagram.set(null);
2063
+ this.highlightedNodeIds.set([]);
2064
+ this.expandedNodeIds.set(new Set());
2065
+ try {
2066
+ const catalog = await this.dataService.getModule(moduleName);
2067
+ if (this.moduleName() !== moduleName) {
2068
+ return;
2069
+ }
2070
+ const graph = catalog?.entityGraph ?? {
2071
+ nodes: (catalog?.entities ?? []).map((item) => ({
2072
+ fullName: item.fullName,
2073
+ module: item.module,
2074
+ name: item.name,
2075
+ title: item.title,
2076
+ icon: item.icon,
2077
+ external: false,
2078
+ })),
2079
+ edges: [],
2080
+ };
2081
+ this.diagram.set(buildExplorerEntityDiagram(graph, (name) => this.dataService.moduleTitle(name)));
2082
+ }
2083
+ catch {
2084
+ if (this.moduleName() === moduleName) {
2085
+ this.loadError.set(true);
2086
+ }
2087
+ }
2088
+ finally {
2089
+ if (this.moduleName() === moduleName) {
2090
+ this.loading.set(false);
2091
+ }
2092
+ }
2093
+ }
2094
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerEntitiesGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2095
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerEntitiesGraphComponent, isStandalone: true, selector: "axp-platform-explorer-entities-graph", inputs: { moduleName: { classPropertyName: "moduleName", publicName: "moduleName", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "axp-explorer-graph-host flex flex-col flex-1 min-h-0" }, ngImport: i0, template: "<div class=\"axp-explorer-graph flex flex-col flex-1 min-h-0\">\n <!-- ----- Loading module entity graph ----- -->\n @if (loading()) {\n <div class=\"flex flex-1 items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- Module has no entities ----- -->\n @else if (!diagram()) {\n <axp-state-message\n icon=\"fa-light fa-diagram-project\"\n [title]=\"'@platform-dev-tools:module.messages.no-entities'\"\n [description]=\"'@platform-dev-tools:module.messages.no-entities-hint'\"\n />\n }\n <!-- ----- Entity-relationship diagram ----- -->\n @else {\n <div class=\"axp-explorer-graph__canvas\">\n <axp-workflow-designer\n [model]=\"diagram()!\"\n [readonly]=\"true\"\n [showPortLabels]=\"true\"\n [showPorts]=\"false\"\n [connectorStub]=\"56\"\n [canvasPadding]=\"80\"\n [minCanvasSize]=\"canvasMinSize\"\n [highlightedNodeIds]=\"highlightedNodeIds()\"\n [nodeBodyTemplate]=\"nodeBodyTpl\"\n (nodeClick)=\"onNodeClick($event)\"\n (designerClick)=\"onCanvasClick()\"\n />\n </div>\n <div class=\"axp-explorer-graph__footer\">\n <p class=\"axp-explorer-graph__hint\">\n {{ '@platform-dev-tools:module.descriptions.entities' | translate | async }}\n </p>\n <div class=\"axp-explorer-graph__legend\">\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__swatch axp-explorer-graph__swatch--internal\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.local-entity' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__swatch axp-explorer-graph__swatch--external\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.external-entity' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-sep\" aria-hidden=\"true\"></span>\n <span class=\"axp-explorer-graph__legend-item\">\n {{ '@platform-dev-tools:module.graph.pk' | translate | async }}\n =\n {{ '@platform-dev-tools:module.graph.pk-hint' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-sep\" aria-hidden=\"true\"></span>\n <span class=\"axp-explorer-graph__legend-item\">\n {{ '@platform-dev-tools:module.graph.cardinality-one' | translate | async }}\n =\n {{ '@platform-dev-tools:module.graph.cardinality-one-hint' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-item\">\n {{ '@platform-dev-tools:module.graph.cardinality-many' | translate | async }}\n =\n {{ '@platform-dev-tools:module.graph.cardinality-many-hint' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-sep\" aria-hidden=\"true\"></span>\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__line axp-explorer-graph__line--lookup\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.lookup' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__line axp-explorer-graph__line--related\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.related' | translate | async }}\n </span>\n </div>\n </div>\n }\n</div>\n\n<ng-template #nodeBodyTpl let-node=\"node\">\n <!-- ----- Entity table ----- -->\n @if (entityOf(node); as entity) {\n <div\n class=\"axp-explorer-graph__entity\"\n [class.axp-explorer-graph__entity--external]=\"entity.external\"\n [class.axp-explorer-graph__entity--expanded]=\"isExpanded(entity.fullName)\"\n >\n <div class=\"axp-explorer-graph__entity-head\">\n <div class=\"axp-explorer-graph__entity-head-main\">\n <span class=\"axp-explorer-graph__entity-name\">{{ entityLabel(entity) }}</span>\n <!--------------------------------------------------------------->\n <!-- ----- Translated catalog title when it differs from the entity name ----- -->\n @if (entity.title && entity.title !== entity.name && entity.title !== entity.fullName) {\n <span class=\"axp-explorer-graph__entity-title\">{{ entity.title | translate | async }}</span>\n }\n </div>\n <!--------------------------------------------------------------->\n <!-- ----- Expand non-lookup properties under the header ----- -->\n @if (entity.properties.length) {\n <button\n type=\"button\"\n class=\"axp-explorer-graph__entity-toggle\"\n [attr.aria-expanded]=\"isExpanded(entity.fullName)\"\n [attr.aria-label]=\"(propertyToggleLabel(isExpanded(entity.fullName)) | translate | async) ?? ''\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"onToggleProperties($event, entity.fullName)\"\n >\n <i\n class=\"fa-light\"\n [class.fa-chevron-down]=\"!isExpanded(entity.fullName)\"\n [class.fa-chevron-up]=\"isExpanded(entity.fullName)\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n </div>\n <!--------------------------------------------------------------->\n <!-- ----- Same-card property list; visibility is toggled from the header ----- -->\n @if (entity.properties.length) {\n <div class=\"axp-explorer-graph__entity-props\" (pointerdown)=\"$event.stopPropagation()\">\n @for (prop of entity.properties; track prop.name) {\n <div class=\"axp-explorer-graph__entity-prop\">\n <!--------------------------------------------------------------->\n <!-- ----- Primary-key badge when the field is id ----- -->\n @if (prop.kind === 'pk') {\n <span class=\"axp-explorer-graph__entity-prop-kind\">\n {{ '@platform-dev-tools:module.graph.pk' | translate | async }}\n </span>\n }\n <span class=\"axp-explorer-graph__entity-prop-name\">{{ prop.name }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n", styles: ["@layer properties;.axp-explorer-graph__footer{display:flex;flex-shrink:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);padding-inline:calc(var(--spacing, .25rem) * 4);padding-top:calc(var(--spacing, .25rem) * 2);padding-bottom:calc(var(--spacing, .25rem) * 3)}.axp-explorer-graph__hint{margin:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-relaxed, 1.625);line-height:var(--leading-relaxed, 1.625);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-explorer-graph__hint{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-explorer-graph__legend{display:flex;flex-wrap:wrap;align-items:center;column-gap:calc(var(--spacing, .25rem) * 4);row-gap:calc(var(--spacing, .25rem) * 2);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-explorer-graph__legend{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-explorer-graph__legend-item{display:inline-flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5)}.axp-explorer-graph__legend-sep{display:none;height:calc(var(--spacing, .25rem) * 3);width:1px;flex-shrink:0;background-color:rgba(var(--ax-sys-color-lightest-surface))}@media(width>=40rem){.axp-explorer-graph__legend-sep{display:inline-block}}.axp-explorer-graph__swatch{display:inline-block;height:calc(var(--spacing, .25rem) * 2.5);width:calc(var(--spacing, .25rem) * 2.5);flex-shrink:0;border-radius:var(--radius-sm, .25rem)}.axp-explorer-graph__swatch--internal{background:rgb(var(--ax-sys-color-primary-500))}.axp-explorer-graph__swatch--external{background:rgb(var(--ax-sys-color-warning-500))}.axp-explorer-graph__line{display:inline-block;height:calc(var(--spacing, .25rem) * .5);width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:calc(infinity * 1px)}.axp-explorer-graph__line--lookup{background:rgb(var(--ax-sys-color-primary-500))}.axp-explorer-graph__line--related{background:rgb(var(--ax-sys-color-success-500))}.axp-explorer-graph__canvas{margin:calc(var(--spacing, .25rem) * 3);margin-bottom:0;min-height:0px;flex:1;overflow:hidden;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));min-height:360px}.axp-explorer-graph__canvas .axp-workflow-designer__edge.axp-primary-surface{stroke:rgb(var(--ax-sys-color-primary-500))}.axp-explorer-graph__canvas .axp-workflow-designer__edge.axp-success-surface{stroke:rgb(var(--ax-sys-color-success-500))}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity){overflow:visible}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape,.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape.axp-primary-surface,.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape.axp-warning-surface{background:rgb(var(--ax-sys-color-primary-lightest-surface));color:rgb(var(--ax-sys-color-on-primary-lightest-surface));overflow:visible;border-radius:8px}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape.axp-warning-surface{background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface))}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-body{padding:0;align-items:stretch;justify-content:stretch;overflow:visible}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity--expanded){z-index:8}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity--expanded) .axp-workflow-designer__node-shape{border-radius:8px 8px 0 0;border-bottom-width:0}.axp-explorer-graph__canvas .axp-workflow-designer__port{width:7px;height:7px;background:rgb(var(--ax-sys-color-neutral-400));pointer-events:none}.axp-explorer-graph__canvas .axp-workflow-designer__port-label-svg,.axp-explorer-graph__canvas .axp-workflow-designer__edge-label-svg{fill:rgb(var(--ax-sys-color-on-surface));stroke:rgb(var(--ax-sys-color-lightest-surface));font-size:11px;font-weight:700;letter-spacing:.02em}.axp-explorer-graph__entity{display:flex;height:100%;width:100%;min-width:0px;flex-direction:column;position:relative;overflow:visible}.axp-explorer-graph__entity-head{display:flex;flex-direction:row;align-items:center;gap:calc(var(--spacing, .25rem) * 2);flex:1 1 auto;min-height:100%;padding:8px 10px;box-sizing:border-box;background:rgb(var(--ax-sys-color-primary-lightest-surface));color:rgb(var(--ax-sys-color-on-primary-lightest-surface));border-radius:8px}.axp-explorer-graph__entity--expanded .axp-explorer-graph__entity-head{border-radius:8px 8px 0 0}.axp-explorer-graph__entity--external .axp-explorer-graph__entity-head{background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface))}.axp-explorer-graph__entity-head-main{display:flex;min-width:0px;flex:1;flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * .5)}.axp-explorer-graph__entity-name{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-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-explorer-graph__entity-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;--tw-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);opacity:.85}.axp-explorer-graph__entity-toggle{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;width:28px;height:28px;margin:0;padding:0;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer}.axp-explorer-graph__entity-toggle:hover{background:rgb(var(--ax-sys-color-on-primary-lightest-surface) / .08)}.axp-explorer-graph__entity--external .axp-explorer-graph__entity-toggle:hover{background:rgb(var(--ax-sys-color-on-warning-lightest-surface) / .08)}.axp-explorer-graph__entity-props{display:none;flex-direction:column;position:absolute;top:100%;left:0;right:0;z-index:6;margin:0;padding:6px 8px 8px;box-sizing:border-box;background:rgb(var(--ax-sys-color-primary-lightest-surface));color:rgb(var(--ax-sys-color-on-primary-lightest-surface));border:2px solid rgb(var(--ax-sys-color-border-primary-lightest-surface));border-top:1px solid rgb(var(--ax-sys-color-on-primary-lightest-surface) / .16);border-radius:0 0 8px 8px;max-height:320px;overflow-x:hidden;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgb(var(--ax-sys-color-on-primary-lightest-surface) / .3) transparent}.axp-explorer-graph__entity--expanded .axp-explorer-graph__entity-props{display:flex}.axp-explorer-graph__entity--external .axp-explorer-graph__entity-props{background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface));border-color:rgb(var(--ax-sys-color-border-warning-lightest-surface));border-top-color:rgb(var(--ax-sys-color-on-warning-lightest-surface) / .16)}.axp-explorer-graph__entity-prop{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 2);font-size:11px;--tw-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);min-height:22px;margin:0;padding:3px 6px;border-radius:4px;box-sizing:border-box}.axp-explorer-graph__entity-prop-kind{flex-shrink:0;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);min-width:1.75rem;opacity:.85}.axp-explorer-graph__entity-prop-name{min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:inherit}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{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-leading: initial;--tw-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i2$1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "component", type: AXPWorkflowDesignerComponent, selector: "axp-workflow-designer", inputs: ["model", "readonly", "highlightedNodeIds", "gridSize", "snapToGrid", "connectionPreviewStyle", "connectorStub", "backgroundPattern", "canConnect", "paletteItems", "showPortLabels", "showPorts", "showConnectorLabels", "canvasPadding", "minCanvasSize", "nodeBodyTemplate"], outputs: ["modelChange", "zoomChange", "designerPointerDown", "designerClick", "designerDoubleClick", "nodePointerDown", "nodeClick", "nodeDoubleClick", "nodeContextMenu", "nodeDragStart", "nodeDragEnd", "portPointerDown", "linkDragStart", "linkDragMove", "linkDragEnd", "connectorClick", "connectorDoubleClick", "connectionCreated", "connectionRejected", "activationChange"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
605
2096
  }
606
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerFeaturesListDetailComponent, decorators: [{
2097
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerEntitiesGraphComponent, decorators: [{
607
2098
  type: Component,
608
- args: [{
609
- selector: 'axp-platform-explorer-features-list-detail',
610
- standalone: true,
611
- encapsulation: ViewEncapsulation.None,
612
- changeDetection: ChangeDetectionStrategy.OnPush,
613
- imports: [CommonModule, AXDataTableModule, AXTranslationModule],
614
- host: { class: 'flex flex-col flex-1 min-h-0' },
615
- template: `
616
- <div class="flex flex-1 min-h-0 pt-0 overflow-auto">
617
- <ax-data-table
618
- class="flex-1 border border-lightest rounded-md overflow-hidden"
619
- [paging]="true"
620
- [allowReordering]="true"
621
- [dataSource]="dataSource"
622
- >
623
- <ax-text-column dataField="key" [caption]="'@platform-dev-tools:module.terms.key' | translate | async" [width]="'240px'" [allowResizing]="true"></ax-text-column>
624
- <ax-text-column dataField="title" [caption]="'@platform-dev-tools:module.terms.title' | translate | async" [cellTemplate]="titleTpl" [width]="'280px'" [allowResizing]="true"></ax-text-column>
625
- <ax-text-column dataField="type" caption="Type" [width]="'100px'" [cellTemplate]="typeTpl" [allowResizing]="true"></ax-text-column>
626
- <ax-text-column dataField="description" [caption]="'@platform-dev-tools:module.terms.description' | translate | async" [cellTemplate]="descTpl" [width]="'340px'" [allowResizing]="true"></ax-text-column>
627
- <ax-text-column dataField="defaultValue" [caption]="'@platform-dev-tools:module.terms.default-value' | translate | async" [width]="'140px'" [allowResizing]="true"></ax-text-column>
628
- </ax-data-table>
629
- </div>
630
- <ng-template #titleTpl let-item>{{ $safeNavigationMigration(item.data?.title) | translate | async }}</ng-template>
631
- <ng-template #typeTpl let-item>{{ item.data?.type ?? '–' }}</ng-template>
632
- <ng-template #descTpl let-item>@if (item.data?.description) { {{ item.data.description | translate | async }} } @else { – }</ng-template>
633
- `,
634
- }]
2099
+ args: [{ selector: 'axp-platform-explorer-entities-graph', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'axp-explorer-graph-host flex flex-col flex-1 min-h-0' }, imports: [CommonModule, AXTranslationModule, AXLoadingModule, AXPStateMessageComponent, AXPWorkflowDesignerComponent], template: "<div class=\"axp-explorer-graph flex flex-col flex-1 min-h-0\">\n <!-- ----- Loading module entity graph ----- -->\n @if (loading()) {\n <div class=\"flex flex-1 items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- Module has no entities ----- -->\n @else if (!diagram()) {\n <axp-state-message\n icon=\"fa-light fa-diagram-project\"\n [title]=\"'@platform-dev-tools:module.messages.no-entities'\"\n [description]=\"'@platform-dev-tools:module.messages.no-entities-hint'\"\n />\n }\n <!-- ----- Entity-relationship diagram ----- -->\n @else {\n <div class=\"axp-explorer-graph__canvas\">\n <axp-workflow-designer\n [model]=\"diagram()!\"\n [readonly]=\"true\"\n [showPortLabels]=\"true\"\n [showPorts]=\"false\"\n [connectorStub]=\"56\"\n [canvasPadding]=\"80\"\n [minCanvasSize]=\"canvasMinSize\"\n [highlightedNodeIds]=\"highlightedNodeIds()\"\n [nodeBodyTemplate]=\"nodeBodyTpl\"\n (nodeClick)=\"onNodeClick($event)\"\n (designerClick)=\"onCanvasClick()\"\n />\n </div>\n <div class=\"axp-explorer-graph__footer\">\n <p class=\"axp-explorer-graph__hint\">\n {{ '@platform-dev-tools:module.descriptions.entities' | translate | async }}\n </p>\n <div class=\"axp-explorer-graph__legend\">\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__swatch axp-explorer-graph__swatch--internal\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.local-entity' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__swatch axp-explorer-graph__swatch--external\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.external-entity' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-sep\" aria-hidden=\"true\"></span>\n <span class=\"axp-explorer-graph__legend-item\">\n {{ '@platform-dev-tools:module.graph.pk' | translate | async }}\n =\n {{ '@platform-dev-tools:module.graph.pk-hint' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-sep\" aria-hidden=\"true\"></span>\n <span class=\"axp-explorer-graph__legend-item\">\n {{ '@platform-dev-tools:module.graph.cardinality-one' | translate | async }}\n =\n {{ '@platform-dev-tools:module.graph.cardinality-one-hint' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-item\">\n {{ '@platform-dev-tools:module.graph.cardinality-many' | translate | async }}\n =\n {{ '@platform-dev-tools:module.graph.cardinality-many-hint' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-sep\" aria-hidden=\"true\"></span>\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__line axp-explorer-graph__line--lookup\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.lookup' | translate | async }}\n </span>\n <span class=\"axp-explorer-graph__legend-item\">\n <span class=\"axp-explorer-graph__line axp-explorer-graph__line--related\" aria-hidden=\"true\"></span>\n {{ '@platform-dev-tools:module.graph.related' | translate | async }}\n </span>\n </div>\n </div>\n }\n</div>\n\n<ng-template #nodeBodyTpl let-node=\"node\">\n <!-- ----- Entity table ----- -->\n @if (entityOf(node); as entity) {\n <div\n class=\"axp-explorer-graph__entity\"\n [class.axp-explorer-graph__entity--external]=\"entity.external\"\n [class.axp-explorer-graph__entity--expanded]=\"isExpanded(entity.fullName)\"\n >\n <div class=\"axp-explorer-graph__entity-head\">\n <div class=\"axp-explorer-graph__entity-head-main\">\n <span class=\"axp-explorer-graph__entity-name\">{{ entityLabel(entity) }}</span>\n <!--------------------------------------------------------------->\n <!-- ----- Translated catalog title when it differs from the entity name ----- -->\n @if (entity.title && entity.title !== entity.name && entity.title !== entity.fullName) {\n <span class=\"axp-explorer-graph__entity-title\">{{ entity.title | translate | async }}</span>\n }\n </div>\n <!--------------------------------------------------------------->\n <!-- ----- Expand non-lookup properties under the header ----- -->\n @if (entity.properties.length) {\n <button\n type=\"button\"\n class=\"axp-explorer-graph__entity-toggle\"\n [attr.aria-expanded]=\"isExpanded(entity.fullName)\"\n [attr.aria-label]=\"(propertyToggleLabel(isExpanded(entity.fullName)) | translate | async) ?? ''\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"onToggleProperties($event, entity.fullName)\"\n >\n <i\n class=\"fa-light\"\n [class.fa-chevron-down]=\"!isExpanded(entity.fullName)\"\n [class.fa-chevron-up]=\"isExpanded(entity.fullName)\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n </div>\n <!--------------------------------------------------------------->\n <!-- ----- Same-card property list; visibility is toggled from the header ----- -->\n @if (entity.properties.length) {\n <div class=\"axp-explorer-graph__entity-props\" (pointerdown)=\"$event.stopPropagation()\">\n @for (prop of entity.properties; track prop.name) {\n <div class=\"axp-explorer-graph__entity-prop\">\n <!--------------------------------------------------------------->\n <!-- ----- Primary-key badge when the field is id ----- -->\n @if (prop.kind === 'pk') {\n <span class=\"axp-explorer-graph__entity-prop-kind\">\n {{ '@platform-dev-tools:module.graph.pk' | translate | async }}\n </span>\n }\n <span class=\"axp-explorer-graph__entity-prop-name\">{{ prop.name }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n", styles: ["@layer properties;.axp-explorer-graph__footer{display:flex;flex-shrink:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);padding-inline:calc(var(--spacing, .25rem) * 4);padding-top:calc(var(--spacing, .25rem) * 2);padding-bottom:calc(var(--spacing, .25rem) * 3)}.axp-explorer-graph__hint{margin:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-relaxed, 1.625);line-height:var(--leading-relaxed, 1.625);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-explorer-graph__hint{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-explorer-graph__legend{display:flex;flex-wrap:wrap;align-items:center;column-gap:calc(var(--spacing, .25rem) * 4);row-gap:calc(var(--spacing, .25rem) * 2);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axp-explorer-graph__legend{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-explorer-graph__legend-item{display:inline-flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5)}.axp-explorer-graph__legend-sep{display:none;height:calc(var(--spacing, .25rem) * 3);width:1px;flex-shrink:0;background-color:rgba(var(--ax-sys-color-lightest-surface))}@media(width>=40rem){.axp-explorer-graph__legend-sep{display:inline-block}}.axp-explorer-graph__swatch{display:inline-block;height:calc(var(--spacing, .25rem) * 2.5);width:calc(var(--spacing, .25rem) * 2.5);flex-shrink:0;border-radius:var(--radius-sm, .25rem)}.axp-explorer-graph__swatch--internal{background:rgb(var(--ax-sys-color-primary-500))}.axp-explorer-graph__swatch--external{background:rgb(var(--ax-sys-color-warning-500))}.axp-explorer-graph__line{display:inline-block;height:calc(var(--spacing, .25rem) * .5);width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:calc(infinity * 1px)}.axp-explorer-graph__line--lookup{background:rgb(var(--ax-sys-color-primary-500))}.axp-explorer-graph__line--related{background:rgb(var(--ax-sys-color-success-500))}.axp-explorer-graph__canvas{margin:calc(var(--spacing, .25rem) * 3);margin-bottom:0;min-height:0px;flex:1;overflow:hidden;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface));min-height:360px}.axp-explorer-graph__canvas .axp-workflow-designer__edge.axp-primary-surface{stroke:rgb(var(--ax-sys-color-primary-500))}.axp-explorer-graph__canvas .axp-workflow-designer__edge.axp-success-surface{stroke:rgb(var(--ax-sys-color-success-500))}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity){overflow:visible}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape,.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape.axp-primary-surface,.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape.axp-warning-surface{background:rgb(var(--ax-sys-color-primary-lightest-surface));color:rgb(var(--ax-sys-color-on-primary-lightest-surface));overflow:visible;border-radius:8px}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-shape.axp-warning-surface{background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface))}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity) .axp-workflow-designer__node-body{padding:0;align-items:stretch;justify-content:stretch;overflow:visible}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity--expanded){z-index:8}.axp-explorer-graph__canvas .axp-workflow-designer__node:has(.axp-explorer-graph__entity--expanded) .axp-workflow-designer__node-shape{border-radius:8px 8px 0 0;border-bottom-width:0}.axp-explorer-graph__canvas .axp-workflow-designer__port{width:7px;height:7px;background:rgb(var(--ax-sys-color-neutral-400));pointer-events:none}.axp-explorer-graph__canvas .axp-workflow-designer__port-label-svg,.axp-explorer-graph__canvas .axp-workflow-designer__edge-label-svg{fill:rgb(var(--ax-sys-color-on-surface));stroke:rgb(var(--ax-sys-color-lightest-surface));font-size:11px;font-weight:700;letter-spacing:.02em}.axp-explorer-graph__entity{display:flex;height:100%;width:100%;min-width:0px;flex-direction:column;position:relative;overflow:visible}.axp-explorer-graph__entity-head{display:flex;flex-direction:row;align-items:center;gap:calc(var(--spacing, .25rem) * 2);flex:1 1 auto;min-height:100%;padding:8px 10px;box-sizing:border-box;background:rgb(var(--ax-sys-color-primary-lightest-surface));color:rgb(var(--ax-sys-color-on-primary-lightest-surface));border-radius:8px}.axp-explorer-graph__entity--expanded .axp-explorer-graph__entity-head{border-radius:8px 8px 0 0}.axp-explorer-graph__entity--external .axp-explorer-graph__entity-head{background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface))}.axp-explorer-graph__entity-head-main{display:flex;min-width:0px;flex:1;flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * .5)}.axp-explorer-graph__entity-name{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-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-explorer-graph__entity-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;--tw-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);opacity:.85}.axp-explorer-graph__entity-toggle{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;width:28px;height:28px;margin:0;padding:0;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer}.axp-explorer-graph__entity-toggle:hover{background:rgb(var(--ax-sys-color-on-primary-lightest-surface) / .08)}.axp-explorer-graph__entity--external .axp-explorer-graph__entity-toggle:hover{background:rgb(var(--ax-sys-color-on-warning-lightest-surface) / .08)}.axp-explorer-graph__entity-props{display:none;flex-direction:column;position:absolute;top:100%;left:0;right:0;z-index:6;margin:0;padding:6px 8px 8px;box-sizing:border-box;background:rgb(var(--ax-sys-color-primary-lightest-surface));color:rgb(var(--ax-sys-color-on-primary-lightest-surface));border:2px solid rgb(var(--ax-sys-color-border-primary-lightest-surface));border-top:1px solid rgb(var(--ax-sys-color-on-primary-lightest-surface) / .16);border-radius:0 0 8px 8px;max-height:320px;overflow-x:hidden;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgb(var(--ax-sys-color-on-primary-lightest-surface) / .3) transparent}.axp-explorer-graph__entity--expanded .axp-explorer-graph__entity-props{display:flex}.axp-explorer-graph__entity--external .axp-explorer-graph__entity-props{background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-on-warning-lightest-surface));border-color:rgb(var(--ax-sys-color-border-warning-lightest-surface));border-top-color:rgb(var(--ax-sys-color-on-warning-lightest-surface) / .16)}.axp-explorer-graph__entity-prop{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 2);font-size:11px;--tw-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);min-height:22px;margin:0;padding:3px 6px;border-radius:4px;box-sizing:border-box}.axp-explorer-graph__entity-prop-kind{flex-shrink:0;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);min-width:1.75rem;opacity:.85}.axp-explorer-graph__entity-prop-name{min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:inherit}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{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-leading: initial;--tw-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
635
2100
  }], ctorParameters: () => [], propDecorators: { moduleName: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleName", required: true }] }] } });
636
2101
 
637
- //#region ---- Imports ----
2102
+ //#region ----- Imports -----
638
2103
  //#endregion
639
- //#region ---- Component ----
2104
+ //#region ----- Component -----
640
2105
  class AXPlatformExplorerDetailComponent {
641
2106
  constructor() {
642
2107
  this.node = input.required(/* @ts-ignore */
643
2108
  ...(ngDevMode ? [{ debugName: "node" }] : /* istanbul ignore next */ []));
644
2109
  }
2110
+ asModule(payload) {
2111
+ return payload;
2112
+ }
2113
+ asEntity(payload) {
2114
+ return payload;
2115
+ }
2116
+ asNamed(payload) {
2117
+ return payload;
2118
+ }
645
2119
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
646
2120
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerDetailComponent, isStandalone: true, selector: "axp-platform-explorer-detail", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: `
647
2121
  <div class="flex flex-col flex-1 min-h-0">
648
2122
  @switch (node().kind) {
649
2123
  @case ('module') {
650
- <axp-platform-explorer-module-detail [payload]="$any(node().payload)" />
2124
+ <axp-platform-explorer-module-detail [payload]="asModule(node().payload)" />
651
2125
  }
652
2126
  @case ('entity') {
653
- <axp-platform-explorer-entity-detail [payload]="$any(node().payload)" />
2127
+ <axp-platform-explorer-entity-detail [payload]="asEntity(node().payload)" />
2128
+ }
2129
+ @case ('command') {
2130
+ <axp-platform-explorer-catalog-detail kind="command" [item]="asNamed(node().payload)" />
654
2131
  }
655
- @case ('permissions') {
656
- <axp-platform-explorer-permissions-list-detail [moduleName]="node().moduleName!" />
2132
+ @case ('query') {
2133
+ <axp-platform-explorer-catalog-detail kind="query" [item]="asNamed(node().payload)" />
657
2134
  }
658
- @case ('features') {
659
- <axp-platform-explorer-features-list-detail [moduleName]="node().moduleName!" />
2135
+ @case ('plugin') {
2136
+ <axp-platform-explorer-catalog-detail kind="plugin" [item]="asNamed(node().payload)" />
660
2137
  }
661
- @default {
662
- <p class="axp-detail__hint">{{ node().children?.length ?? 0 }} items.</p>
2138
+ @case ('widget') {
2139
+ <axp-platform-explorer-catalog-detail kind="widget" [item]="asNamed(node().payload)" />
2140
+ }
2141
+ @case ('diagram') {
2142
+ <axp-platform-explorer-entities-graph [moduleName]="node().moduleName!" />
663
2143
  }
664
2144
  }
665
2145
  </div>
666
- `, isInline: true, styles: [".axp-detail__hint{margin:0;color:var(--ax-neutral-text-secondary, #666)}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPlatformExplorerModuleDetailComponent, selector: "axp-platform-explorer-module-detail", inputs: ["payload"] }, { kind: "component", type: AXPlatformExplorerEntityDetailComponent, selector: "axp-platform-explorer-entity-detail", inputs: ["payload"] }, { kind: "component", type: AXPlatformExplorerPermissionsListDetailComponent, selector: "axp-platform-explorer-permissions-list-detail", inputs: ["moduleName"] }, { kind: "component", type: AXPlatformExplorerFeaturesListDetailComponent, selector: "axp-platform-explorer-features-list-detail", inputs: ["moduleName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2146
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPlatformExplorerModuleDetailComponent, selector: "axp-platform-explorer-module-detail", inputs: ["payload"] }, { kind: "component", type: AXPlatformExplorerEntityDetailComponent, selector: "axp-platform-explorer-entity-detail", inputs: ["payload"] }, { kind: "component", type: AXPlatformExplorerCatalogDetailComponent, selector: "axp-platform-explorer-catalog-detail", inputs: ["kind", "item"] }, { kind: "component", type: AXPlatformExplorerEntitiesGraphComponent, selector: "axp-platform-explorer-entities-graph", inputs: ["moduleName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
667
2147
  }
668
2148
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerDetailComponent, decorators: [{
669
2149
  type: Component,
670
- args: [{ selector: 'axp-platform-explorer-detail', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'flex flex-col flex-1 min-h-0' }, imports: [
2150
+ args: [{
2151
+ selector: 'axp-platform-explorer-detail',
2152
+ standalone: true,
2153
+ encapsulation: ViewEncapsulation.None,
2154
+ changeDetection: ChangeDetectionStrategy.OnPush,
2155
+ host: { class: 'flex flex-col flex-1 min-h-0' },
2156
+ imports: [
671
2157
  AXTranslationModule,
672
2158
  AXPlatformExplorerModuleDetailComponent,
673
2159
  AXPlatformExplorerEntityDetailComponent,
674
- AXPlatformExplorerPermissionsListDetailComponent,
675
- AXPlatformExplorerFeaturesListDetailComponent
676
- ], template: `
2160
+ AXPlatformExplorerCatalogDetailComponent,
2161
+ AXPlatformExplorerEntitiesGraphComponent,
2162
+ ],
2163
+ template: `
677
2164
  <div class="flex flex-col flex-1 min-h-0">
678
2165
  @switch (node().kind) {
679
2166
  @case ('module') {
680
- <axp-platform-explorer-module-detail [payload]="$any(node().payload)" />
2167
+ <axp-platform-explorer-module-detail [payload]="asModule(node().payload)" />
681
2168
  }
682
2169
  @case ('entity') {
683
- <axp-platform-explorer-entity-detail [payload]="$any(node().payload)" />
2170
+ <axp-platform-explorer-entity-detail [payload]="asEntity(node().payload)" />
684
2171
  }
685
- @case ('permissions') {
686
- <axp-platform-explorer-permissions-list-detail [moduleName]="node().moduleName!" />
2172
+ @case ('command') {
2173
+ <axp-platform-explorer-catalog-detail kind="command" [item]="asNamed(node().payload)" />
687
2174
  }
688
- @case ('features') {
689
- <axp-platform-explorer-features-list-detail [moduleName]="node().moduleName!" />
2175
+ @case ('query') {
2176
+ <axp-platform-explorer-catalog-detail kind="query" [item]="asNamed(node().payload)" />
690
2177
  }
691
- @default {
692
- <p class="axp-detail__hint">{{ node().children?.length ?? 0 }} items.</p>
2178
+ @case ('plugin') {
2179
+ <axp-platform-explorer-catalog-detail kind="plugin" [item]="asNamed(node().payload)" />
2180
+ }
2181
+ @case ('widget') {
2182
+ <axp-platform-explorer-catalog-detail kind="widget" [item]="asNamed(node().payload)" />
2183
+ }
2184
+ @case ('diagram') {
2185
+ <axp-platform-explorer-entities-graph [moduleName]="node().moduleName!" />
693
2186
  }
694
2187
  }
695
2188
  </div>
696
- `, styles: [".axp-detail__hint{margin:0;color:var(--ax-neutral-text-secondary, #666)}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
2189
+ `,
2190
+ }]
697
2191
  }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
698
2192
 
699
- //#region ---- Imports ----
2193
+ //#region ----- Imports -----
700
2194
  //#endregion
701
- //#region ---- Convert tree to AXTreeViewNode ----
702
- function toTreeViewNodes(nodes, markModuleExpandable = false) {
703
- return nodes.map((n) => {
704
- const children = n.children ?? [];
705
- const hasChildren = children.length > 0;
706
- const axNode = {
707
- id: n.id,
708
- title: n.label,
709
- children: hasChildren ? toTreeViewNodes(children, false) : undefined,
710
- data: { treeNode: n },
711
- };
712
- if (markModuleExpandable && n.kind === 'module' && !hasChildren) {
713
- axNode['childrenCount'] = 3;
2195
+ //#region ----- Constants -----
2196
+ const CATEGORY_SUFFIXES = ['entities', 'commands', 'queries', 'plugins', 'widgets'];
2197
+ const MODULE_LIST_MIN_WIDTH = 256;
2198
+ const MODULE_LIST_DEFAULT_WIDTH = 288;
2199
+ const MODULE_LIST_MAX_WIDTH = 560;
2200
+ const MODULE_LIST_WIDTH_KEY = 'axp.platform-explorer.module-list-width';
2201
+ function readStoredModuleListWidth() {
2202
+ try {
2203
+ const parsed = Number(localStorage.getItem(MODULE_LIST_WIDTH_KEY));
2204
+ if (Number.isFinite(parsed) &&
2205
+ parsed >= MODULE_LIST_MIN_WIDTH &&
2206
+ parsed <= MODULE_LIST_MAX_WIDTH) {
2207
+ return parsed;
714
2208
  }
715
- return axNode;
716
- });
2209
+ }
2210
+ catch {
2211
+ // ignore missing storage
2212
+ }
2213
+ return MODULE_LIST_DEFAULT_WIDTH;
2214
+ }
2215
+ //#endregion
2216
+ //#region ----- Tree mapping -----
2217
+ function toTreeViewNode(node, childrenCount) {
2218
+ const count = childrenCount ?? node.itemCount;
2219
+ return {
2220
+ id: node.id,
2221
+ title: node.label,
2222
+ childrenCount: count && count > 0 ? count : undefined,
2223
+ data: { treeNode: node },
2224
+ };
2225
+ }
2226
+ function parseCategoryParent(parentId) {
2227
+ for (const suffix of CATEGORY_SUFFIXES) {
2228
+ const token = `-${suffix}`;
2229
+ if (parentId.startsWith('module-') && parentId.endsWith(token)) {
2230
+ return {
2231
+ moduleName: parentId.slice('module-'.length, parentId.length - token.length),
2232
+ kind: suffix,
2233
+ };
2234
+ }
2235
+ }
2236
+ return null;
717
2237
  }
718
2238
  //#endregion
719
- //#region ---- Component ----
2239
+ //#region ----- Component -----
720
2240
  class AXPlatformExplorerPageComponent extends AXPPageLayoutBaseComponent {
721
2241
  constructor() {
722
2242
  super();
723
2243
  this.dataService = inject(PlatformExplorerDataService);
2244
+ this.deviceService = inject(AXPDeviceService);
2245
+ this.route = inject(ActivatedRoute);
724
2246
  this.loading = signal(true, /* @ts-ignore */
725
2247
  ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2248
+ this.loadError = signal(false, /* @ts-ignore */
2249
+ ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
726
2250
  this.selectedNode = signal(null, /* @ts-ignore */
727
2251
  ...(ngDevMode ? [{ debugName: "selectedNode" }] : /* istanbul ignore next */ []));
728
2252
  this.moduleNodes = signal([], /* @ts-ignore */
729
2253
  ...(ngDevMode ? [{ debugName: "moduleNodes" }] : /* istanbul ignore next */ []));
2254
+ this.hasCatalogModules = computed(() => this.moduleNodes().some((node) => node.kind === 'module'), /* @ts-ignore */
2255
+ ...(ngDevMode ? [{ debugName: "hasCatalogModules" }] : /* istanbul ignore next */ []));
2256
+ this.search = signal('', /* @ts-ignore */
2257
+ ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
730
2258
  this.tree = viewChild('tree', /* @ts-ignore */
731
2259
  ...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
732
- /** Datasource for tree: root = modules only; children loaded when module is expanded. */
2260
+ this.entityTabs = this.dataService.entityTabs;
2261
+ this.activeEntitySection = this.dataService.activeEntitySection;
2262
+ this.moduleListMinWidth = MODULE_LIST_MIN_WIDTH;
2263
+ this.moduleListDefaultWidth = MODULE_LIST_DEFAULT_WIDTH;
2264
+ this.moduleListMaxWidth = MODULE_LIST_MAX_WIDTH;
2265
+ this.moduleListWidth = signal(readStoredModuleListWidth(), /* @ts-ignore */
2266
+ ...(ngDevMode ? [{ debugName: "moduleListWidth" }] : /* istanbul ignore next */ []));
2267
+ this.catalogReady = false;
2268
+ this.syncingFromUrl = false;
733
2269
  this.datasource = async (parentId) => {
734
2270
  if (!parentId) {
735
- const nodes = this.moduleNodes();
736
- return toTreeViewNodes(nodes, true);
2271
+ return this.moduleNodes().map((node) => toTreeViewNode(node, node.itemCount));
2272
+ }
2273
+ const category = parseCategoryParent(parentId);
2274
+ if (category) {
2275
+ const children = await this.dataService.getCategoryChildren(category.moduleName, category.kind);
2276
+ return children.map((node) => toTreeViewNode(node));
2277
+ }
2278
+ if (parentId.startsWith('module-')) {
2279
+ const moduleName = parentId.slice('module-'.length);
2280
+ const children = await this.dataService.getModuleChildren(moduleName);
2281
+ return children.map((node) => toTreeViewNode(node, node.itemCount));
737
2282
  }
738
- const children = await this.dataService.getModuleChildren(parentId);
739
- return toTreeViewNodes(children, false);
2283
+ return [];
740
2284
  };
741
- this.initializeModules();
2285
+ this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe((params) => {
2286
+ void this.onQueryChanged(params);
2287
+ });
2288
+ void this.boot();
742
2289
  }
743
- async initializeModules() {
744
- this.loading.set(true);
745
- try {
746
- const nodes = await this.dataService.buildModulesOnlyFiltered();
747
- this.moduleNodes.set(nodes);
748
- }
749
- finally {
750
- this.loading.set(false);
751
- }
2290
+ async onSearchChanged(event) {
2291
+ const text = typeof event.value === 'string' ? event.value : '';
2292
+ this.search.set(text);
2293
+ this.dataService.clearCache();
2294
+ await this.initializeModules(text);
2295
+ await this.tree()?.reloadData();
2296
+ this.writeQuery(this.selectedNode());
2297
+ await this.revealSelection();
752
2298
  }
753
2299
  async getPageTitle() {
754
2300
  const node = this.selectedNode();
755
2301
  if (!node) {
756
- return this.translateService.translateAsync('@platform-dev-tools:module.title');
2302
+ return this.translateService.translateAsync('@platform-dev-tools:module.menus.platform-explorer.title');
757
2303
  }
758
2304
  const moduleTitleKey = node.moduleTitle ?? node.label;
759
2305
  const moduleTitle = await this.translateService.translateAsync(moduleTitleKey);
760
- const contextParts = [];
761
2306
  switch (node.kind) {
2307
+ case 'type-group':
2308
+ return this.translateService.translateAsync('@platform-dev-tools:module.menus.platform-explorer.title');
762
2309
  case 'module':
763
2310
  return moduleTitle;
764
- case 'entities':
765
- contextParts.push(await this.translateService.translateAsync('@platform-dev-tools:module.terms.entities'));
766
- break;
767
2311
  case 'entity':
768
- contextParts.push(await this.translateService.translateAsync('@platform-dev-tools:module.terms.entities'));
769
- contextParts.push(node.label);
770
- break;
771
- case 'permissions':
772
- contextParts.push(await this.translateService.translateAsync('@platform-dev-tools:module.terms.permissions'));
773
- break;
774
- case 'features':
775
- contextParts.push(await this.translateService.translateAsync('@platform-dev-tools:module.terms.features'));
776
- break;
777
- default:
778
- return moduleTitle;
2312
+ case 'command':
2313
+ case 'query':
2314
+ case 'plugin':
2315
+ case 'widget':
2316
+ return [moduleTitle, node.label].join(' · ');
2317
+ default: {
2318
+ const folder = await this.translateService.translateAsync(node.label);
2319
+ return [moduleTitle, folder].join(' · ');
2320
+ }
779
2321
  }
780
- return [moduleTitle, ...contextParts].join(' - ');
781
2322
  }
782
2323
  getPageDescription() {
783
2324
  const node = this.selectedNode();
784
- if (!node)
785
- return null;
2325
+ if (!node) {
2326
+ return this.translateService.translateAsync('@platform-dev-tools:module.description');
2327
+ }
2328
+ if (node.kind === 'entity') {
2329
+ const entity = node.payload;
2330
+ return entity?.fullName ?? null;
2331
+ }
786
2332
  const key = this.descriptionKeyForKind(node.kind);
787
2333
  return key ? this.translateService.translateAsync(key) : null;
788
2334
  }
@@ -796,182 +2342,335 @@ class AXPlatformExplorerPageComponent extends AXPPageLayoutBaseComponent {
796
2342
  }
797
2343
  return items;
798
2344
  }
2345
+ onModuleListResized(event) {
2346
+ if (!event.isUserInteraction || event.value == null) {
2347
+ return;
2348
+ }
2349
+ const width = Math.min(MODULE_LIST_MAX_WIDTH, Math.max(MODULE_LIST_MIN_WIDTH, event.value));
2350
+ this.moduleListWidth.set(width);
2351
+ try {
2352
+ localStorage.setItem(MODULE_LIST_WIDTH_KEY, String(width));
2353
+ }
2354
+ catch {
2355
+ // ignore quota / private mode
2356
+ }
2357
+ }
2358
+ async onNodeClick(event) {
2359
+ const data = event.node?.['data'];
2360
+ const treeNode = data?.treeNode;
2361
+ if (!treeNode || treeNode.kind === 'type-group') {
2362
+ return;
2363
+ }
2364
+ if (isExplorerFolderKind(treeNode.kind)) {
2365
+ const tree = this.tree();
2366
+ if (tree) {
2367
+ await tree.toggleNodeExpansion(treeNode.id);
2368
+ const current = this.selectedNode();
2369
+ if (current) {
2370
+ tree.selectNode(current.id);
2371
+ }
2372
+ else {
2373
+ tree.deselectNode(treeNode.id);
2374
+ }
2375
+ }
2376
+ return;
2377
+ }
2378
+ this.selectPageNode(treeNode);
2379
+ }
2380
+ onEntityTabChanged(event) {
2381
+ const key = event.tab?.key || this.entityTabs()[event.index]?.id;
2382
+ if (!key || key === this.dataService.activeEntitySection()) {
2383
+ return;
2384
+ }
2385
+ this.dataService.selectEntitySection(key);
2386
+ }
799
2387
  descriptionKeyForKind(kind) {
800
2388
  switch (kind) {
801
2389
  case 'module':
802
2390
  return '@platform-dev-tools:module.descriptions.module';
803
- case 'entities':
804
- return '@platform-dev-tools:module.descriptions.entities';
805
- case 'entity':
806
- return '@platform-dev-tools:module.descriptions.entity';
807
- case 'permissions':
808
- return '@platform-dev-tools:module.descriptions.permissions';
809
- case 'features':
810
- return '@platform-dev-tools:module.descriptions.features';
2391
+ case 'diagram':
2392
+ return '@platform-dev-tools:module.descriptions.diagram';
2393
+ case 'commands':
2394
+ case 'command':
2395
+ return '@platform-dev-tools:module.descriptions.command';
2396
+ case 'queries':
2397
+ case 'query':
2398
+ return '@platform-dev-tools:module.descriptions.query';
2399
+ case 'plugins':
2400
+ case 'plugin':
2401
+ return '@platform-dev-tools:module.descriptions.plugin';
2402
+ case 'widgets':
2403
+ case 'widget':
2404
+ return '@platform-dev-tools:module.descriptions.widget';
811
2405
  default:
812
2406
  return null;
813
2407
  }
814
2408
  }
815
- onNodeClick(event) {
816
- const node = event.node;
817
- const data = node?.['data'];
818
- const treeNode = data?.treeNode;
819
- if (treeNode) {
820
- this.selectedNode.set(treeNode);
2409
+ async onQueryChanged(params) {
2410
+ const state = explorerStateFromQuery(params);
2411
+ const nextSearch = state.search ?? '';
2412
+ const searchChanged = nextSearch !== this.search();
2413
+ if (searchChanged) {
2414
+ this.search.set(nextSearch);
2415
+ }
2416
+ if (!this.catalogReady) {
2417
+ return;
2418
+ }
2419
+ if (searchChanged) {
2420
+ this.dataService.clearCache();
2421
+ await this.initializeModules(nextSearch);
2422
+ await this.tree()?.reloadData();
2423
+ }
2424
+ await this.applyRouteState(state);
2425
+ }
2426
+ async boot() {
2427
+ const state = explorerStateFromQuery(this.route.snapshot.queryParamMap);
2428
+ if (state.search) {
2429
+ this.search.set(state.search);
2430
+ }
2431
+ await this.initializeModules(this.search());
2432
+ this.catalogReady = true;
2433
+ await this.applyRouteState(state);
2434
+ }
2435
+ selectPageNode(treeNode) {
2436
+ this.dataService.clearEntityTabs();
2437
+ this.selectedNode.set(treeNode);
2438
+ this.recompute();
2439
+ this.writeQuery(treeNode);
2440
+ }
2441
+ writeQuery(node) {
2442
+ if (this.syncingFromUrl) {
2443
+ return;
2444
+ }
2445
+ void this.router.navigate([], {
2446
+ relativeTo: this.route,
2447
+ queryParams: queryParamsFromExplorer(node, this.search()),
2448
+ replaceUrl: true,
2449
+ });
2450
+ }
2451
+ async applyRouteState(state) {
2452
+ this.syncingFromUrl = true;
2453
+ try {
2454
+ const node = await this.dataService.resolveExplorerSelection(state, this.moduleNodes());
2455
+ if (sameExplorerNode(this.selectedNode(), node)) {
2456
+ await this.revealSelection();
2457
+ return;
2458
+ }
2459
+ this.dataService.clearEntityTabs();
2460
+ this.selectedNode.set(node);
821
2461
  this.recompute();
822
- // When a module node is clicked, expand it so the tree loads its children (entities, permissions, features)
823
- if (treeNode.kind === 'module') {
824
- const treeRef = this.tree();
825
- if (treeRef && typeof treeRef.expandNode === 'function') {
826
- treeRef.expandNode(treeNode.id);
827
- }
2462
+ await this.revealSelection();
2463
+ }
2464
+ finally {
2465
+ this.syncingFromUrl = false;
2466
+ }
2467
+ }
2468
+ async revealSelection() {
2469
+ const node = this.selectedNode();
2470
+ if (!node) {
2471
+ return;
2472
+ }
2473
+ const tree = await this.waitForTree();
2474
+ if (!tree) {
2475
+ return;
2476
+ }
2477
+ const ids = explorerRevealIds(node);
2478
+ for (const id of ids.slice(0, -1)) {
2479
+ try {
2480
+ await tree.expandNode(id);
2481
+ }
2482
+ catch {
2483
+ // node may not be in the filtered tree
2484
+ }
2485
+ }
2486
+ try {
2487
+ tree.selectNode(node.id);
2488
+ }
2489
+ catch {
2490
+ // ignore missing tree node
2491
+ }
2492
+ }
2493
+ async waitForTree() {
2494
+ for (let attempt = 0; attempt < 12; attempt++) {
2495
+ const tree = this.tree();
2496
+ if (tree) {
2497
+ return tree;
828
2498
  }
2499
+ await new Promise((resolve) => setTimeout(resolve, 16));
829
2500
  }
2501
+ return this.tree();
830
2502
  }
831
- onNodeToggle(_event) {
832
- // Tree loads children via datasource(parentId) when a node is expanded; no extra work needed.
2503
+ async initializeModules(search) {
2504
+ this.loading.set(true);
2505
+ this.loadError.set(false);
2506
+ try {
2507
+ const modules = await this.dataService.listModules(search);
2508
+ this.moduleNodes.set(this.dataService.toModuleTreeNodes(modules));
2509
+ }
2510
+ catch {
2511
+ this.loadError.set(true);
2512
+ this.moduleNodes.set([]);
2513
+ }
2514
+ finally {
2515
+ this.loading.set(false);
2516
+ }
833
2517
  }
834
2518
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
835
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerPageComponent, isStandalone: true, selector: "axp-platform-explorer-page", providers: [
2519
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerPageComponent, isStandalone: true, selector: "axp-platform-explorer-page", host: { classAttribute: "axp-platform-explorer" }, providers: [
836
2520
  PlatformExplorerDataService,
837
2521
  {
838
2522
  provide: AXPPageLayoutBase,
839
2523
  useExisting: AXPlatformExplorerPageComponent,
840
2524
  },
841
- ], viewQueries: [{ propertyName: "tree", first: true, predicate: ["tree"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout>\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{ '@platform-dev-tools:module.menus.platform-explorer.title' | translate | async\n }}</axp-layout-title>\n </axp-layout-header>\n <axp-layout-content class=\"axp-platform-explorer__tree-wrap\">\n @if (loading()) {\n <p class=\"axp-platform-explorer__loading\">Loading...</p>\n } @else if (moduleNodes().length === 0) {\n <p class=\"axp-platform-explorer__empty\">{{ '@platform-dev-tools:module.messages.no-modules' | translate | async }}\n </p>\n } @else {\n <ax-tree-view #tree [datasource]=\"datasource\" [selectMode]=\"'single'\" [showIcons]=\"false\"\n [showChildrenBadge]=\"true\" [dragBehavior]=\"'none'\" look=\"default\" [nodeTemplate]=\"nodeTemplate\"\n (onNodeClick)=\"onNodeClick($event)\" (onNodeToggle)=\"onNodeToggle($event)\" />\n }\n </axp-layout-content>\n </axp-layout-start-side>\n\n <axp-page-content class=\"min-h-0 pt-0\">\n @if (selectedNode()) {\n <axp-platform-explorer-detail [node]=\"selectedNode()!\" />\n } @else {\n <p class=\"axp-platform-explorer__hint\">{{ '@platform-dev-tools:module.messages.select-node' | translate | async }}\n </p>\n }\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Node template: icon per kind (with color) + label (translated) + badge -->\n<ng-template #nodeTemplate let-node=\"node\">\n @let data = node['data'];\n @let treeNode = $safeNavigationMigration(data?.treeNode);\n @let kind = $safeNavigationMigration(treeNode?.kind);\n @let count = node['childrenCount'] ?? treeNode?.itemCount;\n <span class=\"axp-platform-explorer__node\">\n @switch (kind) {\n @case ('module') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--module fa-light fa-cube\"\n aria-hidden=\"true\"></i>\n }\n @case ('entities') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entities fa-light fa-table-list\"\n aria-hidden=\"true\"></i>\n }\n @case ('entity') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entity fa-light fa-table\"\n aria-hidden=\"true\"></i>\n }\n @case ('permissions') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--permissions fa-light fa-shield-halved\"\n aria-hidden=\"true\"></i>\n }\n @case ('features') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--features fa-light fa-sliders\"\n aria-hidden=\"true\"></i>\n }\n @default {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--default fa-light fa-circle-dot\"\n aria-hidden=\"true\"></i>\n }\n }\n <span class=\"axp-platform-explorer__node-label\">{{ node['title'] | translate | async }}</span>\n @if (count != null && count > 0) {\n <span class=\"axp-platform-explorer__node-badge\">{{ count }}</span>\n }\n </span>\n</ng-template>", styles: ["@layer properties;.axp-platform-explorer .axp-platform-explorer__tree-wrap{display:flex;min-height:0px;min-width:calc(var(--spacing, .25rem) * 64);flex-direction:column;overflow-x:auto;overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);padding-inline:calc(var(--spacing, .25rem) * 4);max-height:calc(100vh - 180px)}.axp-platform-explorer .axp-platform-explorer__loading,.axp-platform-explorer .axp-platform-explorer__empty{margin:0;padding:calc(var(--spacing, .25rem) * 4);color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__hint{padding:calc(var(--spacing, .25rem) * 6);color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__node{display:flex;width:100%;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer .axp-platform-explorer__node-icon{width:1em;flex-shrink:0}.axp-platform-explorer .axp-platform-explorer__node-icon--module{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--entities{color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__node-icon--entity{color:rgba(var(--ax-sys-color-accent1-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--permissions{color:rgba(var(--ax-sys-color-success-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--features{color:rgba(var(--ax-sys-color-warning-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--default{color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__node-label{min-width:0px;flex:1;white-space:nowrap}.axp-platform-explorer .axp-platform-explorer__node-badge{margin-left:auto;min-width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:calc(infinity * 1px);padding-inline:calc(var(--spacing, .25rem) * 1.5);text-align:center;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: calc(var(--spacing, .25rem) * 5);line-height:calc(var(--spacing, .25rem) * 5);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);background-color:rgba(var(--ax-sys-color-primary-lightest-surface));color:rgba(var(--ax-sys-color-primary-surface))}@property --tw-leading{syntax: \"*\"; inherits: false;}@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-leading: initial;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPThemeLayoutStartSideComponent, selector: "axp-layout-page-start-side, axp-layout-start-side" }, { kind: "component", type: AXPThemeLayoutHeaderComponent, selector: "axp-layout-header" }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "alternate", "nodeTemplate", "idField", "titleField", "highlightText", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick", "doubleClickDuration", "tooltipDelay", "controlledSelection", "selectedIds"], outputs: ["datasourceChange", "selectedIdsChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "component", type: AXPlatformExplorerDetailComponent, selector: "axp-platform-explorer-detail", inputs: ["node"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2525
+ ], viewQueries: [{ propertyName: "tree", first: true, predicate: ["tree"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout>\n <axp-layout-start-side\n class=\"axp-platform-explorer__start-side\"\n [axResizable]=\"!deviceService.isSmall()\"\n [minWidth]=\"moduleListMinWidth\"\n [maxWidth]=\"moduleListMaxWidth\"\n [width]=\"moduleListWidth()\"\n [defaultWidth]=\"moduleListDefaultWidth\"\n [dblClickAction]=\"'reset'\"\n (onResizingEnded)=\"onModuleListResized($event)\"\n (onResizingDblClick)=\"onModuleListResized($event)\"\n >\n <axp-layout-header>\n <axp-layout-title>\n {{ '@platform-dev-tools:module.menus.platform-explorer.title' | translate | async }}\n </axp-layout-title>\n </axp-layout-header>\n <axp-layout-content class=\"axp-platform-explorer__tree-wrap\">\n <ax-search-box\n class=\"axp-platform-explorer__search\"\n look=\"fill\"\n [delayTime]=\"300\"\n [value]=\"search()\"\n [placeholder]=\"('@platform-dev-tools:module.messages.search-modules' | translate | async) ?? ''\"\n (onValueChanged)=\"onSearchChanged($event)\"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n <!-- ----- Loading catalog modules ----- -->\n @if (loading()) {\n <div class=\"axp-platform-explorer__loading\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- No modules in catalog ----- -->\n @else if (!hasCatalogModules()) {\n <axp-state-message\n icon=\"fa-light fa-cubes\"\n [title]=\"'@platform-dev-tools:module.messages.no-modules'\"\n [description]=\"'@platform-dev-tools:module.messages.no-modules-hint'\"\n />\n }\n <!-- ----- Module tree ----- -->\n @else {\n <ax-tree-view\n #tree\n [datasource]=\"datasource\"\n [selectMode]=\"'single'\"\n [showIcons]=\"false\"\n [showChildrenBadge]=\"false\"\n [dragBehavior]=\"'none'\"\n look=\"default\"\n [nodeTemplate]=\"nodeTemplate\"\n (onNodeClick)=\"onNodeClick($event)\"\n />\n }\n </axp-layout-content>\n </axp-layout-start-side>\n\n <!-- ----- Entity layout tabs stay in the sticky page header ----- -->\n @if (entityTabs().length) {\n <axp-page-toolbar class=\"axp-platform-explorer__entity-toolbar\">\n <ax-tabs\n class=\"axp-platform-explorer__entity-tabs\"\n [look]=\"'with-line'\"\n [location]=\"'bottom'\"\n [fitParent]=\"false\"\n (onActiveTabChanged)=\"onEntityTabChanged($event)\"\n >\n @for (tab of entityTabs(); track tab.id) {\n <ax-tab-item\n [key]=\"tab.id\"\n [text]=\"\n ((tab.textKey | translate | async) ?? '') +\n (tab.extraText ? ' \u00B7 ' + ((tab.extraText | translate | async) ?? '') : '')\n \"\n [active]=\"activeEntitySection() === tab.id\"\n />\n }\n </ax-tabs>\n </axp-page-toolbar>\n }\n\n <axp-page-content class=\"axp-platform-explorer__page-content min-h-0 pt-0\">\n <!-- ----- Selected catalog node ----- -->\n @if (selectedNode(); as node) {\n <axp-platform-explorer-detail [node]=\"node\" />\n }\n <!-- ----- Empty selection ----- -->\n @else {\n <axp-state-message\n icon=\"fa-light fa-sitemap\"\n [title]=\"'@platform-dev-tools:module.messages.select-node'\"\n [description]=\"'@platform-dev-tools:module.messages.select-node-hint'\"\n />\n }\n </axp-page-content>\n</axp-page-layout>\n\n<ng-template #nodeTemplate let-node=\"node\">\n @let data = node['data'];\n @let treeNode = data?.treeNode;\n @let kind = treeNode?.kind;\n @let count = node['childrenCount'] ?? treeNode?.itemCount;\n <!-- ----- Manifest type group header ----- -->\n @if (kind === 'type-group') {\n <span\n class=\"axp-platform-explorer__node axp-platform-explorer__node--group\"\n [class.axp-platform-explorer__node--group-divided]=\"treeNode?.divided\"\n >\n <!--->\n <!-- ----- Divider after the previous type group ----- -->\n @if (treeNode?.divided) {\n <span class=\"axp-platform-explorer__group-rule\" aria-hidden=\"true\"></span>\n }\n <span class=\"axp-platform-explorer__group-title\">{{ node['title'] | translate | async }}</span>\n </span>\n }\n <!-- ----- Catalog node ----- -->\n @else {\n <span class=\"axp-platform-explorer__node\">\n @switch (kind) {\n @case ('module') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--module\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-cube'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('entities') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entities fa-light fa-table\" aria-hidden=\"true\"></i>\n }\n @case ('diagram') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--diagram fa-light fa-diagram-project\" aria-hidden=\"true\"></i>\n }\n @case ('entity') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entity\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-table'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('commands') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--commands fa-light fa-terminal\" aria-hidden=\"true\"></i>\n }\n @case ('command') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--command\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-play'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('queries') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--queries fa-light fa-database\" aria-hidden=\"true\"></i>\n }\n @case ('query') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--query\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-magnifying-glass'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('plugins') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--plugins fa-light fa-puzzle-piece\" aria-hidden=\"true\"></i>\n }\n @case ('plugin') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--plugin\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-plug'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('widgets') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--widgets fa-light fa-cubes\" aria-hidden=\"true\"></i>\n }\n @case ('widget') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--widget\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-cube'\"\n aria-hidden=\"true\"\n ></i>\n }\n @default {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--default fa-light fa-circle-dot\" aria-hidden=\"true\"></i>\n }\n }\n <span class=\"axp-platform-explorer__node-label\">{{ node['title'] | translate | async }}</span>\n <!--->\n <!-- ----- Child count badge when the folder has items ----- -->\n @if (count != null && count > 0) {\n <span class=\"axp-platform-explorer__node-badge\">{{ count }}</span>\n }\n </span>\n }\n</ng-template>\n", styles: ["@layer properties;.axp-platform-explorer axp-layout-container{overflow:hidden}.axp-platform-explorer__page-content{display:flex;min-height:0px;flex:1;flex-direction:column;overflow:hidden}axp-platform-explorer-detail,axp-platform-explorer-entity-detail{display:flex;height:100%;min-height:0px;flex:1;flex-direction:column;overflow:hidden}.axp-platform-explorer__tree-wrap{display:flex;min-height:0px;width:100%;min-width:0px;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow-x:hidden;overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);padding-inline:calc(var(--spacing, .25rem) * 3);max-height:calc(100vh - 180px)}.axp-platform-explorer__start-side{position:relative;height:100%;flex-shrink:0;overflow:visible}.axp-platform-explorer #axp-drawer-start{overflow:visible}.axp-platform-explorer__search{flex-shrink:0}.axp-platform-explorer__loading{display:flex;flex:1;align-items:center;justify-content:center;padding:calc(var(--spacing, .25rem) * 6)}.axp-platform-explorer__node{display:flex;width:100%;min-width:0px;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__node--group{pointer-events:none;width:100%;min-width:0px;flex-direction:column;align-items:stretch;gap:calc(var(--spacing, .25rem) * 1.5)}.axp-platform-explorer__node--group-divided{margin-top:var(--spacing, .25rem);padding-top:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__group-rule{display:block;width:100%;border-top-style:var(--tw-border-style);border-top-width:1px;border-color:rgba(var(--ax-sys-color-light-surface))}.axp-platform-explorer__group-title{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__group-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__tree-wrap .ax-tree-view-node:has(.axp-platform-explorer__node--group){pointer-events:none;cursor:default;background:transparent!important;min-height:0!important;padding-block:.25rem;padding-inline-start:0!important}.axp-platform-explorer__tree-wrap .ax-tree-view-node:has(.axp-platform-explorer__node--group) .ax-tree-view-node-content{padding-inline-start:0!important;margin-inline-start:0!important}.axp-platform-explorer__tree-wrap .ax-tree-view-node:has(.axp-platform-explorer__node--group) .ax-tree-view-expand-toggle{display:none!important;width:0!important;min-width:0!important;margin:0!important;padding:0!important}.axp-platform-explorer__node-icon{width:1em;flex-shrink:0;text-align:center}.axp-platform-explorer__node-icon--module{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__node-icon--diagram,.axp-platform-explorer__node-icon--entities,.axp-platform-explorer__node-icon--entity{color:rgba(var(--ax-sys-color-accent1-surface))}.axp-platform-explorer__node-icon--commands,.axp-platform-explorer__node-icon--command{color:rgba(var(--ax-sys-color-success-surface))}.axp-platform-explorer__node-icon--queries,.axp-platform-explorer__node-icon--query{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__node-icon--plugins,.axp-platform-explorer__node-icon--plugin{color:rgba(var(--ax-sys-color-warning-surface))}.axp-platform-explorer__node-icon--widgets,.axp-platform-explorer__node-icon--widget,.axp-platform-explorer__node-icon--default{color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer__node-label{min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.axp-platform-explorer__node-badge{margin-left:auto;min-width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:calc(infinity * 1px);padding-inline:calc(var(--spacing, .25rem) * 1.5);text-align:center;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: calc(var(--spacing, .25rem) * 5);line-height:calc(var(--spacing, .25rem) * 5);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);background-color:rgba(var(--ax-sys-color-primary-lightest-surface));color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__entity-toolbar{width:100%}.axp-platform-explorer__entity-tabs{width:100%;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@property --tw-leading{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-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial;--tw-leading: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i2$1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i3$1.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i4.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "ngmodule", type: AXTabsModule }, { kind: "component", type: i5$1.AXTabsComponent, selector: "ax-tabs", inputs: ["look", "location", "reorderable", "reorderDelay", "renderMode", "stacked", "fitParent", "minWidth", "content"], outputs: ["onActiveTabChanged", "onTabsReordered"] }, { kind: "component", type: i5$1.AXTabItemComponent, selector: "ax-tab-item", inputs: ["disabled", "text", "key", "headerTemplate", "active"], outputs: ["disabledChange", "onClick", "onBlur", "onFocus", "activeChange"] }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPThemeLayoutStartSideComponent, selector: "axp-layout-page-start-side, axp-layout-start-side" }, { kind: "component", type: AXPThemeLayoutHeaderComponent, selector: "axp-layout-header" }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "alternate", "nodeTemplate", "idField", "titleField", "highlightText", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick", "doubleClickDuration", "tooltipDelay", "controlledSelection", "selectedIds"], outputs: ["datasourceChange", "selectedIdsChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "component", type: AXPlatformExplorerDetailComponent, selector: "axp-platform-explorer-detail", inputs: ["node"] }, { kind: "directive", type: AXResizableDirective, selector: "[axResizable]", inputs: ["axResizable", "handles", "minWidth", "maxWidth", "minHeight", "maxHeight", "dblClickAction", "width", "defaultWidth", "height", "defaultHeight", "lockAspectRatio"], outputs: ["axResizableChange", "handlesChange", "minWidthChange", "maxWidthChange", "minHeightChange", "maxHeightChange", "dblClickActionChange", "widthChange", "defaultWidthChange", "heightChange", "defaultHeightChange", "lockAspectRatioChange", "onResizingStarted", "onResizingEnded", "onResizingDblClick"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
842
2526
  }
843
2527
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerPageComponent, decorators: [{
844
2528
  type: Component,
845
- args: [{ selector: 'axp-platform-explorer-page', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2529
+ args: [{ selector: 'axp-platform-explorer-page', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'axp-platform-explorer' }, imports: [
846
2530
  CommonModule,
847
2531
  AXTranslationModule,
2532
+ AXLoadingModule,
2533
+ AXSearchBoxModule,
2534
+ AXDecoratorModule,
2535
+ AXTabsModule,
848
2536
  AXPPageLayoutComponent,
849
2537
  AXPThemeLayoutStartSideComponent,
850
2538
  AXPThemeLayoutHeaderComponent,
851
2539
  AXPThemeLayoutBlockComponent,
2540
+ AXPStateMessageComponent,
852
2541
  AXTreeViewComponent,
853
2542
  AXPlatformExplorerDetailComponent,
2543
+ AXResizableDirective,
854
2544
  ], providers: [
855
2545
  PlatformExplorerDataService,
856
2546
  {
857
2547
  provide: AXPPageLayoutBase,
858
2548
  useExisting: AXPlatformExplorerPageComponent,
859
2549
  },
860
- ], template: "<axp-page-layout>\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{ '@platform-dev-tools:module.menus.platform-explorer.title' | translate | async\n }}</axp-layout-title>\n </axp-layout-header>\n <axp-layout-content class=\"axp-platform-explorer__tree-wrap\">\n @if (loading()) {\n <p class=\"axp-platform-explorer__loading\">Loading...</p>\n } @else if (moduleNodes().length === 0) {\n <p class=\"axp-platform-explorer__empty\">{{ '@platform-dev-tools:module.messages.no-modules' | translate | async }}\n </p>\n } @else {\n <ax-tree-view #tree [datasource]=\"datasource\" [selectMode]=\"'single'\" [showIcons]=\"false\"\n [showChildrenBadge]=\"true\" [dragBehavior]=\"'none'\" look=\"default\" [nodeTemplate]=\"nodeTemplate\"\n (onNodeClick)=\"onNodeClick($event)\" (onNodeToggle)=\"onNodeToggle($event)\" />\n }\n </axp-layout-content>\n </axp-layout-start-side>\n\n <axp-page-content class=\"min-h-0 pt-0\">\n @if (selectedNode()) {\n <axp-platform-explorer-detail [node]=\"selectedNode()!\" />\n } @else {\n <p class=\"axp-platform-explorer__hint\">{{ '@platform-dev-tools:module.messages.select-node' | translate | async }}\n </p>\n }\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Node template: icon per kind (with color) + label (translated) + badge -->\n<ng-template #nodeTemplate let-node=\"node\">\n @let data = node['data'];\n @let treeNode = $safeNavigationMigration(data?.treeNode);\n @let kind = $safeNavigationMigration(treeNode?.kind);\n @let count = node['childrenCount'] ?? treeNode?.itemCount;\n <span class=\"axp-platform-explorer__node\">\n @switch (kind) {\n @case ('module') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--module fa-light fa-cube\"\n aria-hidden=\"true\"></i>\n }\n @case ('entities') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entities fa-light fa-table-list\"\n aria-hidden=\"true\"></i>\n }\n @case ('entity') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entity fa-light fa-table\"\n aria-hidden=\"true\"></i>\n }\n @case ('permissions') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--permissions fa-light fa-shield-halved\"\n aria-hidden=\"true\"></i>\n }\n @case ('features') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--features fa-light fa-sliders\"\n aria-hidden=\"true\"></i>\n }\n @default {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--default fa-light fa-circle-dot\"\n aria-hidden=\"true\"></i>\n }\n }\n <span class=\"axp-platform-explorer__node-label\">{{ node['title'] | translate | async }}</span>\n @if (count != null && count > 0) {\n <span class=\"axp-platform-explorer__node-badge\">{{ count }}</span>\n }\n </span>\n</ng-template>", styles: ["@layer properties;.axp-platform-explorer .axp-platform-explorer__tree-wrap{display:flex;min-height:0px;min-width:calc(var(--spacing, .25rem) * 64);flex-direction:column;overflow-x:auto;overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);padding-inline:calc(var(--spacing, .25rem) * 4);max-height:calc(100vh - 180px)}.axp-platform-explorer .axp-platform-explorer__loading,.axp-platform-explorer .axp-platform-explorer__empty{margin:0;padding:calc(var(--spacing, .25rem) * 4);color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__hint{padding:calc(var(--spacing, .25rem) * 6);color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__node{display:flex;width:100%;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer .axp-platform-explorer__node-icon{width:1em;flex-shrink:0}.axp-platform-explorer .axp-platform-explorer__node-icon--module{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--entities{color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__node-icon--entity{color:rgba(var(--ax-sys-color-accent1-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--permissions{color:rgba(var(--ax-sys-color-success-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--features{color:rgba(var(--ax-sys-color-warning-surface))}.axp-platform-explorer .axp-platform-explorer__node-icon--default{color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer .axp-platform-explorer__node-label{min-width:0px;flex:1;white-space:nowrap}.axp-platform-explorer .axp-platform-explorer__node-badge{margin-left:auto;min-width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:calc(infinity * 1px);padding-inline:calc(var(--spacing, .25rem) * 1.5);text-align:center;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: calc(var(--spacing, .25rem) * 5);line-height:calc(var(--spacing, .25rem) * 5);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);background-color:rgba(var(--ax-sys-color-primary-lightest-surface));color:rgba(var(--ax-sys-color-primary-surface))}@property --tw-leading{syntax: \"*\"; inherits: false;}@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-leading: initial;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
2550
+ ], template: "<axp-page-layout>\n <axp-layout-start-side\n class=\"axp-platform-explorer__start-side\"\n [axResizable]=\"!deviceService.isSmall()\"\n [minWidth]=\"moduleListMinWidth\"\n [maxWidth]=\"moduleListMaxWidth\"\n [width]=\"moduleListWidth()\"\n [defaultWidth]=\"moduleListDefaultWidth\"\n [dblClickAction]=\"'reset'\"\n (onResizingEnded)=\"onModuleListResized($event)\"\n (onResizingDblClick)=\"onModuleListResized($event)\"\n >\n <axp-layout-header>\n <axp-layout-title>\n {{ '@platform-dev-tools:module.menus.platform-explorer.title' | translate | async }}\n </axp-layout-title>\n </axp-layout-header>\n <axp-layout-content class=\"axp-platform-explorer__tree-wrap\">\n <ax-search-box\n class=\"axp-platform-explorer__search\"\n look=\"fill\"\n [delayTime]=\"300\"\n [value]=\"search()\"\n [placeholder]=\"('@platform-dev-tools:module.messages.search-modules' | translate | async) ?? ''\"\n (onValueChanged)=\"onSearchChanged($event)\"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n <!-- ----- Loading catalog modules ----- -->\n @if (loading()) {\n <div class=\"axp-platform-explorer__loading\">\n <ax-loading></ax-loading>\n </div>\n }\n <!-- ----- Catalog query failed ----- -->\n @else if (loadError()) {\n <axp-state-message\n icon=\"fa-light fa-triangle-exclamation\"\n [title]=\"'@platform-dev-tools:module.messages.load-error-title'\"\n [description]=\"'@platform-dev-tools:module.messages.load-error'\"\n />\n }\n <!-- ----- No modules in catalog ----- -->\n @else if (!hasCatalogModules()) {\n <axp-state-message\n icon=\"fa-light fa-cubes\"\n [title]=\"'@platform-dev-tools:module.messages.no-modules'\"\n [description]=\"'@platform-dev-tools:module.messages.no-modules-hint'\"\n />\n }\n <!-- ----- Module tree ----- -->\n @else {\n <ax-tree-view\n #tree\n [datasource]=\"datasource\"\n [selectMode]=\"'single'\"\n [showIcons]=\"false\"\n [showChildrenBadge]=\"false\"\n [dragBehavior]=\"'none'\"\n look=\"default\"\n [nodeTemplate]=\"nodeTemplate\"\n (onNodeClick)=\"onNodeClick($event)\"\n />\n }\n </axp-layout-content>\n </axp-layout-start-side>\n\n <!-- ----- Entity layout tabs stay in the sticky page header ----- -->\n @if (entityTabs().length) {\n <axp-page-toolbar class=\"axp-platform-explorer__entity-toolbar\">\n <ax-tabs\n class=\"axp-platform-explorer__entity-tabs\"\n [look]=\"'with-line'\"\n [location]=\"'bottom'\"\n [fitParent]=\"false\"\n (onActiveTabChanged)=\"onEntityTabChanged($event)\"\n >\n @for (tab of entityTabs(); track tab.id) {\n <ax-tab-item\n [key]=\"tab.id\"\n [text]=\"\n ((tab.textKey | translate | async) ?? '') +\n (tab.extraText ? ' \u00B7 ' + ((tab.extraText | translate | async) ?? '') : '')\n \"\n [active]=\"activeEntitySection() === tab.id\"\n />\n }\n </ax-tabs>\n </axp-page-toolbar>\n }\n\n <axp-page-content class=\"axp-platform-explorer__page-content min-h-0 pt-0\">\n <!-- ----- Selected catalog node ----- -->\n @if (selectedNode(); as node) {\n <axp-platform-explorer-detail [node]=\"node\" />\n }\n <!-- ----- Empty selection ----- -->\n @else {\n <axp-state-message\n icon=\"fa-light fa-sitemap\"\n [title]=\"'@platform-dev-tools:module.messages.select-node'\"\n [description]=\"'@platform-dev-tools:module.messages.select-node-hint'\"\n />\n }\n </axp-page-content>\n</axp-page-layout>\n\n<ng-template #nodeTemplate let-node=\"node\">\n @let data = node['data'];\n @let treeNode = data?.treeNode;\n @let kind = treeNode?.kind;\n @let count = node['childrenCount'] ?? treeNode?.itemCount;\n <!-- ----- Manifest type group header ----- -->\n @if (kind === 'type-group') {\n <span\n class=\"axp-platform-explorer__node axp-platform-explorer__node--group\"\n [class.axp-platform-explorer__node--group-divided]=\"treeNode?.divided\"\n >\n <!--->\n <!-- ----- Divider after the previous type group ----- -->\n @if (treeNode?.divided) {\n <span class=\"axp-platform-explorer__group-rule\" aria-hidden=\"true\"></span>\n }\n <span class=\"axp-platform-explorer__group-title\">{{ node['title'] | translate | async }}</span>\n </span>\n }\n <!-- ----- Catalog node ----- -->\n @else {\n <span class=\"axp-platform-explorer__node\">\n @switch (kind) {\n @case ('module') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--module\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-cube'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('entities') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entities fa-light fa-table\" aria-hidden=\"true\"></i>\n }\n @case ('diagram') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--diagram fa-light fa-diagram-project\" aria-hidden=\"true\"></i>\n }\n @case ('entity') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--entity\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-table'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('commands') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--commands fa-light fa-terminal\" aria-hidden=\"true\"></i>\n }\n @case ('command') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--command\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-play'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('queries') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--queries fa-light fa-database\" aria-hidden=\"true\"></i>\n }\n @case ('query') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--query\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-magnifying-glass'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('plugins') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--plugins fa-light fa-puzzle-piece\" aria-hidden=\"true\"></i>\n }\n @case ('plugin') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--plugin\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-plug'\"\n aria-hidden=\"true\"\n ></i>\n }\n @case ('widgets') {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--widgets fa-light fa-cubes\" aria-hidden=\"true\"></i>\n }\n @case ('widget') {\n <i\n class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--widget\"\n [ngClass]=\"treeNode?.icon || 'fa-light fa-cube'\"\n aria-hidden=\"true\"\n ></i>\n }\n @default {\n <i class=\"axp-platform-explorer__node-icon axp-platform-explorer__node-icon--default fa-light fa-circle-dot\" aria-hidden=\"true\"></i>\n }\n }\n <span class=\"axp-platform-explorer__node-label\">{{ node['title'] | translate | async }}</span>\n <!--->\n <!-- ----- Child count badge when the folder has items ----- -->\n @if (count != null && count > 0) {\n <span class=\"axp-platform-explorer__node-badge\">{{ count }}</span>\n }\n </span>\n }\n</ng-template>\n", styles: ["@layer properties;.axp-platform-explorer axp-layout-container{overflow:hidden}.axp-platform-explorer__page-content{display:flex;min-height:0px;flex:1;flex-direction:column;overflow:hidden}axp-platform-explorer-detail,axp-platform-explorer-entity-detail{display:flex;height:100%;min-height:0px;flex:1;flex-direction:column;overflow:hidden}.axp-platform-explorer__tree-wrap{display:flex;min-height:0px;width:100%;min-width:0px;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow-x:hidden;overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);padding-inline:calc(var(--spacing, .25rem) * 3);max-height:calc(100vh - 180px)}.axp-platform-explorer__start-side{position:relative;height:100%;flex-shrink:0;overflow:visible}.axp-platform-explorer #axp-drawer-start{overflow:visible}.axp-platform-explorer__search{flex-shrink:0}.axp-platform-explorer__loading{display:flex;flex:1;align-items:center;justify-content:center;padding:calc(var(--spacing, .25rem) * 6)}.axp-platform-explorer__node{display:flex;width:100%;min-width:0px;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__node--group{pointer-events:none;width:100%;min-width:0px;flex-direction:column;align-items:stretch;gap:calc(var(--spacing, .25rem) * 1.5)}.axp-platform-explorer__node--group-divided{margin-top:var(--spacing, .25rem);padding-top:calc(var(--spacing, .25rem) * 2)}.axp-platform-explorer__group-rule{display:block;width:100%;border-top-style:var(--tw-border-style);border-top-width:1px;border-color:rgba(var(--ax-sys-color-light-surface))}.axp-platform-explorer__group-title{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axp-platform-explorer__group-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axp-platform-explorer__tree-wrap .ax-tree-view-node:has(.axp-platform-explorer__node--group){pointer-events:none;cursor:default;background:transparent!important;min-height:0!important;padding-block:.25rem;padding-inline-start:0!important}.axp-platform-explorer__tree-wrap .ax-tree-view-node:has(.axp-platform-explorer__node--group) .ax-tree-view-node-content{padding-inline-start:0!important;margin-inline-start:0!important}.axp-platform-explorer__tree-wrap .ax-tree-view-node:has(.axp-platform-explorer__node--group) .ax-tree-view-expand-toggle{display:none!important;width:0!important;min-width:0!important;margin:0!important;padding:0!important}.axp-platform-explorer__node-icon{width:1em;flex-shrink:0;text-align:center}.axp-platform-explorer__node-icon--module{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__node-icon--diagram,.axp-platform-explorer__node-icon--entities,.axp-platform-explorer__node-icon--entity{color:rgba(var(--ax-sys-color-accent1-surface))}.axp-platform-explorer__node-icon--commands,.axp-platform-explorer__node-icon--command{color:rgba(var(--ax-sys-color-success-surface))}.axp-platform-explorer__node-icon--queries,.axp-platform-explorer__node-icon--query{color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__node-icon--plugins,.axp-platform-explorer__node-icon--plugin{color:rgba(var(--ax-sys-color-warning-surface))}.axp-platform-explorer__node-icon--widgets,.axp-platform-explorer__node-icon--widget,.axp-platform-explorer__node-icon--default{color:var(--color-neutral-500, oklch(55.6% 0 none))}.axp-platform-explorer__node-label{min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.axp-platform-explorer__node-badge{margin-left:auto;min-width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:calc(infinity * 1px);padding-inline:calc(var(--spacing, .25rem) * 1.5);text-align:center;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: calc(var(--spacing, .25rem) * 5);line-height:calc(var(--spacing, .25rem) * 5);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);background-color:rgba(var(--ax-sys-color-primary-lightest-surface));color:rgba(var(--ax-sys-color-primary-surface))}.axp-platform-explorer__entity-toolbar{width:100%}.axp-platform-explorer__entity-tabs{width:100%;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@property --tw-leading{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-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial;--tw-leading: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
861
2551
  }], ctorParameters: () => [], propDecorators: { tree: [{ type: i0.ViewChild, args: ['tree', { isSignal: true }] }] } });
862
2552
 
863
- //#region ---- Imports ----
2553
+ //#region ----- Imports -----
864
2554
  //#endregion
865
- //#region ---- Component ----
866
- class AXPlatformExplorerTreeNodeComponent {
2555
+ //#region ----- Component -----
2556
+ class AXPlatformExplorerListDetailComponent {
867
2557
  constructor() {
868
- this.node = input.required(/* @ts-ignore */
869
- ...(ngDevMode ? [{ debugName: "node" }] : /* istanbul ignore next */ []));
870
- this.selectedId = input(null, /* @ts-ignore */
871
- ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
872
- this.nodeSelect = output();
873
- this.expanded = signal(true, /* @ts-ignore */
874
- ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
875
- }
876
- childrenLength() {
877
- return this.node().children?.length ?? 0;
878
- }
879
- hasChildren() {
880
- const children = this.node().children;
881
- return !!children && children.length > 0;
882
- }
883
- isSelected() {
884
- return this.selectedId() === this.node().id;
885
- }
886
- toggle() {
887
- if (this.hasChildren()) {
888
- this.expanded.update((v) => !v);
889
- }
890
- }
891
- select() {
892
- this.nodeSelect.emit(this.node());
893
- }
894
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerTreeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
895
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerTreeNodeComponent, isStandalone: true, selector: "axp-platform-explorer-tree-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, selectedId: { classPropertyName: "selectedId", publicName: "selectedId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodeSelect: "nodeSelect" }, ngImport: i0, template: `
896
- <div class="axp-tree-node" [class.axp-tree-node--selected]="isSelected()">
897
- <button
898
- type="button"
899
- class="axp-tree-node__toggler"
900
- [class.axp-tree-node__toggler--open]="expanded()"
901
- [attr.aria-expanded]="expanded()"
902
- (click)="toggle()"
903
- >
904
- @if (hasChildren()) {
905
- <span class="axp-tree-node__icon" aria-hidden="true">{{ expanded() ? '▼' : '▶' }}</span>
906
- } @else {
907
- <span class="axp-tree-node__icon axp-tree-node__icon--empty"></span>
908
- }
909
- </button>
910
- <button
911
- type="button"
912
- class="axp-tree-node__label"
913
- (click)="select()"
914
- >
915
- {{ node().label }}
916
- @if (childrenLength() > 0) {
917
- <span class="axp-tree-node__badge">({{ childrenLength() }})</span>
2558
+ this.moduleName = input.required(/* @ts-ignore */
2559
+ ...(ngDevMode ? [{ debugName: "moduleName" }] : /* istanbul ignore next */ []));
2560
+ this.kind = input.required(/* @ts-ignore */
2561
+ ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
2562
+ this.dataService = inject(PlatformExplorerDataService);
2563
+ this.dataSource = new AXDataSource({
2564
+ key: 'name',
2565
+ pageSize: 20,
2566
+ load: async (e) => {
2567
+ const rows = await this.loadRows();
2568
+ const skip = e?.skip ?? 0;
2569
+ const take = e?.take ?? 20;
2570
+ return { items: rows.slice(skip, skip + take), total: rows.length };
2571
+ },
2572
+ });
2573
+ effect(() => {
2574
+ this.moduleName();
2575
+ this.kind();
2576
+ this.dataSource.refresh();
2577
+ });
2578
+ }
2579
+ async loadRows() {
2580
+ const catalog = await this.dataService.getModule(this.moduleName());
2581
+ if (!catalog) {
2582
+ return [];
918
2583
  }
919
- </button>
920
- </div>
921
- @if (hasChildren() && expanded()) {
922
- <div class="axp-tree-node__children">
923
- @for (child of node().children ?? []; track child.id) {
924
- <axp-platform-explorer-tree-node
925
- [node]="child"
926
- [selectedId]="selectedId()"
927
- (nodeSelect)="nodeSelect.emit($event)"
928
- />
2584
+ switch (this.kind()) {
2585
+ case 'entities':
2586
+ return catalog.entities.map((item) => ({
2587
+ name: item.name,
2588
+ title: item.title ?? item.name,
2589
+ module: item.module,
2590
+ }));
2591
+ case 'commands':
2592
+ return catalog.commands;
2593
+ case 'queries':
2594
+ return catalog.queries;
2595
+ case 'plugins':
2596
+ return catalog.plugins;
2597
+ case 'widgets':
2598
+ return catalog.widgets;
2599
+ default:
2600
+ return [];
929
2601
  }
930
- </div>
931
2602
  }
932
- `, isInline: true, styles: [":host{display:block}.axp-tree-node{display:flex;align-items:center;min-height:28px}.axp-tree-node--selected .axp-tree-node__label{font-weight:600;background:var(--ax-neutral-surface-hover, rgba(0, 0, 0, .04))}.axp-tree-node__toggler{width:24px;height:28px;padding:0;border:none;background:none;cursor:pointer;color:var(--ax-neutral-text-secondary, #666);flex-shrink:0}.axp-tree-node__toggler--open .axp-tree-node__icon{transform:rotate(0)}.axp-tree-node__icon{font-size:10px;display:inline-block}.axp-tree-node__icon--empty{width:10px}.axp-tree-node__label{flex:1;text-align:start;padding:4px 8px;border:none;background:none;cursor:pointer;font-size:13px;border-radius:4px}.axp-tree-node__label:hover{background:var(--ax-neutral-surface-hover, rgba(0, 0, 0, .04))}.axp-tree-node__badge{margin-inline-start:4px;font-size:11px;color:var(--ax-neutral-text-secondary, #666);font-weight:400}.axp-tree-node__children{padding-inline-start:16px}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: AXPlatformExplorerTreeNodeComponent, selector: "axp-platform-explorer-tree-node", inputs: ["node", "selectedId"], outputs: ["nodeSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2603
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerListDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2604
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPlatformExplorerListDetailComponent, isStandalone: true, selector: "axp-platform-explorer-list-detail", inputs: { moduleName: { classPropertyName: "moduleName", publicName: "moduleName", isSignal: true, isRequired: true, transformFunction: null }, kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "flex flex-col flex-1 min-h-0" }, ngImport: i0, template: `
2605
+ <div class="flex flex-1 min-h-0 p-3 overflow-auto">
2606
+ <ax-data-table
2607
+ class="flex-1 border border-lightest rounded-md overflow-hidden"
2608
+ [paging]="true"
2609
+ [dataSource]="dataSource"
2610
+ >
2611
+ <ax-text-column [dataField]="'name'" [caption]="'@platform-dev-tools:module.terms.name' | translate | async" [width]="'280px'" [allowResizing]="true"></ax-text-column>
2612
+ <ax-text-column [dataField]="'title'" [caption]="'@platform-dev-tools:module.terms.title' | translate | async" [cellTemplate]="titleTpl" [width]="'280px'" [allowResizing]="true"></ax-text-column>
2613
+ <ax-text-column [dataField]="'description'" [caption]="'@platform-dev-tools:module.terms.description' | translate | async" [cellTemplate]="descTpl" [allowResizing]="true"></ax-text-column>
2614
+ </ax-data-table>
2615
+ </div>
2616
+ <ng-template #titleTpl let-item>
2617
+ @if (item.data?.title) {
2618
+ {{ item.data.title | translate | async }}
2619
+ }
2620
+ @else {
2621
+ –
2622
+ }
2623
+ </ng-template>
2624
+ <ng-template #descTpl let-item>
2625
+ @if (item.data?.description) {
2626
+ {{ item.data.description | translate | async }}
2627
+ }
2628
+ @else {
2629
+ –
2630
+ }
2631
+ </ng-template>
2632
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i1$1.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i1$1.AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "translate", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
933
2633
  }
934
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerTreeNodeComponent, decorators: [{
2634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPlatformExplorerListDetailComponent, decorators: [{
935
2635
  type: Component,
936
- args: [{ selector: 'axp-platform-explorer-tree-node', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXPlatformExplorerTreeNodeComponent], template: `
937
- <div class="axp-tree-node" [class.axp-tree-node--selected]="isSelected()">
938
- <button
939
- type="button"
940
- class="axp-tree-node__toggler"
941
- [class.axp-tree-node__toggler--open]="expanded()"
942
- [attr.aria-expanded]="expanded()"
943
- (click)="toggle()"
944
- >
945
- @if (hasChildren()) {
946
- <span class="axp-tree-node__icon" aria-hidden="true">{{ expanded() ? '▼' : '▶' }}</span>
947
- } @else {
948
- <span class="axp-tree-node__icon axp-tree-node__icon--empty"></span>
949
- }
950
- </button>
951
- <button
952
- type="button"
953
- class="axp-tree-node__label"
954
- (click)="select()"
2636
+ args: [{
2637
+ selector: 'axp-platform-explorer-list-detail',
2638
+ standalone: true,
2639
+ encapsulation: ViewEncapsulation.None,
2640
+ changeDetection: ChangeDetectionStrategy.OnPush,
2641
+ host: { class: 'flex flex-col flex-1 min-h-0' },
2642
+ imports: [CommonModule, AXDataTableModule, AXTranslationModule],
2643
+ template: `
2644
+ <div class="flex flex-1 min-h-0 p-3 overflow-auto">
2645
+ <ax-data-table
2646
+ class="flex-1 border border-lightest rounded-md overflow-hidden"
2647
+ [paging]="true"
2648
+ [dataSource]="dataSource"
955
2649
  >
956
- {{ node().label }}
957
- @if (childrenLength() > 0) {
958
- <span class="axp-tree-node__badge">({{ childrenLength() }})</span>
959
- }
960
- </button>
2650
+ <ax-text-column [dataField]="'name'" [caption]="'@platform-dev-tools:module.terms.name' | translate | async" [width]="'280px'" [allowResizing]="true"></ax-text-column>
2651
+ <ax-text-column [dataField]="'title'" [caption]="'@platform-dev-tools:module.terms.title' | translate | async" [cellTemplate]="titleTpl" [width]="'280px'" [allowResizing]="true"></ax-text-column>
2652
+ <ax-text-column [dataField]="'description'" [caption]="'@platform-dev-tools:module.terms.description' | translate | async" [cellTemplate]="descTpl" [allowResizing]="true"></ax-text-column>
2653
+ </ax-data-table>
961
2654
  </div>
962
- @if (hasChildren() && expanded()) {
963
- <div class="axp-tree-node__children">
964
- @for (child of node().children ?? []; track child.id) {
965
- <axp-platform-explorer-tree-node
966
- [node]="child"
967
- [selectedId]="selectedId()"
968
- (nodeSelect)="nodeSelect.emit($event)"
969
- />
970
- }
971
- </div>
972
- }
973
- `, styles: [":host{display:block}.axp-tree-node{display:flex;align-items:center;min-height:28px}.axp-tree-node--selected .axp-tree-node__label{font-weight:600;background:var(--ax-neutral-surface-hover, rgba(0, 0, 0, .04))}.axp-tree-node__toggler{width:24px;height:28px;padding:0;border:none;background:none;cursor:pointer;color:var(--ax-neutral-text-secondary, #666);flex-shrink:0}.axp-tree-node__toggler--open .axp-tree-node__icon{transform:rotate(0)}.axp-tree-node__icon{font-size:10px;display:inline-block}.axp-tree-node__icon--empty{width:10px}.axp-tree-node__label{flex:1;text-align:start;padding:4px 8px;border:none;background:none;cursor:pointer;font-size:13px;border-radius:4px}.axp-tree-node__label:hover{background:var(--ax-neutral-surface-hover, rgba(0, 0, 0, .04))}.axp-tree-node__badge{margin-inline-start:4px;font-size:11px;color:var(--ax-neutral-text-secondary, #666);font-weight:400}.axp-tree-node__children{padding-inline-start:16px}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
974
- }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], selectedId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedId", required: false }] }], nodeSelect: [{ type: i0.Output, args: ["nodeSelect"] }] } });
2655
+ <ng-template #titleTpl let-item>
2656
+ @if (item.data?.title) {
2657
+ {{ item.data.title | translate | async }}
2658
+ }
2659
+ @else {
2660
+ –
2661
+ }
2662
+ </ng-template>
2663
+ <ng-template #descTpl let-item>
2664
+ @if (item.data?.description) {
2665
+ {{ item.data.description | translate | async }}
2666
+ }
2667
+ @else {
2668
+ –
2669
+ }
2670
+ </ng-template>
2671
+ `,
2672
+ }]
2673
+ }], ctorParameters: () => [], propDecorators: { moduleName: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleName", required: true }] }], kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: true }] }] } });
975
2674
 
976
2675
  //#region ---- Imports ----
977
2676
  //#endregion
@@ -999,11 +2698,6 @@ class AXMPlatformDevToolsModule {
999
2698
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformDevToolsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1000
2699
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformDevToolsModule }); }
1001
2700
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPlatformDevToolsModule, providers: [
1002
- {
1003
- provide: AXP_MODULE_MANIFEST_PROVIDER,
1004
- useValue: PlatformDevToolsManifest,
1005
- multi: true,
1006
- },
1007
2701
  {
1008
2702
  provide: AXP_PERMISSION_DEFINITION_PROVIDER,
1009
2703
  useClass: AXMPermissionDefinitionProvider,
@@ -1029,11 +2723,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1029
2723
  exports: [],
1030
2724
  declarations: [],
1031
2725
  providers: [
1032
- {
1033
- provide: AXP_MODULE_MANIFEST_PROVIDER,
1034
- useValue: PlatformDevToolsManifest,
1035
- multi: true,
1036
- },
1037
2726
  {
1038
2727
  provide: AXP_PERMISSION_DEFINITION_PROVIDER,
1039
2728
  useClass: AXMPermissionDefinitionProvider,
@@ -1058,5 +2747,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1058
2747
  * Generated bundle index. Do not edit.
1059
2748
  */
1060
2749
 
1061
- export { AXMPermissionDefinitionProvider, AXMPlatformDevToolsModule, AXPlatformExplorerDetailComponent, AXPlatformExplorerEntityDetailComponent, AXPlatformExplorerFeaturesListDetailComponent, AXPlatformExplorerModuleDetailComponent, AXPlatformExplorerPageComponent, AXPlatformExplorerPermissionsListDetailComponent, AXPlatformExplorerTreeNodeComponent, PlatformDevToolsManifest, PlatformExplorerDataService };
2750
+ export { AXMPermissionDefinitionProvider, AXMPlatformDevToolsModule, AXPlatformExplorerCatalogDetailComponent, AXPlatformExplorerDetailComponent, AXPlatformExplorerEntitiesGraphComponent, AXPlatformExplorerEntityDetailComponent, AXPlatformExplorerListDetailComponent, AXPlatformExplorerModuleDetailComponent, AXPlatformExplorerPageComponent, PlatformExplorerDataService };
1062
2751
  //# sourceMappingURL=acorex-platform-module-platform-dev-tools-client.mjs.map