UNPKG

@teachinglab/omd

Version:

omd

267 lines (241 loc) 8.66 kB
/** * OMD Configuration Manager * Dynamically loads and manages configuration from JSON file */ // Configuration data - will be loaded from JSON or provided by user let configData = null; let configLoadPromise = null; // Default configuration (fallback when no config file is available) const defaultConfig = { multiplication: { symbol: "·", forceImplicit: false, implicitCombinations: { constantVariable: true, variableConstant: false, parenthesisAfterVariable: true, parenthesisAfterConstant: true, variableParenthesis: true, parenthesisParenthesis: true, parenthesisVariable: true, parenthesisConstant: true, variableVariable: true } }, stepVisualizer: { dotSizes: { level0: 8, level1: 6, level2: 4 }, fontWeights: { level0: 400, level1: 400, level2: 400 } } }; /** * Loads configuration from JSON file or uses default * @param {string|Object} configSource - Optional path to config file or config object * @returns {Promise<Object>} Promise that resolves to the configuration object */ async function loadConfig(configSource = null) { if (configLoadPromise && !configSource) { return configLoadPromise; } // If a config object is provided directly, use it if (configSource && typeof configSource === 'object') { configData = { ...defaultConfig, ...configSource }; return configData; } configLoadPromise = (async () => { try { // Detect environment and use appropriate loading method if (typeof window !== 'undefined') { // Browser environment - use fetch const configPath = configSource || './omd/config/omdConfig.json'; const response = await fetch(configPath); if (!response.ok) { console.warn(`Config file not found at ${configPath}, using default configuration`); configData = defaultConfig; return configData; } const configText = await response.text(); const loadedConfig = JSON.parse(configText); configData = { ...defaultConfig, ...loadedConfig }; return configData; } else { // Node.js environment - use fs const fs = await import('fs'); const path = await import('path'); const { fileURLToPath } = await import('url'); const __dirname = path.dirname(fileURLToPath(import.meta.url)); const configPath = configSource || path.join(__dirname, 'omdConfig.json'); try { const fileContent = await fs.promises.readFile(configPath, 'utf-8'); const loadedConfig = JSON.parse(fileContent); configData = { ...defaultConfig, ...loadedConfig }; } catch (err) { console.warn(`Config file not found at ${configPath}, using default configuration`); configData = defaultConfig; } return configData; } } catch (error) { console.warn('Error loading config, using default configuration:', error); configData = defaultConfig; return configData; } })(); return configLoadPromise; } /** * Gets the configuration object, loading it if necessary * @returns {Promise<Object>} Promise that resolves to the configuration object */ async function getConfig() { if (!configData) { await loadConfig(); } return configData; } /** * Gets the configuration object synchronously (for backwards compatibility) * If config hasn't been loaded yet, returns default config * @returns {Object} The configuration object */ function getConfigSync() { if (!configData) { // Auto-initialize with defaults if not loaded configData = defaultConfig; } return configData; } /** * Initialize configuration loading (call this early in app lifecycle) * @param {string|Object} configSource - Optional path to config file or config object * @returns {Promise<void>} */ export async function initializeConfig(configSource = null) { await loadConfig(configSource); } /** * Set configuration directly without loading from file * @param {Object} config - Configuration object */ export function setConfig(config) { configData = { ...defaultConfig, ...config }; } /** * Get the default configuration * @returns {Object} The default configuration object */ export function getDefaultConfig() { return { ...defaultConfig }; } /** * Utility function to check if a specific feature is enabled * @param {string} category - The configuration category (e.g., 'multiplication', 'simplification') * @param {string} setting - The specific setting to check * @returns {boolean} Whether the setting is enabled */ export function isEnabled(category, setting) { const config = getConfigSync(); return config[category]?.[setting] ?? false; } /** * Check if implicit multiplication should be used for a specific combination * @param {string} combination - The type of combination (e.g., 'constantVariable') * @returns {boolean} Whether implicit multiplication should be used */ export function useImplicitMultiplication(combination = null) { const config = getConfigSync(); if (combination) { return config.multiplication.implicitCombinations[combination] ?? false; } return config.multiplication.forceImplicit; } /** * Get the configured multiplication symbol * @returns {string} The multiplication symbol to use for display */ export function getMultiplicationSymbol() { const config = getConfigSync(); return config.multiplication.symbol; } /** * Update configuration settings at runtime * @param {string} category - The configuration category * @param {string} setting - The setting to update * @param {any} value - The new value */ export function updateConfig(category, setting, value) { const config = getConfigSync(); if (config[category] && config[category].hasOwnProperty(setting)) { config[category][setting] = value; } } /** * Get a configuration value by path * @param {string} path - Dot-separated path to the config value (e.g., 'multiplication.symbol') * @returns {any} The configuration value */ export function getConfigValue(path) { const config = getConfigSync(); return path.split('.').reduce((obj, key) => obj?.[key], config); } /** * Set a configuration value by path * @param {string} path - Dot-separated path to the config value * @param {any} value - The new value */ export function setConfigValue(path, value) { const config = getConfigSync(); const keys = path.split('.'); const lastKey = keys.pop(); const target = keys.reduce((obj, key) => obj[key] = obj[key] || {}, config); target[lastKey] = value; } /** * Reset configuration to defaults */ export function resetConfig() { throw new Error('resetConfig not available - edit omdConfig.json file directly'); } /** * Reload configuration from JSON file * @returns {Promise<Object>} Promise that resolves to the reloaded configuration */ export async function reloadConfig() { configData = null; configLoadPromise = null; return await loadConfig(); } /** * Get the raw configuration object (async) * @returns {Promise<Object>} Promise that resolves to the configuration object */ export async function getConfigAsync() { return await getConfig(); } /** * Get dot radius for a given step level (0,1,2) * @param {number} level * @returns {number} */ export function getDotRadius(level=0){ const cfg=getConfigSync(); const key=`level${level}`; return cfg.stepVisualizer?.dotSizes?.[key]??6; } /** * Get font weight for a given step level (0,1,2) * @param {number} level * @returns {number} */ export function getFontWeight(level=0){ const cfg=getConfigSync(); const key=`level${level}`; return cfg.stepVisualizer?.fontWeights?.[key]??400; }