libs/nx/src/generators/journey-src/lib/create-pages/single-page/merge-blocks-and-panels-into-sections.ts
Properties |
| blocks | |
| Type |
BuildingBlockConfiguration[]
|
| expanded | |
| Type |
string | boolean
|
| i18nKey | |
| Type |
string
|
| style | |
| Type |
StyleOfThePanel
|
| 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, '\\"');
}