EDITOR SAVE IMPLEMENTATION
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
Editor Save Implementation
📋 Overview
This document details the implementation of the page save functionality in the editor-web-lib. The save system properly syncs widgets from the EditorService to the PagesService storage.
🏗️ Architecture
Data Flow
EditorService (Flash Persistence)
├─ widgetsByPage[pageId] → Current page widgets
├─ page → Current page metadata
│
↓ Save Action Triggered
│
SaveCurrentPageAction
├─ Reads current page from EditorService
├─ Reads current widgets from EditorService
├─ Merges widgets into page object
│
↓ Persist to Storage
│
PagesService (Flash Persistence)
└─ pages[] → Persistent page storage with widgets
Key Components
1. SaveCurrentPageAction (@libs-structural/editor-web-lib/actions)
Purpose: Public action that saves the current page being edited
Responsibilities:
- Gets current page from
EditorService.page() - Gets current widgets from
EditorService.widgets() - Merges widgets into page object
- Calls
PagesService.updatePage()to persist - Returns success/error result
Implementation:
execute(params, context): Observable<SaveCurrentPageOutput> {
// 1. Get current page from editor
const currentPage = this.editorService.page();
// 2. Get current widgets for this page
const currentWidgets = this.editorService.widgets();
// 3. Create updated page with widgets
const pageToSave = {
...currentPage,
widgets: currentWidgets,
updatedAt: new Date().toISOString(),
};
// 4. Save via PagesService
return from(this.pagesService.updatePage(currentPage.id, pageToSave));
}
Action ID: editor:save-current-page
Parameters: None (uses current editor state)
Returns:
{
success: boolean;
error?: string;
}
2. EditorLayoutComponent (@libs-structural/editor-web-lib/components)
Purpose: Main editor toolbar with save button
Save Button Implementation:
<button class="btn btn-primary" (click)="saveProject()" [disabled]="isSaving()">
@if (isSaving()) {
<i class="fas fa-spinner fa-spin"></i> Saving...
} @else {
<i class="fas fa-save"></i> Save
}
</button>
Save Method:
saveProject(): void {
if (this.isSaving()) return;
this.isSaving.set(true);
this.actionRegistry.execute('editor:save-current-page', {}).subscribe({
next: (result) => {
if (result.success) {
this.logger.info('✅ Page saved successfully');
} else {
this.logger.error('❌ Save failed:', result.error);
}
this.isSaving.set(false);
},
error: (error) => {
this.logger.error('❌ Save action failed:', error);
this.isSaving.set(false);
}
});
}
3. EditorService (@libs-ui/editor-ui/services)
Purpose: Manages editor state with per-page widget storage
Flash Persistence State:
readonly editorState = autoFlashState('editor-state', {
page: null as Page | null,
widgets: [] as Widget[], // Legacy (backward compat)
widgetsByPage: {} as { [pageId: string]: Widget[] }, // 🔥 NEW
// ... other state
});
Page-Aware Widgets:
readonly widgets = computed(() => {
const state = this.editorState();
const currentPageId = state.page?.id;
if (!currentPageId) {
return state.widgets || []; // Fallback to legacy
}
const widgetsByPage = state.widgetsByPage || {};
return widgetsByPage[currentPageId] || [];
});
4. PagesService (@libs-foundational/core-angular)
Purpose: Manages persistent storage of pages
Storage Key: pages
Update Method:
async updatePage(pageId: string, updates: Partial<World>): Promise<World | undefined> {
const pages = this._pages();
const index = pages.findIndex(p => p.id === pageId);
if (index === -1) return undefined;
const updatedPage: World = {
...pages[index],
...updates,
id: pageId, // Preserve ID
updatedAt: new Date().toISOString(),
version: (pages[index].version || 1) + 1
};
const newPages = [...pages];
newPages[index] = updatedPage;
await this.savePagesToStorage(newPages);
this._pages.set(newPages);
return updatedPage;
}
🔄 Data Synchronization
Editor → Pages Flow
User adds/modifies widgets in editor
↓
EditorService.addWidget() / updateWidget()
↓
Updates widgetsByPage[currentPageId]
↓
Flash Persistence automatically syncs
↓
User clicks "Save" button
↓
SaveCurrentPageAction.execute()
↓
Reads widgets from EditorService
↓
Merges into page object
↓
PagesService.updatePage()
↓
Persistent storage updated
Pages → Editor Flow
User navigates to /editor/:pageId
↓
EditorWorkspaceComponent.loadPageById()
↓
Loads page from PagesService or Space Map
↓
Calls EditorService.setPage()
↓
EditorService loads widgets from widgetsByPage[pageId]
↓
If no widgets in widgetsByPage, initializes from page.widgets
↓
Widgets rendered in editor canvas
🎯 Storage Strategy
Flash Persistence Layers
Editor State (
editor-statekey)- Per-page widget storage
- Editor UI state (zoom, mode, etc.)
- Undo/redo history
- Flash synced across tabs
Pages Storage (
pageskey)- Complete page objects with widgets
- Metadata (title, description, status)
- Version tracking
- Persisted to IndexedDB
Data Isolation
- Editor widgets are stored per page ID to prevent pollution
- Page widgets are the persistent source of truth
- Save action syncs editor widgets → page widgets
- Load action syncs page widgets → editor widgets
📊 Data Model
Page (World) Structure
interface World {
id: string; // Unique page ID
title: string; // Page title
slug: string; // URL-friendly slug
description?: string; // Page description
widgets: Widget[]; // 🎯 WIDGET ARRAY (synced on save)
layout: PageLayout; // Layout configuration
meta: PageMetadata; // SEO/meta tags
permissions: PagePermissions; // Access control
status: PageStatus; // DRAFT | PUBLISHED | ARCHIVED
version: number; // Auto-incremented on save
createdAt: string; // ISO datetime
updatedAt: string; // ISO datetime (updated on save)
cosmos?: PageCosmos; // Space/Universe association
ownerId?: string; // Owner user ID
}
Widget Structure
interface Widget {
id: string; // Unique widget ID
type: WidgetType; // button | text | image | etc.
position: ResponsiveValue<Position>; // Responsive positioning
size: ResponsiveValue<Size>; // Responsive sizing
styles: ResponsiveValue<WidgetStyles>; // Responsive styles
properties: Record<string, any>; // Widget-specific props
children?: Widget[]; // Nested widgets
flowId?: string; // Singularity flow binding
}
🧪 Testing the Save Flow
Manual Test Steps
- Navigate to editor:
/space/:slug/editor/:pageId - Add widgets to the canvas
- Modify widget properties
- Click Save button in toolbar
- Check console for:
✅ Page saved successfully - Reload the page
- Verify widgets persist
Expected Console Logs
[SaveCurrentPageAction] Syncing page with widgets to storage
pageId: "page-home"
pageTitle: "Home Page"
widgetCount: 3
[SaveCurrentPageAction] ✅ Page saved successfully
pageId: "page-home"
pageTitle: "Home Page"
version: 2
widgetCount: 3
[EditorLayout] ✅ Page saved successfully
Storage Verification
Open DevTools → Application → IndexedDB → gg-storage
Check pages key:
[
{
"id": "page-home",
"title": "Home Page",
"widgets": [
{
"id": "widget-1",
"type": "button",
"position": { ... },
"properties": { ... }
}
],
"version": 2,
"updatedAt": "2025-11-24T..."
}
]
🚀 Next Steps
1. Auto-Save (TODO #3)
Implement automatic save on widget changes with debouncing:
// In EditorService
effect(() => {
const widgets = this.widgets();
const page = this.page();
if (!page) return;
// Debounce auto-save (5 seconds)
this.autoSaveTimer.next();
});
private autoSaveTimer = timer(5000).pipe(
debounceTime(5000),
switchMap(() => this.actionRegistry.execute('editor:save-current-page', {}))
);
2. Page Routing (TODO #4)
Implement dynamic routing for pages:
// editor.routes.ts
{
path: 'editor',
children: [
{
path: ':pageId',
component: EditorWorkspaceComponent,
resolve: { page: PageResolver }
}
]
}
3. Draft System (TODO #3)
Add draft/published workflow:
interface DraftSaveOptions {
isDraft: boolean;
autoPublish?: boolean;
schedulePublish?: Date;
}
4. Version History
Track page versions for undo/recovery:
interface PageVersion {
id: string;
pageId: string;
version: number;
widgets: Widget[];
createdAt: string;
createdBy: string;
}
📝 Summary
✅ Completed:
- SaveCurrentPageAction implementation
- EditorLayoutComponent save button
- Widget sync from EditorService → PagesService
- Flash Persistence integration
- Comprehensive logging
⚠️ TODO:
- Auto-save with debouncing
- Draft/published workflow
- Version history
- Page routing improvements
- Success/error toast notifications
🔗 Related Documentation
Last Updated: 2025-11-24
Status: ✅ Implemented & Tested
Next Review: After auto-save implementation