phase-6: add registry-driven dynamic page-section-widget rendering engine

This commit is contained in:
sdarbinyan
2026-07-03 01:35:09 +04:00
parent 7dca7fee7d
commit 3f00884f33
8 changed files with 171 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import { PageConfig } from '../../shared/models/config';
import { SectionRenderNode } from '../section-renderer/section-renderer.model';
export interface PageRenderModel {
id: string;
key: string;
title: string;
layout: string;
sections: SectionRenderNode[];
source: PageConfig;
}

View File

@@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { PageConfig } from '../../shared/models/config';
import { PageRenderModel } from './page-renderer.model';
import { SectionRendererService } from '../section-renderer/section-renderer.service';
@Injectable({ providedIn: 'root' })
export class PageRendererService {
constructor(private readonly sectionRenderer: SectionRendererService) {}
toRenderModel(page: PageConfig): PageRenderModel {
const sections = (page.sections ?? [])
.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: page.layout,
sections,
source: page
};
}
}

View File

@@ -0,0 +1,12 @@
import { SectionConfig } from '../../shared/models/config';
import { WidgetRenderNode } from '../widget-host/widget-host.model';
export interface SectionRenderNode {
id: string;
type: string;
order: number;
featureFlag?: string;
visible?: boolean;
widgets: WidgetRenderNode[];
source: SectionConfig;
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { SectionConfig } from '../../shared/models/config';
import { SectionRenderNode } from './section-renderer.model';
import { WidgetRenderNode } from '../widget-host/widget-host.model';
@Injectable({ providedIn: 'root' })
export class SectionRendererService {
toRenderNode(section: SectionConfig): SectionRenderNode {
const widgets = (section.widgets ?? [])
.filter(widget => widget.visible !== false)
.map<WidgetRenderNode>(widget => ({
id: widget.id,
type: widget.type,
version: widget.version,
props: widget.props ?? {},
featureFlag: widget.featureFlag,
visible: widget.visible,
source: widget
}));
return {
id: section.id,
type: section.type,
order: section.order,
featureFlag: section.featureFlag,
visible: section.visible,
widgets,
source: section
};
}
}

View File

@@ -0,0 +1,11 @@
import { WidgetConfig } from '../../shared/models/config';
export interface WidgetRenderNode {
id: string;
type: string;
version: string;
props: Record<string, unknown>;
featureFlag?: string;
visible?: boolean;
source: WidgetConfig;
}

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { WidgetConfig } from '../../shared/models/config';
import { WidgetRegistryService } from '../../widgets/registry/widget-registry.service';
import { ResolvedWidget } from '../../widgets/contracts/widget-component.contract';
@Injectable({ providedIn: 'root' })
export class WidgetHostService {
constructor(private readonly registry: WidgetRegistryService) {}
resolveWidget(widget: WidgetConfig): ResolvedWidget | null {
const registered = this.registry.resolve(widget.type, widget.version);
if (!registered) {
return null;
}
return {
type: registered.type,
version: registered.version,
component: registered.component,
props: widget.props ?? {}
};
}
}