refactor: finalize bootstrap-driven layout and widget runtime

This commit is contained in:
sdarbinyan
2026-07-05 04:17:31 +04:00
parent 6550250d13
commit c901ec1e49
19 changed files with 697 additions and 181 deletions

View File

@@ -2,13 +2,17 @@ import { Injectable } 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';
@Injectable({ providedIn: 'root' })
export class SectionEngineService {
constructor(private readonly sectionRenderer: SectionRendererService) {}
toPageRenderModel(page: PageConfig): PageRenderModel {
const sections = (page.sections ?? [])
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));
@@ -17,9 +21,111 @@ export class SectionEngineService {
id: page.id,
key: page.key,
title: page.title,
layout: page.layout,
layout: layoutType,
sections,
source: page
};
}
private resolveLayoutType(layout: PageConfig['layout']): string {
if (typeof layout === 'string') {
return layout;
}
return (layout as PlatformLayoutConfig)?.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);
}
}