move all pattern config resolution to separate class
This commit is contained in:
parent
6d20884ae3
commit
863237ccfe
5 changed files with 534 additions and 499 deletions
|
@ -11,9 +11,9 @@ import { Store } from './store.mjs'
|
||||||
import { Hooks } from './hooks.mjs'
|
import { Hooks } from './hooks.mjs'
|
||||||
import { version } from '../data.mjs'
|
import { version } from '../data.mjs'
|
||||||
import { __loadPatternDefaults } from './config.mjs'
|
import { __loadPatternDefaults } from './config.mjs'
|
||||||
|
import { PatternConfig, getPluginName } from './patternConfig.mjs'
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
const DISTANCE_DEBUG = false
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
// CONSTRUCTOR //
|
// CONSTRUCTOR //
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
@ -25,7 +25,7 @@ const DISTANCE_DEBUG = false
|
||||||
* @param {object} config - The Design config
|
* @param {object} config - The Design config
|
||||||
* @return {object} this - The Pattern instance
|
* @return {object} this - The Pattern instance
|
||||||
*/
|
*/
|
||||||
export function Pattern(designConfig) {
|
export function Pattern(designConfig = {}) {
|
||||||
// Non-enumerable properties
|
// Non-enumerable properties
|
||||||
__addNonEnumProp(this, 'plugins', {})
|
__addNonEnumProp(this, 'plugins', {})
|
||||||
__addNonEnumProp(this, 'width', 0)
|
__addNonEnumProp(this, 'width', 0)
|
||||||
|
@ -39,19 +39,9 @@ export function Pattern(designConfig) {
|
||||||
__addNonEnumProp(this, 'Attributes', Attributes)
|
__addNonEnumProp(this, 'Attributes', Attributes)
|
||||||
__addNonEnumProp(this, 'macros', {})
|
__addNonEnumProp(this, 'macros', {})
|
||||||
__addNonEnumProp(this, '__initialized', false)
|
__addNonEnumProp(this, '__initialized', false)
|
||||||
__addNonEnumProp(this, '__designParts', {})
|
__addNonEnumProp(this, 'config.parts', {})
|
||||||
__addNonEnumProp(this, '__inject', {})
|
__addNonEnumProp(this, 'config.resolvedDependencies', {})
|
||||||
__addNonEnumProp(this, '__dependencies', {})
|
|
||||||
__addNonEnumProp(this, '__resolvedDependencies', {})
|
|
||||||
__addNonEnumProp(this, '__resolvedParts', [])
|
|
||||||
__addNonEnumProp(this, '__storeMethods', new Set())
|
__addNonEnumProp(this, '__storeMethods', new Set())
|
||||||
__addNonEnumProp(this, '__mutated', {
|
|
||||||
optionDistance: {},
|
|
||||||
partDistance: {},
|
|
||||||
partHide: {},
|
|
||||||
partHideAll: {},
|
|
||||||
})
|
|
||||||
__addNonEnumProp(this, '__draftOrder', [])
|
|
||||||
__addNonEnumProp(this, '__hide', {})
|
__addNonEnumProp(this, '__hide', {})
|
||||||
|
|
||||||
// Enumerable properties
|
// Enumerable properties
|
||||||
|
@ -59,6 +49,7 @@ export function Pattern(designConfig) {
|
||||||
this.config = {} // Will hold the resolved pattern after calling __init()
|
this.config = {} // Will hold the resolved pattern after calling __init()
|
||||||
this.store = new Store() // Pattern-wide store
|
this.store = new Store() // Pattern-wide store
|
||||||
this.setStores = [] // Per-set stores
|
this.setStores = [] // Per-set stores
|
||||||
|
__addNonEnumProp(this, '__configResolver', new PatternConfig(this)) // handles config resolution during __init() as well as runtime part adding
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -74,16 +65,11 @@ export function Pattern(designConfig) {
|
||||||
* @return {object} this - The Pattern instance
|
* @return {object} this - The Pattern instance
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.addPart = function (part, resolveImmediately = false) {
|
Pattern.prototype.addPart = function (part, resolveImmediately = false) {
|
||||||
if (typeof part?.draft === 'function') {
|
if (this.__configResolver.validatePart(part) && this.designConfig.parts.indexOf(part) === -1) {
|
||||||
if (part.name) {
|
this.designConfig.parts.push(part)
|
||||||
this.designConfig.parts.push(part)
|
if (resolveImmediately) this.__configResolver.addPart(part)
|
||||||
if (resolveImmediately) {
|
else this.__initialized = false
|
||||||
this.store.log.debug(`Perfoming runtime resolution of new part ${part.name}`)
|
}
|
||||||
this.__resolvePart([part])
|
|
||||||
} else this.__initialized = false
|
|
||||||
} else this.store.log.error(`Part must have a name`)
|
|
||||||
} else this.store.log.error(`Part must have a draft() method`)
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,15 +118,15 @@ Pattern.prototype.createPartForSet = function (partName, set = 0) {
|
||||||
this.parts[set][partName] = this.__createPartWithContext(partName, set)
|
this.parts[set][partName] = this.__createPartWithContext(partName, set)
|
||||||
|
|
||||||
// Handle inject/inheritance
|
// Handle inject/inheritance
|
||||||
if (typeof this.__inject[partName] === 'string') {
|
if (typeof this.config.inject[partName] === 'string') {
|
||||||
this.setStores[set].log.debug(
|
this.setStores[set].log.debug(
|
||||||
`Creating part \`${partName}\` from part \`${this.__inject[partName]}\``
|
`Creating part \`${partName}\` from part \`${this.config.inject[partName]}\``
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
this.parts[set][partName].__inject(this.parts[set][this.__inject[partName]])
|
this.parts[set][partName].__inject(this.parts[set][this.config.inject[partName]])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setStores[set].log.error([
|
this.setStores[set].log.error([
|
||||||
`Could not inject part \`${this.__inject[partName]}\` into part \`${partName}\``,
|
`Could not inject part \`${this.config.inject[partName]}\` into part \`${partName}\``,
|
||||||
err,
|
err,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -161,11 +147,11 @@ Pattern.prototype.createPartForSet = function (partName, set = 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern.prototype.draftPartForSet = function (partName, set) {
|
Pattern.prototype.draftPartForSet = function (partName, set) {
|
||||||
if (typeof this.__designParts?.[partName]?.draft === 'function') {
|
if (typeof this.config.parts?.[partName]?.draft === 'function') {
|
||||||
this.activePart = partName
|
this.activePart = partName
|
||||||
try {
|
try {
|
||||||
this.__runHooks('prePartDraft')
|
this.__runHooks('prePartDraft')
|
||||||
const result = this.__designParts[partName].draft(this.parts[set][partName].shorthand())
|
const result = this.config.parts[partName].draft(this.parts[set][partName].shorthand())
|
||||||
this.__runHooks('postPartDraft')
|
this.__runHooks('postPartDraft')
|
||||||
if (typeof result === 'undefined') {
|
if (typeof result === 'undefined') {
|
||||||
this.setStores[set].log.error(
|
this.setStores[set].log.error(
|
||||||
|
@ -361,209 +347,6 @@ Pattern.prototype.use = function (plugin, data) {
|
||||||
// PRIVATE METHODS //
|
// PRIVATE METHODS //
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a part as a simple dependency
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {string} name - The name of the dependency
|
|
||||||
* @param {object} part - The part configuration
|
|
||||||
* @param {object} dep - The dependency configuration
|
|
||||||
* @return {object} this - The Pattern instance
|
|
||||||
*/
|
|
||||||
Pattern.prototype.__addDependency = function (dependencyList, part, dep) {
|
|
||||||
this[dependencyList][part.name] = this[dependencyList][part.name] || []
|
|
||||||
if (dependencyList == '__resolvedDependencies' && DISTANCE_DEBUG)
|
|
||||||
this.store.log.debug(`add ${dep.name} to ${part.name} dependencyResolution`)
|
|
||||||
if (this[dependencyList][part.name].indexOf(dep.name) === -1)
|
|
||||||
this[dependencyList][part.name].push(dep.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.__resolvedParts.includes(part.name)) return this
|
|
||||||
|
|
||||||
// Add parts, using set to keep them unique in the array
|
|
||||||
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}\``)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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}\``)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.options) this.config.options = {}
|
|
||||||
if (part.options) {
|
|
||||||
const partDistance = this.__mutated.partDistance?.[part.name] || 0
|
|
||||||
for (const optionName in part.options) {
|
|
||||||
const optionDistance = this.__mutated.optionDistance[optionName]
|
|
||||||
if (!optionDistance) {
|
|
||||||
this.__mutated.optionDistance[optionName] = partDistance
|
|
||||||
// Keep design parts immutable in the pattern or risk subtle bugs
|
|
||||||
this.config.options[optionName] = Object.freeze(part.options[optionName])
|
|
||||||
this.store.log.debug(`🔵 __${optionName}__ option loaded from part \`${part.name}\``)
|
|
||||||
} else {
|
|
||||||
if (DISTANCE_DEBUG)
|
|
||||||
this.store.log.debug(
|
|
||||||
`optionDistance for __${optionName}__ is __${optionDistance}__ and partDistance for \`${part.name}\` is __${partDistance}__`
|
|
||||||
)
|
|
||||||
if (optionDistance > partDistance) {
|
|
||||||
this.config.options[optionName] = part.options[optionName]
|
|
||||||
this.__mutated.optionDistance[optionName] = partDistance
|
|
||||||
this.store.log.debug(`🟣 __${optionName}__ option overwritten by \`${part.name}\``)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPluginName(plugin) {
|
|
||||||
if (Array.isArray(plugin)) {
|
|
||||||
if (plugin[0].name) return plugin[0].name
|
|
||||||
if (plugin[0].plugin.name) return plugin[0].plugin.name
|
|
||||||
} else {
|
|
||||||
if (plugin.name) return plugin.name
|
|
||||||
if (plugin.plugin?.name) return plugin.plugin.name
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 (!part.plugins) return this
|
|
||||||
if (!this.config.plugins) this.config.plugins = {}
|
|
||||||
const plugins = { ...this.config.plugins }
|
|
||||||
// Side-step immutability of the part object to ensure plugins is an array
|
|
||||||
let partPlugins = part.plugins
|
|
||||||
if (!Array.isArray(partPlugins)) partPlugins = [partPlugins]
|
|
||||||
// Go through list of part plugins
|
|
||||||
for (let plugin of partPlugins) {
|
|
||||||
const name = getPluginName(plugin)
|
|
||||||
this.store.log.debug(
|
|
||||||
plugin.plugin
|
|
||||||
? `🔌 Resolved __${name}__ conditional plugin in \`${part.name}\``
|
|
||||||
: `🔌 Resolved __${name}__ plugin in \`${part.name}\``
|
|
||||||
)
|
|
||||||
// Handle [plugin, data] scenario
|
|
||||||
if (Array.isArray(plugin)) {
|
|
||||||
const pluginObj = { ...plugin[0], data: plugin[1] }
|
|
||||||
plugin = pluginObj
|
|
||||||
}
|
|
||||||
if (!plugins[name]) {
|
|
||||||
// New plugin, so we load it
|
|
||||||
plugins[name] = plugin
|
|
||||||
this.store.log.info(
|
|
||||||
plugin.condition
|
|
||||||
? `New plugin conditionally added: \`${name}\``
|
|
||||||
: `New plugin added: \`${name}\``
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Existing plugin, takes some more work
|
|
||||||
if (plugin.plugin && plugin.condition) {
|
|
||||||
// Multiple instances of the same plugin with different conditions
|
|
||||||
// will all be added, so we need to change the name.
|
|
||||||
if (plugins[name]?.condition) {
|
|
||||||
plugins[name + '_'] = plugin
|
|
||||||
this.store.log.info(
|
|
||||||
`Plugin \`${name}\` was conditionally added again. Renaming to ${name}_.`
|
|
||||||
)
|
|
||||||
} else
|
|
||||||
this.store.log.info(
|
|
||||||
`Plugin \`${name}\` was requested conditionally, but is already added explicitly. Not loading.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// swap from a conditional if needed
|
|
||||||
else if (plugins[name].condition) {
|
|
||||||
plugins[name] = plugin
|
|
||||||
this.store.log.info(`Plugin \`${name}\` was explicitly added. Changing from conditional.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config.plugins = { ...plugins }
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a store for a set (of settings)
|
* Creates a store for a set (of settings)
|
||||||
*
|
*
|
||||||
|
@ -620,7 +403,7 @@ Pattern.prototype.__createPartWithContext = function (name, set) {
|
||||||
const part = new Part()
|
const part = new Part()
|
||||||
part.name = name
|
part.name = name
|
||||||
part.set = set
|
part.set = set
|
||||||
part.stack = this.__designParts[name]?.stack || name
|
part.stack = this.config.parts[name]?.stack || name
|
||||||
part.context = {
|
part.context = {
|
||||||
parts: this.parts[set],
|
parts: this.parts[set],
|
||||||
config: this.config,
|
config: this.config,
|
||||||
|
@ -700,14 +483,11 @@ Pattern.prototype.__init = function () {
|
||||||
* This methods does that, and resolves the design config + user settings
|
* This methods does that, and resolves the design config + user settings
|
||||||
*/
|
*/
|
||||||
this.__resolveParts() // Resolves parts
|
this.__resolveParts() // Resolves parts
|
||||||
.__resolveDependencies() // Resolves dependencies
|
.__resolveConfig() // Gets the config from the resolver
|
||||||
.__resolveDraftOrder() // Resolves draft order
|
|
||||||
.__loadPlugins() // Loads plugins
|
.__loadPlugins() // Loads plugins
|
||||||
.__loadConfigData() // Makes config data available in store
|
.__loadConfigData() // Makes config data available in store
|
||||||
.__filterOptionalMeasurements() // Removes required m's from optional list
|
|
||||||
.__loadOptionDefaults() // Merges default options with user provided ones
|
|
||||||
|
|
||||||
this.store.log.info(`Pattern initialized. Draft order is: ${this.__draftOrder.join(', ')}`)
|
this.store.log.info(`Pattern initialized. Draft order is: ${this.config.draftOrder.join(', ')}`)
|
||||||
this.__runHooks('postInit')
|
this.__runHooks('postInit')
|
||||||
|
|
||||||
this.__initialized = true
|
this.__initialized = true
|
||||||
|
@ -726,10 +506,10 @@ Pattern.prototype.__isPartHidden = function (partName) {
|
||||||
if (Array.isArray(this.settings[this.activeSet || 0].only)) {
|
if (Array.isArray(this.settings[this.activeSet || 0].only)) {
|
||||||
if (this.settings[this.activeSet || 0].only.includes(partName)) return false
|
if (this.settings[this.activeSet || 0].only.includes(partName)) return false
|
||||||
}
|
}
|
||||||
if (this.__designParts?.[partName]?.hide) return true
|
if (this.config.parts?.[partName]?.hide) return true
|
||||||
if (this.__designParts?.[partName]?.hideAll) return true
|
if (this.config.parts?.[partName]?.hideAll) return true
|
||||||
if (this.__mutated.partHide?.[partName]) return true
|
if (this.config.partHide?.[partName]) return true
|
||||||
if (this.__mutated.partHideAll?.[partName]) return true
|
if (this.config.partHideAll?.[partName]) return true
|
||||||
if (this.parts?.[this.activeSet]?.[partName]?.hidden) return true
|
if (this.parts?.[this.activeSet]?.[partName]?.hidden) return true
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -822,40 +602,6 @@ Pattern.prototype.__loadConfigData = function () {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges defaults for options with user-provided options
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @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)) {
|
|
||||||
// Don't overwrite user-provided settings.options
|
|
||||||
if (typeof this.settings[i].options[name] === 'undefined') {
|
|
||||||
if (typeof option === 'object') {
|
|
||||||
if (typeof option.pct !== 'undefined') this.settings[i].options[name] = option.pct / 100
|
|
||||||
else if (typeof option.mm !== 'undefined') this.settings[i].options[name] = option.mm
|
|
||||||
else if (typeof option.deg !== 'undefined') this.settings[i].options[name] = option.deg
|
|
||||||
else if (typeof option.count !== 'undefined')
|
|
||||||
this.settings[i].options[name] = option.count
|
|
||||||
else if (typeof option.bool !== 'undefined') this.settings[i].options[name] = option.bool
|
|
||||||
else if (typeof option.dflt !== 'undefined') this.settings[i].options[name] = option.dflt
|
|
||||||
else {
|
|
||||||
let err = 'Unknown option type: ' + JSON.stringify(option)
|
|
||||||
this.store.log.error(err)
|
|
||||||
throw new Error(err)
|
|
||||||
}
|
|
||||||
} else this.settings[i].options[name] = option
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a plugin
|
* Loads a plugin
|
||||||
*
|
*
|
||||||
|
@ -1049,8 +795,8 @@ Pattern.prototype.__needs = function (partName, set = 0) {
|
||||||
// Walk the only parts, checking each one for a match in its dependencies
|
// Walk the only parts, checking each one for a match in its dependencies
|
||||||
for (const part of only) {
|
for (const part of only) {
|
||||||
if (part === partName) return true
|
if (part === partName) return true
|
||||||
if (this.__resolvedDependencies[part]) {
|
if (this.config.resolvedDependencies[part]) {
|
||||||
for (const dependency of this.__resolvedDependencies[part]) {
|
for (const dependency of this.config.resolvedDependencies[part]) {
|
||||||
if (dependency === partName) return true
|
if (dependency === partName) return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1183,117 +929,11 @@ Pattern.prototype.__pack = function () {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Pattern.prototype.__resolveConfig = function () {
|
||||||
* Resolves the draft order based on the configuation
|
this.config = this.__configResolver.asConfig()
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {object} graph - The object of resolved dependencies, used to call itself recursively
|
|
||||||
* @return {Pattern} this - The Pattern instance
|
|
||||||
*/
|
|
||||||
Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDependencies) {
|
|
||||||
const sorted = Object.keys(this.__designParts).sort(
|
|
||||||
(p1, p2) => this.__mutated.partDistance[p2] - this.__mutated.partDistance[p1]
|
|
||||||
)
|
|
||||||
this.__draftOrder = sorted
|
|
||||||
this.config.draftOrder = sorted
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern.prototype.__resolvePartDependencyChain = function (depChain, dependency, depType) {
|
|
||||||
const part = depChain[0]
|
|
||||||
|
|
||||||
this.__designParts[dependency.name] = Object.freeze(dependency)
|
|
||||||
this.__addDependency('__dependencies', part, dependency)
|
|
||||||
|
|
||||||
depChain.forEach((c) => this.__addDependency('__resolvedDependencies', c, dependency))
|
|
||||||
|
|
||||||
switch (depType) {
|
|
||||||
case 'from':
|
|
||||||
this.__setFromHide(part, part.name, dependency.name)
|
|
||||||
this.__inject[part.name] = dependency.name
|
|
||||||
break
|
|
||||||
case 'after':
|
|
||||||
this.__setAfterHide(part, part.name, dependency.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Pattern.prototype.__resolveMutatedPartDistance = function (partName) {
|
|
||||||
const proposed_dependent_part_distance = this.__mutated.partDistance[partName] + 1
|
|
||||||
let didChange = false
|
|
||||||
if (!this.__dependencies[partName]) return false
|
|
||||||
this.__dependencies[partName].forEach((dependency) => {
|
|
||||||
if (
|
|
||||||
typeof this.__mutated.partDistance[dependency] === 'undefined' ||
|
|
||||||
this.__mutated.partDistance[dependency] < proposed_dependent_part_distance
|
|
||||||
) {
|
|
||||||
didChange = true
|
|
||||||
this.__mutated.partDistance[dependency] = proposed_dependent_part_distance
|
|
||||||
this.__resolveMutatedPartDistance(dependency)
|
|
||||||
}
|
|
||||||
if (DISTANCE_DEBUG)
|
|
||||||
this.store.log.debug(
|
|
||||||
`"${depType}:" partDistance for \`${dependency}\` is __${this.__mutated.partDistance[dependency]}__`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return didChange
|
|
||||||
}
|
|
||||||
|
|
||||||
const depTypes = ['from', 'after']
|
|
||||||
Pattern.prototype.__resolvePartDependencies = function (depChain, distance) {
|
|
||||||
// Resolve part Dependencies. first from then after
|
|
||||||
const part = depChain[0]
|
|
||||||
this.__resolvedDependencies[part.name] = this.__resolvedDependencies[part.name] || []
|
|
||||||
|
|
||||||
depTypes.forEach((d) => {
|
|
||||||
if (part[d]) {
|
|
||||||
if (DISTANCE_DEBUG) this.store.log.debug(`Processing \`${part.name}\` "${d}:"`)
|
|
||||||
|
|
||||||
const depsOfType = Array.isArray(part[d]) ? part[d] : [part[d]]
|
|
||||||
|
|
||||||
depsOfType.forEach((dot) => {
|
|
||||||
let count = Object.keys(this.__designParts).length
|
|
||||||
// if any changes resulted from resolving this part mutation
|
|
||||||
this.__resolvePartDependencyChain(depChain, dot, d)
|
|
||||||
// if a new part was added, resolve the part
|
|
||||||
const newCount = Object.keys(this.__designParts).length
|
|
||||||
if (count < newCount) {
|
|
||||||
this.__resolvePart([dot, ...depChain], distance)
|
|
||||||
count = newCount
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.__resolveMutatedPartDistance(part.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
Pattern.prototype.__resolvePart = function (depChain, distance = 0) {
|
|
||||||
const part = depChain[0]
|
|
||||||
if (distance === 0) {
|
|
||||||
this.__designParts[part.name] = Object.freeze(part)
|
|
||||||
}
|
|
||||||
distance++
|
|
||||||
if (typeof this.__mutated.partDistance[part.name] === 'undefined') {
|
|
||||||
this.__mutated.partDistance[part.name] = distance
|
|
||||||
|
|
||||||
if (DISTANCE_DEBUG)
|
|
||||||
this.store.log.debug(
|
|
||||||
`Base partDistance for \`${part.name}\` is __${this.__mutated.partDistance[part.name]}__`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide when hideAll is set
|
|
||||||
if (part.hideAll) {
|
|
||||||
this.__mutated.partHide[part.name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
this.__resolvePartDependencies(depChain, distance)
|
|
||||||
|
|
||||||
// add the part's config
|
|
||||||
this.__addPartConfig(part)
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Resolves parts and their dependencies
|
* Resolves parts and their dependencies
|
||||||
*
|
*
|
||||||
|
@ -1302,35 +942,15 @@ Pattern.prototype.__resolvePart = function (depChain, distance = 0) {
|
||||||
* @param {int} distance - Keeps track of how far the dependency is from the pattern
|
* @param {int} distance - Keeps track of how far the dependency is from the pattern
|
||||||
* @return {Pattern} this - The Pattern instance
|
* @return {Pattern} this - The Pattern instance
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.__resolveParts = function (count = 0, distance = 0) {
|
Pattern.prototype.__resolveParts = function () {
|
||||||
for (const part of this.designConfig.parts) {
|
this.designConfig.parts.forEach((p) => this.__configResolver.addPart(p))
|
||||||
this.__resolvePart([part], distance)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print final part distances.
|
// Print final part distances.
|
||||||
for (const part of this.designConfig.parts) {
|
this.__configResolver.logPartDistances()
|
||||||
let qualifier = DISTANCE_DEBUG ? 'final' : ''
|
|
||||||
this.store.log.debug(
|
|
||||||
`⚪️ \`${part.name}\` ${qualifier} options priority is __${
|
|
||||||
this.__mutated.partDistance[part.name]
|
|
||||||
}__`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves parts depdendencies into a flat array
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @return {Pattern} this - The Pattern instance
|
|
||||||
*/
|
|
||||||
Pattern.prototype.__resolveDependencies = function () {
|
|
||||||
this.config.resolvedDependencies = this.__resolvedDependencies
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs subscriptions to a given lifecycle hook
|
* Runs subscriptions to a given lifecycle hook
|
||||||
*
|
*
|
||||||
|
@ -1364,49 +984,6 @@ Pattern.prototype.__setBase = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets visibility of a dependency based on its config
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Part} part - The part of which this is a dependency
|
|
||||||
* @param {string} name - The name of the part
|
|
||||||
* @param {string} depName - The name of the dependency
|
|
||||||
* @param {int} set - The index of the set in the list of settings
|
|
||||||
* @return {Pattern} this - The Pattern instance
|
|
||||||
*/
|
|
||||||
Pattern.prototype.__setFromHide = function (part, name, depName) {
|
|
||||||
if (
|
|
||||||
part.hideDependencies ||
|
|
||||||
part.hideAll ||
|
|
||||||
this.__mutated.partHide[name] ||
|
|
||||||
this.__mutated.partHideAll[name]
|
|
||||||
) {
|
|
||||||
this.__mutated.partHide[depName] = true
|
|
||||||
this.__mutated.partHideAll[depName] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets visibility of an 'after' dependency based on its config
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Part} part - The part of which this is a dependency
|
|
||||||
* @param {string} name - The name of the part
|
|
||||||
* @param {string} depName - The name of the dependency
|
|
||||||
* @param {int} set - The index of the set in the list of settings
|
|
||||||
* @return {Pattern} this - The Pattern instance
|
|
||||||
*/
|
|
||||||
Pattern.prototype.__setAfterHide = function (part, name, depName) {
|
|
||||||
if (this.__mutated.partHide[name] || this.__mutated.partHideAll[name]) {
|
|
||||||
this.__mutated.partHide[depName] = true
|
|
||||||
this.__mutated.partHideAll[depName] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the absolute value of a snapped percentage option
|
* Returns the absolute value of a snapped percentage option
|
||||||
*
|
*
|
||||||
|
@ -1493,22 +1070,3 @@ Pattern.prototype.__wants = function (partName, set = 0) {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
// STATIC PRIVATE FUNCTIONS //
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges dependencies into a flat list
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {array} dep - New dependencies
|
|
||||||
* @param {array} current - Current dependencies
|
|
||||||
* @return {array} deps - Merged dependencies
|
|
||||||
*/
|
|
||||||
function mergeDependencies(dep = [], current = []) {
|
|
||||||
// Current dependencies
|
|
||||||
const list = [].concat(current, dep)
|
|
||||||
|
|
||||||
return [...new Set(list)]
|
|
||||||
}
|
|
||||||
|
|
419
packages/core/src/patternConfig.mjs
Normal file
419
packages/core/src/patternConfig.mjs
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
import { __addNonEnumProp } from './utils.mjs'
|
||||||
|
|
||||||
|
export function getPluginName(plugin) {
|
||||||
|
if (Array.isArray(plugin)) {
|
||||||
|
if (plugin[0].name) return plugin[0].name
|
||||||
|
if (plugin[0].plugin.name) return plugin[0].plugin.name
|
||||||
|
} else {
|
||||||
|
if (plugin.name) return plugin.name
|
||||||
|
if (plugin.plugin?.name) return plugin.plugin.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PatternConfig(pattern) {
|
||||||
|
this.pattern = pattern
|
||||||
|
this.store = pattern.store
|
||||||
|
__addNonEnumProp(this, 'plugins', { ...(pattern.designConfig.plugins || {}) })
|
||||||
|
__addNonEnumProp(this, 'options', { ...(pattern.designConfig.options || {}) })
|
||||||
|
__addNonEnumProp(this, 'measurements', [...(pattern.designConfig.measurements || [])])
|
||||||
|
__addNonEnumProp(this, 'optionalMeasurements', [
|
||||||
|
...(pattern.designConfig.optionalMeasurements || []),
|
||||||
|
])
|
||||||
|
__addNonEnumProp(this, 'inject', {})
|
||||||
|
__addNonEnumProp(this, 'directDependencies', {})
|
||||||
|
__addNonEnumProp(this, 'resolvedDependencies', {})
|
||||||
|
__addNonEnumProp(this, 'parts', {})
|
||||||
|
__addNonEnumProp(this, '__resolvedParts', {})
|
||||||
|
__addNonEnumProp(this, '__mutated', {
|
||||||
|
optionDistance: {},
|
||||||
|
partDistance: {},
|
||||||
|
partHide: {},
|
||||||
|
partHideAll: {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const DISTANCE_DEBUG = false
|
||||||
|
|
||||||
|
PatternConfig.prototype.validatePart = function (part) {
|
||||||
|
if (typeof part?.draft !== 'function') {
|
||||||
|
this.store.log.error(`Part must have a draft() method`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!part.name) {
|
||||||
|
this.store.log.error(`Part must have a name`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
PatternConfig.prototype.addPart = function (part) {
|
||||||
|
if (this.validatePart(part)) this.__resolvePart([part])
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternConfig.prototype.logPartDistances = function () {
|
||||||
|
for (const partName in this.parts) {
|
||||||
|
let qualifier = DISTANCE_DEBUG ? 'final' : ''
|
||||||
|
this.store.log.debug(
|
||||||
|
`⚪️ \`${partName}\` ${qualifier} options priority is __${this.__mutated.partDistance[partName]}__`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternConfig.prototype.asConfig = function () {
|
||||||
|
return {
|
||||||
|
parts: this.parts,
|
||||||
|
plugins: this.plugins,
|
||||||
|
measurements: this.measurements,
|
||||||
|
options: this.options,
|
||||||
|
optionalMeasurements: this.optionalMeasurements,
|
||||||
|
resolvedDependencies: this.resolvedDependencies,
|
||||||
|
directDependencies: this.directDependencies,
|
||||||
|
inject: this.inject,
|
||||||
|
draftOrder: this.__resolveDraftOrder(),
|
||||||
|
partHide: this.__mutated.partHide,
|
||||||
|
partHideAll: this.__mutated.partHideAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternConfig.prototype.__resolvePart = function (depChain, distance = 0) {
|
||||||
|
const part = depChain[0]
|
||||||
|
if (distance === 0) {
|
||||||
|
this.parts[part.name] = Object.freeze(part)
|
||||||
|
}
|
||||||
|
distance++
|
||||||
|
if (typeof this.__mutated.partDistance[part.name] === 'undefined') {
|
||||||
|
this.__mutated.partDistance[part.name] = distance
|
||||||
|
|
||||||
|
if (DISTANCE_DEBUG)
|
||||||
|
this.store.log.debug(
|
||||||
|
`Base partDistance for \`${part.name}\` is __${this.__mutated.partDistance[part.name]}__`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide when hideAll is set
|
||||||
|
if (part.hideAll) {
|
||||||
|
this.__mutated.partHide[part.name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__resolvePartDependencies(depChain, distance)
|
||||||
|
|
||||||
|
// add the part's config
|
||||||
|
this.__addPartConfig(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__addPartConfig = function (part) {
|
||||||
|
if (this.__resolvedParts[part.name]) return this
|
||||||
|
|
||||||
|
// Add parts, using set to keep them unique in the array
|
||||||
|
// this.designConfig.parts = [...new Set(this.designConfig.parts).add(part)]
|
||||||
|
|
||||||
|
return this.__addPartOptions(part)
|
||||||
|
.__addPartMeasurements(part, true)
|
||||||
|
.__addPartMeasurements(part, false)
|
||||||
|
.__addPartPlugins(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__addPartOptions = function (part) {
|
||||||
|
if (!part.options) return this
|
||||||
|
|
||||||
|
const partDistance = this.__mutated.partDistance?.[part.name] || 0
|
||||||
|
for (const optionName in part.options) {
|
||||||
|
const option = part.options[optionName]
|
||||||
|
const optionDistance = this.__mutated.optionDistance[optionName]
|
||||||
|
if (optionDistance && DISTANCE_DEBUG)
|
||||||
|
this.store.log.debug(
|
||||||
|
`optionDistance for __${optionName}__ is __${optionDistance}__ and partDistance for \`${part.name}\` is __${partDistance}__`
|
||||||
|
)
|
||||||
|
if (!optionDistance || optionDistance > partDistance) {
|
||||||
|
this.__mutated.optionDistance[optionName] = partDistance
|
||||||
|
// Keep design parts immutable in the pattern or risk subtle bugs
|
||||||
|
this.options[optionName] = Object.freeze(option)
|
||||||
|
this.store.log.debug(
|
||||||
|
optionDistance
|
||||||
|
? `🟣 __${optionName}__ option overwritten by \`${part.name}\``
|
||||||
|
: `🔵 __${optionName}__ option loaded from part \`${part.name}\``
|
||||||
|
)
|
||||||
|
this.__loadOptionDefault(optionName, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__addPartMeasurements = function (part, optional = false) {
|
||||||
|
const listType = optional ? 'optionalMeasurements' : 'measurements'
|
||||||
|
if (part[listType]) {
|
||||||
|
part[listType].forEach((m) => {
|
||||||
|
const isInReqList = this.measurements.indexOf(m) !== -1
|
||||||
|
const optInd = this.optionalMeasurements.indexOf(m)
|
||||||
|
const isInOptList = optInd !== -1
|
||||||
|
|
||||||
|
if (isInReqList) return
|
||||||
|
if (optional && !isInOptList) this.optionalMeasurements.push(m)
|
||||||
|
if (!optional) {
|
||||||
|
this.measurements.push(m)
|
||||||
|
|
||||||
|
if (isInOptList) this.optionalMeasurements.splice(optInd, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.log.debug(
|
||||||
|
`🟠 __${m}__ measurement is ${optional ? 'optional' : 'required'} in \`${part.name}\``
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__addPartPlugins = function (part) {
|
||||||
|
if (!part.plugins) return this
|
||||||
|
|
||||||
|
const plugins = this.plugins
|
||||||
|
// Side-step immutability of the part object to ensure plugins is an array
|
||||||
|
let partPlugins = part.plugins
|
||||||
|
if (!Array.isArray(partPlugins)) partPlugins = [partPlugins]
|
||||||
|
// Go through list of part plugins
|
||||||
|
for (let plugin of partPlugins) {
|
||||||
|
const name = getPluginName(plugin)
|
||||||
|
this.store.log.debug(
|
||||||
|
plugin.plugin
|
||||||
|
? `🔌 Resolved __${name}__ conditional plugin in \`${part.name}\``
|
||||||
|
: `🔌 Resolved __${name}__ plugin in \`${part.name}\``
|
||||||
|
)
|
||||||
|
// Handle [plugin, data] scenario
|
||||||
|
if (Array.isArray(plugin)) {
|
||||||
|
const pluginObj = { ...plugin[0], data: plugin[1] }
|
||||||
|
plugin = pluginObj
|
||||||
|
}
|
||||||
|
if (!plugins[name]) {
|
||||||
|
// New plugin, so we load it
|
||||||
|
plugins[name] = plugin
|
||||||
|
this.store.log.info(
|
||||||
|
plugin.condition
|
||||||
|
? `New plugin conditionally added: \`${name}\``
|
||||||
|
: `New plugin added: \`${name}\``
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Existing plugin, takes some more work
|
||||||
|
if (plugin.plugin && plugin.condition) {
|
||||||
|
// Multiple instances of the same plugin with different conditions
|
||||||
|
// will all be added, so we need to change the name.
|
||||||
|
if (plugins[name]?.condition) {
|
||||||
|
plugins[name + '_'] = plugin
|
||||||
|
this.store.log.info(
|
||||||
|
`Plugin \`${name}\` was conditionally added again. Renaming to ${name}_.`
|
||||||
|
)
|
||||||
|
} else
|
||||||
|
this.store.log.info(
|
||||||
|
`Plugin \`${name}\` was requested conditionally, but is already added explicitly. Not loading.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// swap from a conditional if needed
|
||||||
|
else if (plugins[name].condition) {
|
||||||
|
plugins[name] = plugin
|
||||||
|
this.store.log.info(`Plugin \`${name}\` was explicitly added. Changing from conditional.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternConfig.prototype.__loadOptionDefault = function (optionName, option) {
|
||||||
|
this.pattern.settings.forEach((set) => {
|
||||||
|
if (typeof set.options[optionName] !== 'undefined') return
|
||||||
|
if (typeof option === 'object') {
|
||||||
|
if (typeof option.pct !== 'undefined') set.options[optionName] = option.pct / 100
|
||||||
|
else if (typeof option.mm !== 'undefined') set.options[optionName] = option.mm
|
||||||
|
else if (typeof option.deg !== 'undefined') set.options[optionName] = option.deg
|
||||||
|
else if (typeof option.count !== 'undefined') set.options[optionName] = option.count
|
||||||
|
else if (typeof option.bool !== 'undefined') set.options[optionName] = option.bool
|
||||||
|
else if (typeof option.dflt !== 'undefined') set.options[optionName] = option.dflt
|
||||||
|
else {
|
||||||
|
let err = 'Unknown option type: ' + JSON.stringify(option)
|
||||||
|
this.store.log.error(err)
|
||||||
|
throw new Error(err)
|
||||||
|
}
|
||||||
|
} else set.options[optionName] = option
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternConfig.prototype.__resolvePartDependencyChain = function (depChain, dependency, depType) {
|
||||||
|
const part = depChain[0]
|
||||||
|
|
||||||
|
this.parts[dependency.name] = Object.freeze(dependency)
|
||||||
|
this.__addDependency('directDependencies', part, dependency)
|
||||||
|
|
||||||
|
depChain.forEach((c) => this.__addDependency('resolvedDependencies', c, dependency))
|
||||||
|
|
||||||
|
switch (depType) {
|
||||||
|
case 'from':
|
||||||
|
this.__setFromHide(part, part.name, dependency.name)
|
||||||
|
this.inject[part.name] = dependency.name
|
||||||
|
break
|
||||||
|
case 'after':
|
||||||
|
this.__setAfterHide(part, part.name, dependency.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternConfig.prototype.__resolveMutatedPartDistance = function (partName) {
|
||||||
|
const proposed_dependent_part_distance = this.__mutated.partDistance[partName] + 1
|
||||||
|
let didChange = false
|
||||||
|
if (!this.directDependencies[partName]) return false
|
||||||
|
this.directDependencies[partName].forEach((dependency) => {
|
||||||
|
if (
|
||||||
|
typeof this.__mutated.partDistance[dependency] === 'undefined' ||
|
||||||
|
this.__mutated.partDistance[dependency] < proposed_dependent_part_distance
|
||||||
|
) {
|
||||||
|
didChange = true
|
||||||
|
this.__mutated.partDistance[dependency] = proposed_dependent_part_distance
|
||||||
|
this.__resolveMutatedPartDistance(dependency)
|
||||||
|
}
|
||||||
|
if (DISTANCE_DEBUG)
|
||||||
|
this.store.log.debug(
|
||||||
|
`partDistance for \`${dependency}\` is __${this.__mutated.partDistance[dependency]}__`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return didChange
|
||||||
|
}
|
||||||
|
|
||||||
|
const depTypes = ['from', 'after']
|
||||||
|
PatternConfig.prototype.__resolvePartDependencies = function (depChain, distance) {
|
||||||
|
// Resolve part Dependencies. first from then after
|
||||||
|
const part = depChain[0]
|
||||||
|
this.resolvedDependencies[part.name] = this.resolvedDependencies[part.name] || []
|
||||||
|
|
||||||
|
depTypes.forEach((d) => {
|
||||||
|
if (part[d]) {
|
||||||
|
if (DISTANCE_DEBUG) this.store.log.debug(`Processing \`${part.name}\` "${d}:"`)
|
||||||
|
|
||||||
|
const depsOfType = Array.isArray(part[d]) ? part[d] : [part[d]]
|
||||||
|
|
||||||
|
depsOfType.forEach((dot) => {
|
||||||
|
let count = Object.keys(this.parts).length
|
||||||
|
// if any changes resulted from resolving this part mutation
|
||||||
|
this.__resolvePartDependencyChain(depChain, dot, d)
|
||||||
|
// if a new part was added, resolve the part
|
||||||
|
const newCount = Object.keys(this.parts).length
|
||||||
|
if (count < newCount) {
|
||||||
|
this.__resolvePart([dot, ...depChain], distance)
|
||||||
|
count = newCount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.__resolveMutatedPartDistance(part.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a part as a simple dependency
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} name - The name of the dependency
|
||||||
|
* @param {object} part - The part configuration
|
||||||
|
* @param {object} dep - The dependency configuration
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__addDependency = function (dependencyList, part, dep) {
|
||||||
|
this[dependencyList][part.name] = this[dependencyList][part.name] || []
|
||||||
|
if (dependencyList == 'resolvedDependencies' && DISTANCE_DEBUG)
|
||||||
|
this.store.log.debug(`add ${dep.name} to ${part.name} dependencyResolution`)
|
||||||
|
if (this[dependencyList][part.name].indexOf(dep.name) === -1)
|
||||||
|
this[dependencyList][part.name].push(dep.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the draft order based on the configuation
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {object} graph - The object of resolved dependencies, used to call itself recursively
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__resolveDraftOrder = function () {
|
||||||
|
this.__draftOrder = Object.keys(this.parts).sort(
|
||||||
|
(p1, p2) => this.__mutated.partDistance[p2] - this.__mutated.partDistance[p1]
|
||||||
|
)
|
||||||
|
|
||||||
|
return this.__draftOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets visibility of a dependency based on its config
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Part} part - The part of which this is a dependency
|
||||||
|
* @param {string} name - The name of the part
|
||||||
|
* @param {string} depName - The name of the dependency
|
||||||
|
* @param {int} set - The index of the set in the list of settings
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__setFromHide = function (part, name, depName) {
|
||||||
|
if (
|
||||||
|
part.hideDependencies ||
|
||||||
|
part.hideAll ||
|
||||||
|
this.__mutated.partHide[name] ||
|
||||||
|
this.__mutated.partHideAll[name]
|
||||||
|
) {
|
||||||
|
this.__mutated.partHide[depName] = true
|
||||||
|
this.__mutated.partHideAll[depName] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets visibility of an 'after' dependency based on its config
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Part} part - The part of which this is a dependency
|
||||||
|
* @param {string} name - The name of the part
|
||||||
|
* @param {string} depName - The name of the dependency
|
||||||
|
* @param {int} set - The index of the set in the list of settings
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternConfig.prototype.__setAfterHide = function (part, name, depName) {
|
||||||
|
if (this.__mutated.partHide[name] || this.__mutated.partHideAll[name]) {
|
||||||
|
this.__mutated.partHide[depName] = true
|
||||||
|
this.__mutated.partHideAll[depName] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
|
@ -32,12 +32,12 @@ describe('Pattern', () => {
|
||||||
expect(typeof pattern.Snippet).to.equal('function')
|
expect(typeof pattern.Snippet).to.equal('function')
|
||||||
expect(typeof pattern.Attributes).to.equal('function')
|
expect(typeof pattern.Attributes).to.equal('function')
|
||||||
expect(typeof pattern.macros).to.equal('object')
|
expect(typeof pattern.macros).to.equal('object')
|
||||||
expect(typeof pattern.__designParts).to.equal('object')
|
// expect(typeof pattern.__designParts).to.equal('object')
|
||||||
expect(typeof pattern.__inject).to.equal('object')
|
// expect(typeof pattern.config.inject).to.equal('object')
|
||||||
expect(typeof pattern.__dependencies).to.equal('object')
|
// expect(typeof pattern.config.directDependencies).to.equal('object')
|
||||||
expect(typeof pattern.__resolvedDependencies).to.equal('object')
|
// expect(typeof pattern.__resolvedDependencies).to.equal('object')
|
||||||
expect(typeof pattern.__hide).to.equal('object')
|
// expect(typeof pattern.__hide).to.equal('object')
|
||||||
expect(Array.isArray(pattern.__draftOrder)).to.equal(true)
|
// expect(Array.isArray(pattern.__draftOrder)).to.equal(true)
|
||||||
expect(pattern.width).to.equal(0)
|
expect(pattern.width).to.equal(0)
|
||||||
expect(pattern.height).to.equal(0)
|
expect(pattern.height).to.equal(0)
|
||||||
expect(pattern.is).to.equal('')
|
expect(pattern.is).to.equal('')
|
||||||
|
@ -145,7 +145,7 @@ describe('Pattern', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should resolve parts', () => {
|
it('Pattern.__init() should resolve parts', () => {
|
||||||
expect(pattern.designConfig.parts.length).to.equal(3)
|
expect(Object.keys(pattern.config.parts)).to.have.lengthOf(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should resolve plugins', () => {
|
it('Pattern.__init() should resolve plugins', () => {
|
||||||
|
@ -324,14 +324,14 @@ describe('Pattern', () => {
|
||||||
expect(pattern.config.options.optionR.list[1]).to.equal('green')
|
expect(pattern.config.options.optionR.list[1]).to.equal('green')
|
||||||
expect(pattern.config.options.optionR.list[2]).to.equal('blue')
|
expect(pattern.config.options.optionR.list[2]).to.equal('blue')
|
||||||
// Dependencies
|
// Dependencies
|
||||||
expect(pattern.__dependencies.partB).to.include('partA')
|
expect(pattern.config.directDependencies.partB).to.include('partA')
|
||||||
expect(pattern.__dependencies.partC).to.include('partB')
|
expect(pattern.config.directDependencies.partC).to.include('partB')
|
||||||
expect(pattern.__dependencies.partR).to.include('partC')
|
expect(pattern.config.directDependencies.partR).to.include('partC')
|
||||||
expect(pattern.__dependencies.partR).to.include('partA')
|
expect(pattern.config.directDependencies.partR).to.include('partA')
|
||||||
// Inject
|
// Inject
|
||||||
expect(pattern.__inject.partB).to.equal('partA')
|
expect(pattern.config.inject.partB).to.equal('partA')
|
||||||
expect(pattern.__inject.partC).to.equal('partB')
|
expect(pattern.config.inject.partC).to.equal('partB')
|
||||||
expect(pattern.__inject.partR).to.equal('partA')
|
expect(pattern.config.inject.partR).to.equal('partA')
|
||||||
// Draft order
|
// Draft order
|
||||||
expect(pattern.config.draftOrder[0]).to.equal('partA')
|
expect(pattern.config.draftOrder[0]).to.equal('partA')
|
||||||
expect(pattern.config.draftOrder[1]).to.equal('partB')
|
expect(pattern.config.draftOrder[1]).to.equal('partB')
|
||||||
|
@ -472,12 +472,12 @@ describe('Pattern', () => {
|
||||||
expect(pattern.config.options.optionD.list[1]).to.equal('green')
|
expect(pattern.config.options.optionD.list[1]).to.equal('green')
|
||||||
expect(pattern.config.options.optionD.list[2]).to.equal('blue')
|
expect(pattern.config.options.optionD.list[2]).to.equal('blue')
|
||||||
// Dependencies
|
// Dependencies
|
||||||
expect(pattern.__dependencies.partB[0]).to.equal('partA')
|
expect(pattern.config.directDependencies.partB[0]).to.equal('partA')
|
||||||
expect(pattern.__dependencies.partC[0]).to.equal('partB')
|
expect(pattern.config.directDependencies.partC[0]).to.equal('partB')
|
||||||
expect(pattern.__dependencies.partD[0]).to.equal('partC')
|
expect(pattern.config.directDependencies.partD[0]).to.equal('partC')
|
||||||
// Inject
|
// Inject
|
||||||
expect(pattern.__inject.partB).to.equal('partA')
|
expect(pattern.config.inject.partB).to.equal('partA')
|
||||||
expect(pattern.__inject.partC).to.equal('partB')
|
expect(pattern.config.inject.partC).to.equal('partB')
|
||||||
// Draft order
|
// Draft order
|
||||||
expect(pattern.config.draftOrder[0]).to.equal('partA')
|
expect(pattern.config.draftOrder[0]).to.equal('partA')
|
||||||
expect(pattern.config.draftOrder[1]).to.equal('partB')
|
expect(pattern.config.draftOrder[1]).to.equal('partB')
|
||||||
|
|
|
@ -12,31 +12,31 @@ describe('Pattern', () => {
|
||||||
|
|
||||||
const part2 = {
|
const part2 = {
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
from: part1,
|
after: part1,
|
||||||
draft: ({ part }) => part,
|
draft: ({ part }) => part,
|
||||||
}
|
}
|
||||||
|
|
||||||
const part3 = {
|
const part3 = {
|
||||||
name: 'test3',
|
name: 'test3',
|
||||||
after: part2,
|
from: part2,
|
||||||
draft: ({ part }) => part,
|
draft: ({ part }) => part,
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('with resolveImmediately: true', () => {
|
describe('with resolveImmediately: true', () => {
|
||||||
it('Should add the part to the internal part object', () => {
|
it('Should add the part to parts object', () => {
|
||||||
const design = new Design({ parts: [part1] })
|
const design = new Design({ parts: [part1] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.__init()
|
pattern.__init()
|
||||||
pattern.addPart(part2, true)
|
pattern.addPart(part2, true)
|
||||||
expect(pattern.__designParts.test2).to.equal(part2)
|
expect(pattern.config.parts.test2).to.equal(part2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should resolve injected dependencies for the new part', () => {
|
it('Should resolve injected dependencies for the new part', () => {
|
||||||
const design = new Design({ parts: [part1] })
|
const design = new Design({ parts: [part1] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.__init()
|
pattern.__init()
|
||||||
pattern.addPart(part2, true)
|
pattern.addPart(part3, true)
|
||||||
expect(pattern.__inject.test2).to.equal('test')
|
expect(pattern.config.inject.test3).to.equal('test2')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should resolve all dependencies for the new part', () => {
|
it('Should resolve all dependencies for the new part', () => {
|
||||||
|
@ -45,7 +45,7 @@ describe('Pattern', () => {
|
||||||
pattern.__init()
|
pattern.__init()
|
||||||
pattern.addPart(part3, true)
|
pattern.addPart(part3, true)
|
||||||
expect(pattern.config.resolvedDependencies.test3).to.have.members(['test', 'test2'])
|
expect(pattern.config.resolvedDependencies.test3).to.have.members(['test', 'test2'])
|
||||||
expect(pattern.__designParts.test2).to.equal(part2)
|
expect(pattern.config.parts.test2).to.equal(part2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should add a the measurements for the new part', () => {
|
it('Should add a the measurements for the new part', () => {
|
||||||
|
@ -79,7 +79,7 @@ describe('Pattern', () => {
|
||||||
expect(pattern.config.plugins.testPlugin).to.equal(plugin)
|
expect(pattern.config.plugins.testPlugin).to.equal(plugin)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should add the options for the new part', () => {
|
it('Should resolve the options for the new part', () => {
|
||||||
const design = new Design({ parts: [part1] })
|
const design = new Design({ parts: [part1] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.__init()
|
pattern.__init()
|
||||||
|
@ -96,6 +96,61 @@ describe('Pattern', () => {
|
||||||
pattern.addPart(part2, true)
|
pattern.addPart(part2, true)
|
||||||
expect(pattern.config.options.opt1).to.equal(opt1)
|
expect(pattern.config.options.opt1).to.equal(opt1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should resolve the dependency options for the new part', () => {
|
||||||
|
const design = new Design({ parts: [part1] })
|
||||||
|
const pattern = new design()
|
||||||
|
pattern.__init()
|
||||||
|
|
||||||
|
const opt1 = { pct: 10, min: 0, max: 50 }
|
||||||
|
const part2 = {
|
||||||
|
name: 'test2',
|
||||||
|
options: {
|
||||||
|
opt1,
|
||||||
|
},
|
||||||
|
draft: ({ part }) => part,
|
||||||
|
}
|
||||||
|
|
||||||
|
const part3 = {
|
||||||
|
name: 'test3',
|
||||||
|
from: part2,
|
||||||
|
draft: ({ part }) => part,
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern.addPart(part3, true)
|
||||||
|
expect(pattern.config.options.opt1).to.equal(opt1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should resolve the overwritten options for the new part', () => {
|
||||||
|
const design = new Design({ parts: [part1] })
|
||||||
|
const pattern = new design()
|
||||||
|
pattern.__init()
|
||||||
|
|
||||||
|
const opt1 = { pct: 10, min: 0, max: 50 }
|
||||||
|
const part2 = {
|
||||||
|
name: 'test2',
|
||||||
|
options: {
|
||||||
|
opt1: { pct: 15, min: 10, max: 55 },
|
||||||
|
},
|
||||||
|
draft: ({ part }) => part,
|
||||||
|
}
|
||||||
|
|
||||||
|
const part3 = {
|
||||||
|
name: 'test3',
|
||||||
|
from: part2,
|
||||||
|
options: {
|
||||||
|
opt1,
|
||||||
|
},
|
||||||
|
draft: ({ part }) => part,
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern.addPart(part3, true)
|
||||||
|
expect(pattern.config.options.opt1).to.equal(opt1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with resolveImmediately: false', () => {
|
||||||
|
it('does not create duplications in the configuration')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,6 +42,7 @@ describe('Snapped options', () => {
|
||||||
snap: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144],
|
snap: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
draft: ({ part }) => part,
|
||||||
}
|
}
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const patternA = new design({ options: { test: 0.13 }, measurements }).draft()
|
const patternA = new design({ options: { test: 0.13 }, measurements }).draft()
|
||||||
|
@ -67,6 +68,7 @@ describe('Snapped options', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
draft: ({ part }) => part,
|
||||||
}
|
}
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const patternA = new design({ options: { test: 0.13 }, measurements, units: 'metric' }).draft()
|
const patternA = new design({ options: { test: 0.13 }, measurements, units: 'metric' }).draft()
|
||||||
|
@ -94,6 +96,7 @@ describe('Snapped options', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
draft: ({ part }) => part,
|
||||||
}
|
}
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const patternA = new design({
|
const patternA = new design({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue