File

libs/nx/src/generators/journey-src/lib/create-pages/single-page/merge-blocks-and-panels-into-sections.ts

Extends

PanelDefinition

Index

Properties

Properties

blocks
Type BuildingBlockConfiguration[]
expanded
Type string | boolean
i18nKey
Type string
style
Type StyleOfThePanel
collapsed (Optional)
Type boolean | string
Default value false
Description

Whether this panel should be collapsed initially. Supports a boolean, jsonPath expression or PFE_EXPRESSION condition. By default, the panel is expanded. false

id
Type string
Description

Unique ID for the Panel to be referred to by Building Blocks.

section (Optional)
Type string
Description

Optionally provide a section Id for this panel. A panel without a section will be treated as its own section.

title (Optional)
Type string
Description

The given value is used as the default title for the generated panel. i18n needs to be supported by the generated application and will use this value as the default translation.

import {
  BuildingBlockConfiguration,
  PageConfigurationWithTransformedDynamicForms,
  PageSectionDefinition,
  PanelDefinition,
  StyleOfThePanel
} from '@allianz/taly-core/schemas';
import { logger } from '@nx/devkit';

export type SectionOrBlock = SectionRuntimeDefinition | BuildingBlockConfiguration;
type PanelOrSectionOrBlock = PanelRuntimeDefinition | SectionOrBlock;

export function isPanel(item: PanelOrSectionOrBlock): item is PanelRuntimeDefinition {
  return (
    Object.prototype.hasOwnProperty.call(item, 'blocks') &&
    Object.prototype.hasOwnProperty.call(item, 'style')
  );
}

// separate panel type to aggregate blocks for the runtime processing
export interface PanelRuntimeDefinition extends PanelDefinition {
  i18nKey: string;
  blocks: BuildingBlockConfiguration[];
  style: StyleOfThePanel;
  expanded: string | boolean;
}

export interface SectionRuntimeDefinition extends PageSectionDefinition {
  sectionItems: (BuildingBlockConfiguration | PanelRuntimeDefinition)[];
  isSimpleSection: boolean;
  isConfiguredSection: boolean;
  backgroundColor: string | undefined;
  compact: boolean;
}

/**
 * Given a list of blocks, sections and panels merge everything except standalone BBs into sections configuration.
 *
 * Important: This method have to retain the order of elements, so we don't calculate two lists of panels and unbound blocks
 * but a mixed list to allow `panel, block, panel, block` instead of `panel, panel, panel, block, block`
 */
export function mergeBlocksAndPanelsIntoSections(
  page: PageConfigurationWithTransformedDynamicForms,
  isExpertApp = false
): SectionRuntimeDefinition[] {
  const { sections = [], panels = [], blocks = [] } = page;

  const processedSections = blocks.reduce((accu: SectionRuntimeDefinition[], block, index) => {
    if (block.section) {
      // find configured section
      const section = sections.find((item) => item.id === block.section);
      if (!section) {
        logger.warn(
          `The Building Block "${block.id}" is configured as part of section "${block.section}", but "${block.section}" is not defined. "${block.id}" will be displayed as a standalone Building Block. If you want "${block.id}" to be part of a section, please add "${block.section}" to the "sections" list in the page configuration.\n`
        );
        accu.push(createNewRuntimeSection(block, index));
        return accu;
      }

      // find existing section in the accu list
      const sectionInAccu = accu.find((item) => item.id === section.id);
      if (!sectionInAccu) {
        accu.push(createRuntimeSectionFromConfiguration(section, block));
        return accu;
      }

      sectionInAccu.sectionItems.push(block);
      return accu;
    } else if (block.panel) {
      // find configured panel
      const panel = panels.find((panel) => panel.id === block.panel);
      if (!panel) {
        logger.warn(
          `The Building Block "${block.id}" is configured as part of panel "${block.panel}", but "${block.panel}" is not defined. "${block.id}" will be displayed as a standalone Building Block. If you want "${block.id}" to be part of a panel, please add "${block.panel}" to the "panels" list in the page configuration.\n`
        );
        accu.push(createNewRuntimeSection(block, index));
        return accu;
      }

      // find existing panel in the accu list
      const panelInAccu = findPanelInSections(accu, panel.id);
      if (panelInAccu) {
        panelInAccu.blocks.push(block);
        return accu;
      }

      const newPanel = createRuntimePanelFromConfiguration(panel, block, page.id, isExpertApp);

      if (!newPanel.section) {
        // check if it's a consecutive panel. If so, put it in the same section
        const lastSectionWithStandalonePanel = findLastSectionWithStandalonePanel(accu);
        if (lastSectionWithStandalonePanel) {
          lastSectionWithStandalonePanel.sectionItems.push(newPanel);
          return accu;
        }

        accu.push(createNewRuntimeSection(newPanel, index));
        return accu;
      }

      const section = sections.find((item) => item.id === newPanel.section);
      if (!section) {
        logger.warn(
          `The panel "${panel.id}" is configured as part of section "${panel.section}", but "${panel.section}" is not defined. "${panel.id}" will be displayed as a standalone panel. If you want "${panel.id}" to be part of a section, please add "${panel.section}" to the "sections" list in the page configuration.\n`
        );
        // check if it's a consecutive panel. If so, put it in the same section
        const lastSectionWithStandalonePanel = findLastSectionWithStandalonePanel(accu);
        if (lastSectionWithStandalonePanel) {
          lastSectionWithStandalonePanel.sectionItems.push(newPanel);
          return accu;
        }

        accu.push(createNewRuntimeSection(newPanel, index));
        return accu;
      }

      // find existing section in the accu list
      const sectionInAccu = accu.find((item) => item.id === section.id);
      if (!sectionInAccu) {
        accu.push(createRuntimeSectionFromConfiguration(section, newPanel));
        return accu;
      }

      sectionInAccu.sectionItems.push(newPanel);
      return accu;
    } else {
      accu.push(createNewRuntimeSection(block, index));
      return accu;
    }
  }, []);

  for (const section of processedSections) {
    section.isSimpleSection =
      section.sectionItems.length === 1 ||
      section.sectionItems.every((subSection) => isPanel(subSection));
  }

  return processedSections;
}

function findPanelInSections(sections: SectionRuntimeDefinition[], panelId: string) {
  const allPanels = sections
    .flatMap((section) => section.sectionItems)
    .filter((sectionItem) => isPanel(sectionItem)) as PanelRuntimeDefinition[];
  return allPanels.find((panel) => panel.id === panelId);
}

function findLastSectionWithStandalonePanel(sections: SectionRuntimeDefinition[]) {
  const lastSection = sections[sections.length - 1];
  if (!lastSection) {
    return;
  }

  if (lastSection.isConfiguredSection) {
    return;
  }
  const lastSectionItems = lastSection.sectionItems;
  const lastSectionItem = lastSectionItems[lastSectionItems.length - 1];
  if (isPanel(lastSectionItem)) {
    return lastSection;
  }
  return undefined;
}

function createNewRuntimeSection(
  sectionItem: BuildingBlockConfiguration | PanelRuntimeDefinition,
  index: number
): SectionRuntimeDefinition {
  return {
    id: `customId${index}`,
    sectionItems: [sectionItem],
    isConfiguredSection: false,
    isSimpleSection: true,
    backgroundColor:
      (sectionItem as BuildingBlockConfiguration)?.buildingBlockStyle?.backgroundColor ?? undefined,
    compact: (sectionItem as BuildingBlockConfiguration)?.buildingBlockStyle?.compact ?? false
  };
}

function createRuntimeSectionFromConfiguration(
  section: PageSectionDefinition,
  sectionItem: BuildingBlockConfiguration | PanelRuntimeDefinition
): SectionRuntimeDefinition {
  return {
    id: section.id,
    title: section.title,
    dividerLineBelow: section.dividerLineBelow,
    sectionItems: [sectionItem],
    isConfiguredSection: true,
    isSimpleSection: true,
    backgroundColor: undefined,
    compact: false
  };
}

function createRuntimePanelFromConfiguration(
  panelConfig: PanelDefinition,
  blockConfig: BuildingBlockConfiguration,
  pageId: string,
  isExpertApp: boolean
): PanelRuntimeDefinition {
  return {
    i18nKey: `@@${pageId}.panels.${panelConfig.id}`, // to yield this in the template: i18n-title="@@my-page.panels.panel1"
    ...panelConfig,
    expanded: getExpandedCondition(panelConfig.collapsed),
    style: panelConfig.style || (isExpertApp ? StyleOfThePanel.Regular : StyleOfThePanel.Light),
    blocks: [blockConfig]
  };
}

function getExpandedCondition(collapsed: string | boolean | undefined) {
  const PFE_EXPRESSION_CONDITION_REGEX = /({.*?})/gm;

  const isPfeStateKeyOrCondition = typeof collapsed === 'string';
  if (isPfeStateKeyOrCondition) {
    const escapedCollapsed = escapeForHtmlTemplate(collapsed);
    const isCondition = PFE_EXPRESSION_CONDITION_REGEX.test(escapedCollapsed);

    return isCondition ? `'!(${escapedCollapsed})'` : `'!{${escapedCollapsed}}'`;
  }
  return !collapsed;
}

function escapeForHtmlTemplate(condition: string) {
  return condition.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '\\"');
}

results matching ""

    No results matching ""