Files
marketplaces/src/app/dynamic-renderer/section-engine/section-engine.service.ts

145 lines
5.3 KiB
TypeScript
Raw Normal View History

import { Injectable, inject } from '@angular/core';
2026-07-05 01:55:41 +04:00
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';
2026-07-05 01:55:41 +04:00
@Injectable({ providedIn: 'root' })
export class SectionEngineService {
private readonly configService = inject(ConfigService);
2026-07-05 01:55:41 +04:00
constructor(private readonly sectionRenderer: SectionRendererService) {}
toPageRenderModel(page: PageConfig): PageRenderModel {
const layoutType = this.resolveLayoutType(page.layout);
const sections = this.normalizeSectionsByLayout(page.sections ?? [], layoutType)
2026-07-05 01:55:41 +04:00
.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,
2026-07-05 01:55:41 +04:00
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);
}
2026-07-05 01:55:41 +04:00
}