Implementation Plan

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Responsive Implementation Plan for Widget Editor

Overview

This plan details the process of implementing complete responsiveness for the Angular 20-based widget editor, following the existing Nx monorepo architecture. The goal is to develop a native responsiveness solution that allows both the editor and the widgets created to be fully adaptable to any device.

Fundamental Principles

Implementation Phases

Phase 1: Base Infrastructure (Weeks 1-2)

1.1 Responsive Data Models

// Instructions: Update core models to support responsiveness

// File: libs/core-models/src/lib/models/responsive/responsive-value.model.ts
// Create base model for responsive values
export interface ResponsiveValue<T> {
  base: T; // Default value
  sm?: T; // mobile (>= 640px)
  md?: T; // tablet (>= 768px)
  lg?: T; // desktop (>= 1024px)
  xl?: T; // large desktop (>= 1280px)
  '2xl'?: T; // extra large (>= 1536px)
}

// File: libs/core-models/src/lib/models/widget.model.ts
// Update widget model with responsive properties
export interface Widget {
  id: string;
  type: string;
  name: string;
  position: Position | ResponsiveValue<Position>;
  size: Size | ResponsiveValue<Size>;
  styles?: WidgetStyles | ResponsiveValue<WidgetStyles>;
  content?: any;
  config?: any;
}

export interface Position {
  x: number;
  y: number;
  z?: number;
}

export interface Size {
  width: number | string;
  height: number | string;
  minWidth?: number | string;
  minHeight?: number | string;
  maxWidth?: number | string;
  maxHeight?: number | string;
}

export interface WidgetStyles {
  backgroundColor?: string;
  color?: string;
  borderRadius?: string | number;
  border?: string;
  padding?: string | number;
  margin?: string | number;
  // Additional styles as needed
}

1.2 Viewport Services and Responsiveness

// Instructions: Implement services for viewport detection and responsive values

// File: libs/core-models/src/lib/services/responsive/viewport.service.ts
@Injectable({
  providedIn: 'root',
})
export class ViewportService {
  // Signals for reactive state
  private _currentBreakpoint = signal<BreakpointKey>('base');
  private _viewportSize = signal<{ width: number; height: number }>({ width: 0, height: 0 });
  private _orientation = signal<'portrait' | 'landscape'>('landscape');
  private _deviceType = signal<'mobile' | 'tablet' | 'desktop'>('desktop');
  private _isTouch = signal<boolean>(false);

  // Public readonly signals
  public readonly currentBreakpoint = this._currentBreakpoint.asReadonly();
  public readonly viewportSize = this._viewportSize.asReadonly();

  constructor() {
    // Initialize viewport size
    this._viewportSize.set({
      width: window.innerWidth,
      height: window.innerHeight,
    });
    this.updateCurrentBreakpoint(window.innerWidth);

    // Configure ResizeObserver
    const resizeObserver = new ResizeObserver(entries => {
      for (const entry of entries) {
        if (entry.target === document.documentElement) {
          const { width, height } = entry.contentRect;
          this._viewportSize.set({ width, height });
          this.updateCurrentBreakpoint(width);
        }
      }
    });

    resizeObserver.observe(document.documentElement);
  }

  // Methods to determine breakpoint and get responsive values
  public getValueForBreakpoint<T>(responsiveValue: ResponsiveValue<T>): T {
    // Implement logic to get value based on current breakpoint
    // ...
  }
}

// File: libs/core-models/src/lib/services/responsive-widget.service.ts
// Service to calculate responsive styles for widgets
@Injectable({
  providedIn: 'root',
})
export class ResponsiveWidgetService {
  constructor(private viewportService: ViewportService) {}

  // Method to get widget styles for current breakpoint
  getWidgetStyles(widget: Widget): Record<string, any> {
    // Implement logic to calculate styles based on breakpoint
    // ...
  }
}

1.3 Viewport State

// Instructions: Implement state for viewport information

// File: libs/core-models/src/lib/state/viewport/viewport.model.ts
export interface ViewportStateModel {
  currentBreakpoint: BreakpointKey;
  viewportSize: {
    width: number;
    height: number;
  };
  deviceType: 'mobile' | 'tablet' | 'desktop';
  orientation: 'portrait' | 'landscape';
  breakpoints: Breakpoints;
}

// File: libs/core-models/src/lib/state/viewport/viewport.actions.ts
export class UpdateViewport {
  static readonly type = '[Viewport] Update';
  constructor(
    public payload: {
      width: number;
      height: number;
    }
  ) {}
}

