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:
- Gestire configurazioni per ambiente senza duplicare codice
- Testare configurazioni temporaneamente senza perdere quelle originali
- Applicare configurazioni condizionali basate su contesto (utente, dispositivo, progetto)
- Implementare feature flags per funzionalità sperimentali
- Supportare multi-tenancy con configurazioni per cliente
- Tracciare e debuggare tutti i cambiamenti di configurazione
Vuoi che implementiamo qualche esempio specifico o passiamo alla fase successiva?