feat(builder): add project editor

This commit is contained in:
sdarbinyan
2026-07-10 13:43:53 +04:00
parent 7161a81068
commit e2c8747fcc
50 changed files with 1656 additions and 42 deletions

View File

@@ -1,5 +1,5 @@
import { Injectable, inject } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { Injectable, inject, signal } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { catchError, shareReplay, tap } from 'rxjs/operators';
import { BootstrapConfig } from '../../shared/models/config';
import { CONFIG_PROVIDER } from './config-provider.token';
@@ -10,12 +10,20 @@ export class ConfigService {
private bootstrapSnapshot: BootstrapConfig | null = null;
private bootstrap$?: Observable<BootstrapConfig>;
private readonly revisionState = signal(0);
readonly bootstrapRevision = this.revisionState.asReadonly();
loadBootstrap(forceRefresh: boolean = false): Observable<BootstrapConfig> {
if (this.bootstrapSnapshot && !forceRefresh && this.bootstrap$) {
return this.bootstrap$;
}
if (!this.bootstrap$ || forceRefresh) {
this.bootstrap$ = this.provider.loadBootstrap().pipe(
tap(config => {
this.bootstrapSnapshot = config;
this.revisionState.update(value => value + 1);
}),
shareReplay(1),
catchError(error => {
@@ -32,4 +40,11 @@ export class ConfigService {
getBootstrapSnapshot(): BootstrapConfig | null {
return this.bootstrapSnapshot;
}
applyBootstrapOverride(next: BootstrapConfig): void {
const cloned = JSON.parse(JSON.stringify(next)) as BootstrapConfig;
this.bootstrapSnapshot = cloned;
this.bootstrap$ = of(cloned);
this.revisionState.update(value => value + 1);
}
}

View File

@@ -47,14 +47,18 @@ export class FooterResolverService {
resolveFooterModel(): Observable<FooterResolvedModel> {
return this.configService.loadBootstrap().pipe(
map((bootstrap) => ({
groups: this.resolveFooterGroups(bootstrap),
paymentIcons: this.resolvePaymentIcons(bootstrap.footer),
copyrightText: this.resolveCopyrightText(bootstrap)
}))
map((bootstrap) => this.resolveFooterModelFromBootstrap(bootstrap))
);
}
resolveFooterModelFromBootstrap(bootstrap: BootstrapConfig): FooterResolvedModel {
return {
groups: this.resolveFooterGroups(bootstrap),
paymentIcons: this.resolvePaymentIcons(bootstrap.footer),
copyrightText: this.resolveCopyrightText(bootstrap)
};
}
private resolveFooterGroups(bootstrap: BootstrapConfig): FooterResolvedGroup[] {
const footer = bootstrap.navigation?.footer ?? [];
const lang = this.languageService.currentLanguage();

View File

@@ -5,6 +5,7 @@ import { BrandingEngineService } from '../../theme/runtime/branding-engine.servi
import { ThemeEngineService } from '../../theme/runtime/theme-engine.service';
import { WidgetRegistryBootstrapService } from '../../widgets/registry/widget-registry.bootstrap.service';
import { PlatformRuntimeStateService } from './platform-runtime-state.service';
import { BootstrapConfig } from '../../shared/models/config';
@Injectable({ providedIn: 'root' })
export class PlatformRuntimeService {
@@ -55,4 +56,22 @@ export class PlatformRuntimeService {
}
});
}
reloadFromBootstrap(bootstrap: BootstrapConfig): void {
this.configService.applyBootstrapOverride(bootstrap);
this.widgetRegistryBootstrap.registerFromManifest().subscribe({
next: () => this.applyRuntimeBootstrap(bootstrap),
error: () => this.applyRuntimeBootstrap(bootstrap),
});
}
private applyRuntimeBootstrap(bootstrap: BootstrapConfig): void {
this.themeEngine.applyTheme(bootstrap.theme);
this.brandingEngine.applyBranding(bootstrap.branding);
this.runtimeState.markInitialized({
localization: bootstrap.localization,
featureFlags: bootstrap.featureFlags,
permissions: bootstrap.permissions
});
}
}