export class SetBreakpoint {
  static readonly type = '[Viewport] Set Breakpoint';
  constructor(public payload: BreakpointKey) {}
}

// File: libs/core-models/src/lib/state/viewport/viewport.state.ts
@State<ViewportStateModel>({
  name: 'viewport',
  defaults: {
    currentBreakpoint: 'base',
    viewportSize: { width: 0, height: 0 },
    deviceType: 'desktop',
    orientation: 'landscape',
    breakpoints: {
      sm: 640,
      md: 768,
      lg: 1024,
      xl: 1280,
      '2xl': 1536,
    },
  },
})
@Injectable()
export class ViewportState {
  // Implement selectors and actions
  // ...
}

1.4 Editor State Update

// Instructions: Update editor state to support responsive editing

// File: libs/core-models/src/lib/state/editor/editor.model.ts
// Add to EditorStateModel
export interface EditorStateModel {
  // Existing fields...
  activeBreakpoint: BreakpointKey; // Breakpoint being edited
  showResponsiveControls: boolean; // Show responsive controls
  responsivePreviewMode: 'single' | 'multiple'; // Preview mode
  // Rest of fields...
}

// File: libs/core-models/src/lib/state/editor/editor.actions.ts
// Add actions
export class SetActiveBreakpoint {
  static readonly type = '[Editor] Set Active Breakpoint';
  constructor(public payload: BreakpointKey) {}
}

export class ToggleResponsiveControls {
  static readonly type = '[Editor] Toggle Responsive Controls';
  constructor(public visible?: boolean) {}
}

Phase 2: Base Directives and Components (Weeks 3-4)

2.1 Responsive Directives

// Instructions: Implement directives for responsive manipulation

// File: libs/shared-ui/src/lib/directives/responsive-draggable.directive.ts
@Directive({
  standalone: true,
  selector: '[responsiveDraggable]',
})
export class ResponsiveDraggableDirective implements OnInit, OnDestroy {
  @Input() widget!: Widget;
  @Input() activeBreakpoint: BreakpointKey = 'base';

  // Implement logic to drag that updates only the active breakpoint
  // ...
}

// File: libs/shared-ui/src/lib/directives/responsive-resizable.directive.ts
@Directive({
  standalone: true,
  selector: '[responsiveResizable]',
})
export class ResponsiveResizableDirective implements OnInit, OnDestroy {
  @Input() widget!: Widget;
  @Input() activeBreakpoint: BreakpointKey = 'base';

  // Implement logic to resize for specific breakpoint
  // ...
}

2.2 Base Widget Component Update

// Instructions: Update base widgets to support responsiveness

