arch(sprint1): drive widget registry from manifest

This commit is contained in:
sdarbinyan
2026-07-03 02:07:12 +04:00
parent 8132c1a535
commit 8d47920fa3
3 changed files with 93 additions and 15 deletions

View File

@@ -27,7 +27,8 @@ export class PlatformRuntimeService {
this.configService.loadBootstrap().pipe(take(1)).subscribe({ this.configService.loadBootstrap().pipe(take(1)).subscribe({
next: (bootstrap) => { next: (bootstrap) => {
this.widgetRegistryBootstrap.registerDefaults(); this.widgetRegistryBootstrap.registerFromManifest().subscribe({
next: () => {
this.themeEngine.applyTheme(bootstrap.theme); this.themeEngine.applyTheme(bootstrap.theme);
this.brandingEngine.applyBranding(bootstrap.branding); this.brandingEngine.applyBranding(bootstrap.branding);
this.runtimeState.markInitialized({ this.runtimeState.markInitialized({
@@ -37,6 +38,18 @@ export class PlatformRuntimeService {
}); });
this.initializing = false; this.initializing = false;
}, },
error: () => {
this.themeEngine.applyTheme(bootstrap.theme);
this.brandingEngine.applyBranding(bootstrap.branding);
this.runtimeState.markInitialized({
localization: bootstrap.localization,
featureFlags: bootstrap.featureFlags,
permissions: bootstrap.permissions
});
this.initializing = false;
}
});
},
error: () => { error: () => {
this.initializing = false; this.initializing = false;
} }

View File

@@ -1,16 +1,59 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, map, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { HeroWidgetComponent, FooterNavigationWidgetComponent, ProductCarouselWidgetComponent } from '../ui'; import { HeroWidgetComponent, FooterNavigationWidgetComponent, ProductCarouselWidgetComponent } from '../ui';
import { RegisteredWidget } from '../contracts/widget-component.contract';
import { WidgetRegistryService } from './widget-registry.service'; import { WidgetRegistryService } from './widget-registry.service';
interface WidgetManifestItem {
type: string;
version: string;
componentKey: string;
enabled?: boolean;
}
interface WidgetManifest {
widgets: WidgetManifestItem[];
}
const APPROVED_WIDGET_COMPONENTS: Record<string, RegisteredWidget['component']> = {
'hero': HeroWidgetComponent,
'footer-navigation': FooterNavigationWidgetComponent,
'product-carousel': ProductCarouselWidgetComponent
};
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class WidgetRegistryBootstrapService { export class WidgetRegistryBootstrapService {
constructor(private readonly registry: WidgetRegistryService) {} private readonly manifestUrl = '/assets/mock/bootstrap/widget-manifest.json';
registerDefaults(): void { constructor(
this.registry.registerMany([ private readonly registry: WidgetRegistryService,
{ type: 'hero', version: '1.0.0', component: HeroWidgetComponent }, private readonly http: HttpClient
{ type: 'footer-navigation', version: '1.0.0', component: FooterNavigationWidgetComponent }, ) {}
{ type: 'product-carousel', version: '1.0.0', component: ProductCarouselWidgetComponent }
]); registerFromManifest(): Observable<void> {
return this.http.get<WidgetManifest>(this.manifestUrl).pipe(
map((manifest) => {
const widgets = (manifest.widgets ?? [])
.filter((item) => item.enabled !== false)
.map((item): RegisteredWidget | null => {
const component = APPROVED_WIDGET_COMPONENTS[item.componentKey];
if (!component) {
return null;
}
return {
type: item.type,
version: item.version,
component
};
})
.filter((item): item is RegisteredWidget => item !== null);
this.registry.registerMany(widgets);
}),
catchError(() => of(undefined))
);
} }
} }

View File

@@ -0,0 +1,22 @@
{
"widgets": [
{
"type": "hero",
"version": "1.0.0",
"componentKey": "hero",
"enabled": true
},
{
"type": "footer-navigation",
"version": "1.0.0",
"componentKey": "footer-navigation",
"enabled": true
},
{
"type": "product-carousel",
"version": "1.0.0",
"componentKey": "product-carousel",
"enabled": true
}
]
}