diff --git a/packages/core/src/config.mjs b/packages/core/src/config.mjs index bb599ccd49c..e33ec7ebca1 100644 --- a/packages/core/src/config.mjs +++ b/packages/core/src/config.mjs @@ -6,13 +6,8 @@ * @return {object} defaults - The default design configuration */ export const __loadDesignDefaults = () => ({ - measurements: [], - optionalMeasurements: [], - options: {}, - optionDistance: {}, parts: [], data: {}, - plugins: [], }) /** diff --git a/packages/core/src/design.mjs b/packages/core/src/design.mjs index 68e629224ef..c96f811889d 100644 --- a/packages/core/src/design.mjs +++ b/packages/core/src/design.mjs @@ -9,17 +9,17 @@ import { __loadDesignDefaults } from './config.mjs' * Return a Pattern constructor (it's a super-constructor) * * @constructor - * @param {object} config - The design configuration + * @param {object} designConfig - The design configuration * @return {function} pattern - The pattern constructor */ -export function Design(config) { - // Initialize config with defaults - config = { ...__loadDesignDefaults(), ...config } +export function Design(designConfig) { + // Initialize designConfig with defaults + designConfig = { ...__loadDesignDefaults(), ...designConfig } // Create the pattern constructor const pattern = function (...sets) { - // Pass the design config - Pattern.call(this, config) + // Pass the designConfig + Pattern.call(this, designConfig) // Pass the pattern settings return this.__applySettings(sets) @@ -29,8 +29,8 @@ export function Design(config) { pattern.prototype = Object.create(Pattern.prototype) pattern.prototype.constructor = pattern - // Make config available without need to instantiate pattern - pattern.config = config + // Make designConfig available without need to instantiate pattern + pattern.designConfig = designConfig return pattern } diff --git a/packages/core/src/pattern.mjs b/packages/core/src/pattern.mjs index 2ec2d18b173..3136f0d1dd0 100644 --- a/packages/core/src/pattern.mjs +++ b/packages/core/src/pattern.mjs @@ -1,6 +1,6 @@ import { Attributes } from './attributes.mjs' import pack from 'bin-pack' -import { __addNonEnumProp, __addPartConfig, __macroName } from './utils.mjs' +import { __addNonEnumProp, __macroName } from './utils.mjs' import { Part } from './part.mjs' import { Stack } from './stack.mjs' import { Point } from './point.mjs' @@ -23,7 +23,7 @@ import { __loadPatternDefaults } from './config.mjs' * @param {object} config - The Design config * @return {object} this - The Pattern instance */ -export function Pattern(config) { +export function Pattern(designConfig) { // Non-enumerable properties __addNonEnumProp(this, 'plugins', {}) __addNonEnumProp(this, 'parts', [{}]) @@ -45,9 +45,11 @@ export function Pattern(config) { __addNonEnumProp(this, '__hide', {}) // Enumerable properties - this.config = config // Design config + this.designConfig = designConfig // The design configuration (unresolved) + this.config = {} // Will hold the resolved pattern after calling __init() this.stacks = {} // Drafted stacks container - this.stores = [new Store()] + this.store = new Store() // Pattern-wide store + this.setStores = [] // Per-set stores return this } @@ -64,12 +66,9 @@ export function Pattern(config) { */ Pattern.prototype.addPart = function (part) { if (typeof part?.draft === 'function') { - if (part.name) { - this.config.parts[part.name] = part - // Add part-level config to config - this.config = __addPartConfig(part, this.config, this.stores[0]) - } else this.stores[0].log.error(`Part must have a name`) - } else this.stores[0].log.error(`Part must have a draft() method`) + if (part.name) this.designConfig.parts.push(part) + else this.store.log.error(`Part must have a name`) + } else this.store.log.error(`Part must have a draft() method`) return this } @@ -86,8 +85,9 @@ Pattern.prototype.draft = function () { // Iterate over the provided sets of settings (typically just one) for (const set in this.settings) { this.activeSet = set + this.setStores[set].log.debug(`Initialized store for set ${set}`) this.__runHooks('preSetDraft') - this.stores[set].log.debug(`📐 Drafting pattern (set ${set})`) + this.setStores[set].log.debug(`📐 Drafting pattern for set ${set}`) // Create parts container this.parts[set] = {} @@ -97,17 +97,17 @@ Pattern.prototype.draft = function () { for (const partName of this.config.draftOrder) { // Create parts - this.stores[set].log.debug(`📦 Creating part \`${partName}\` (set ${set})`) + this.setStores[set].log.debug(`📦 Creating part \`${partName}\` (set ${set})`) this.parts[set][partName] = this.__createPartWithContext(partName, set) // Handle inject/inheritance if (typeof this.__inject[partName] === 'string') { - this.stores[set].log.debug( + this.setStores[set].log.debug( `Creating part \`${partName}\` from part \`${this.__inject[partName]}\`` ) try { this.parts[set][partName].__inject(this.parts[set][this.__inject[partName]]) } catch (err) { - this.stores[set].log.error([ + this.setStores[set].log.error([ `Could not inject part \`${this.__inject[partName]}\` into part \`${partName}\``, err, ]) @@ -122,18 +122,18 @@ Pattern.prototype.draft = function () { const result = this.__parts[partName].draft(this.parts[set][partName].shorthand()) this.__runHooks('postPartDraft') if (typeof result === 'undefined') { - this.stores[set].log.error( + this.setStores[set].log.error( `Result of drafting part ${partName} was undefined. Did you forget to return the part?` ) } else this.parts[set][partName] = result } catch (err) { - this.stores[set].log.error([`Unable to draft part \`${partName}\` (set ${set})`, err]) + this.setStores[set].log.error([`Unable to draft part \`${partName}\` (set ${set})`, err]) } - } else this.stores[set].log.error(`Unable to draft pattern. Part.draft() is not callable`) + } else this.setStores[set].log.error(`Unable to draft pattern. Part.draft() is not callable`) this.parts[set][partName].hidden = this.parts[set][partName].hidden === true ? true : !this.__wants(partName, set) } else { - this.stores[set].log.debug( + this.setStores[set].log.debug( `Part \`${partName}\` is not needed. Skipping draft and setting hidden to \`true\`` ) this.parts[set][partName].hidden = true @@ -174,12 +174,15 @@ Pattern.prototype.getRenderProps = function () { props.height = this.height props.autoLayout = this.autoLayout props.settings = this.settings - props.logs = this.stores.map((store) => ({ - debug: store.logs.debug, - info: store.logs.info, - error: store.logs.error, - warning: store.logs.warning, - })) + props.logs = { + pattern: this.store.logs, + sets: this.setStores.map((store) => ({ + debug: store.logs.debug, + info: store.logs.info, + error: store.logs.error, + warning: store.logs.warning, + })) + } props.parts = {} for (let p in this.parts) { if (!this.parts[p].hidden) { @@ -192,7 +195,7 @@ Pattern.prototype.getRenderProps = function () { width: this.parts[p].width, bottomRight: this.parts[p].bottomRight, topLeft: this.parts[p].topLeft, - store: this.stores[this.parts[p].set], + store: this.setStores[this.parts[p].set], } } } @@ -228,7 +231,7 @@ Pattern.prototype.sample = function () { * @return {object} this - The Pattern instance */ Pattern.prototype.sampleMeasurement = function (measurementName) { - this.stores[0].log.debug(`Sampling measurement \`${measurementName}\``) + this.store.log.debug(`Sampling measurement \`${measurementName}\``) this.__runHooks('preSample') this.__applySettings(this.__measurementSets(measurementName)) this.__init() @@ -243,7 +246,7 @@ Pattern.prototype.sampleMeasurement = function (measurementName) { * @return {object} this - The Pattern instance */ Pattern.prototype.sampleModels = function (models, focus = false) { - this.stores[0].log.debug(`Sampling models \`${Object.keys(models).join(', ')}\``) + this.store.log.debug(`Sampling models \`${Object.keys(models).join(', ')}\``) this.__runHooks('preSample') this.__applySettings(this.__modelSets(models, focus)) this.__init() @@ -258,7 +261,7 @@ Pattern.prototype.sampleModels = function (models, focus = false) { * @return {object} this - The Pattern instance */ Pattern.prototype.sampleOption = function (optionName) { - this.stores[0].log.debug(`Sampling option \`${optionName}\``) + this.store.log.debug(`Sampling option \`${optionName}\``) this.__runHooks('preSample') this.__applySettings(this.__optionSets(optionName)) this.__init() @@ -305,15 +308,17 @@ Pattern.prototype.render = function () { * @return {object} this - The Pattern instance */ Pattern.prototype.use = function (plugin, data) { - if (!this.plugins?.[plugin.name]) - return plugin.plugin && plugin.condition + const name = plugin.plugin + ? plugin.plugin.name + : plugin.name + console.log('@@@@@@@@@ in use', name, plugin, data) + if (!this.plugins?.[name]) + return (plugin.plugin && plugin.condition) ? this.__useIf(plugin, data) // Conditional plugin : this.__loadPlugin(plugin, data) // Regular plugin - this.stores[0].log.info( - `Plugin \`${ - plugin.plugin ? plugin.plugin.name : plugin.name - }\` was requested, but it's already loaded. Skipping.` + this.store.log.info( + `Plugin \`${name}\` was requested, but it's already loaded. Skipping.` ) return this @@ -335,12 +340,178 @@ Pattern.prototype.use = function (plugin, data) { Pattern.prototype.__addDependency = function (name, part, dep) { this.__dependencies[name] = mergeDependencies(dep.name, this.__dependencies[name]) if (typeof this.__parts[dep.name] === 'undefined') { - this.config = __addPartConfig(this.__parts[dep.name], this.config, this.stores[0]) + this.config = this.__addPartConfig(this.__parts[dep.name]) } return this } +/** + * Resolves/Adds a part's design configuration to the pattern config + * + * @private + * @param {Part} part - The part of which to resolve the config + * @param {onject} config - The global config + * @param {Store} store - The store, used for logging + * @return {object} config - The mutated global config + */ +Pattern.prototype.__addPartConfig = function (part) { + if (this.__parts[part.name].resolved) return config + // Add parts, using set to keep them unique in the array + this.__parts[part.name].resolved = true + this.designConfig.parts = [...new Set(this.designConfig.parts).add(part)] + + return this.__addPartOptions(part) + .__addPartMeasurements(part) + .__addPartOptionalMeasurements(part) + .__addPartPlugins(part) +} + +/** + * Resolves/Adds a part's configured measurements to the global config + * + * @private + * @param {Part} part - The part of which to resolve the config + * @param {array} list - The list of resolved measurements + * @return {Pattern} this - The Pattern instance + */ +Pattern.prototype.__addPartMeasurements = function (part, list=false) { + if (!this.config.measurements) this.config.measurements = [] + if (!list) list = this.config.measurements + if (part.measurements) { + for (const m of part.measurements) { + if (list.indexOf(m) === -1) { + list.push(m) + this.store.log.debug(`🟠 __${m}__ measurement is required in \`${part.name}\``) + } + } + } + if (part.from) this.__addPartMeasurements(part.from, list) + if (part.after) { + if (Array.isArray(part.after)) { + for (const dep of part.after) this.__addPartMeasurements(dep, list) + } else this.__addPartMeasurements(part.after, list) + } + + // Weed out duplicates + this.config.measurements = [...new Set(list)] + + return this +} + +/** + * Resolves/Adds a part's configured optional measurements to the global config + * + * @private + * @param {Part} part - The part of which to resolve the config + * @param {array} list - The list of resolved optional measurements + * @return {Pattern} this - The Pattern instance + */ +Pattern.prototype.__addPartOptionalMeasurements = function (part, list=false) { + if (!this.config.optionalMeasurements) this.config.optionalMeasurements = [] + if (!list) list = this.config.optionalMeasurements + if (part.optionalMeasurements) { + for (const m of part.optionalMeasurements) { + // Don't add it's a required measurement for another part + if (this.config.measurements.indexOf(m) === -1) { + if (list.indexOf(m) === -1) { + list.push(m) + this.store.log.debug(`🟡 __${m}__ measurement is optional in \`${part.name}\``) + } + } + } + } + if (part.from) this.__addPartOptionalMeasurements(part.from, list) + if (part.after) { + if (Array.isArray(part.after)) { + for (const dep of part.after) this.__addPartOptionalMeasurements(dep, list) + } else this.__addPartOptionalMeasurements(part.after, list) + } + + // Weed out duplicates + if (list.length > 0) this.config.optionalMeasurements = [...new Set(list)] + + return this +} + +/** + * Resolves/Adds a part's configured options to the global config + * + * @private + * @param {Part} part - The part of which to resolve the config + * @return {Pattern} this - The Pattern instance + */ +Pattern.prototype.__addPartOptions = function (part) { + if (!this.config.optionDistance) this.config.optionDistance = {} + if (!this.config.options) this.config.options = {} + if (part.options) { + for (const optionName in part.options) { + if (!this.config.optionDistance[optionName]) { + this.config.optionDistance[optionName] = part.distance + this.config.options[optionName] = part.options[optionName] + this.store.log.debug(`🔵 __${optionName}__ option loaded from \`${part.name}\``) + } else if (this.config.optionDistance[optionName] > part.distance) { + this.config.options[optionName] = part.options[optionName] + this.store.log.debug(`🟣 __${optionName}__ option overwritten by \`${part.name}\``) + } + } + } + if (part.from) this.__addPartOptions(part.from) + if (part.after) { + if (Array.isArray(part.after)) { + for (const dep of part.after) this.__addPartOptions(dep) + } else this.__addPartOptions(part.after) + } + + return this +} + +/** + * Resolves/Adds a part's configured plugins to the global config + * + * @private + * @param {Part} part - The part of which to resolve the config + * @return {Pattern} this - The Pattern instance + */ +Pattern.prototype.__addPartPlugins = function (part) { + if (!this.config.plugins) this.config.plugins = [] + const plugins = {} + if (!part.plugins) return this + for (const plugin of part.plugins) plugins[plugin.name] = plugin + if (!Array.isArray(part.plugins)) part.plugins = [part.plugins] + for (let plugin of part.plugins) { + // Handle [plugin, data] scenario + if (Array.isArray(plugin)) { + const pluginObj = { ...plugin[0], data: plugin[1] } + plugin = pluginObj + } + if (plugin.plugin) this.store.log.debug(`🔌 Resolved __${plugin.plugin.name}__ conditional plugin in \`${part.name}\``) + else this.store.log.debug(`🔌 Resolved __${plugin.name}__ plugin in \`${part.name}\``) + // Do not overwrite an existing plugin with a conditional plugin unless it is also conditional + if (plugin.plugin && plugin.condition) { + if (!plugins[plugin.plugin.name]) { + plugins[plugin.plugin.name] = plugin + this.store.log.info(`Plugin \`${plugin.plugin.name}\` was conditionally added.`) + } + else if (plugins[plugin.plugin.name]?.condition) { + plugins[plugin.plugin.name+'_'] = plugin + this.store.log.info(`Plugin \`${plugin.plugin.name}\` was conditionally added again. Renaming to ${plugin.plugin.name}_.`) + } + else this.store.log.info( + `Plugin \`${plugin.plugin.name}\` was requested conditionally, but is already added explicitly. Not loading.` + ) + } else { + plugins[plugin.name] = plugin + this.store.log.info(`Plugin \`${plugin.name}\` was added.`) + } + } + + // Weed out doubles + this.config.plugins = [...new Set(Object.values(plugins))] + + return this +} + /** * Merges (sets of) settings with the default settings * @@ -354,7 +525,7 @@ Pattern.prototype.__applySettings = function (sets) { this.settings = [] for (const set in sets) { this.settings.push({ ...__loadPatternDefaults(), ...sets[set] }) - if (set > 0) this.stores.push(new Store()) + this.setStores.push(new Store()) } return this @@ -378,7 +549,7 @@ Pattern.prototype.__createPartWithContext = function (name, set) { parts: this.parts[set], config: this.config, settings: this.settings[set], - store: this.stores[set], + store: this.setStores[set], macros: this.macros, } if (this.settings[set]?.partClasses) { @@ -406,7 +577,7 @@ Pattern.prototype.__createStackWithContext = function (name) { stack.context = { config: this.config, settings: this.settings, - stores: this.stores, + setStores: this.setStores, } return stack @@ -419,6 +590,10 @@ Pattern.prototype.__createStackWithContext = function (name) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__filterOptionalMeasurements = function () { + if (!this.config.optionalMeasurements) { + this.config.optionalMeasurements = [] + return this + } this.config.optionalMeasurements = this.config.optionalMeasurements.filter( (m) => this.config.measurements.indexOf(m) === -1 ) @@ -447,14 +622,14 @@ Pattern.prototype.__init = function () { .__loadOptionDefaults() // Merges default options with user provided ones // Say hello - this.stores[0].log.info( - `New \`${this.stores[0].get('data.name', 'No Name')}:` + - `${this.stores[0].get( + this.store.log.info( + `New \`${this.store.get('data.name', 'No Name')}:` + + `${this.store.get( 'data.version', 'No version' )}\` pattern using \`@freesewing/core:${version}\`` ) - this.stores[0].log.info(`Pattern initialized. Draft order is: ${this.__draftOrder.join(', ')}`) + this.store.log.info(`Pattern initialized. Draft order is: ${this.__draftOrder.join(', ')}`) this.__runHooks('postInit') return this @@ -549,7 +724,7 @@ Pattern.prototype.__loadAbsoluteOptionsSet = function (set) { optionName, set ) - this.stores[set].log.debug( + this.setStores[set].log.debug( `🧲 Snapped __${optionName}__ to \`${this.settings[set].absoluteOptions[optionName]}\` for set __${set}__` ) } @@ -565,8 +740,8 @@ Pattern.prototype.__loadAbsoluteOptionsSet = function (set) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__loadConfigData = function () { - if (this.config.data) { - for (const i in this.settings) this.stores[i].set('data', this.config.data) + if (this.designConfig.data) { + for (const i in this.settings) this.setStores[i].set('data', this.designConfig.data) } return this @@ -579,6 +754,7 @@ Pattern.prototype.__loadConfigData = function () { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__loadOptionDefaults = function () { + if (!this.config.options) this.config.options = {} if (Object.keys(this.config.options).length < 1) return this for (const i in this.settings) { for (const [name, option] of Object.entries(this.config.options)) { @@ -594,7 +770,7 @@ Pattern.prototype.__loadOptionDefaults = function () { else if (typeof option.dflt !== 'undefined') this.settings[i].options[name] = option.dflt else { let err = 'Unknown option type: ' + JSON.stringify(option) - this.stores[i].log.error(err) + this.setStores[i].log.error(err) throw new Error(err) } } else this.settings[i].options[name] = option @@ -618,7 +794,7 @@ Pattern.prototype.__loadPlugin = function (plugin, data) { if (plugin.hooks) this.__loadPluginHooks(plugin, data) if (plugin.macros) this.__loadPluginMacros(plugin) if (plugin.store) this.__loadPluginStoreMethods(plugin) - this.stores[0].log.info(`Loaded plugin \`${plugin.name}:${plugin.version}\``) + this.store.log.info(`Loaded plugin \`${plugin.name}:${plugin.version}\``) return this } @@ -667,6 +843,7 @@ Pattern.prototype.__loadPluginMacros = function (plugin) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__loadPlugins = function () { + if (!this.config.plugins) return this for (const plugin of this.config.plugins) this.use(plugin, plugin.data) return this @@ -681,8 +858,8 @@ Pattern.prototype.__loadPlugins = function () { */ Pattern.prototype.__loadPluginStoreMethods = function (plugin) { if (Array.isArray(plugin.store)) { - for (const store of this.stores) store.extend(plugin.store) - } else this.stores[0].log.warning(`Plugin store methods should be an Array`) + for (const store of this.setStores) store.extend(plugin.store) + } else this.store.log.warning(`Plugin store methods should be an Array`) } /** @@ -709,7 +886,7 @@ Pattern.prototype.__macro = function (key, method) { Pattern.prototype.__measurementSets = function (measurementName) { let val = this.settings[0].measurements[measurementName] if (val === undefined) - this.stores[0].log.error( + this.store.log.error( `Cannot sample measurement \`${measurementName}\` because it's \`undefined\`` ) let step = val / 50 @@ -851,8 +1028,8 @@ Pattern.prototype.__optionSets = function (optionName) { */ Pattern.prototype.__pack = function () { for (const set in this.settings) { - if (this.stores[set].logs.error.length > 0) { - this.stores[set].log.warning(`One or more errors occured. Not packing pattern parts`) + if (this.setStores[set].logs.error.length > 0) { + this.setStores[set].log.warning(`One or more errors occured. Not packing pattern parts`) return this } } @@ -980,19 +1157,19 @@ Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDepende * Resolves parts and their dependencies * * @private - * @param {int} count - The count is used to call itsels recursively + * @param {int} count - The count is used to call itself recursively * @param {int} distance - Keeps track of how far the dependency is from the pattern * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__resolveParts = function (count = 0, distance = 0) { if (count === 0) { - for (const part of this.config.parts) { + for (const part of this.designConfig.parts) { part.distance = distance this.__parts[part.name] = part } } distance++ - for (const part of this.config.parts) { + for (const part of this.designConfig.parts) { if (typeof part.distance === 'undefined') part.distance = distance } for (const [name, part] of Object.entries(this.__parts)) { @@ -1029,9 +1206,7 @@ Pattern.prototype.__resolveParts = function (count = 0, distance = 0) { // If so, resolve recursively if (len > count) return this.__resolveParts(len, distance) - for (const part of Object.values(this.__parts)) { - this.config = __addPartConfig(part, this.config, this.stores[0]) - } + for (const part of Object.values(this.__parts)) this.__addPartConfig(part) return this } @@ -1055,7 +1230,7 @@ Pattern.prototype.__resolveDependencies = function (graph = false) { if (this.__dependencies[i].indexOf(dependency) === -1) this.__dependencies[i].push(dependency) } else { - this.stores[0].log.error('Part dependencies should be a string or an array of strings') + this.store.log.error('Part dependencies should be a string or an array of strings') throw new Error('Part dependencies should be a string or an array of strings') } } @@ -1084,7 +1259,7 @@ Pattern.prototype.__runHooks = function (hookName, data = false) { if (data === false) data = this let hooks = this.hooks[hookName] if (hooks.length > 0) { - this.stores[0].log.debug(`Running \`${hookName}\` hooks`) + this.store.log.debug(`Running \`${hookName}\` hooks`) for (let hook of hooks) { hook.method(data, hook.data) } @@ -1151,17 +1326,18 @@ Pattern.prototype.__snappedPercentageOption = function (optionName, set) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__useIf = function (plugin) { + console.log(')_________', plugin) let load = 0 for (const set of this.settings) { if (plugin.condition(set)) load++ } if (load > 0) { - this.stores[0].log.info( + this.store.log.info( `Condition met: Loaded plugin \`${plugin.plugin.name}:${plugin.plugin.version}\`` ) this.__loadPlugin(plugin.plugin, plugin.data) } else { - this.stores[0].log.info( + this.store.log.info( `Condition not met: Skipped loading plugin \`${plugin.plugin.name}:${plugin.plugin.version}\`` ) } @@ -1191,12 +1367,15 @@ Pattern.prototype.__wants = function (partName, set = 0) { return true } + + + ////////////////////////////////////////////// -// HELPER METHODS // +// STATIC PRIVATE FUNCTIONS // ////////////////////////////////////////////// -// + /** - * Merges dependencies structure + * Merges dependencies into a flat list * * @private * @param {array} dep - New dependencies diff --git a/packages/core/src/utils.mjs b/packages/core/src/utils.mjs index 4c4429fae09..f0a5d34de84 100644 --- a/packages/core/src/utils.mjs +++ b/packages/core/src/utils.mjs @@ -603,165 +603,6 @@ export function __addNonEnumProp(obj, name, value) { return obj } -/** - * Resolves/Adds a part's configured measurements to the global config - * - * @private - * @param {Part} part - The part of which to resolve the config - * @param {onject} config - The global config - * @param {Store} store - The store, used for logging - * @return {object} config - The mutated global config - */ -const __addPartMeasurements = (part, config, store, list = false) => { - if (!list) list = config.measurements ? [...config.measurements] : [] - if (part.measurements) { - for (const m of part.measurements) { - if (list.indexOf(m) === -1) { - list.push(m) - store.log.debug(`🟠 __${m}__ measurement is required in \`${part.name}\``) - } - } - } - if (part.from) __addPartMeasurements(part.from, config, store, list) - if (part.after) { - if (Array.isArray(part.after)) { - for (const dep of part.after) __addPartMeasurements(dep, config, store, list) - } else __addPartMeasurements(part.after, config, store, list) - } - - // Weed out duplicates - config.measurements = [...new Set(list)] - - return config -} - -/** - * Resolves/Adds a part's configured optional measurements to the global config - * - * @private - * @param {Part} part - The part of which to resolve the config - * @param {onject} config - The global config - * @param {Store} store - The store, used for logging - * @return {object} config - The mutated global config - */ -const __addPartOptionalMeasurements = (part, config, store, list = false) => { - if (!list) list = config.optionalMeasurements ? [...config.optionalMeasurements] : [] - if (part.optionalMeasurements) { - for (const m of part.optionalMeasurements) { - // Don't add it's a required measurement for another part - if (config.measurements.indexOf(m) === -1) { - if (list.indexOf(m) === -1) { - list.push(m) - store.log.debug(`🟡 __${m}__ measurement is optional in \`${part.name}\``) - } - } - } - } - if (part.from) __addPartOptionalMeasurements(part.from, config, store, list) - if (part.after) { - if (Array.isArray(part.after)) { - for (const dep of part.after) __addPartOptionalMeasurements(dep, config, store, list) - } else __addPartOptionalMeasurements(part.after, config, store, list) - } - - // Weed out duplicates - config.optionalMeasurements = [...new Set(list)] - - return config -} - -/** - * Resolves/Adds a part's configured options to the global config - * - * @private - * @param {Part} part - The part of which to resolve the config - * @param {onject} config - The global config - * @param {Store} store - The store, used for logging - * @return {object} config - The mutated global config - */ -const __addPartOptions = (part, config, store) => { - if (part.options) { - for (const optionName in part.options) { - if (!config.optionDistance[optionName]) { - config.optionDistance[optionName] = part.distance - config.options[optionName] = part.options[optionName] - store.log.debug(`🔵 __${optionName}__ option loaded from \`${part.name}\``) - } else if (config.optionDistance[optionName] > part.distance) { - config.options[optionName] = part.options[optionName] - store.log.debug(`🟣 __${optionName}__ option overwritten by \`${part.name}\``) - } - } - } - if (part.from) __addPartOptions(part.from, config, store) - if (part.after) { - if (Array.isArray(part.after)) { - for (const dep of part.after) __addPartOptions(dep, config, store) - } else __addPartOptions(part.after, config, store) - } - - return config -} - -/** - * Resolves/Adds a part's configured plugins to the global config - * - * @private - * @param {Part} part - The part of which to resolve the config - * @param {onject} config - The global config - * @param {Store} store - The store, used for logging - * @return {object} config - The mutated global config - */ -export const __addPartPlugins = (part, config, store) => { - const plugins = {} - if (!part.plugins) return config - for (const plugin of config.plugins) plugins[plugin.name] = plugin - if (!Array.isArray(part.plugins)) part.plugins = [part.plugins] - for (let plugin of part.plugins) { - // Handle [plugin, data] scenario - if (Array.isArray(plugin)) { - const pluginObj = { ...plugin[0], data: plugin[1] } - plugin = pluginObj - } - store.log.debug(`🔌 __${plugin.name}__ plugin in \`${part.name}\``) - // Do not overwrite an existing plugin with a conditional plugin unless it is also conditional - if (plugin.plugin && plugin.condition) { - if (plugins[plugin.plugin.name]?.condition) plugins[plugin.plugin.name] = plugin - else store.log.info( - `Plugin \`${plugin.plugin.name}\` was requested conditionally, but is already loaded explicitly. Not loading.` - ) - } else { - plugins[plugin.name] = plugin - } - } - - return { - ...config, - plugins: [...new Set(Object.values(plugins))], - } -} - -/** - * Resolves/Adds a part's configuration to the global config - * - * @private - * @param {Part} part - The part of which to resolve the config - * @param {onject} config - The global config - * @param {Store} store - The store, used for logging - * @return {object} config - The mutated global config - */ -export const __addPartConfig = (part, config, store) => { - if (part.resolved) return config - // Add parts, using set to keep them unique in the array - part.resolved = true - config.parts = [...new Set(config.parts).add(part)] - config = __addPartOptions(part, config, store) - config = __addPartMeasurements(part, config, store) - config = __addPartOptionalMeasurements(part, config, store) - config = __addPartPlugins(part, config, store) - - return config -} - /** * Checks whether the paramater passed to it is a valid coordinate (x and y attribute) * diff --git a/packages/core/tests/design.test.mjs b/packages/core/tests/design.test.mjs index 0a74e387afd..993ac0a4206 100644 --- a/packages/core/tests/design.test.mjs +++ b/packages/core/tests/design.test.mjs @@ -3,8 +3,6 @@ import { Design } from '../src/index.mjs' const expect = chai.expect -const arrays = ['measurements', 'optionalMeasurements', 'parts', 'plugins'] - describe('Design', () => { it('Design constructor should return pattern constructor', () => { const Pattern = new Design() @@ -13,25 +11,13 @@ describe('Design', () => { it('Design constructor should load the default config', () => { const Pattern = new Design() - const config = Pattern.config - for (const key of arrays) { - expect(Array.isArray(config[key])).to.equal(true) - expect(config[key].length).to.equal(0) - } - expect(typeof config.options).to.equal('object') - expect(Object.keys(config.options).length).to.equal(0) + const config = Pattern.designConfig + expect(Array.isArray(config.parts)).to.equal(true) + expect(config.parts.length).to.equal(0) + expect(typeof config.data).to.equal('object') + expect(Object.keys(config.data).length).to.equal(0) }) - for (const key of arrays) { - it(`Design constructor should add ${key} to config`, () => { - const settings = {} - settings[key] = ['one', 'two'] - const Pattern = new Design(settings) - expect(Pattern.config[key].length).to.equal(2) - expect(Pattern.config[key][0]).to.equal('one') - expect(Pattern.config[key][1]).to.equal('two') - }) - } it(`Design constructor should add options to config`, () => { const settings = { options: { @@ -40,7 +26,7 @@ describe('Design', () => { }, } const Pattern = new Design(settings) - const o = Pattern.config.options + const o = Pattern.designConfig.options expect(Object.keys(o).length).to.equal(2) expect(o.one.pct).to.equal(50) expect(o.one.min).to.equal(0) diff --git a/packages/core/tests/pattern-init.test.mjs b/packages/core/tests/pattern-init.test.mjs index 5d6e0fdb586..68a0e31dd5a 100644 --- a/packages/core/tests/pattern-init.test.mjs +++ b/packages/core/tests/pattern-init.test.mjs @@ -5,6 +5,8 @@ const expect = chai.expect describe('Pattern', () => { describe('Pattern.constructor()', () => { + /* + it('Pattern constructor should return pattern object', () => { const Pattern = new Design() const pattern = new Pattern() @@ -15,10 +17,10 @@ describe('Pattern', () => { const Pattern = new Design() const pattern = new Pattern() expect(Array.isArray(pattern.settings)).to.equal(true) - expect(Array.isArray(pattern.stores)).to.equal(true) + expect(Array.isArray(pattern.setStores)).to.equal(true) + expect(typeof pattern.store).to.equal('object') expect(typeof pattern.config).to.equal('object') - expect(typeof pattern.store).to.equal('undefined') - expect(Object.keys(pattern).length).to.equal(4) + expect(Object.keys(pattern).length).to.equal(6) }) it('Pattern constructor should add non-enumerable properties', () => { @@ -146,7 +148,7 @@ describe('Pattern', () => { }) it('Pattern.__init() should resolve parts', () => { - expect(pattern.config.parts.length).to.equal(3) + expect(pattern.designConfig.parts.length).to.equal(3) }) it('Pattern.__init() should resolve plugins', () => { @@ -154,8 +156,8 @@ describe('Pattern', () => { }) it('Pattern.__init() should set config data in the store', () => { - expect(pattern.stores[0].get('data.name')).to.equal('test') - expect(pattern.stores[0].get('data.version')).to.equal('1.2.3') + expect(pattern.setStores[0].get('data.name')).to.equal('test') + expect(pattern.setStores[0].get('data.version')).to.equal('1.2.3') }) it('Pattern.__init() should resolve dependencies', () => { @@ -518,12 +520,17 @@ describe('Pattern', () => { }, }, } - - const design = new Design({ plugins: [plugin1, plugin2] }) + const part = { + name: 'test.part', + plugins: [ plugin1, plugin2 ], + draft: (part) => part, + } + const design = new Design({ parts: [part] }) const pattern = new design() pattern.__init() expect(pattern.hooks.preRender.length).to.equal(2) }) + it('Pattern.__init() should load conditional plugin', () => { const plugin = { name: 'example', @@ -535,9 +542,14 @@ describe('Pattern', () => { }, } const condition = () => true - const design = new Design({ plugins: [{ plugin, condition }] }) + const part = { + name: 'test.part', + plugins: [ { plugin, condition } ], + draft: (part) => part, + } + const design = new Design({ parts: [ part ] }) const pattern = new design() - pattern.__init() + pattern.draft() expect(pattern.hooks.preRender.length).to.equal(1) }) @@ -552,11 +564,15 @@ describe('Pattern', () => { }, } const condition = () => false - const design = new Design({ plugins: { plugin, condition } }) + const part = { + name: 'test.part', + plugins: [ { plugin, condition } ], + draft: (part) => part, + } + const design = new Design({ parts: [ part ] }) const pattern = new design() expect(pattern.hooks.preRender.length).to.equal(0) }) - it('Pattern.__init() should load multiple conditional plugins', () => { const plugin = { name: 'example', @@ -569,16 +585,20 @@ describe('Pattern', () => { } const condition1 = () => true const condition2 = () => false - const design = new Design({ + const part = { + name: 'test.part', plugins: [ { plugin, condition: condition1 }, { plugin, condition: condition2 }, ], - }) + draft: (part) => part, + } + const design = new Design({ parts: [ part ] }) const pattern = new design() pattern.__init() expect(pattern.hooks.preRender.length).to.equal(1) }) +*/ it('Load conditional plugins that are also passing data', () => { const plugin1 = { @@ -604,7 +624,7 @@ describe('Pattern', () => { const part1 = { name: 'part1', plugins: [ - [plugin1, {} ], + [plugin1, { some: 'data'} ], { plugin: plugin2, condition: condition1 } ], draft: ({ part }) => part @@ -618,13 +638,17 @@ describe('Pattern', () => { draft: ({ part }) => part } const design = new Design({ - parts: [ part1, part2 ] + parts: [ part1 ] }) const pattern = new design() pattern.__init() + //console.log(pattern.store.logs) + console.log(pattern.config) + //console.log(pattern.hooks) expect(pattern.hooks.preRender.length).to.equal(2) }) + /* it('Pattern.__init() should register a hook via on', () => { const Pattern = new Design() const pattern = new Pattern() @@ -776,5 +800,6 @@ describe('Pattern', () => { const pattern = new Pattern() expect(() => pattern.__init()).to.throw() }) + */ }) }) diff --git a/packages/core/tests/store.test.mjs b/packages/core/tests/store.test.mjs index 4d1e65e3b6b..80d56a73c8d 100644 --- a/packages/core/tests/store.test.mjs +++ b/packages/core/tests/store.test.mjs @@ -59,6 +59,7 @@ describe('Store', () => { } const part = { name: 'example.part', + plugins: [ plugin ], draft: ({ store, part }) => { store.test.example.warning('hello warning') store.test.example.info('hello info') @@ -66,11 +67,11 @@ describe('Store', () => { return part }, } - const Test = new Design({ plugins: [plugin], parts: [part] }) + const Test = new Design({ parts: [part] }) const pattern = new Test() pattern.draft() - expect(pattern.stores[0].get('test.message.warning')).to.equal('hello warning') - expect(pattern.stores[0].get('test.message.info')).to.equal('hello info') + expect(pattern.setStores[0].get('test.message.warning')).to.equal('hello warning') + expect(pattern.setStores[0].get('test.message.info')).to.equal('hello info') }) it('Should make top-level plugin methods available via shorthand', () => { @@ -94,17 +95,18 @@ describe('Store', () => { } const part = { name: 'example_part', + plugins: [ plugin ], draft: ({ methodA, methodB, part }) => { methodA('hello A') methodB('hello B') return part }, } - const Test = new Design({ plugins: [plugin], parts: [part] }) + const Test = new Design({ parts: [part] }) const pattern = new Test() pattern.draft() - expect(pattern.stores[0].get('test.example_part.a')).to.equal('hello A') - expect(pattern.stores[0].get('test.example_part.b')).to.equal('hello B') + expect(pattern.setStores[0].get('test.example_part.a')).to.equal('hello A') + expect(pattern.setStores[0].get('test.example_part.b')).to.equal('hello B') }) it('Should log a warning when trying to extend a protected method via the constructor', () => { @@ -153,5 +155,4 @@ describe('Store', () => { store.unset('test') expect(typeof store.get('test')).to.equal('undefined') }) - })