import { Injectable, inject } from '@angular/core'; import { PageConfig } from '../../shared/models/config'; import { PageRenderModel } from '../page-renderer/page-renderer.model'; import { SectionRendererService } from '../section-renderer/section-renderer.service'; import { PlatformLayoutConfig, PlatformLayoutType } from '../../shared/models/config'; import { SectionConfig } from '../../shared/models/config'; import { ConfigService } from '../../core/config/config.service'; @Injectable({ providedIn: 'root' }) export class SectionEngineService { private readonly configService = inject(ConfigService); constructor(private readonly sectionRenderer: SectionRendererService) {} toPageRenderModel(page: PageConfig): PageRenderModel { const layoutType = this.resolveLayoutType(page.layout); const sections = this.normalizeSectionsByLayout(page.sections ?? [], layoutType) .filter((section) => section.visible !== false) .sort((a, b) => a.order - b.order) .map((section) => this.sectionRenderer.toRenderNode(section)); return { id: page.id, key: page.key, title: page.title, layout: layoutType, sections, source: page }; } /** * Falls back to the site-wide builder setting (bootstrap.layout.type, * "Site Layout" in the theme editor) when a page has no layout of its * own - previously that global setting was saved but never read by * rendering at all, so it had no visible effect. */ private resolveLayoutType(layout: PageConfig['layout']): string { if (typeof layout === 'string') { return layout; } const pageLayoutType = (layout as PlatformLayoutConfig)?.type; if (pageLayoutType) { return pageLayoutType; } return this.configService.getBootstrapSnapshot()?.layout?.type ?? 'default'; } private normalizeSectionsByLayout(sections: PageConfig['sections'], layoutType: string): PageConfig['sections'] { const normalized = [...sections]; const lowerLayout = (layoutType as PlatformLayoutType).toLowerCase(); if (lowerLayout === 'minimal') { return normalized.filter(section => { const type = section.type.toLowerCase(); return type === 'product-collection' || type === 'products' || type === 'catalog'; }); } if (lowerLayout === 'sidebar-left') { return this.composeSidebarLayout(normalized); } if (lowerLayout === 'carousel-home') { return normalized.sort((left, right) => this.getCarouselPriority(left.type) - this.getCarouselPriority(right.type)); } if (lowerLayout === 'default') { return normalized.sort((left, right) => this.getDefaultPriority(left.type) - this.getDefaultPriority(right.type)); } return normalized; } private getDefaultPriority(type: string): number { const normalizedType = type.toLowerCase(); if (normalizedType === 'categories') return 10; if (normalizedType === 'product-collection' || normalizedType === 'products') return 20; if (normalizedType === 'hero') return 30; return 100; } private getSidebarPriority(type: string): number { const normalizedType = type.toLowerCase(); if (normalizedType === 'categories') return 10; if (normalizedType === 'product-collection' || normalizedType === 'products') return 20; if (normalizedType === 'hero') return 30; return 100; } private getCarouselPriority(type: string): number { const normalizedType = type.toLowerCase(); if (normalizedType === 'hero' || normalizedType === 'banner') return 10; if (normalizedType === 'product-collection' || normalizedType === 'product-carousel') return 20; if (normalizedType === 'categories') return 30; return 100; } private composeSidebarLayout(sections: PageConfig['sections']): PageConfig['sections'] { const categoriesSection = sections.find(section => section.type.toLowerCase() === 'categories'); const productsSection = sections.find(section => { const type = section.type.toLowerCase(); return type === 'product-collection' || type === 'products' || type === 'catalog'; }); if (!categoriesSection || !productsSection) { return [...sections].sort((left, right) => this.getSidebarPriority(left.type) - this.getSidebarPriority(right.type)); } const combined: SectionConfig = { ...categoriesSection, id: `${categoriesSection.id}-sidebar-layout`, type: 'sidebar-left', order: Math.min(categoriesSection.order, productsSection.order), layout: { strategy: 'grid', columns: 2, gap: '1.5rem', align: 'start' }, widgets: [ ...categoriesSection.widgets.map((widget, index) => ({ ...widget, order: index, padding: widget.padding ?? '0', visibility: widget.visibility ?? { desktop: true, tablet: true, mobile: true } })), ...productsSection.widgets.map((widget, index) => ({ ...widget, order: 100 + index, padding: widget.padding ?? '0', visibility: widget.visibility ?? { desktop: true, tablet: true, mobile: true } })) ] }; return [ combined, ...sections.filter(section => section.id !== categoriesSection.id && section.id !== productsSection.id) ].sort((left, right) => left.order - right.order); } }