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

@@ -0,0 +1,106 @@
import { Injectable, computed, inject, signal } from '@angular/core';
import { take } from 'rxjs/operators';
import { BootstrapConfig, DEFAULT_CATALOG_CONFIG, DEFAULT_HEADER_CONFIG, DEFAULT_PRODUCT_PAGE_CONFIG, DEFAULT_USER_EXPERIENCE_CONFIG } from '../../../shared/models/config';
import { ConfigService } from '../../../core/config/config.service';
import { ProjectEditorIoService } from '../services/project-editor-io.service';
import { ProjectEditorPreviewService } from '../services/project-editor-preview.service';
import { ProjectEditorState } from '../models/project-editor.model';
@Injectable({ providedIn: 'root' })
export class ProjectEditorFacade {
private readonly configService = inject(ConfigService);
private readonly ioService = inject(ProjectEditorIoService);
private readonly previewService = inject(ProjectEditorPreviewService);
private readonly state = signal<ProjectEditorState>({
bootstrap: null,
importError: null,
activeSection: 'general',
});
readonly bootstrap = computed(() => this.state().bootstrap);
readonly importError = computed(() => this.state().importError);
readonly activeSection = computed(() => this.state().activeSection);
readonly homepagePage = computed(() => this.bootstrap()?.pages.find(page => page.key === 'home' || page.route.path === '/') ?? null);
readonly homepageWidgets = computed(() => this.homepagePage()?.sections.flatMap(section => section.widgets.map(widget => ({ sectionId: section.id, sectionType: section.type, widget }))) ?? []);
loadBootstrap(): void {
this.configService.loadBootstrap(true).pipe(take(1)).subscribe({
next: config => this.state.update(current => ({ ...current, bootstrap: this.normalize(JSON.parse(JSON.stringify(config)) as BootstrapConfig), importError: null })),
error: () => this.state.update(current => ({ ...current, bootstrap: null, importError: 'builder.importError' })),
});
}
updateBootstrap(updater: (current: BootstrapConfig) => BootstrapConfig): void {
const current = this.state().bootstrap;
if (!current) {
return;
}
this.state.update(state => ({
...state,
bootstrap: this.normalize(updater(JSON.parse(JSON.stringify(current)) as BootstrapConfig)),
}));
}
exportBootstrap(): string {
const current = this.state().bootstrap;
return current ? this.ioService.exportBootstrap(current) : '';
}
importBootstrap(raw: string): void {
try {
const imported = this.normalize(this.ioService.importBootstrap(raw));
this.state.update(current => ({ ...current, bootstrap: imported, importError: null }));
} catch {
this.state.update(current => ({ ...current, importError: 'builder.importError' }));
}
}
preview(): void {
const current = this.state().bootstrap;
if (!current) {
return;
}
this.previewService.preview(current);
}
setActiveSection(activeSection: ProjectEditorState['activeSection']): void {
this.state.update(current => ({ ...current, activeSection }));
}
private normalize(config: BootstrapConfig): BootstrapConfig {
return {
...config,
header: {
...DEFAULT_HEADER_CONFIG,
...(config.header ?? {})
},
catalog: {
...DEFAULT_CATALOG_CONFIG,
...(config.catalog ?? {})
},
productPage: {
...DEFAULT_PRODUCT_PAGE_CONFIG,
...(config.productPage ?? {})
},
userExperience: {
...DEFAULT_USER_EXPERIENCE_CONFIG,
...(config.userExperience ?? {})
},
localization: {
...config.localization,
supportedLocales: config.localization?.supportedLocales?.length ? config.localization.supportedLocales : config.tenant.supportedLocales,
defaultLocale: config.localization?.defaultLocale || config.tenant.defaultLocale,
currencyByLocale: config.localization?.currencyByLocale ?? {},
dictionaries: config.localization?.dictionaries ?? []
},
pages: [...(config.pages ?? [])].sort((left, right) => {
const leftOrder = Math.min(...left.sections.map(section => section.order));
const rightOrder = Math.min(...right.sections.map(section => section.order));
return leftOrder - rightOrder;
})
};
}
}