Merge pull request #3564 from eriese/eriese-progressive-parts
feature (Pattern Config Resolution) Resolve Part Configurations Progressively
This commit is contained in:
commit
31873b5f96
11 changed files with 1248 additions and 623 deletions
|
@ -53,7 +53,7 @@ charlie:
|
|||
core:
|
||||
_:
|
||||
'bezier-js': '6.1.0'
|
||||
'bin-pack': '1.0.2'
|
||||
'bin-pack-with-constraints': '1.0.1'
|
||||
'hooks': '0.3.2'
|
||||
'lodash.get': &_get '4.4.2'
|
||||
'lodash.set': &_set '4.3.2'
|
||||
|
@ -62,6 +62,9 @@ core:
|
|||
dev:
|
||||
'eslint': &eslint '8.34.0'
|
||||
'nyc': '15.1.0'
|
||||
'mocha': *mocha
|
||||
'chai': *chai
|
||||
'sinon': &sinon '^15.0.1'
|
||||
diana:
|
||||
peer:
|
||||
'@freesewing/brian': *freesewing
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"peerDependencies": {},
|
||||
"dependencies": {
|
||||
"bezier-js": "6.1.0",
|
||||
"bin-pack": "1.0.2",
|
||||
"bin-pack-with-constraints": "1.0.1",
|
||||
"hooks": "0.3.2",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.set": "4.3.2",
|
||||
|
@ -60,7 +60,10 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.34.0",
|
||||
"nyc": "15.1.0"
|
||||
"nyc": "15.1.0",
|
||||
"mocha": "10.0.0",
|
||||
"chai": "4.2.0",
|
||||
"sinon": "^15.0.1"
|
||||
},
|
||||
"files": [
|
||||
"dist/*",
|
||||
|
|
496
packages/core/src/pattern-config.mjs
Normal file
496
packages/core/src/pattern-config.mjs
Normal file
|
@ -0,0 +1,496 @@
|
|||
import { __addNonEnumProp } from './utils.mjs'
|
||||
|
||||
/**
|
||||
* Get the name of the given plugin config
|
||||
*
|
||||
* @param {(Object|Object[])} plugin the plugin to get the name of
|
||||
* @return {(string|false)} the name, or false if there isn't one
|
||||
*/
|
||||
export function getPluginName(plugin) {
|
||||
const toCheck = Array.isArray(plugin) ? plugin[0] : plugin
|
||||
return toCheck.name || toCheck.plugin?.name || false
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// CONSTRUCTOR //
|
||||
/////////////////
|
||||
/**
|
||||
* A class for handling config resolution for a Pattern
|
||||
* @class
|
||||
* @param {Pattern} pattern the pattern whose config is being handled
|
||||
*/
|
||||
export function PatternConfig(pattern) {
|
||||
/** @type {Store} the pattern's store, for logging */
|
||||
this.store = pattern.store
|
||||
|
||||
/** @type {Object} resolved plugins keyed by name */
|
||||
this.plugins = { ...(pattern.designConfig.plugins || {}) }
|
||||
/** @type {Object} resolved options keyed by name */
|
||||
this.options = { ...(pattern.designConfig.options || {}) }
|
||||
/** @type {string[]} required measurements */
|
||||
this.measurements = [...(pattern.designConfig.measurements || [])]
|
||||
/** @type {string[]} optional measurements */
|
||||
this.optionalMeasurements = [...(pattern.designConfig.optionalMeasurements || [])]
|
||||
/** @type {Object} the names of the parts that will be injected */
|
||||
this.inject = {}
|
||||
/** @type {Object} arrays of parts that are direct dependencies of the key */
|
||||
this.directDependencies = {}
|
||||
/** @type {Object} arrays of all dependencies of the key */
|
||||
this.resolvedDependencies = {}
|
||||
/** @type {Object} parts to include in the pattern */
|
||||
this.parts = {}
|
||||
/** @type {Object} which parts are hidden */
|
||||
this.partHide = {}
|
||||
|
||||
/** @type {Object} to track when to overwrite options */
|
||||
__addNonEnumProp(this, '__mutated', {
|
||||
optionDistance: {},
|
||||
partDistance: {},
|
||||
})
|
||||
|
||||
/** @type {Object} tracking for dependency hiding */
|
||||
__addNonEnumProp(this, '__hiding', {
|
||||
all: {},
|
||||
deps: {},
|
||||
})
|
||||
}
|
||||
|
||||
/** @type {Boolean} change me to true to get full debugging of the resolution process */
|
||||
const DISTANCE_DEBUG = false
|
||||
|
||||
////////////////////
|
||||
// PUBLIC METHODs //
|
||||
////////////////////
|
||||
|
||||
/**
|
||||
* Validate that a part meets the requirements to be added to the pattern
|
||||
* @param {Object} part a part configuration
|
||||
* @return {boolean} whether the part is valid
|
||||
*/
|
||||
PatternConfig.prototype.isPartValid = 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Chainable method to add a part to the configuration
|
||||
* @param {Object} part
|
||||
*/
|
||||
PatternConfig.prototype.addPart = function (part) {
|
||||
if (this.isPartValid(part)) this.__addPart([part])
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Log the final report on part inheritance order */
|
||||
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]}__`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a configuration in the structure expected by the pattern
|
||||
* @return {Object} contains parts, plugins, measurements, options, optionalMeasurements, resolvedDependencies, directDependencies, inject, draftOrder, partHide
|
||||
*/
|
||||
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.partHide,
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// PRIVATE METHODS //
|
||||
/////////////////////
|
||||
|
||||
/**
|
||||
* Add a part's configuration
|
||||
* Uses recursion to also add that part's dependencies
|
||||
* @private
|
||||
* @param {Object[]} depChain an array starting with the current part to add and containing its dependents/descendents in order
|
||||
*/
|
||||
PatternConfig.prototype.__addPart = function (depChain) {
|
||||
// the current part is the head of the chain
|
||||
const part = depChain[0]
|
||||
// the longer the chain, the deeper the part is down it
|
||||
const distance = depChain.length
|
||||
if (!this.parts[part.name]) this.parts[part.name] = Object.freeze(part)
|
||||
else return
|
||||
|
||||
// if it hasn't been registered with a distance, do that now
|
||||
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]}__`
|
||||
)
|
||||
}
|
||||
|
||||
// Handle various hiding possibilities
|
||||
if (part.hide || part.hideAll) this.partHide[part.name] = true
|
||||
if (part.hideDependencies) this.__hiding.deps[part.name] = true
|
||||
if (part.hideAll) {
|
||||
this.__hiding.all[part.name] = true
|
||||
}
|
||||
|
||||
// resolve its dependencies
|
||||
this.__resolvePartDependencies(depChain)
|
||||
|
||||
// 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
|
||||
* @return this
|
||||
*/
|
||||
PatternConfig.prototype.__addPartConfig = function (part) {
|
||||
return this.__addPartOptions(part) // add options
|
||||
.__addPartMeasurements(part, false) // add required measurements
|
||||
.__addPartMeasurements(part, true) // add optional measurements
|
||||
.__addPartPlugins(part) // add plugins
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves/Adds a part's configured options to the global config
|
||||
*
|
||||
* @private
|
||||
* @param {Part} part - The part of which to resolve the config
|
||||
* @return {PatternConfig} this - The PatternConfig instance
|
||||
*/
|
||||
PatternConfig.prototype.__addPartOptions = function (part) {
|
||||
// skip empty options
|
||||
if (!part.options) return this
|
||||
|
||||
// get the part's option priority
|
||||
const partDistance = this.__mutated.partDistance?.[part.name] || 0
|
||||
|
||||
// loop through options
|
||||
for (const optionName in part.options) {
|
||||
const option = part.options[optionName]
|
||||
// get the priority of this option's current registration
|
||||
const optionDistance = this.__mutated.optionDistance[optionName]
|
||||
// debug the comparison
|
||||
if (optionDistance && DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
`optionDistance for __${optionName}__ is __${optionDistance}__ and partDistance for \`${part.name}\` is __${partDistance}__`
|
||||
)
|
||||
|
||||
// if it's never been registered, or it's registered at a further distance
|
||||
if (!optionDistance || optionDistance > partDistance) {
|
||||
// Keep options immutable in the pattern or risk subtle bugs
|
||||
this.options[optionName] = Object.freeze(option)
|
||||
// register the new distance
|
||||
this.__mutated.optionDistance[optionName] = partDistance
|
||||
// debug appropriately
|
||||
this.store.log.debug(
|
||||
optionDistance
|
||||
? `🟣 __${optionName}__ option overwritten by \`${part.name}\``
|
||||
: `🔵 __${optionName}__ option loaded from part \`${part.name}\``
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 {boolean} optional - are these measurements optional?
|
||||
* @return {PatternConfig} this - The PatternConfig instance
|
||||
*/
|
||||
PatternConfig.prototype.__addPartMeasurements = function (part, optional = false) {
|
||||
// which list are we drawing from?
|
||||
const listType = optional ? 'optionalMeasurements' : 'measurements'
|
||||
// if the part has measurements of this type, go through them
|
||||
if (part[listType]) {
|
||||
part[listType].forEach((m) => {
|
||||
// we need to know what lists it's already present on
|
||||
const isInReqList = this.measurements.indexOf(m) !== -1
|
||||
// if it's already registered as required, we're done here
|
||||
if (isInReqList) return
|
||||
|
||||
// check if it's registered as optional
|
||||
const optInd = this.optionalMeasurements.indexOf(m)
|
||||
const isInOptList = optInd !== -1
|
||||
|
||||
// if it is optional and not in the list, push it
|
||||
if (optional && !isInOptList) this.optionalMeasurements.push(m)
|
||||
// if it's not optional
|
||||
if (!optional) {
|
||||
// push it to required list
|
||||
this.measurements.push(m)
|
||||
|
||||
// make sure it's not also registered as optional
|
||||
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 {PatternConfig} this - The PatternConfig 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
|
||||
}
|
||||
|
||||
// the two types of dependencies
|
||||
const depTypes = ['from', 'after']
|
||||
|
||||
/**
|
||||
* Recursively register part dependencies
|
||||
* triggers {@link __addPart} on new parts found during resolution
|
||||
* @param {Object[]} depChain an array starting with the current part to register and containing its dependents/descendents in order
|
||||
* @return {PatternConfig} this
|
||||
* @private
|
||||
*/
|
||||
PatternConfig.prototype.__resolvePartDependencies = function (depChain) {
|
||||
// the current part is the head of the chain
|
||||
const part = depChain[0]
|
||||
// get or make its array of resolved dependencies
|
||||
this.resolvedDependencies[part.name] = this.resolvedDependencies[part.name] || []
|
||||
|
||||
// for each dependency type (from, after)
|
||||
depTypes.forEach((d) => {
|
||||
// if the part has dependencies of that type
|
||||
if (part[d]) {
|
||||
if (DISTANCE_DEBUG) this.store.log.debug(`Processing \`${part.name}\` "${d}:"`)
|
||||
|
||||
// enforce an array
|
||||
const depsOfType = Array.isArray(part[d]) ? part[d] : [part[d]]
|
||||
|
||||
// each dependency
|
||||
depsOfType.forEach((dot) => {
|
||||
// add it as a direct dependency of the current part
|
||||
this.__addDependency('directDependencies', part.name, dot.name)
|
||||
// add it as a resolved dependency of all parts in the chain
|
||||
depChain.forEach((c) => this.__addDependency('resolvedDependencies', c.name, dot.name))
|
||||
|
||||
// handle hiding and injecting
|
||||
this.__handlePartDependencyOfType(part, dot.name, d)
|
||||
|
||||
// if the dependency isn't registered, register it
|
||||
if (!this.parts[dot.name]) {
|
||||
// add the part's configuration. this will recursively add the part's dependencies to all parts in the chain
|
||||
this.__addPart([dot, ...depChain])
|
||||
} else {
|
||||
// if it's already registered, recursion won't happen, but we still need to add its resolved dependencies to all parts in the chain
|
||||
this.resolvedDependencies[dot.name].forEach((r) => {
|
||||
depChain.forEach((c) => this.__addDependency('resolvedDependencies', c.name, r))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// now that the chain has been registered, recalculate the part distances
|
||||
this.__resolveMutatedPartDistance(part.name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a part as either a direct or a resolved dependency
|
||||
* @param {string} dependencyList which list to add the part to, 'resolvedDependencies' or 'directDependencies'
|
||||
* @param {string} partName the name of the part to add the dependency to in the list
|
||||
* @param {string} depName the name of the dependency to add to the list
|
||||
* @private
|
||||
*/
|
||||
PatternConfig.prototype.__addDependency = function (dependencyList, partName, depName) {
|
||||
this[dependencyList][partName] = this[dependencyList][partName] || []
|
||||
if (dependencyList == 'resolvedDependencies' && DISTANCE_DEBUG)
|
||||
this.store.log.debug(`add ${depName} to ${partName} dependencyResolution`)
|
||||
if (this[dependencyList][partName].indexOf(depName) === -1)
|
||||
this[dependencyList][partName].push(depName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dependency-type specific config business
|
||||
* @param {Object} part the part to add the dependency to
|
||||
* @param {string} depName the name of the dependency to add
|
||||
* @param {string} depType the type of dependency, 'from' or 'after'
|
||||
* @private
|
||||
*/
|
||||
PatternConfig.prototype.__handlePartDependencyOfType = function (part, depName, depType) {
|
||||
switch (depType) {
|
||||
case 'from':
|
||||
this.__setFromHide(part, depName)
|
||||
this.inject[part.name] = depName
|
||||
break
|
||||
case 'after':
|
||||
this.__setAfterHide(part, depName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve part option priority
|
||||
* Recursively bumps priorities down the dependency chain
|
||||
* @param {string} partName the name of the part to resolve
|
||||
* @private
|
||||
*/
|
||||
PatternConfig.prototype.__resolveMutatedPartDistance = function (partName) {
|
||||
// if the part has no dependencies, bail
|
||||
if (!this.directDependencies[partName]) return
|
||||
|
||||
// propose that each of the part's direct dependencies should be at a distance 1 further than the part's distance
|
||||
const proposed_dependency_distance = this.__mutated.partDistance[partName] + 1
|
||||
// check each direct dependency
|
||||
this.directDependencies[partName].forEach((dependency) => {
|
||||
// if the dependency doesn't have a distance, or that distance is less than the proposal
|
||||
if (
|
||||
typeof this.__mutated.partDistance[dependency] === 'undefined' ||
|
||||
this.__mutated.partDistance[dependency] < proposed_dependency_distance
|
||||
) {
|
||||
// set the new distance
|
||||
this.__mutated.partDistance[dependency] = proposed_dependency_distance
|
||||
// bump the dependency's dependencies as well
|
||||
this.__resolveMutatedPartDistance(dependency)
|
||||
}
|
||||
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
`partDistance for \`${dependency}\` is __${this.__mutated.partDistance[dependency]}__`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'from' dependency based on its config
|
||||
*
|
||||
* @private
|
||||
* @param {Part} part - The part of which this is a dependency
|
||||
* @param {string} depName - The name of the dependency
|
||||
* @return {Pattern} this - The Pattern instance
|
||||
*/
|
||||
PatternConfig.prototype.__setFromHide = function (part, depName) {
|
||||
if (this.__hiding.deps[part.name]) {
|
||||
this.partHide[depName] = true
|
||||
this.__hiding.deps[depName] = true
|
||||
}
|
||||
if (this.__hiding.all[part.name]) {
|
||||
this.partHide[depName] = true
|
||||
this.__hiding.all[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} 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, depName) {
|
||||
if (this.__hiding.all[part.name]) {
|
||||
this.partHide[depName] = true
|
||||
this.__hiding.all[depName] = true
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
77
packages/core/src/pattern-draft-queue.mjs
Normal file
77
packages/core/src/pattern-draft-queue.mjs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* A queue for handling the draft order of pattern parts
|
||||
* Unlike most queues, traversing this queue is non-destructive
|
||||
* so that the queue can be traversed many times.
|
||||
* The goal is to allow the queue to be manipulated while being traversed
|
||||
* @class
|
||||
* @param {Pattern} pattern the pattern that will use the queue
|
||||
*/
|
||||
export function PatternDraftQueue(pattern) {
|
||||
// save the config resolver
|
||||
this.__configResolver = pattern.__configResolver
|
||||
// get the draft order in its current state
|
||||
this.queue = this.__resolveDraftOrder()
|
||||
// start at 0
|
||||
this.start()
|
||||
}
|
||||
|
||||
/** Go back to the beginning of the queue */
|
||||
PatternDraftQueue.prototype.start = function () {
|
||||
this.queueIndex = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a part to end of the queue. Useful for queueing up parts a draft time
|
||||
* @param {string} partName the name to the part to add
|
||||
*/
|
||||
PatternDraftQueue.prototype.addPart = function (partName) {
|
||||
if (!this.contains(partName)) this.queue.push(partName)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the queue has a next part without moving the queue index
|
||||
* @return {Boolean} whether there is a next part in the queue
|
||||
*/
|
||||
PatternDraftQueue.prototype.hasNext = function () {
|
||||
return this.queueIndex < this.queue.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next part in the queue without moving the queue index
|
||||
* @return {string} the next part in the queue
|
||||
*/
|
||||
PatternDraftQueue.prototype.peek = function () {
|
||||
return this.queue[this.queueIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next part in the queue and move the queue index
|
||||
* @return {string} the next part in the queue
|
||||
*/
|
||||
PatternDraftQueue.prototype.next = function () {
|
||||
const next = this.peek()
|
||||
this.queueIndex++
|
||||
return next
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a part is already queued
|
||||
* @param {string} partName the name of the part
|
||||
* @return {boolean} whether the part is in the queue
|
||||
*/
|
||||
PatternDraftQueue.prototype.contains = function (partName) {
|
||||
return this.queue.indexOf(partName) !== -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the draft order based on the configuation
|
||||
* @private
|
||||
* @return A list of parts in the order they should be drafted
|
||||
*/
|
||||
PatternDraftQueue.prototype.__resolveDraftOrder = function () {
|
||||
const partDistances = this.__configResolver.__mutated.partDistance
|
||||
return Object.keys(this.__configResolver.parts).sort(
|
||||
(p1, p2) => partDistances[p2] - partDistances[p1]
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Attributes } from './attributes.mjs'
|
||||
import pack from 'bin-pack'
|
||||
import pack from 'bin-pack-with-constraints'
|
||||
import { __addNonEnumProp, __macroName } from './utils.mjs'
|
||||
import { Part } from './part.mjs'
|
||||
import { Stack } from './stack.mjs'
|
||||
|
@ -11,10 +11,10 @@ import { Store } from './store.mjs'
|
|||
import { Hooks } from './hooks.mjs'
|
||||
import { version } from '../data.mjs'
|
||||
import { __loadPatternDefaults } from './config.mjs'
|
||||
import { PatternConfig, getPluginName } from './pattern-config.mjs'
|
||||
import { PatternDraftQueue } from './pattern-draft-queue.mjs'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
|
||||
const DISTANCE_DEBUG = false
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
@ -26,7 +26,13 @@ const DISTANCE_DEBUG = false
|
|||
* @param {object} config - The Design config
|
||||
* @return {object} this - The Pattern instance
|
||||
*/
|
||||
export function Pattern(designConfig) {
|
||||
export function Pattern(designConfig = {}) {
|
||||
// Enumerable properties
|
||||
this.designConfig = cloneDeep(designConfig) // The design configuration (unresolved)
|
||||
this.config = {} // Will hold the resolved pattern after calling __init()
|
||||
this.store = new Store() // Pattern-wide store
|
||||
this.setStores = [] // Per-set stores
|
||||
|
||||
// Non-enumerable properties
|
||||
__addNonEnumProp(this, 'plugins', {})
|
||||
__addNonEnumProp(this, 'width', 0)
|
||||
|
@ -40,26 +46,10 @@ export function Pattern(designConfig) {
|
|||
__addNonEnumProp(this, 'Attributes', Attributes)
|
||||
__addNonEnumProp(this, 'macros', {})
|
||||
__addNonEnumProp(this, '__initialized', false)
|
||||
__addNonEnumProp(this, '__designParts', {})
|
||||
__addNonEnumProp(this, '__inject', {})
|
||||
__addNonEnumProp(this, '__dependencies', {})
|
||||
__addNonEnumProp(this, '__resolvedDependencies', {})
|
||||
__addNonEnumProp(this, '__resolvedParts', [])
|
||||
__addNonEnumProp(this, 'config.parts', {})
|
||||
__addNonEnumProp(this, 'config.resolvedDependencies', {})
|
||||
__addNonEnumProp(this, '__storeMethods', new Set())
|
||||
__addNonEnumProp(this, '__mutated', {
|
||||
optionDistance: {},
|
||||
partDistance: {},
|
||||
partHide: {},
|
||||
partHideAll: {},
|
||||
})
|
||||
__addNonEnumProp(this, '__draftOrder', [])
|
||||
__addNonEnumProp(this, '__hide', {})
|
||||
|
||||
// Enumerable properties
|
||||
this.designConfig = cloneDeep(designConfig) // The design configuration (unresolved)
|
||||
this.config = {} // Will hold the resolved pattern after calling __init()
|
||||
this.store = new Store() // Pattern-wide store
|
||||
this.setStores = [] // Per-set stores
|
||||
__addNonEnumProp(this, '__configResolver', new PatternConfig(this)) // handles config resolution during __init() as well as runtime part adding
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -69,19 +59,26 @@ export function Pattern(designConfig) {
|
|||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* FIXME: Allows adding parts to the config at runtime
|
||||
* Allows adding parts to the config at runtime
|
||||
*
|
||||
* @param {object} part - The part to add
|
||||
* @param {boolean} resolveImmediately - Should the part be resolved now, or wait until the next call to {@link __init()}?
|
||||
* It is useful to resolve immediately if one part is being added at runtime
|
||||
* It might be useful to not resolve immediately if a number of parts will be added over multiple calls
|
||||
* @return {object} this - The Pattern instance
|
||||
*/
|
||||
Pattern.prototype.addPart = function (part) {
|
||||
if (typeof part?.draft === 'function') {
|
||||
if (part.name) {
|
||||
this.designConfig.parts.push(part)
|
||||
this.__initialized = false
|
||||
} else this.store.log.error(`Part must have a name`)
|
||||
} else this.store.log.error(`Part must have a draft() method`)
|
||||
|
||||
Pattern.prototype.addPart = function (part, resolveImmediately = true) {
|
||||
if (
|
||||
this.__configResolver.isPartValid(part) &&
|
||||
!this.designConfig.parts.find((p) => p.name == part.name)
|
||||
) {
|
||||
this.store.log.debug(`Adding Part \`${part.name}\` at runtime`)
|
||||
this.designConfig.parts.push(part)
|
||||
if (resolveImmediately) {
|
||||
if (this.__configResolver.addPart(part) && typeof this.draftQueue !== 'undefined')
|
||||
this.draftQueue.addPart(part.name)
|
||||
} else this.__initialized = false
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -92,6 +89,7 @@ Pattern.prototype.addPart = function (part) {
|
|||
*/
|
||||
Pattern.prototype.draft = function () {
|
||||
this.__init()
|
||||
this.draftQueue = new PatternDraftQueue(this)
|
||||
this.__runHooks('preDraft')
|
||||
// Keep container for drafted parts fresh
|
||||
this.parts = []
|
||||
|
@ -110,8 +108,9 @@ Pattern.prototype.draft = function () {
|
|||
// Handle snap for pct options
|
||||
this.__loadAbsoluteOptionsSet(set)
|
||||
|
||||
for (const partName of this.config.draftOrder) {
|
||||
this.createPartForSet(partName, set)
|
||||
this.draftQueue.start()
|
||||
while (this.draftQueue.hasNext()) {
|
||||
this.createPartForSet(this.draftQueue.next(), set)
|
||||
}
|
||||
this.__runHooks('postSetDraft')
|
||||
}
|
||||
|
@ -130,15 +129,15 @@ Pattern.prototype.createPartForSet = function (partName, set = 0) {
|
|||
this.parts[set][partName] = this.__createPartWithContext(partName, set)
|
||||
|
||||
// Handle inject/inheritance
|
||||
if (typeof this.__inject[partName] === 'string') {
|
||||
if (typeof this.config.inject[partName] === 'string') {
|
||||
this.setStores[set].log.debug(
|
||||
`Creating part \`${partName}\` from part \`${this.__inject[partName]}\``
|
||||
`Creating part \`${partName}\` from part \`${this.config.inject[partName]}\``
|
||||
)
|
||||
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) {
|
||||
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,
|
||||
])
|
||||
}
|
||||
|
@ -159,11 +158,11 @@ Pattern.prototype.createPartForSet = function (partName, set = 0) {
|
|||
}
|
||||
|
||||
Pattern.prototype.draftPartForSet = function (partName, set) {
|
||||
if (typeof this.__designParts?.[partName]?.draft === 'function') {
|
||||
if (typeof this.config.parts?.[partName]?.draft === 'function') {
|
||||
this.activePart = partName
|
||||
try {
|
||||
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')
|
||||
if (typeof result === 'undefined') {
|
||||
this.setStores[set].log.error(
|
||||
|
@ -359,227 +358,6 @@ Pattern.prototype.use = function (plugin, data) {
|
|||
// 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 (name, part, dep) {
|
||||
this.__dependencies[name] = mergeDependencies(dep.name, this.__dependencies[name])
|
||||
if (typeof this.__designParts[dep.name] === 'undefined') {
|
||||
this.config = this.__addPartConfig(this.__designParts[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.__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}\``)
|
||||
}
|
||||
}
|
||||
}
|
||||
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.options) this.config.options = {}
|
||||
if (part.options) {
|
||||
for (const optionName in part.options) {
|
||||
if (!this.__mutated.optionDistance[optionName]) {
|
||||
this.__mutated.optionDistance[optionName] = this.__mutated.partDistance?.[part.name] || 0
|
||||
// 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 ' +
|
||||
this.__mutated.optionDistance[optionName] +
|
||||
', and partDistance for ' +
|
||||
part.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[part.name]
|
||||
)
|
||||
if (this.__mutated.optionDistance[optionName] > this.__mutated.partDistance[part.name]) {
|
||||
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
|
||||
}
|
||||
|
||||
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.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.config.plugins = { ...plugins }
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a store for a set (of settings)
|
||||
*
|
||||
|
@ -636,7 +414,7 @@ Pattern.prototype.__createPartWithContext = function (name, set) {
|
|||
const part = new Part()
|
||||
part.name = name
|
||||
part.set = set
|
||||
part.stack = this.__designParts[name]?.stack || name
|
||||
part.stack = this.config.parts[name]?.stack || name
|
||||
part.context = {
|
||||
parts: this.parts[set],
|
||||
config: this.config,
|
||||
|
@ -676,24 +454,6 @@ Pattern.prototype.__createStackWithContext = function (name) {
|
|||
return stack
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter optional measurements out id they are also required measurments
|
||||
*
|
||||
* @private
|
||||
* @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
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the pattern coniguration and settings
|
||||
*
|
||||
|
@ -716,14 +476,12 @@ Pattern.prototype.__init = function () {
|
|||
* This methods does that, and resolves the design config + user settings
|
||||
*/
|
||||
this.__resolveParts() // Resolves parts
|
||||
.__resolveDependencies() // Resolves dependencies
|
||||
.__resolveDraftOrder() // Resolves draft order
|
||||
.__resolveConfig() // Gets the config from the resolver
|
||||
.__loadOptionDefaults() // Merges default options with user provided ones
|
||||
.__loadPlugins() // Loads plugins
|
||||
.__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.__initialized = true
|
||||
|
@ -739,16 +497,13 @@ Pattern.prototype.__init = function () {
|
|||
* @return {bool} hidden - true if the part is hidden, or false if not
|
||||
*/
|
||||
Pattern.prototype.__isPartHidden = function (partName) {
|
||||
const partHidden = this.parts?.[this.activeSet]?.[partName]?.hidden || false
|
||||
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 partHidden
|
||||
}
|
||||
if (this.__designParts?.[partName]?.hide) return true
|
||||
if (this.__designParts?.[partName]?.hideAll) return true
|
||||
if (this.__mutated.partHide?.[partName]) return true
|
||||
if (this.__mutated.partHideAll?.[partName]) return true
|
||||
if (this.parts?.[this.activeSet]?.[partName]?.hidden) return true
|
||||
if (this.config.partHide?.[partName]) return true
|
||||
|
||||
return false
|
||||
return partHidden
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -761,21 +516,11 @@ Pattern.prototype.__isPartHidden = function (partName) {
|
|||
Pattern.prototype.__isStackHidden = function (stackName) {
|
||||
if (!this.stacks[stackName]) return true
|
||||
const parts = this.stacks[stackName].getPartNames()
|
||||
if (Array.isArray(this.settings[this.activeStack || 0].only)) {
|
||||
for (const partName of parts) {
|
||||
if (this.settings[this.activeStack || 0].only.includes(partName)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
for (const partName of parts) {
|
||||
if (this.__designParts?.[partName]?.hide) return true
|
||||
if (this.__designParts?.[partName]?.hideAll) return true
|
||||
if (this.__mutated.partHide?.[partName]) return true
|
||||
if (this.__mutated.partHideAll?.[partName]) return true
|
||||
if (this.parts?.[this.activeSet]?.[partName]?.hidden) return true
|
||||
if (!this.__isPartHidden(partName)) return false
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1075,11 +820,7 @@ Pattern.prototype.__needs = function (partName, set = 0) {
|
|||
// Walk the only parts, checking each one for a match in its dependencies
|
||||
for (const part of only) {
|
||||
if (part === partName) return true
|
||||
if (this.__resolvedDependencies[part]) {
|
||||
for (const dependency of this.__resolvedDependencies[part]) {
|
||||
if (dependency === partName) return true
|
||||
}
|
||||
}
|
||||
if (this.config.resolvedDependencies[part]?.indexOf(partName) !== -1) return true
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -1179,7 +920,8 @@ Pattern.prototype.__pack = function () {
|
|||
}
|
||||
}
|
||||
if (this.settings[0].layout === true) {
|
||||
let size = pack(bins, { inPlace: true })
|
||||
// some plugins will add a width constraint to the settings, but we can safely pass undefined if not
|
||||
let size = pack(bins, { inPlace: true, maxWidth: this.settings[0].maxWidth })
|
||||
for (let bin of bins) {
|
||||
this.autoLayout.stacks[bin.id] = { move: {} }
|
||||
let stack = this.stacks[bin.id]
|
||||
|
@ -1210,65 +952,12 @@ Pattern.prototype.__pack = function () {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recursively solves part dependencies for a part
|
||||
*
|
||||
* Gets the configuration for the config resolver and sets it on the pattern
|
||||
* @private
|
||||
* @param {object} seen - Object to keep track of seen dependencies
|
||||
* @param {string} part - Name of the part
|
||||
* @param {object} graph - Dependency graph, used to call itself recursively
|
||||
* @param {array} deps - List of dependencies
|
||||
* @return {Array} deps - The list of dependencies
|
||||
* @return {Pattern} this - The Pattern instance
|
||||
*/
|
||||
Pattern.prototype.__resolveDependency = function (
|
||||
seen,
|
||||
part,
|
||||
graph = this.dependencies,
|
||||
deps = []
|
||||
) {
|
||||
if (typeof seen[part] === 'undefined') seen[part] = true
|
||||
if (typeof graph[part] === 'string') graph[part] = [graph[part]]
|
||||
if (Array.isArray(graph[part])) {
|
||||
if (graph[part].length === 0) return []
|
||||
else {
|
||||
if (deps.indexOf(graph[part]) === -1) deps.push(...graph[part])
|
||||
for (let apart of graph[part]) deps.concat(this.__resolveDependency(seen, apart, graph, deps))
|
||||
}
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDependencies) {
|
||||
let sorted = []
|
||||
let visited = {}
|
||||
Object.keys(graph).forEach(function visit(name, ancestors) {
|
||||
if (!Array.isArray(ancestors)) ancestors = []
|
||||
ancestors.push(name)
|
||||
visited[name] = true
|
||||
if (typeof graph[name] !== 'undefined') {
|
||||
graph[name].forEach(function (dep) {
|
||||
if (visited[dep]) return
|
||||
visit(dep, ancestors.slice(0))
|
||||
})
|
||||
}
|
||||
if (sorted.indexOf(name) < 0) sorted.push(name)
|
||||
})
|
||||
|
||||
// Don't forget about parts without dependencies
|
||||
for (const part in this.__designParts) {
|
||||
if (sorted.indexOf(part) === -1) sorted.push(part)
|
||||
}
|
||||
|
||||
this.__draftOrder = sorted
|
||||
this.config.draftOrder = sorted
|
||||
|
||||
Pattern.prototype.__resolveConfig = function () {
|
||||
this.config = this.__configResolver.asConfig()
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -1276,154 +965,13 @@ Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDepende
|
|||
* Resolves parts and their dependencies
|
||||
*
|
||||
* @private
|
||||
* @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.designConfig.parts) {
|
||||
// Keep design parts immutable in the pattern or risk subtle bugs
|
||||
this.__designParts[part.name] = Object.freeze(part)
|
||||
}
|
||||
}
|
||||
distance++
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Distance incremented to ' + distance)
|
||||
for (const part of this.designConfig.parts) {
|
||||
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]
|
||||
)
|
||||
}
|
||||
}
|
||||
for (const [name, part] of Object.entries(this.__designParts)) {
|
||||
const current_part_distance = this.__mutated.partDistance[part.name]
|
||||
const proposed_dependent_part_distance = current_part_distance + 1
|
||||
// Hide when hideAll is set
|
||||
if (part.hideAll) this.__mutated.partHide[part.name] = true
|
||||
// Inject (from)
|
||||
if (part.from) {
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Processing ' + part.name + ' "from:"')
|
||||
this.__setFromHide(part, name, part.from.name)
|
||||
this.__designParts[part.from.name] = part.from
|
||||
this.__inject[name] = part.from.name
|
||||
if (
|
||||
typeof this.__mutated.partDistance[part.from.name] === 'undefined' ||
|
||||
this.__mutated.partDistance[part.from.name] < proposed_dependent_part_distance
|
||||
) {
|
||||
this.__mutated.partDistance[part.from.name] = proposed_dependent_part_distance
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'"from:" partDistance for ' +
|
||||
part.from.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[part.from.name]
|
||||
)
|
||||
}
|
||||
}
|
||||
// Simple dependency (after)
|
||||
if (part.after) {
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Processing ' + part.name + ' "after:"')
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) {
|
||||
this.__setAfterHide(part, name, dep.name)
|
||||
this.__designParts[dep.name] = dep
|
||||
this.__addDependency(name, part, dep)
|
||||
if (
|
||||
typeof this.__mutated.partDistance[dep.name] === 'undefined' ||
|
||||
this.__mutated.partDistance[dep.name] < proposed_dependent_part_distance
|
||||
) {
|
||||
this.__mutated.partDistance[dep.name] = proposed_dependent_part_distance
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'"after:" partDistance for ' +
|
||||
dep.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[dep.name]
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.__setAfterHide(part, name, part.after.name)
|
||||
this.__designParts[part.after.name] = part.after
|
||||
this.__addDependency(name, part, part.after)
|
||||
if (
|
||||
typeof this.__mutated.partDistance[part.after.name] === 'undefined' ||
|
||||
this.__mutated.partDistance[part.after.name] < proposed_dependent_part_distance
|
||||
) {
|
||||
this.__mutated.partDistance[part.after.name] = proposed_dependent_part_distance
|
||||
if (DISTANCE_DEBUG)
|
||||
this.store.log.debug(
|
||||
'"after:" partDistance for ' +
|
||||
part.after.name +
|
||||
' is ' +
|
||||
this.__mutated.partDistance[part.after.name]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Did we discover any new dependencies?
|
||||
const len = Object.keys(this.__designParts).length
|
||||
// If so, resolve recursively
|
||||
if (len > count) {
|
||||
if (DISTANCE_DEBUG) this.store.log.debug('Recursing...')
|
||||
return this.__resolveParts(len, distance)
|
||||
}
|
||||
Pattern.prototype.__resolveParts = function () {
|
||||
this.designConfig.parts.forEach((p) => this.__configResolver.addPart(p))
|
||||
|
||||
// Print final part distances.
|
||||
for (const part of this.designConfig.parts) {
|
||||
let qualifier = ''
|
||||
if (DISTANCE_DEBUG) qualifier = 'final '
|
||||
this.store.log.debug(
|
||||
'⚪️ `' +
|
||||
part.name +
|
||||
'` ' +
|
||||
qualifier +
|
||||
'options priority is __' +
|
||||
this.__mutated.partDistance[part.name] +
|
||||
'__'
|
||||
)
|
||||
}
|
||||
|
||||
for (const part of Object.values(this.__designParts)) this.__addPartConfig(part)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves parts depdendencies into a flat array
|
||||
*
|
||||
* @private
|
||||
* @param {object} graph - The graph is used to call itsels recursively
|
||||
* @return {Pattern} this - The Pattern instance
|
||||
*/
|
||||
Pattern.prototype.__resolveDependencies = function (graph = false) {
|
||||
if (!graph) graph = this.__dependencies
|
||||
for (const i in this.__inject) {
|
||||
const dependency = this.__inject[i]
|
||||
if (typeof this.__dependencies[i] === 'undefined') this.__dependencies[i] = dependency
|
||||
else if (this.__dependencies[i] !== dependency) {
|
||||
if (typeof this.__dependencies[i] === 'string') {
|
||||
this.__dependencies[i] = [this.__dependencies[i], dependency]
|
||||
} else if (Array.isArray(this.__dependencies[i])) {
|
||||
if (this.__dependencies[i].indexOf(dependency) === -1)
|
||||
this.__dependencies[i].push(dependency)
|
||||
} else {
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let resolved = {}
|
||||
let seen = {}
|
||||
for (let part in graph) resolved[part] = this.__resolveDependency(seen, part, graph)
|
||||
for (let part in seen) if (typeof resolved[part] === 'undefined') resolved[part] = []
|
||||
|
||||
this.__resolvedDependencies = resolved
|
||||
this.config.resolvedDependencies = resolved
|
||||
this.__configResolver.logPartDistances()
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -1461,49 +1009,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
|
||||
*
|
||||
|
@ -1590,34 +1095,3 @@ Pattern.prototype.__wants = function (partName, set = 0) {
|
|||
|
||||
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 = []
|
||||
if (Array.isArray(current)) list.push(...current)
|
||||
else if (typeof current === 'string') list.push(current)
|
||||
|
||||
if (Array.isArray(dep)) list.push(...dep)
|
||||
else if (typeof dep === 'string') list.push(dep)
|
||||
|
||||
// Dependencies should be parts names (string) not the object
|
||||
const deps = []
|
||||
for (const part of [...new Set(list)]) {
|
||||
if (typeof part === 'object') deps.push(part.name)
|
||||
else deps.push(part)
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ export function Store(methods = []) {
|
|||
logs.warning.push(...data)
|
||||
},
|
||||
error: function (...data) {
|
||||
if (typeof window !== 'undefined') console.error(...data)
|
||||
logs.error.push(...data)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@ describe('Pattern', () => {
|
|||
expect(typeof pattern.Snippet).to.equal('function')
|
||||
expect(typeof pattern.Attributes).to.equal('function')
|
||||
expect(typeof pattern.macros).to.equal('object')
|
||||
expect(typeof pattern.__designParts).to.equal('object')
|
||||
expect(typeof pattern.__inject).to.equal('object')
|
||||
expect(typeof pattern.__dependencies).to.equal('object')
|
||||
expect(typeof pattern.__resolvedDependencies).to.equal('object')
|
||||
expect(typeof pattern.__hide).to.equal('object')
|
||||
expect(Array.isArray(pattern.__draftOrder)).to.equal(true)
|
||||
// expect(typeof pattern.__designParts).to.equal('object')
|
||||
// expect(typeof pattern.config.inject).to.equal('object')
|
||||
// expect(typeof pattern.config.directDependencies).to.equal('object')
|
||||
// expect(typeof pattern.__resolvedDependencies).to.equal('object')
|
||||
// expect(typeof pattern.__hide).to.equal('object')
|
||||
// expect(Array.isArray(pattern.__draftOrder)).to.equal(true)
|
||||
expect(pattern.width).to.equal(0)
|
||||
expect(pattern.height).to.equal(0)
|
||||
expect(pattern.is).to.equal('')
|
||||
|
@ -111,7 +111,7 @@ describe('Pattern', () => {
|
|||
parts: [partC],
|
||||
})
|
||||
const pattern = new Pattern()
|
||||
pattern.draft()
|
||||
pattern.__init()
|
||||
|
||||
it('Pattern.__init() should resolve all measurements', () => {
|
||||
expect(
|
||||
|
@ -145,7 +145,7 @@ describe('Pattern', () => {
|
|||
})
|
||||
|
||||
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', () => {
|
||||
|
@ -153,8 +153,8 @@ describe('Pattern', () => {
|
|||
})
|
||||
|
||||
it('Pattern.__init() should set config data in the store', () => {
|
||||
expect(pattern.setStores[0].get('data.name')).to.equal('test')
|
||||
expect(pattern.setStores[0].get('data.version')).to.equal('1.2.3')
|
||||
expect(pattern.store.get('data.name')).to.equal('test')
|
||||
expect(pattern.store.get('data.version')).to.equal('1.2.3')
|
||||
})
|
||||
|
||||
it('Pattern.__init() should resolve dependencies', () => {
|
||||
|
@ -181,6 +181,89 @@ describe('Pattern', () => {
|
|||
expect(pattern.config.draftOrder[2]).to.equal('test.partC')
|
||||
})
|
||||
|
||||
it('Pattern.__init() should overwrite options from dependencies', () => {
|
||||
const partD = {
|
||||
name: 'test.partD',
|
||||
from: partB,
|
||||
options: {
|
||||
optB: { deg: 25, min: 15, max: 45 },
|
||||
},
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
const Pattern = new Design({
|
||||
data: {
|
||||
name: 'test',
|
||||
version: '1.2.3',
|
||||
},
|
||||
parts: [partD],
|
||||
})
|
||||
const pattern = new Pattern()
|
||||
pattern.__init()
|
||||
for (const [key, value] of Object.entries(partD.options.optB)) {
|
||||
expect(pattern.config.options.optB[key]).to.equal(value)
|
||||
}
|
||||
})
|
||||
|
||||
it('Pattern.__init() should overwrite options from complex dependencies', () => {
|
||||
const partD = {
|
||||
name: 'test.partD',
|
||||
from: partB,
|
||||
options: {
|
||||
optB: { deg: 25, min: 15, max: 45 },
|
||||
},
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
const partE = {
|
||||
name: 'test.partE',
|
||||
from: partD,
|
||||
options: {
|
||||
optB: { deg: 10, min: 15, max: 50 },
|
||||
},
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
const Pattern = new Design({
|
||||
data: {
|
||||
name: 'test',
|
||||
version: '1.2.3',
|
||||
},
|
||||
parts: [partC, partE],
|
||||
})
|
||||
const pattern = new Pattern()
|
||||
pattern.__init()
|
||||
for (const [key, value] of Object.entries(partE.options.optB)) {
|
||||
expect(pattern.config.options.optB[key]).to.equal(value)
|
||||
}
|
||||
})
|
||||
|
||||
it('Pattern.__init() should resolve nested dependencies for multiple parts that depend on the same part', () => {
|
||||
const partD = {
|
||||
name: 'test.partD',
|
||||
from: partB,
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
const Pattern = new Design({
|
||||
data: {
|
||||
name: 'test',
|
||||
version: '1.2.3',
|
||||
},
|
||||
parts: [partC, partD],
|
||||
})
|
||||
const pattern = new Pattern()
|
||||
pattern.__init()
|
||||
expect(pattern.config.resolvedDependencies['test.partD']).to.have.members([
|
||||
'test.partA',
|
||||
'test.partB',
|
||||
])
|
||||
expect(pattern.config.resolvedDependencies['test.partC']).to.have.members([
|
||||
'test.partA',
|
||||
'test.partB',
|
||||
])
|
||||
})
|
||||
|
||||
// I am aware this does too much for one unit test, but this is to simplify TDD
|
||||
// we can split it up later
|
||||
it('Pattern.__init() should resolve nested injections', () => {
|
||||
|
@ -267,14 +350,14 @@ describe('Pattern', () => {
|
|||
expect(pattern.config.options.optionR.list[1]).to.equal('green')
|
||||
expect(pattern.config.options.optionR.list[2]).to.equal('blue')
|
||||
// Dependencies
|
||||
expect(pattern.__dependencies.partB[0]).to.equal('partA')
|
||||
expect(pattern.__dependencies.partC[0]).to.equal('partB')
|
||||
expect(pattern.__dependencies.partR[0]).to.equal('partC')
|
||||
expect(pattern.__dependencies.partR[1]).to.equal('partA')
|
||||
expect(pattern.config.directDependencies.partB).to.include('partA')
|
||||
expect(pattern.config.directDependencies.partC).to.include('partB')
|
||||
expect(pattern.config.directDependencies.partR).to.include('partC')
|
||||
expect(pattern.config.directDependencies.partR).to.include('partA')
|
||||
// Inject
|
||||
expect(pattern.__inject.partB).to.equal('partA')
|
||||
expect(pattern.__inject.partC).to.equal('partB')
|
||||
expect(pattern.__inject.partR).to.equal('partA')
|
||||
expect(pattern.config.inject.partB).to.equal('partA')
|
||||
expect(pattern.config.inject.partC).to.equal('partB')
|
||||
expect(pattern.config.inject.partR).to.equal('partA')
|
||||
// Draft order
|
||||
expect(pattern.config.draftOrder[0]).to.equal('partA')
|
||||
expect(pattern.config.draftOrder[1]).to.equal('partB')
|
||||
|
@ -415,12 +498,12 @@ describe('Pattern', () => {
|
|||
expect(pattern.config.options.optionD.list[1]).to.equal('green')
|
||||
expect(pattern.config.options.optionD.list[2]).to.equal('blue')
|
||||
// Dependencies
|
||||
expect(pattern.__dependencies.partB[0]).to.equal('partA')
|
||||
expect(pattern.__dependencies.partC[0]).to.equal('partB')
|
||||
expect(pattern.__dependencies.partD[0]).to.equal('partC')
|
||||
expect(pattern.config.directDependencies.partB[0]).to.equal('partA')
|
||||
expect(pattern.config.directDependencies.partC[0]).to.equal('partB')
|
||||
expect(pattern.config.directDependencies.partD[0]).to.equal('partC')
|
||||
// Inject
|
||||
expect(pattern.__inject.partB).to.equal('partA')
|
||||
expect(pattern.__inject.partC).to.equal('partB')
|
||||
expect(pattern.config.inject.partB).to.equal('partA')
|
||||
expect(pattern.config.inject.partC).to.equal('partB')
|
||||
// Draft order
|
||||
expect(pattern.config.draftOrder[0]).to.equal('partA')
|
||||
expect(pattern.config.draftOrder[1]).to.equal('partB')
|
||||
|
@ -528,7 +611,7 @@ describe('Pattern', () => {
|
|||
expect(pattern.hooks.preRender.length).to.equal(2)
|
||||
})
|
||||
|
||||
it('Pattern.__init() should load conditional plugin', () => {
|
||||
it('Pattern.__init() should load conditional plugin if condition is met', () => {
|
||||
const plugin = {
|
||||
name: 'example',
|
||||
version: 1,
|
||||
|
@ -550,7 +633,7 @@ describe('Pattern', () => {
|
|||
expect(pattern.hooks.preRender.length).to.equal(1)
|
||||
})
|
||||
|
||||
it('Pattern.__init() should not load conditional plugin', () => {
|
||||
it('Pattern.__init() should not load conditional plugin if condition is not mett', () => {
|
||||
const plugin = {
|
||||
name: 'example',
|
||||
version: 1,
|
||||
|
@ -606,6 +689,38 @@ describe('Pattern', () => {
|
|||
expect(pattern.hooks.preRender.length).to.equal(1)
|
||||
})
|
||||
|
||||
it('Pattern.__init() should load a conditional plugin multiple times with different conditions', () => {
|
||||
const plugin1 = {
|
||||
name: 'example1',
|
||||
version: 1,
|
||||
hooks: {
|
||||
preRender: function (svg) {
|
||||
svg.attributes.add('freesewing:plugin-example', 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const condition1 = () => true
|
||||
const condition2 = () => false
|
||||
const part = {
|
||||
name: 'test.part',
|
||||
plugins: [{ plugin: plugin1, condition: condition1 }],
|
||||
draft: (part) => part,
|
||||
}
|
||||
const part2 = {
|
||||
name: 'test.part2',
|
||||
plugins: [{ plugin: plugin1, condition: condition2 }],
|
||||
draft: (part) => part,
|
||||
}
|
||||
const design = new Design({ parts: [part, part2] })
|
||||
const pattern = new design()
|
||||
pattern.__init()
|
||||
expect(pattern.config.plugins).to.be.an('object').that.has.all.keys('example1', 'example1_')
|
||||
expect(pattern.config.plugins.example1.plugin).to.deep.equal(plugin1)
|
||||
expect(pattern.config.plugins.example1_.plugin).to.deep.equal(plugin1)
|
||||
expect(pattern.hooks.preRender.length).to.equal(1)
|
||||
})
|
||||
|
||||
it('Load conditional plugins that are also passing data', () => {
|
||||
const plugin1 = {
|
||||
name: 'example1',
|
||||
|
@ -699,6 +814,191 @@ describe('Pattern', () => {
|
|||
expect(count).to.equal(2)
|
||||
})
|
||||
|
||||
describe('Hiding parts', () => {
|
||||
const blankDraft = ({ part }) => part
|
||||
const afterPart = {
|
||||
name: 'afterPart',
|
||||
draft: blankDraft,
|
||||
}
|
||||
const fromPart = {
|
||||
name: 'fromPart',
|
||||
draft: blankDraft,
|
||||
}
|
||||
describe('{hide: true}', () => {
|
||||
const mainPart = {
|
||||
name: 'mainPart',
|
||||
after: afterPart,
|
||||
from: fromPart,
|
||||
hide: true,
|
||||
draft: blankDraft,
|
||||
}
|
||||
|
||||
const Test = new Design({
|
||||
name: 'test',
|
||||
parts: [mainPart],
|
||||
})
|
||||
|
||||
const pattern = new Test()
|
||||
pattern.__init()
|
||||
|
||||
it('Should hide the part', () => {
|
||||
expect(pattern.__isPartHidden('mainPart')).to.be.true
|
||||
})
|
||||
|
||||
it("Should not hide the part's dependencies", () => {
|
||||
expect(pattern.__isPartHidden('fromPart')).to.be.false
|
||||
expect(pattern.__isPartHidden('afterPart')).to.be.false
|
||||
})
|
||||
|
||||
describe('Nested Parts', () => {
|
||||
const mainPart = {
|
||||
name: 'mainPart',
|
||||
after: afterPart,
|
||||
from: fromPart,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const grandChild = {
|
||||
name: 'grandChild',
|
||||
from: mainPart,
|
||||
hide: true,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const Test = new Design({
|
||||
name: 'test',
|
||||
parts: [grandChild],
|
||||
})
|
||||
|
||||
const pattern = new Test()
|
||||
pattern.__init()
|
||||
|
||||
it('should not hide nested `from` dependencies', () => {
|
||||
expect(pattern.__isPartHidden('fromPart')).to.be.false
|
||||
expect(pattern.__isPartHidden('mainPart')).to.be.false
|
||||
})
|
||||
|
||||
it('should not hide nested `after` dependencies', () => {
|
||||
expect(pattern.__isPartHidden('afterPart')).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('{hideDependencies: true}', () => {
|
||||
const mainPart = {
|
||||
name: 'mainPart',
|
||||
hideDependencies: true,
|
||||
after: afterPart,
|
||||
from: fromPart,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const Test = new Design({
|
||||
name: 'test',
|
||||
parts: [mainPart],
|
||||
})
|
||||
|
||||
const pattern = new Test()
|
||||
pattern.__init()
|
||||
|
||||
it('Should not hide the part', () => {
|
||||
expect(pattern.__isPartHidden('mainPart')).to.be.false
|
||||
})
|
||||
it("Should hide the part's `from` dependencies", () => {
|
||||
expect(pattern.__isPartHidden('fromPart')).to.be.true
|
||||
})
|
||||
it("Should not hide the part's `after` dependencies", () => {
|
||||
expect(pattern.__isPartHidden('afterPart')).to.be.false
|
||||
})
|
||||
|
||||
describe('Nested Parts', () => {
|
||||
const mainPart = {
|
||||
name: 'mainPart',
|
||||
after: afterPart,
|
||||
from: fromPart,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const grandChild = {
|
||||
name: 'grandChild',
|
||||
from: mainPart,
|
||||
hideDependencies: true,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const Test = new Design({
|
||||
name: 'test',
|
||||
parts: [grandChild],
|
||||
})
|
||||
|
||||
const pattern = new Test()
|
||||
pattern.__init()
|
||||
|
||||
it('should hide nested `from` dependencies', () => {
|
||||
expect(pattern.__isPartHidden('fromPart')).to.be.true
|
||||
expect(pattern.__isPartHidden('mainPart')).to.be.true
|
||||
})
|
||||
|
||||
it('should not hide nested `after` dependencies', () => {
|
||||
expect(pattern.__isPartHidden('afterPart')).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('{hideAll: true}', () => {
|
||||
const mainPart = {
|
||||
name: 'mainPart',
|
||||
hideAll: true,
|
||||
after: afterPart,
|
||||
from: fromPart,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const Test = new Design({
|
||||
name: 'test',
|
||||
parts: [mainPart],
|
||||
})
|
||||
|
||||
const pattern = new Test()
|
||||
pattern.__init()
|
||||
|
||||
it('Should hide the part', () => {
|
||||
expect(pattern.__isPartHidden('mainPart')).to.be.true
|
||||
})
|
||||
it("Should hide the part's `from` dependencies", () => {
|
||||
expect(pattern.__isPartHidden('fromPart')).to.be.true
|
||||
})
|
||||
it("Should hide the part's `after` dependencies", () => {
|
||||
expect(pattern.__isPartHidden('afterPart')).to.be.true
|
||||
})
|
||||
|
||||
describe('Nested Parts', () => {
|
||||
const mainPart = {
|
||||
name: 'mainPart',
|
||||
after: afterPart,
|
||||
from: fromPart,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const grandChild = {
|
||||
name: 'grandChild',
|
||||
from: mainPart,
|
||||
hideAll: true,
|
||||
draft: blankDraft,
|
||||
}
|
||||
const Test = new Design({
|
||||
name: 'test',
|
||||
parts: [grandChild],
|
||||
})
|
||||
|
||||
const pattern = new Test()
|
||||
pattern.__init()
|
||||
|
||||
it('should hide nested `from` dependencies', () => {
|
||||
expect(pattern.__isPartHidden('fromPart')).to.be.true
|
||||
expect(pattern.__isPartHidden('mainPart')).to.be.true
|
||||
})
|
||||
|
||||
it('should hide nested `after` dependencies', () => {
|
||||
expect(pattern.__isPartHidden('afterPart')).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('Should check whether created parts get the pattern context', () => {
|
||||
let partContext
|
||||
const plugin = {
|
||||
|
|
|
@ -54,11 +54,14 @@ describe('Pattern', () => {
|
|||
name: 'test',
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
const design = new Design({ parts: [test] })
|
||||
const you = {
|
||||
name: 'you',
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
const design = new Design({ parts: [test, you] })
|
||||
const pattern = new design({ only: ['you'] })
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].logs.debug.length).to.equal(4)
|
||||
expect(pattern.setStores[0].logs.debug[3]).to.equal(
|
||||
expect(pattern.setStores[0].logs.debug).to.include(
|
||||
'Part `test` is not needed. Skipping draft and setting hidden to `true`'
|
||||
)
|
||||
})
|
||||
|
|
202
packages/core/tests/pattern-runtime-parts.test.mjs
Normal file
202
packages/core/tests/pattern-runtime-parts.test.mjs
Normal file
|
@ -0,0 +1,202 @@
|
|||
import chai from 'chai'
|
||||
import { Design } from '../src/index.mjs'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
describe('Pattern', () => {
|
||||
describe('.addPart()', () => {
|
||||
const part1 = {
|
||||
name: 'test',
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
const part2 = {
|
||||
name: 'test2',
|
||||
after: part1,
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
const part3 = {
|
||||
name: 'test3',
|
||||
from: part2,
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
describe('with resolveImmediately: true', () => {
|
||||
it('Should add the part to parts object', () => {
|
||||
const design = new Design({ parts: [part1] })
|
||||
const pattern = new design()
|
||||
pattern.__init()
|
||||
pattern.addPart(part2, true)
|
||||
expect(pattern.config.parts.test2).to.equal(part2)
|
||||
})
|
||||
|
||||
it('Should resolve injected dependencies for the new part', () => {
|
||||
const design = new Design({ parts: [part1] })
|
||||
const pattern = new design()
|
||||
pattern.__init()
|
||||
pattern.addPart(part3, true)
|
||||
expect(pattern.config.inject.test3).to.equal('test2')
|
||||
})
|
||||
|
||||
it('Should resolve all dependencies for the new part', () => {
|
||||
const design = new Design({ parts: [part1] })
|
||||
const pattern = new design()
|
||||
pattern.__init()
|
||||
pattern.addPart(part3, true)
|
||||
expect(pattern.config.resolvedDependencies.test3).to.have.members(['test', 'test2'])
|
||||
expect(pattern.config.parts.test2).to.equal(part2)
|
||||
})
|
||||
|
||||
it('Should add a the measurements for the new part', () => {
|
||||
const design = new Design({ parts: [part1] })
|
||||
const pattern = new design()
|
||||
pattern.__init()
|
||||
|
||||
const part2 = {
|
||||
name: 'test2',
|
||||
measurements: ['neck'],
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
pattern.addPart(part2, true)
|
||||
expect(pattern.config.measurements).to.include('neck')
|
||||
})
|
||||
|
||||
it('Should add the plugins for the new part', () => {
|
||||
const design = new Design({ parts: [part1] })
|
||||
const pattern = new design()
|
||||
pattern.__init()
|
||||
|
||||
const plugin = { name: 'testPlugin' }
|
||||
const part2 = {
|
||||
name: 'test2',
|
||||
plugins: [plugin],
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
pattern.addPart(part2, true)
|
||||
expect(pattern.config.plugins.testPlugin).to.equal(plugin)
|
||||
})
|
||||
|
||||
it('Should resolve the 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,
|
||||
}
|
||||
|
||||
pattern.addPart(part2, true)
|
||||
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('during drafting', () => {
|
||||
it('adds the part to the draft queue', () => {
|
||||
const design = new Design({ parts: [part1] })
|
||||
const pattern = new design()
|
||||
pattern.use({
|
||||
name: 'draftTimePartPlugin',
|
||||
hooks: {
|
||||
postPartDraft: (pattern) => {
|
||||
const newPart = {
|
||||
name: 'newPartTest',
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
|
||||
pattern.addPart(newPart)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
pattern.draft()
|
||||
expect(pattern.draftQueue.contains('newPartTest')).to.be.true
|
||||
})
|
||||
it('drafts the part', () => {
|
||||
const design = new Design({ parts: [part1] })
|
||||
const pattern = new design()
|
||||
const part2Draft = ({ part }) => part
|
||||
const draftSpy = sinon.spy(part2Draft)
|
||||
pattern.use({
|
||||
name: 'draftTimePartPlugin',
|
||||
hooks: {
|
||||
postPartDraft: (pattern) => {
|
||||
const newPart = {
|
||||
name: 'newPartTest',
|
||||
draft: draftSpy,
|
||||
}
|
||||
|
||||
pattern.addPart(newPart)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
pattern.draft()
|
||||
expect(draftSpy.calledOnce).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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],
|
||||
},
|
||||
},
|
||||
draft: ({ part }) => part,
|
||||
}
|
||||
const design = new Design({ parts: [part] })
|
||||
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 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 patternA = new design({
|
||||
|
|
77
yarn.lock
77
yarn.lock
|
@ -4750,6 +4750,34 @@
|
|||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
||||
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
|
||||
|
||||
"@sinonjs/commons@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3"
|
||||
integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/fake-timers@10.0.2", "@sinonjs/fake-timers@^10.0.2":
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c"
|
||||
integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^2.0.0"
|
||||
|
||||
"@sinonjs/samsam@^7.0.1":
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-7.0.1.tgz#5b5fa31c554636f78308439d220986b9523fc51f"
|
||||
integrity sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^2.0.0"
|
||||
lodash.get "^4.4.2"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918"
|
||||
integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==
|
||||
|
||||
"@socket.io/component-emitter@~3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
|
||||
|
@ -6226,10 +6254,10 @@ bin-links@^3.0.0:
|
|||
rimraf "^3.0.0"
|
||||
write-file-atomic "^4.0.0"
|
||||
|
||||
bin-pack@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bin-pack/-/bin-pack-1.0.2.tgz#c2a014edbf0bed70a3292062ed46577b96120679"
|
||||
integrity sha512-aOk0SxEon5LF9cMxQFViSKb4qccG6rs7XKyMXIb1J8f8LA2acTIWnHdT0IOTe4gYBbqgjdbuTZ5f+UP+vlh4Mw==
|
||||
bin-pack-with-constraints@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bin-pack-with-constraints/-/bin-pack-with-constraints-1.0.1.tgz#47e67b481724a8a5a3644ec9c9ccf881c2725096"
|
||||
integrity sha512-fiPxvZAWuIqLpK79Ov58PztT/ZiGbpDB7usifleilJ/4aqhSx0ivY5kRifESJnR2rq51pScbUJ0LgCebufGJnA==
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.2.0"
|
||||
|
@ -12259,6 +12287,11 @@ just-diff@^5.0.1:
|
|||
resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.0.3.tgz#4c9c514dec5526b25ab977590e3c39a0cf271554"
|
||||
integrity sha512-a8p80xcpJ6sdurk5PxDKb4mav9MeKjA3zFKZpCWBIfvg8mznfnmb13MKZvlrwJ+Lhis0wM3uGAzE0ArhFHvIcg==
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
|
||||
integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
|
@ -12933,7 +12966,7 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1:
|
|||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4"
|
||||
integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==
|
||||
|
||||
luxon@3.2.1:
|
||||
luxon@latest:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.2.1.tgz#14f1af209188ad61212578ea7e3d518d18cee45f"
|
||||
integrity sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==
|
||||
|
@ -14337,6 +14370,17 @@ next@13.1.6:
|
|||
"@next/swc-win32-ia32-msvc" "13.1.6"
|
||||
"@next/swc-win32-x64-msvc" "13.1.6"
|
||||
|
||||
nise@^5.1.2:
|
||||
version "5.1.4"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.4.tgz#491ce7e7307d4ec546f5a659b2efe94a18b4bbc0"
|
||||
integrity sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^2.0.0"
|
||||
"@sinonjs/fake-timers" "^10.0.2"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
nlcst-to-string@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz#9315dfab80882bbfd86ddf1b706f53622dc400cc"
|
||||
|
@ -15535,6 +15579,13 @@ path-to-regexp@0.1.7:
|
|||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-type@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
|
||||
|
@ -18346,6 +18397,18 @@ simple-wcswidth@^1.0.1:
|
|||
resolved "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz#8ab18ac0ae342f9d9b629604e54d2aa1ecb018b2"
|
||||
integrity sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==
|
||||
|
||||
sinon@^15.0.1:
|
||||
version "15.0.1"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-15.0.1.tgz#ce062611a0b131892e2c18f03055b8eb6e8dc234"
|
||||
integrity sha512-PZXKc08f/wcA/BMRGBze2Wmw50CWPiAH3E21EOi4B49vJ616vW4DQh4fQrqsYox2aNR/N3kCqLuB0PwwOucQrg==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^2.0.0"
|
||||
"@sinonjs/fake-timers" "10.0.2"
|
||||
"@sinonjs/samsam" "^7.0.1"
|
||||
diff "^5.0.0"
|
||||
nise "^5.1.2"
|
||||
supports-color "^7.2.0"
|
||||
|
||||
sirv@^1.0.7:
|
||||
version "1.0.19"
|
||||
resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49"
|
||||
|
@ -19131,7 +19194,7 @@ supports-color@^5.3.0, supports-color@^5.5.0:
|
|||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^7.0.0, supports-color@^7.1.0:
|
||||
supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
|
@ -19650,7 +19713,7 @@ type-component@0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/type-component/-/type-component-0.0.1.tgz#952a6c81c21efd24d13d811d0c8498cb860e1956"
|
||||
integrity sha512-mDZRBQS2yZkwRQKfjJvQ8UIYJeBNNWCq+HBNstl9N5s9jZ4dkVYXEGkVPsSCEh5Ld4JM1kmrZTzjnrqSAIQ7dw==
|
||||
|
||||
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue