Sistema di Override Avanzato - ConfigManager

Obiettivo

Fornire un sistema flessibile e potente per personalizzare la configurazione del page builder in diversi contesti, permettendo override temporanei, per ambiente, e condizionali senza modificare la configurazione base.

1. Override per Ambiente

Implementazione nel ConfigManager

/**
 * Override solo per l'ambiente corrente
 * Permette di sovrascrivere configurazioni specifiche per development/production
 */
setEnvironmentOverride(path, value) {
  const envPath = `environments.${this.environment}.${path}`;
  this.set(envPath, value);
  this.applyEnvironmentConfig(); // Riapplica immediatamente le configurazioni ambiente
}
 
/**
 * Ottiene valore specifico per ambiente con fallback
 */
getEnvironmentValue(path, fallbackPath = null) {
  const envPath = `environments.${this.environment}.${path}`;
  const envValue = this.get(envPath);
  
  if (envValue !== null) {
    return envValue;
  }
  
  return fallbackPath ? this.get(fallbackPath) : this.get(path);
}
 
/**
 * Rimuove override per ambiente
 */
removeEnvironmentOverride(path) {
  const envPath = `environments.${this.environment}.${path}`;
  const keys = envPath.split('.');
  const lastKey = keys.pop();
  let current = this.config;
  
  for (const key of keys) {
    if (current && current[key]) {
      current = current[key];
    } else {
      return; // Path non esiste
    }
  }
  
  delete current[lastKey];
  this.applyEnvironmentConfig();
}

Esempi Pratici di Override per Ambiente

A) Debug e Logging Diversi per Ambiente

// In development: abilita debug completo
configManager.setEnvironmentOverride('debug', {
  enabled: true,
  logLevel: 'debug',
  showComponentIds: true,
  monitorClassChanges: true,
  showPerformanceMetrics: true
});
 
// In production: solo errori critici
if (configManager.environment === 'production') {
  configManager.setEnvironmentOverride('debug', {
    enabled: false,
    logLevel: 'error',
    showComponentIds: false
  });
}
 
// Uso nel codice
const debugEnabled = configManager.getEnvironmentValue('debug.enabled');
if (debugEnabled) {
  console.log('Debug info...');
}

B) Scripts e Risorse Diverse per Ambiente

// Development: include script di hot reload e debug tools
configManager.setEnvironmentOverride('pageBuilder.canvas.scripts', [
  'https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js',
  'https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4',
  '/js/dev-tools.js',
  '/js/hot-reload.js'
]);
 
// Production: solo script essenziali
if (configManager.environment === 'production') {
  configManager.setEnvironmentOverride('pageBuilder.canvas.scripts', [
    'https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js',
    'https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4'
  ]);
}

C) Dispositivi Diversi per Ambiente

// Development: tutti i dispositivi per testing
const allDevices = configManager.get('devices');
 
// Staging: solo dispositivi principali
if (configManager.environment === 'staging') {
  configManager.setEnvironmentOverride('devices', 
    allDevices.filter(device => ['desktop', 'mobile', 'lg'].includes(device.id))
  );
}
 
// Demo: solo desktop per semplicità
if (configManager.environment === 'demo') {
  configManager.setEnvironmentOverride('devices', 
    allDevices.filter(device => device.id === 'desktop')
  );
}

2. Override Temporanei

Implementazione Avanzata

/**
 * Override temporaneo con callback
 * Utile per testing, rendering condizionale, o operazioni isolate
 */
temporaryOverride(overrides, callback) {
  // Backup completo della configurazione
  const backup = JSON.parse(JSON.stringify(this.config));
  
  try {
    // Applica override temporaneo
    this.config = this.deepMerge(this.config, overrides);
    
    // Esegui callback con configurazione modificata
    const result = callback();
    
    // Se callback restituisce Promise, gestiscila
    if (result && typeof result.then === 'function') {
      return result.finally(() => {
        this.config = backup;
      });
    }
    
    return result;
  } finally {
    // Ripristina configurazione originale (solo per callback sincroni)
    if (!result || typeof result.then !== 'function') {
      this.config = backup;
    }
  }
}
 
/**
 * Override temporaneo asincrono
 */
async temporaryOverrideAsync(overrides, asyncCallback) {
  const backup = JSON.parse(JSON.stringify(this.config));
  
  try {
    this.config = this.deepMerge(this.config, overrides);
    return await asyncCallback();
  } finally {
    this.config = backup;
  }
}
 
/**
 * Override con timeout automatico
 */
temporaryOverrideWithTimeout(overrides, duration = 5000) {
  const backup = JSON.parse(JSON.stringify(this.config));
  this.config = this.deepMerge(this.config, overrides);
  
  setTimeout(() => {
    this.config = backup;
    console.log('🔄 Temporary override expired and reverted');
  }, duration);
  
  // Restituisce funzione per revert manuale
  return () => {
    this.config = backup;
  };
}

Esempi di Override Temporanei

A) Testing di Configurazioni

// Test rendering con colori diversi
const testResults = [];
 
const colorSchemes = [
  { name: 'Blue Theme', colors: { presets: { primary: ['blue-500', 'blue-600'] } } },
  { name: 'Red Theme', colors: { presets: { primary: ['red-500', 'red-600'] } } },
  { name: 'Green Theme', colors: { presets: { primary: ['green-500', 'green-600'] } } }
];
 
for (const scheme of colorSchemes) {
  const result = configManager.temporaryOverride(scheme, () => {
    // Simula rendering del componente con nuovo tema
    const colors = configManager.getColorOptions('primary');
    return {
      scheme: scheme.name,
      primaryColors: colors,
      renderTime: performance.now()
    };
  });
  
  testResults.push(result);
}
 
console.log('Theme test results:', testResults);

B) Preview di Configurazioni

// Preview configurazione prima di salvarla
function previewConfiguration(newConfig) {
  return configManager.temporaryOverride(newConfig, () => {
    // Ricrea l'editor con nuova configurazione per preview
    const previewEditor = grapesjs.init({
      container: '#preview-container',
      ...configManager.getGrapesJSConfig()
    });
    
    // Renderizza componenti di esempio
    previewEditor.setComponents(`
      <div class="tw-grid">
        <div class="tw-text">Sample Text</div>
        <div class="tw-button">Sample Button</div>
      </div>
    `);
    
    return {
      html: previewEditor.getHtml(),
      css: previewEditor.getCss(),
      editor: previewEditor
    };
  });
}
 
// Uso
const previewResult = previewConfiguration({
  colors: { presets: { primary: ['purple-500'] } },
  spacing: { ranges: { medium: { max: 32 } } }
});

C) A/B Testing di Configurazioni

/**
 * Sistema A/B testing per configurazioni
 */
class ConfigABTesting {
  constructor(configManager) {
    this.configManager = configManager;
    this.tests = new Map();
  }
  
  // Definisce un test A/B
  defineTest(testId, variants) {
    this.tests.set(testId, {
      variants,
      results: new Map()
    });
  }
  
  // Esegue una variante del test
  runVariant(testId, variantId, callback) {
    const test = this.tests.get(testId);
    if (!test) throw new Error(`Test ${testId} not found`);
    
    const variant = test.variants[variantId];
    if (!variant) throw new Error(`Variant ${variantId} not found`);
    
    const startTime = performance.now();
    
    return this.configManager.temporaryOverride(variant.config, () => {
      const result = callback();
      const endTime = performance.now();
      
      // Memorizza risultati
      test.results.set(variantId, {
        result,
        performanceTime: endTime - startTime,
        timestamp: new Date()
      });
      
      return result;
    });
  }
  
  // Ottiene risultati del test
  getTestResults(testId) {
    const test = this.tests.get(testId);
    return test ? Object.fromEntries(test.results) : null;
  }
}
 
// Esempio di uso
const abTesting = new ConfigABTesting(configManager);
 
// Definisce test per spacing
abTesting.defineTest('spacing-test', {
  compact: {
    name: 'Compact Spacing',
    config: {
      spacing: { ranges: { medium: { max: 12 } } }
    }
  },
  generous: {
    name: 'Generous Spacing',
    config: {
      spacing: { ranges: { medium: { max: 32 } } }
    }
  }
});
 
// Testa varianti
const compactResult = abTesting.runVariant('spacing-test', 'compact', () => {
  // Logica di test
  return 'Compact layout generated';
});
 
const generousResult = abTesting.runVariant('spacing-test', 'generous', () => {
  // Logica di test
  return 'Generous layout generated';
});
 
console.log('A/B Test Results:', abTesting.getTestResults('spacing-test'));

3. Override Condizionali

Implementazione di Override Condizionali

/**
 * Applica override basati su condizioni
 */
applyConditionalOverrides(conditions) {
  const conditionalConfigs = this.get('conditionalOverrides', {});
  
  Object.entries(conditions).forEach(([conditionKey, conditionValue]) => {
    const overrideConfig = conditionalConfigs[conditionKey];
    
    if (overrideConfig && overrideConfig[conditionValue]) {
      console.log(`🎯 Applying conditional override: ${conditionKey}=${conditionValue}`);
      this.config = this.deepMerge(this.config, overrideConfig[conditionValue]);
    }
  });
}
 
/**
 * Registra override condizionale
 */
registerConditionalOverride(conditionKey, conditionValue, config) {
  if (!this.config.conditionalOverrides) {
    this.config.conditionalOverrides = {};
  }
  
  if (!this.config.conditionalOverrides[conditionKey]) {
    this.config.conditionalOverrides[conditionKey] = {};
  }
  
  this.config.conditionalOverrides[conditionKey][conditionValue] = config;
}
 
/**
 * Override basato su feature flags
 */
applyFeatureFlags(flags) {
  const enabledFeatures = Object.entries(flags)
    .filter(([key, enabled]) => enabled)
    .map(([key]) => key);
    
  const featureConfigs = this.get('featureConfigs', {});
  
  enabledFeatures.forEach(feature => {
    if (featureConfigs[feature]) {
      console.log(`🚀 Enabling feature: ${feature}`);
      this.config = this.deepMerge(this.config, featureConfigs[feature]);
    }
  });
}

Esempi di Override Condizionali

A) Override Basato su Dispositivo dell’Utente

// Registra configurazioni per diversi dispositivi
configManager.registerConditionalOverride('userDevice', 'mobile', {
  devices: configManager.get('devices').filter(d => ['mobile', 'sm'].includes(d.id)),
  spacing: { ranges: { medium: { max: 16 } } }, // Spacing ridotto su mobile
  pageBuilder: {
    panels: {
      styleManager: { appendTo: '#mobile-styles-manager' } // Pannello diverso per mobile
    }
  }
});
 
configManager.registerConditionalOverride('userDevice', 'desktop', {
  devices: configManager.get('devices'), // Tutti i dispositivi
  spacing: { ranges: { medium: { max: 24 } } }
});
 
// Applica configurazione basata su rilevamento dispositivo
function detectAndApplyDeviceConfig() {
  const isMobile = window.innerWidth <= 768;
  const userDevice = isMobile ? 'mobile' : 'desktop';
  
  configManager.applyConditionalOverrides({
    userDevice: userDevice
  });
  
  console.log(`📱 Applied ${userDevice} configuration`);
}
 
// Applica al caricamento e al resize
detectAndApplyDeviceConfig();
window.addEventListener('resize', detectAndApplyDeviceConfig);

B) Override Basato su Ruolo Utente

// Configurazioni per diversi ruoli
configManager.registerConditionalOverride('userRole', 'admin', {
  debug: { enabled: true, showComponentIds: true },
  pageBuilder: {
    canvas: {
      scripts: [...configManager.get('pageBuilder.canvas.scripts'), '/js/admin-tools.js']
    }
  }
});
 
configManager.registerConditionalOverride('userRole', 'editor', {
  devices: configManager.get('devices').filter(d => d.id !== '2xl'), // Rimuovi 2xl per editor
  debug: { enabled: false }
});
 
configManager.registerConditionalOverride('userRole', 'viewer', {
  devices: configManager.get('devices').filter(d => d.id === 'desktop'), // Solo desktop
  debug: { enabled: false },
  // Disabilita alcune funzionalità
  components: { editable: false }
});
 
// Applica configurazione ruolo (da dati utente del backend)
function applyUserRoleConfig(userData) {
  configManager.applyConditionalOverrides({
    userRole: userData.role
  });
}
 
// Esempio di uso
applyUserRoleConfig({ role: 'admin' });

C) Feature Flags per Funzionalità Sperimentali

// Registra configurazioni per feature sperimentali
configManager.set('featureConfigs', {
  advancedAnimations: {
    components: {
      animation: {
        enabled: true,
        presets: ['fadeIn', 'slideUp', 'bounce']
      }
    }
  },
  
  experimentalBlocks: {
    components: {
      blocks: {
        experimental: true,
        types: ['ai-text', 'dynamic-chart', 'video-background']
      }
    }
  },
  
  betaStyleManager: {
    styleManager: {
      version: 'beta',
      features: ['colorPicker', 'gradientEditor', 'fontManager']
    }
  }
});
 
// Applica feature flags (da configurazione dinamica o API)
async function loadAndApplyFeatureFlags() {
  try {
    // Simula caricamento feature flags da API
    const response = await fetch('/api/feature-flags');
    const flags = await response.json();
    
    configManager.applyFeatureFlags(flags);
    console.log('🎯 Feature flags applied:', flags);
  } catch (error) {
    console.warn('Failed to load feature flags, using defaults');
  }
}
 
// Esempio feature flags
const exampleFlags = {
  advancedAnimations: true,
  experimentalBlocks: false,
  betaStyleManager: true
};
 
configManager.applyFeatureFlags(exampleFlags);

D) Override Basato su Progetto/Cliente

/**
 * Sistema di configurazione per multi-tenant/multi-progetto
 */
class ProjectConfigManager {
  constructor(configManager) {
    this.configManager = configManager;
    this.projectConfigs = new Map();
  }
  
  // Registra configurazione per progetto
  registerProject(projectId, config) {
    this.projectConfigs.set(projectId, config);
  }
  
  // Applica configurazione progetto
  applyProjectConfig(projectId) {
    const projectConfig = this.projectConfigs.get(projectId);
    if (!projectConfig) {
      throw new Error(`Project configuration not found: ${projectId}`);
    }
    
    this.configManager.config = this.configManager.deepMerge(
      this.configManager.config,
      projectConfig
    );
    
    console.log(`🏢 Applied project configuration: ${projectId}`);
  }
  
  // Override temporaneo per progetto
  withProjectConfig(projectId, callback) {
    const projectConfig = this.projectConfigs.get(projectId);
    if (!projectConfig) {
      throw new Error(`Project configuration not found: ${projectId}`);
    }
    
    return this.configManager.temporaryOverride(projectConfig, callback);
  }
}
 
// Esempio di uso
const projectManager = new ProjectConfigManager(configManager);
 
// Registra configurazioni per diversi progetti
projectManager.registerProject('client-a', {
  colors: {
    presets: {
      primary: ['#FF6B6B', '#FF5252', '#FF4444'], // Colori brand cliente A
      secondary: ['#4ECDC4', '#26A69A', '#00897B']
    }
  },
  spacing: { ranges: { medium: { max: 20 } } }
});
 
projectManager.registerProject('client-b', {
  colors: {
    presets: {
      primary: ['#6C5CE7', '#5B4AF7', '#4834D4'], // Colori brand cliente B
      secondary: ['#FD79A8', '#F368E0', '#E84393']
    }
  },
  devices: configManager.get('devices').filter(d => ['desktop', 'mobile'].includes(d.id)) // Solo desktop e mobile
});
 
// Applica configurazione cliente
const currentProject = 'client-a'; // Da routing o parametri
projectManager.applyProjectConfig(currentProject);
 
// O usa temporaneamente per preview
const previewResult = projectManager.withProjectConfig('client-b', () => {
  return {
    colors: configManager.getColorOptions('primary'),
    devices: configManager.get('devices')
  };
});

4. Monitoring e Debug degli Override

Sistema di Tracking Override

/**
 * Aggiunge tracking degli override per debug
 */
enableOverrideTracking() {
  this.overrideHistory = [];
  this.originalMethods = {};
  
  // Traccia set()
  this.originalMethods.set = this.set.bind(this);
  this.set = (path, value) => {
    this.overrideHistory.push({
      type: 'set',
      path,
      value,
      timestamp: new Date(),
      stack: new Error().stack
    });
    return this.originalMethods.set(path, value);
  };
  
  // Traccia merge()
  this.originalMethods.merge = this.merge.bind(this);
  this.merge = (path, value) => {
    this.overrideHistory.push({
      type: 'merge',
      path,
      value,
      timestamp: new Date(),
      stack: new Error().stack
    });
    return this.originalMethods.merge(path, value);
  };
}
 
/**
 * Ottiene cronologia degli override
 */
getOverrideHistory() {
  return this.overrideHistory || [];
}
 
/**
 * Debug override attivi
 */
debugActiveOverrides() {
  console.group('🔍 Active Configuration Overrides');
  
  const history = this.getOverrideHistory();
  history.forEach((entry, index) => {
    console.log(`${index + 1}. ${entry.type.toUpperCase()}: ${entry.path}`, entry.value);
  });
  
  console.groupEnd();
}

Questo sistema di override avanzato ti permette di:

  1. Gestire configurazioni per ambiente senza duplicare codice
  2. Testare configurazioni temporaneamente senza perdere quelle originali
  3. Applicare configurazioni condizionali basate su contesto (utente, dispositivo, progetto)
  4. Implementare feature flags per funzionalità sperimentali
  5. Supportare multi-tenancy con configurazioni per cliente
  6. Tracciare e debuggare tutti i cambiamenti di configurazione

Vuoi che implementiamo qualche esempio specifico o passiamo alla fase successiva?