feat(project-editor): add draft/publish status, dirty tracking, save/publish to facade
Wires ProjectValidator into ProjectEditorFacade and extends ProjectEditorState
with status ('draft'|'published') and lastSavedBootstrap. Adds dirty computed
(diffed against lastSavedBootstrap), validationIssues computed, and save()/publish()
methods. publish() calls PlatformRuntimeService.reloadFromBootstrap() and refuses
when validation issues exist. loadBootstrap() seeds lastSavedBootstrap so a
freshly-loaded bootstrap is not dirty.
This commit is contained in:
@@ -7,6 +7,8 @@ import { ProjectEditorIoService } from '../services/project-editor-io.service';
|
|||||||
import { ProjectEditorPreviewService } from '../services/project-editor-preview.service';
|
import { ProjectEditorPreviewService } from '../services/project-editor-preview.service';
|
||||||
import { LocaleSyncService } from '../services/locale-sync.service';
|
import { LocaleSyncService } from '../services/locale-sync.service';
|
||||||
import { ProjectEditorState } from '../models/project-editor.model';
|
import { ProjectEditorState } from '../models/project-editor.model';
|
||||||
|
import { PlatformRuntimeService } from '../../../core/runtime/platform-runtime.service';
|
||||||
|
import { ProjectValidator } from '../services/project-validator.service';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ProjectEditorFacade {
|
export class ProjectEditorFacade {
|
||||||
@@ -14,11 +16,15 @@ export class ProjectEditorFacade {
|
|||||||
private readonly ioService = inject(ProjectEditorIoService);
|
private readonly ioService = inject(ProjectEditorIoService);
|
||||||
private readonly previewService = inject(ProjectEditorPreviewService);
|
private readonly previewService = inject(ProjectEditorPreviewService);
|
||||||
private readonly localeSync = inject(LocaleSyncService);
|
private readonly localeSync = inject(LocaleSyncService);
|
||||||
|
private readonly runtime = inject(PlatformRuntimeService);
|
||||||
|
private readonly validator = inject(ProjectValidator);
|
||||||
|
|
||||||
private readonly state = signal<ProjectEditorState>({
|
private readonly state = signal<ProjectEditorState>({
|
||||||
bootstrap: null,
|
bootstrap: null,
|
||||||
importError: null,
|
importError: null,
|
||||||
activeSection: 'general',
|
activeSection: 'general',
|
||||||
|
status: 'draft',
|
||||||
|
lastSavedBootstrap: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
readonly bootstrap = computed(() => this.state().bootstrap);
|
readonly bootstrap = computed(() => this.state().bootstrap);
|
||||||
@@ -26,10 +32,25 @@ export class ProjectEditorFacade {
|
|||||||
readonly activeSection = computed(() => this.state().activeSection);
|
readonly activeSection = computed(() => this.state().activeSection);
|
||||||
readonly homepagePage = computed(() => this.bootstrap()?.pages.find(page => page.key === 'home' || page.route.path === '/') ?? null);
|
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 }))) ?? []);
|
readonly homepageWidgets = computed(() => this.homepagePage()?.sections.flatMap(section => section.widgets.map(widget => ({ sectionId: section.id, sectionType: section.type, widget }))) ?? []);
|
||||||
|
readonly status = computed(() => this.state().status);
|
||||||
|
readonly validationIssues = computed(() => {
|
||||||
|
const current = this.bootstrap();
|
||||||
|
return current ? this.validator.validate(current) : [];
|
||||||
|
});
|
||||||
|
readonly dirty = computed(() => {
|
||||||
|
const current = this.bootstrap();
|
||||||
|
if (!current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return JSON.stringify(current) !== JSON.stringify(this.state().lastSavedBootstrap);
|
||||||
|
});
|
||||||
|
|
||||||
loadBootstrap(): void {
|
loadBootstrap(): void {
|
||||||
this.configService.loadBootstrap(true).pipe(take(1)).subscribe({
|
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 })),
|
next: config => {
|
||||||
|
const normalized = this.normalize(JSON.parse(JSON.stringify(config)) as BootstrapConfig);
|
||||||
|
this.state.update(current => ({ ...current, bootstrap: normalized, importError: null, lastSavedBootstrap: normalized, status: 'draft' }));
|
||||||
|
},
|
||||||
error: () => this.state.update(current => ({ ...current, bootstrap: null, importError: 'builder.importError' })),
|
error: () => this.state.update(current => ({ ...current, bootstrap: null, importError: 'builder.importError' })),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -89,6 +110,28 @@ export class ProjectEditorFacade {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
const current = this.state().bootstrap;
|
||||||
|
if (!current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.state.update(state => ({ ...state, lastSavedBootstrap: JSON.parse(JSON.stringify(current)) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
publish(): boolean {
|
||||||
|
const current = this.state().bootstrap;
|
||||||
|
if (!current || this.validationIssues().length > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.runtime.reloadFromBootstrap(current);
|
||||||
|
this.state.update(state => ({
|
||||||
|
...state,
|
||||||
|
status: 'published',
|
||||||
|
lastSavedBootstrap: JSON.parse(JSON.stringify(current)),
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
addNavLink(target: 'header' | 'footer'): void {
|
addNavLink(target: 'header' | 'footer'): void {
|
||||||
this.updateBootstrap(current => {
|
this.updateBootstrap(current => {
|
||||||
const list = current.navigation[target];
|
const list = current.navigation[target];
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export interface ProjectEditorState {
|
|||||||
bootstrap: BootstrapConfig | null;
|
bootstrap: BootstrapConfig | null;
|
||||||
importError: string | null;
|
importError: string | null;
|
||||||
activeSection: ProjectEditorSectionId;
|
activeSection: ProjectEditorSectionId;
|
||||||
|
status: 'draft' | 'published';
|
||||||
|
lastSavedBootstrap: BootstrapConfig | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectEditorWidgetPreset {
|
export interface ProjectEditorWidgetPreset {
|
||||||
|
|||||||
Reference in New Issue
Block a user