libs/core/devtools/src/lib/journey-details/journey-details.component.ts

Metadata

Relationships

Depends on

Index

Properties
Inputs
Outputs
HostBindings
Accessors

Constructor

constructor()

Inputs

darkMode
Type : boolean
Default value : false
dynamicFormEditEnabled
Type : boolean
Default value : true

Outputs

editDynamicForm
Type : JourneyDetailsEditDynamicFormEvent

HostBindings

class.taly-journey-details--dark
Type : any

Properties

_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 )
uniqueBuildingBlocks
Type : unknown
Default value : 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; })

Accessors

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;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""