// File: libs/widgets/src/lib/components/container-widget/container-widget.component.ts
@Component({
  standalone: true,
  selector: 'app-container-widget',
  templateUrl: './container-widget.component.html',
  styleUrls: ['./container-widget.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContainerWidgetComponent implements OnInit {
  widget = input<Widget>();
  activeBreakpoint = input<BreakpointKey>('base');

  private responsiveWidgetService = inject(ResponsiveWidgetService);
  private viewportService = inject(ViewportService);

  styles = computed(() => {
    return this.responsiveWidgetService.getWidgetStyles(this.widget());
  });

  // Implement additional logic for responsive container
  // ...
}

// Update similarly other basic widgets (TextWidget, ImageWidget, etc.)

2.3 UI Components for Responsive Editing

// Instructions: Create UI components for responsive editing

// File: libs/editor-ui/src/lib/components/responsive-toolbar/responsive-toolbar.component.ts
@Component({
  standalone: true,
  selector: 'app-responsive-toolbar',
  template: `
    <div class="responsive-toolbar">
      <button
        *ngFor="let bp of breakpoints"
        [class.active]="bp === activeBreakpoint()"
        (click)="setActiveBreakpoint(bp)"
      >
        {{ bp }}
      </button>
      <!-- More controls -->
    </div>
  `,
  styleUrls: ['./responsive-toolbar.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ResponsiveToolbarComponent {
  private editorState = inject(EditorState);

  activeBreakpoint = this.editorState.select(state => state.activeBreakpoint);
  breakpoints: BreakpointKey[] = ['base', 'sm', 'md', 'lg', 'xl', '2xl'];

  setActiveBreakpoint(bp: BreakpointKey) {
    this.editorState.dispatch(new SetActiveBreakpoint(bp));
  }
}

// File: libs/editor-ui/src/lib/components/device-preview/device-preview.component.ts
@Component({
  standalone: true,
  selector: 'app-device-preview',
  templateUrl: './device-preview.component.html',
  styleUrls: ['./device-preview.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DevicePreviewComponent {
  // Implement component to simulate different devices
  // ...
}

Phase 3: Integration with Editor (Weeks 5-6)

3.1 Canvas and Workspace Update

// Instructions: Update editor canvas to support responsive editing

// File: libs/widgets/src/lib/widgets/editor-canvas.component.ts
// Update with responsive support
@Component({
  //...
})
export class EditorCanvasComponent {
  private editorState = inject(EditorState);
  private viewportState = inject(ViewportState);

  activeBreakpoint = this.editorState.select(state => state.activeBreakpoint);
  currentViewport = this.viewportState.select();

  // Implement logic to render widgets based on active breakpoint
  // ...
}

// File: apps/editor-web/src/app/features/editor/components/editor-workspace/editor-workspace.component.ts
// Update workspace to include responsive controls
@Component({
  //...
})
export class EditorWorkspaceComponent {
  // Add logic to integrate responsive toolbar and preview
  // ...
}

3.2 Properties Panel Update

// Instructions: Update properties panel to edit by breakpoint

// File: libs/editor-ui/src/lib/components/properties-panel/properties-panel.component.ts
@Component({
  //...
})
export class PropertiesPanelComponent {
  private editorState = inject(EditorState);

  selectedWidget = this.editorState.select(state => state.selectedWidget);
  activeBreakpoint = this.editorState.select(state => state.activeBreakpoint);

  // Implement UI to edit properties specific to breakpoint
  // ...

  updateWidgetProperty(widget: Widget, property: string, value: any) {
    // Logic to update only active breakpoint
    // ...
  }
}

3.3 WidgetTree Update

// Instructions: Update widget tree with responsive indicators

// File: libs/editor-ui/src/lib/components/widget-tree/widget-tree.component.ts
@Component({
  //...
})
export class WidgetTreeComponent {
  // Add responsive configuration indicators
  // ...

  hasResponsiveOverrides(widget: Widget): boolean {
    // Verify if widget has specific configurations for breakpoint
    // ...
  }
}

Phase 4: Actions and Editor Improvements (Weeks 7-8)

4.1 Propagation Between Breakpoints

// Instructions: Implement actions to propagate changes between breakpoints

// File: libs/core-models/src/lib/state/widgets/widgets.actions.ts
// Add actions
export class PropagateWidgetProperty {
  static readonly type = '[Widgets] Propagate Property';
  constructor(
    public payload: {
      widgetId: string;
      property: string;
      fromBreakpoint: BreakpointKey;
      toBreakpoints: BreakpointKey[];
    }
  ) {}
}

export class ResetWidgetToBaseBreakpoint {
  static readonly type = '[Widgets] Reset To Base';
  constructor(
    public payload: {
      widgetId: string;
      properties?: string[];
    }
  ) {}
}

4.2 Multi-Device Preview Tools

// Instructions: Implement multi-device preview tools

// File: libs/editor-ui/src/lib/components/multi-device-preview/multi-device-preview.component.ts
@Component({
  standalone: true,
  selector: 'app-multi-device-preview',
  templateUrl: './multi-device-preview.component.html',
  styleUrls: ['./multi-device-preview.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MultiDevicePreviewComponent {
  // Implement simultaneous view of multiple devices
  // ...
}

4.3 Responsive Validation

// Instructions: Implement responsive validation tools

// File: libs/core-models/src/lib/services/responsive-validator.service.ts
@Injectable({
  providedIn: 'root',
})
export class ResponsiveValidatorService {
  validatePage(page: Page): ResponsiveValidationResult {
    // Implement validation rules:
    // - Minimum sizes for touch elements
    // - Legible text
    // - Elements not cut
    // - etc.
    // ...
  }
}

Phase 5: Optimization and Refinement (Weeks 9-10)

5.1 Performance Optimization

// Instructions: Optimize performance for responsive manipulation

// File: libs/core-models/src/lib/services/viewport.service.ts
// Add throttling to avoid excessive updates
// ...

// Optimize change detection in components
// ...

5.2 Tailwind Configuration

// Instructions: Update Tailwind configuration for consistent breakpoints

// File: apps/editor-web/tailwind.config.js
module.exports = {
  theme: {
    screens: {
      sm: '640px',
      md: '768px',
      lg: '1024px',
      xl: '1280px',
      '2xl': '1536px',
    },
    // Rest of the configuration...
  },
  // ...
};

5.3 Documentation and Usage Guides

// Instructions: Create documentation for developers and users

// 1. Development Guide: How to extend widgets with responsive support
// 2. User Guide: How to use responsive tools
// 3. Examples and best practices

Success Criteria

The implementation will be considered successful when:

  1. Responsive Editor: The editor works correctly on all common screen resolutions (from 320px to 2560px wide)
  2. Responsive Widgets: Widgets can be specifically configured for different breakpoints
  3. Intuitive Editing: The user interface facilitates editing by breakpoint with clear visual tools
  4. Optimal Performance: The application maintains a fluid performance on all devices
  5. Exact Preview: The preview accurately shows how widgets will appear on different devices
  6. Controlled Propagation: Changes can selectively propagate between breakpoints

Tests

Unit Tests

// Instructions: Implement tests for responsive components and services

// File: libs/core-models/src/lib/services/viewport.service.spec.ts
describe('ViewportService', () => {
  // Test breakpoint detection
  // Test responsive value calculation
  // ...
});

// File: libs/shared-ui/src/lib/directives/responsive-draggable.directive.spec.ts
describe('ResponsiveDraggableDirective', () => {
  // Test that only updates active breakpoint
  // ...
});

E2E Tests

// Instructions: Implement E2E tests to verify responsive experience

// File: apps/editor-web-e2e/src/e2e/responsive.cy.ts
describe('Responsive Editing', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('should display responsive toolbar', () => {
    // ...
  });

  it('should update widget only for active breakpoint', () => {
    // ...
  });

  it('should preview correctly in different devices', () => {
    // ...
  });
});

Technical Recommendations

  1. Prefer Signals over RxJS: Use signals for local reactive state whenever possible for better performance
  2. Change Detection OnPush: Keep ChangeDetectionStrategy.OnPush in all components
  3. Immutability: Always create new objects when updating responsive properties
  4. Memoization: Use computed() to derive values and avoid repeated calculations
  5. Lazy Loading: Implement lazy loading of heavy components
  6. Virtualization: Use virtualization for long lists (especially in widget tree)
  7. Device Testing: Regularly test on real devices, not just simulators

Continuous Integration

  1. Visual Regression Testing: Implement visual regression tests for different breakpoints
  2. Lighthouse CI: Configure automated performance tests
  3. Device Matrix: Define device matrix for automated tests

This implementation plan provides a clear path to integrate complete responsive capabilities into the existing widget editor, maintaining the current architecture and following the project's mandatory guidelines.

Appendices

Appendix A: Responsive System Architecture Diagram

┌─────────────────────────────────┐
│    Responsive Widget System     │
└───────────────┬─────────────────┘
                │
    ┌───────────┴───────────┐
    │                       │
┌───▼───┐             ┌─────▼─────┐
│Models │             │  Services  │
└───┬───┘             └─────┬─────┘
    │                       │
    │     ┌───────────┐     │
    └────►│  Signals  │◄────┘
          └─────┬─────┘
                │
    ┌───────────┴───────────┐
    │                       │
┌───▼────┐            ┌─────▼─────┐
│ States │            │ Components │
└────────┘            └───────────┘

Appendix B: Responsive Data Flow

  1. Initial Data Loading: The widget configuration is loaded from storage
  2. Viewport Detection: The active breakpoint is determined based on viewport size
  3. Value Resolution: Responsive values for the active breakpoint are applied to the widget
  4. Component Rendering: UI components render widget with breakpoint-specific values
  5. User Interaction: User modifies widget properties for the active breakpoint
  6. Model Update: Only values for the active breakpoint are updated in the model
  7. Persistence: The updated responsive configuration is saved to storage

Appendix C: Example Widget Implementation

A simple example of how a text widget would use the responsive system:

// Text widget component
@Component({
  selector: 'app-text-widget',
  standalone: true,
  imports: [CommonModule, ResponsiveWidgetPropsDirective],
  template: `
    <div
      class="text-widget"
      libResponsiveWidgetProps
      [widget]="widget"
      [activeBreakpoint]="activeBreakpoint"
      #widgetProps="responsiveWidgetProps"
    >
      <div
        [style.font-size.px]="widgetProps.getStyleProp('fontSize')"
        [style.color]="widgetProps.getStyleProp('color')"
        [style.font-weight]="widgetProps.getStyleProp('fontWeight')"
      >
        {{ widget.content }}
      </div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TextWidgetComponent {
  @Input() widget!: Widget;
  @Input() activeBreakpoint: BreakpointKey = 'base';
  @Output() widgetChanged = new EventEmitter<Widget>();

  // Using the responsiveness system with style calculations
  readonly textStyles = computed(() => {
    const styles = this.widgetProps?.getWidgetStyles() || {};

    return {
      fontSize: styles.fontSize || '16px',
      fontWeight: styles.fontWeight || 'normal',
      color: styles.color || '#000000',
      // Other styles...
    };
  });
}

Appendix D: Browser/Device Testing Matrix

Type Display Size Devices to Test Priority
Mobile Small 320px-375px iPhone SE, Galaxy S9 High
Mobile Medium 375px-428px iPhone 13, Google Pixel 6 High
Mobile Large 428px-576px iPhone 13 Pro Max, Galaxy S22 Ultra Medium
Tablet Portrait 576px-768px iPad Mini, Galaxy Tab High
Tablet Landscape 768px-992px iPad, Surface Go Medium
Laptop 992px-1200px MacBook Air, Surface Laptop High
Desktop 1200px-1600px 24" Monitors High
Large Desktop 1600px+ 27"+ Monitors Medium

Appendix E: Performance Benchmarks

Metric Target Value Measurement Method
Time to Interactive < 3s Lighthouse
First Contentful Paint < 1.5s Lighthouse
Input Latency < 50ms DevTools Performance
Widget Rendering (10 widgets) < 100ms Angular Profiler
Widget Dragging FPS > 45fps DevTools Performance
Memory Usage < 100MB Chrome Task Manager
Bundle Size (main) < 500KB Webpack Bundle Analyzer

Appendix F: Migration Strategy

For existing widgets without responsive properties:

  1. Automatic Base Conversion: Convert existing scalar properties to responsive objects with base value
  2. Property Wrapping: Wrap all relevant widget properties in ResponsiveValue
  3. Schema Migration: Update database schemas to store responsive values
  4. Validation: Implement validation tools to ensure consistent experiences across breakpoints

Appendix G: Support Material

  1. Interactive Tutorials: Step-by-step guides for working with responsive editing
  2. UI Cheat Sheet: Quick reference for responsive editing controls
  3. Best Practices Guide: Recommendations for creating responsive widgets
  4. Troubleshooting Guide: Solutions to common responsive issues

Appendix H: Common Patterns

Widget Configuration Panel Pattern

@Component({
  selector: 'app-widget-config-panel',
  template: `
    <div class="config-panel">
      <div class="breakpoint-selector">
        <button
          *ngFor="let bp of breakpoints"
          [class.active]="bp === activeBreakpoint()"
          (click)="setActiveBreakpoint(bp)"
        >
          {{ bp }}
        </button>
      </div>

      <ng-container *ngIf="activeWidget()">
        <!-- Form controls that update only active breakpoint -->
        <div class="form-group">
          <label>Width</label>
          <input
            type="text"
            [value]="getWidgetSizeProp('width')"
            (input)="updateWidgetSizeProp('width', $event)"
          />

          <label>Height</label>
          <input
            type="text"
            [value]="getWidgetSizeProp('height')"
            (input)="updateWidgetSizeProp('height', $event)"
          />

          <!-- More properties -->
        </div>

        <div class="property-controls">
          <button (click)="propagateToLargerBreakpoints()">Apply to Larger Screens</button>
          <button (click)="propagateToSmallerBreakpoints()">Apply to Smaller Screens</button>
        </div>
      </ng-container>
    </div>
  `,
})
export class WidgetConfigPanelComponent {
  // Reactive state with signals
  private _activeBreakpoint = signal<BreakpointKey>('base');
  activeBreakpoint = this._activeBreakpoint.asReadonly();

  activeWidget = this.widgetState.selectedWidget;
  breakpoints: BreakpointKey[] = ['base', 'sm', 'md', 'lg', 'xl', '2xl'];

  constructor(private widgetState: WidgetStateService) {}

  setActiveBreakpoint(bp: BreakpointKey) {
    this._activeBreakpoint.set(bp);
  }

  getWidgetSizeProp(prop: keyof Size): any {
    const widget = this.activeWidget();
    if (!widget) return null;

    // Get value for the active breakpoint
    // ...
  }

  updateWidgetSizeProp(prop: keyof Size, event: Event) {
    const widget = this.activeWidget();
    if (!widget) return;

    const value = (event.target as HTMLInputElement).value;

    // Create immutable update for just the active breakpoint
    // ...
  }

  propagateToLargerBreakpoints() {
    // Implementation to propagate current values to larger breakpoints
    // ...
  }

  propagateToSmallerBreakpoints() {
    // Implementation to propagate current values to smaller breakpoints
    // ...
  }
}