libs/core/devtools/src/lib/journey-details/journey-details.component.ts
| changeDetection | ChangeDetectionStrategy.OnPush |
| selector | taly-journey-details |
| standalone | true |
| imports |
NxTreeComponent
NxActionComponent
NxIconComponent
NxActionIconDirective
NxButtonComponent
NxListComponent
NxAccordionDirective
NxExpansionPanelComponent
NxExpansionPanelTitleDirective
NxLinkComponent
|
| styleUrls | ./journey-details.component.scss |
| templateUrl | ./journey-details.component.html |
| styleUrl | ./journey-details.component.scss |
NxGridModule
NxTreeComponent
NxTreeNodeDefDirective
NxTreeNodeComponent
NxActionComponent
NxTreeNodeActionItem
NxTreeNodePaddingDirective
NxIconComponent
NxActionIconDirective
NxTreeNodeToggleDirective
NxButtonComponent
NxListComponent
NxAccordionDirective
NxExpansionPanelComponent
NxExpansionPanelHeaderComponent
NxExpansionPanelTitleDirective
NxLinkComponent
NormalizeUrlModule
Properties |
Inputs |
Outputs |
HostBindings |
Accessors |
constructor()
|
| darkMode | |
Type : boolean
|
|
Default value : false
|
|
| dynamicFormEditEnabled | |
Type : boolean
|
|
Default value : true
|
|
| editDynamicForm | |
Type : JourneyDetailsEditDynamicFormEvent
|
|
| class.taly-journey-details--dark |
Type : any
|
| _dataSource |
Type : NxTreeFlatDataSource<TreeNode | NxFlatTreeNode>
|
| _hasChild |
Type : unknown
|
Default value : () => {...}
|
| _treeControl |
Type : unknown
|
Default value : new NxFlatTreeControl()
|
| data |
Type : unknown
|
Default value : signal<TreeNode[]>([])
|
| tenantName |
Type : unknown
|
Default value : computed(() =>
isSignal(this.pfeConfiguration) ? this.pfeConfiguration().tenant : this.pfeConfiguration.tenant
)
|
| darkModeClass |
getdarkModeClass()
|
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
HostBinding,
inject,
input,
isSignal,
output,
Signal,
signal,
untracked
} from '@angular/core';
import { NxGridModule } from '@allianz/ng-aquila/grid';
import { PAGES_CONFIGURATION_RUNTIME_CONFIG_MODE } from '@allianz/taly-core';
import { PAGES_CONFIGURATION } from '@allianz/taly-core';
import {
BuildingBlockConfiguration,
DynamicFormBBConfiguration,
DynamicFormConfiguration,
Frame,
HeaderAction,
isDynamicFormBbConfig,
PageConfiguration,
PageConfigurationWithTransformedDynamicForms
} from '@allianz/taly-core/schemas';
import { NxActionComponent, NxActionIconDirective } from '@allianz/ng-aquila/action';
import { NxIconComponent } from '@allianz/ng-aquila/icon';
import {
NxFlatTreeControl,
NxFlatTreeNode,
NxTreeComponent,
NxTreeFlatDataSource,
NxTreeNode,
NxTreeNodeActionItem,
NxTreeNodeComponent,
NxTreeNodeDefDirective,
NxTreeNodePaddingDirective,
NxTreeNodeToggleDirective
} from '@allianz/ng-aquila/tree';
import { NxButtonComponent } from '@allianz/ng-aquila/button';
import {
NxAccordionDirective,
NxExpansionPanelComponent,
NxExpansionPanelHeaderComponent,
NxExpansionPanelTitleDirective
} from '@allianz/ng-aquila/accordion';
import { NGX_PFE_CONFIGURATION, PfeNavigationService } from '@allianz/ngx-pfe';
import { NxListComponent } from '@allianz/ng-aquila/list';
import { NxLinkComponent } from '@allianz/ng-aquila/link';
import { NormalizeUrlModule } from '@allianz/taly-core';
type PagesConfig = {
pages: PageConfiguration[] | PageConfigurationWithTransformedDynamicForms[];
frame?: Frame;
};
interface PageOrBBTreeNodeAction {
handler: (node: PageOrBBTreeNode) => void;
label: string;
icon?: string;
}
type TreeNode = PageOrBBTreeNode | BuildingBlockDataTreeNode;
interface BuildingBlockDataTreeNode extends NxTreeNode {
type:
| 'building-block'
| 'banner-block'
| 'dynamic-form'
| 'overarching-details-block'
| 'header-action';
selector?: string;
module?: string;
package?: string;
}
interface PageOrBBTreeNode extends NxTreeNode {
isPage: boolean;
label: string;
icon?: string;
form?: DynamicFormConfiguration;
actions?: PageOrBBTreeNodeAction[];
children?: TreeNode[];
}
export interface JourneyDetailsEditDynamicFormEvent {
form: DynamicFormConfiguration;
bbId: string | undefined;
pageId: string;
}
@Component({
selector: 'taly-journey-details',
templateUrl: './journey-details.component.html',
styleUrl: './journey-details.component.scss',
imports: [
NxGridModule,
NxTreeComponent,
NxTreeNodeDefDirective,
NxTreeNodeComponent,
NxActionComponent,
NxTreeNodeActionItem,
NxTreeNodePaddingDirective,
NxIconComponent,
NxActionIconDirective,
NxTreeNodeToggleDirective,
NxButtonComponent,
NxListComponent,
NxAccordionDirective,
NxExpansionPanelComponent,
NxExpansionPanelHeaderComponent,
NxExpansionPanelTitleDirective,
NxLinkComponent,
NormalizeUrlModule
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TalyDevtoolsJourneyDetailsComponent {
private pagesConfig = signal(inject<PagesConfig>(PAGES_CONFIGURATION));
private pagesConfigRuntime = inject<Signal<PagesConfig> | null>(
PAGES_CONFIGURATION_RUNTIME_CONFIG_MODE,
{
optional: true
}
);
private pfeNavigationService = inject(PfeNavigationService);
private pfeConfiguration = inject(NGX_PFE_CONFIGURATION);
dynamicFormEditEnabled = input(true);
darkMode = input(false);
editDynamicForm = output<JourneyDetailsEditDynamicFormEvent>();
@HostBinding('class.taly-journey-details--dark') get darkModeClass() {
return this.darkMode();
}
tenantName = computed(() =>
isSignal(this.pfeConfiguration) ? this.pfeConfiguration().tenant : this.pfeConfiguration.tenant
);
data = signal<TreeNode[]>([]);
_hasChild = (_: number, node: NxFlatTreeNode) => node.expandable;
_treeControl = new NxFlatTreeControl();
_dataSource: NxTreeFlatDataSource<TreeNode, NxFlatTreeNode>;
uniqueBuildingBlocks = computed(() => {
if (this.data().length === 0) {
return 0;
}
const buildingBlockSet = new Set<string>();
for (const page of this.data()) {
for (const bb of page.children ?? []) {
if (bb.children) {
const child = bb.children[0] as BuildingBlockDataTreeNode;
if (child.package && child.module) {
buildingBlockSet.add(`${child.package}-${child.module}`);
} else if (child.type === 'dynamic-form') {
buildingBlockSet.add(`dynamic-form`);
}
}
}
}
return buildingBlockSet.size;
});
constructor() {
this.data.set(this.computeTreeData());
this._dataSource = new NxTreeFlatDataSource(this._treeControl, this.data());
effect(() => {
if (this.pagesConfigRuntime?.()) {
this.pagesConfig.set(this.pagesConfigRuntime());
}
this.data.set(this.computeTreeData());
this._dataSource.data = untracked(() => this.data());
});
}
private computeTreeData(): TreeNode[] {
const headerActions = this.pagesConfig().frame?.header?.actions ?? [];
const headerActionNodes: TreeNode[] = headerActions.length
? [
{
label: 'Header Actions',
icon: 'setting-o',
isPage: false,
children: headerActions.map((action) => this.getHeaderActionData(action))
}
]
: [];
const pageNodes: TreeNode[] = this.pagesConfig().pages.map((page) => ({
label: page.id,
icon: 'file',
isPage: true,
children: [
...(page.bannerBlock
? [this.getBuildingBlockData(page.bannerBlock, page.id, 'banner-block')]
: []),
...(page.overarchingDetailsBlock
? [
this.getBuildingBlockData(
page.overarchingDetailsBlock,
page.id,
'overarching-details-block'
)
]
: []),
...(page.blocks?.map((block) => this.getBuildingBlockData(block, page.id)) ?? [])
],
actions: [
{
label: 'Navigate',
icon: 'arrow-right',
handler: (node: PageOrBBTreeNode) => {
if (node.isPage) {
this.pfeNavigationService.navigate(node.label);
}
}
}
]
}));
return [...headerActionNodes, ...pageNodes];
}
private getBuildingBlockData(
block: BuildingBlockConfiguration | DynamicFormBBConfiguration,
pageId: string,
type: BuildingBlockDataTreeNode['type'] = 'building-block'
): PageOrBBTreeNode {
let icon = 'product-puzzle';
let form: DynamicFormConfiguration | undefined;
if (isDynamicFormBbConfig(block)) {
type = 'dynamic-form';
icon = 'fountain-pen';
form = block.form;
} else if (
block.package === '@allianz/taly-core/building-blocks' &&
block.module === 'DynamicFormBuildingBlockModule'
) {
type = 'dynamic-form';
icon = 'fountain-pen';
form = block.resources?.['talyDynamicFormConfig'] as DynamicFormConfiguration;
}
return {
isPage: false,
label: block.id,
icon,
form,
children: [
{
type,
...('selector' in block && type != 'dynamic-form' ? { selector: block.selector } : {}),
...('module' in block && type != 'dynamic-form' ? { module: block.module } : {}),
...('package' in block && type != 'dynamic-form' ? { package: block.package } : {})
}
],
actions:
form && this.dynamicFormEditEnabled()
? this.getDynamicFormEditActions(form, block.id, pageId)
: undefined
};
}
private getHeaderActionData(action: HeaderAction): PageOrBBTreeNode {
return {
isPage: false,
label: action.id,
icon: 'product-puzzle',
children: [
{
type: 'header-action' as const,
selector: action.selector,
module: action.module,
package: action.package
}
]
};
}
private getDynamicFormEditActions(
form: DynamicFormConfiguration,
bbId: string,
pageId: string
): PageOrBBTreeNodeAction[] {
return [
{
label: 'Edit',
icon: 'edit',
handler: () => {
if (form) {
const cleanedForm: DynamicFormConfiguration = JSON.parse(
JSON.stringify(form).replaceAll('@localize.', '')
);
this.editDynamicForm.emit({
form: cleanedForm,
bbId: this.pagesConfigRuntime ? undefined : bbId,
pageId
});
}
}
}
];
}
}
<div nxLayout="grid nopadding" class="grid-container">
<div nxRow>
<div nxCol="12" class="journey-details_container">
<nx-accordion variant="light" multi="true">
<nx-expansion-panel [expanded]="true">
<nx-expansion-panel-header>
<nx-expansion-panel-title>Journey Details</nx-expansion-panel-title>
</nx-expansion-panel-header>
<ul nxList="small" class="nx-margin-bottom-0">
@if (tenantName()) {
<li>
Tenant Name: <strong>{{ tenantName() }}</strong>
</li>
}
<li>
Unique Building Blocks: <strong>{{ uniqueBuildingBlocks() }}</strong>
</li>
<li>
<nx-link
><a [href]="'/journey.model.json' | talyNormalizeUrl" target="_blank" rel="noopener"
>Open Journey Model (OpenAPI JSON)</a
></nx-link
>
</li>
</ul>
</nx-expansion-panel>
<nx-expansion-panel [expanded]="true">
<nx-expansion-panel-header>
<nx-expansion-panel-title>Page Details</nx-expansion-panel-title>
</nx-expansion-panel-header>
<nx-tree [dataSource]="_dataSource" [treeControl]="_treeControl">
<nx-tree-node *nxTreeNodeDef="let node" nxTreeNodePadding nxTreeNodePaddingOffset="40">
@if (node.label) {
<div class="tree-node-label leaf-node">
@if (node.icon) {
<nx-icon nxActionIcon [name]="node.icon" class="nx-margin-right-2xs"></nx-icon>
} <span>{{ node.label }}</span>
<div class="tree-node-actions">
@for (action of node.actions; track action) {
<button
nxButton="tertiary"
size="small"
type="button"
(click)="action.handler(node, $event); $event.stopPropagation()"
>
@if (action.icon) {
<nx-icon nxIconPositionStart [name]="action.icon"></nx-icon>
} {{ action.label }}
</button>
}
</div>
</div>
} @if (node.type) {
<div class="node-details nx-margin-bottom-s">
<div class="building-block-details">
<strong>Type:</strong>
@switch (node.type) { @case ('banner-block') { Banner Block } @case
('overarching-details-block') { Overarching Details Block } @case
('building-block') { Building Block } @case ('dynamic-form') { Dynamic Form }
@case ('header-action') { Header Action } }
</div>
@if (node.selector) {
<div><strong>Selector:</strong> {{ node.selector }}</div>
} @if (node.module) {
<div><strong>Module:</strong> {{ node.module }}</div>
} @if (node.package) {
<div><strong>Package:</strong> {{ node.package }}</div>
}
</div>
}
</nx-tree-node>
<nx-tree-node *nxTreeNodeDef="let node; when: _hasChild">
<button
class="tree-node-action-button"
nxAction
nxTreeNodeActionItem
nxTreeNodeToggle
nxTreeNodePadding
nxTreeNodePaddingOffset="40"
expandable
[expanded]="_treeControl.isExpanded(node)"
type="button"
>
<div class="tree-node-label action-node">
@if (node.icon) {
<nx-icon nxActionIcon [name]="node.icon" class="nx-margin-right-2xs"></nx-icon>
} <span>{{ node.label }}</span>
<div class="tree-node-actions">
@for (action of node.actions; track action) {
<button
nxButton="tertiary"
size="small"
type="button"
(click)="action.handler(node, $event); $event.stopPropagation()"
>
@if (action.icon) {
<nx-icon nxIconPositionStart [name]="action.icon"></nx-icon>
} {{ action.label }}
</button>
}
</div>
</div>
</button>
</nx-tree-node>
</nx-tree>
</nx-expansion-panel>
</nx-accordion>
</div>
</div>
</div>
./journey-details.component.scss
:host {
display: block;
}
:host(.taly-journey-details--dark) {
--text-01: #fbf9f9;
--header-border-color: #585459;
--header-background-color: #151c30;
--plain-button-color: var(--text-01);
--plain-button-hover-color: var(--hover-primary);
--plain-button-active-color: var(--active-primary);
--hover-primary: #ececec;
--active-primary: #ececec;
--accordion-light-header-text-color: var(--text-01);
--accordion-light-header-hover-text-color: var(--text-01);
--accordion-light-border-color: #585459;
--accordion-light-chevron-color: var(--text-01);
--accordion-light-hover-chevron-color: var(--text-01);
--list-text-color: var(--text-01);
--action-color: var(--text-01);
--action-background-color: var(--header-background-color);
--action-hover-background-color: #b1b1b1;
--link-color: var(--text-01);
--link-hover-color: var(--text-01);
--button-tertiary-text-color: var(--text-01);
li:before {
background-color: var(--text-01) !important;
}
color: var(--text-01);
background-color: var(--header-background-color);
}
.node-label {
display: flex;
align-items: center;
gap: 4px;
font-weight: bold;
font-size: 16px;
}
.node-details {
font-size: 14px;
}
.tree-node-actions {
display: flex;
gap: 4px;
button {
margin: 0 !important;
}
}
.tree-node-action-button {
padding-top: 0 !important;
padding-right: 0 !important;
padding-bottom: 0 !important;
}
.tree-node {
&-label {
display: flex;
align-items: center;
width: 100%;
&.leaf-node {
padding: var(--action-padding);
}
&.action-node {
padding: var(--action-padding);
padding-left: 0;
}
}
&-actions {
margin-left: auto;
}
}
nx-accordion {
display: block;
margin-bottom: 16px;
}
.grid-container {
overflow-x: hidden;
}