import {
BannerBlockConfiguration,
BuildingBlockConfiguration,
OverarchingDetailsBlockConfiguration,
PageConfiguration,
SectionConfiguration
} from '@allianz/taly-core/schemas';
import { JourneyTargetType, getAllBuildingBlocksInPage } from '@allianz/taly-sdk';
import { Tree, formatFiles, generateFiles, joinPathFragments } from '@nx/devkit';
import * as strings from '@nx/devkit/src/utils/string-utils';
import ejs from 'ejs';
import fs from 'fs';
import pick from 'lodash/pick';
import uniqWith from 'lodash/uniqWith';
import { Experiments } from '../../experimental/experimental';
import { createPageDataAssignment } from './create-page-data-assignment';
import { createPageDataOverridesString } from './create-page-data-overrides';
import * as exampleState from './example-state';
import { BuildingBlockWithExampleMap } from './example-state';
import {
SectionRuntimeDefinition,
isPanel,
mergeBlocksAndPanelsIntoSections
} from './merge-blocks-and-panels-into-sections';
import { getNotificationsConfigForTemplate } from './notifications/notifications';
import { renderMarkdownAsHtml } from './render-markdown-as-html';
import { SinglePageOptions } from './single-page-options';
export async function singlePage(tree: Tree, options: SinglePageOptions) {
const { page } = options;
let pageTitle = '';
let subline = '';
if (typeof page.title === 'string') {
pageTitle = page.title;
} else if (typeof page.title === 'object' && !page.title?.showAsStage) {
pageTitle = page.title.headline;
subline = page.title?.subline || '';
}
const name = page.id;
const processedSections = mergeBlocksAndPanelsIntoSections(page, options.expert);
const allBlocks = getAllBuildingBlocksInPage(page);
/**
* TODO: Guarantee, that there is no conflict with static imports in the template itself (TS AST task).
*/
const buildingBlockImports = collectBuildingBlockImports(allBlocks);
let exampleMap = new Map();
let exampleAssignments;
if (options.showDebugPanel) {
exampleMap = exampleState.collectFromBuildingBlocks(allBlocks);
exampleAssignments = exampleState.renderAllVariableDeclarations(exampleMap);
}
const hasStage = typeof page.title === 'object' && page.title?.showAsStage;
const hasSectionHeadline =
processedSections.filter((processedSection: SectionRuntimeDefinition) => processedSection.title)
.length > 0;
const hasHeadline = Boolean(pageTitle) || hasSectionHeadline;
const bannerBlock = page.bannerBlock;
const hasSidebarBlocks = processedSections.some((processedSection) =>
processedSection.sectionItems.some(
(sectionItem) => (sectionItem as BuildingBlockConfiguration).sidebar
)
);
const hasCompactBb = hasCompactBuildingBlock(processedSections);
const pageDataAssignment = createPageDataAssignment(page);
const pageDataOverrides: string = createPageDataOverridesString(page.id, allBlocks);
const hasSectionWithDividerLine = page.sections?.some((section) => section.dividerLineBelow);
const smallPrintParagraphs =
page.smallPrint?.map((smallPrintParagraph) => renderMarkdownAsHtml(smallPrintParagraph)) ?? [];
const hasNotifications = Boolean(page.notifications?.length);
const notifications = getNotificationsConfigForTemplate(page.notifications);
const singleBbWithBg = hasSingleBuildingBlockWithBackground(page, options.navigationSections);
generateFiles(tree, joinPathFragments(__dirname, 'files'), options.destinationDirectory, {
name: name,
dasherizedName: strings.dasherize(name),
title: pageTitle,
subtitle: subline,
hasHeadline,
hasSectionHeadline,
hasStage,
pageID: page.id,
isFirstPage: options.isFirstPage,
isPanel,
processedSections,
bannerBlock,
buildingBlockImports,
hasSidebarBlocks,
smallPrintParagraphs,
notifications,
hasNotifications,
// eslint-disable-next-line @typescript-eslint/no-use-before-define
renderBuildingBlock: renderBuildingBlockFactory(
exampleMap,
options.showDebugPanel ?? false,
options.showroom && options.target === JourneyTargetType.App
),
exampleAssignments,
...strings,
showDebugPanel: options.showDebugPanel ?? false,
pageDataAssignment: pageDataAssignment,
provideMetaServiceInComponent: options.provideMetaServiceInComponent ?? false,
expert: options.expert ?? false,
hasCompactBb,
hasSectionWithDividerLine,
pageDataOverrides,
showroom: options.showroom,
isStandalone: options.target === JourneyTargetType.App,
singleBbWithBg,
overarchingDetails: options.page.overarchingDetailsBlock,
useNewVerticalSpacing: options.experimental?.includes(Experiments.VerticalSpacing) ?? false
});
await formatFiles(tree);
}
/**
* Render a Building Block. Pulled out of the actual template and rendered
* instead by code by using the underlying `template` method to prevent duplication
* in the template as we have two places to output Building Blocks.
*/
const renderBuildingBlockFactory =
(
exampleStateItems: BuildingBlockWithExampleMap,
showDebugPanel: boolean,
respectDebugToolsService: boolean
) =>
(block: BuildingBlockConfiguration) => {
const exampleStateIdentifier = exampleState.createIdentifier(block.id);
const value = exampleStateItems.has(block.id) ? exampleStateIdentifier : null;
const properties = {
id: block.id,
aclTag: block.aclTag ?? block.id,
selector: block.selector,
sidebar: block.sidebar,
templateReferenceID: `bb${strings.classify(block.id)}`,
exampleStateIdentifier: value,
showDebugPanel,
respectDebugToolsService
};
const content = fs
.readFileSync(joinPathFragments(__dirname, 'partials/building-block'))
.toString();
const renderedContent = ejs.render(content, properties, {});
return renderedContent;
};
interface BuildingBlockImport {
module: string;
package: string;
}
function collectBuildingBlockImports(
blocks: (
| BuildingBlockConfiguration
| OverarchingDetailsBlockConfiguration
| BannerBlockConfiguration
)[]
): BuildingBlockImport[] {
const moduleAndPackage = blocks.map((item) => pick(item, ['package', 'module']));
const uniqBlockImports = uniqWith(moduleAndPackage, function (a, b) {
return a.package === b.package && a.module === b.module;
});
return uniqBlockImports;
}
export function hasSingleBuildingBlockWithBackground(
page: PageConfiguration,
navigationSections: SectionConfiguration[]
) {
if (
page.bannerBlock?.id ||
page.overarchingDetailsBlock?.id ||
page.notifications?.length ||
page.title ||
page.blocks?.length !== 1
) {
return false;
}
const hasNavigation = navigationSections.length && !page.pageData?.navigation?.hidden;
if (hasNavigation) {
return false;
}
const backgroundColor = (page.blocks[0] as BuildingBlockConfiguration)?.buildingBlockStyle
?.backgroundColor;
return Boolean(backgroundColor && backgroundColor !== 'transparent');
}
function hasCompactBuildingBlock(allSections: SectionRuntimeDefinition[]) {
return allSections.some((section) => section.compact);
}