Merge branch 'proposal-parts' into v3-phase1
This commit is contained in:
commit
eccdd23ded
21 changed files with 2281 additions and 1305 deletions
|
@ -1,29 +1,55 @@
|
|||
import Pattern from './pattern'
|
||||
import { addPartConfig } from './utils.js'
|
||||
|
||||
// Default hide method for options
|
||||
const hide = () => false
|
||||
|
||||
/*
|
||||
* The Design constructor. Returns a Pattern constructor
|
||||
* So it's sort of a super-constructor
|
||||
*/
|
||||
export default function Design(config, plugins = false, conditionalPlugins = false) {
|
||||
// Add default hide() method to config.options
|
||||
for (const option in config.options) {
|
||||
if (typeof config.options[option] === 'object') {
|
||||
config.options[option] = {
|
||||
hide,
|
||||
...config.options[option],
|
||||
}
|
||||
// Add part options/measurements/optionalMeasurements to config
|
||||
if (!config.options) config.options = {}
|
||||
if (!config.measurements) config.measurements = []
|
||||
if (!config.optionalMeasurements) config.optionalMeasurements = []
|
||||
if (Array.isArray(config.parts)) {
|
||||
const parts = {}
|
||||
for (const part of config.parts) {
|
||||
if (typeof part === 'object') {
|
||||
parts[part.name] = part
|
||||
config = addPartConfig(parts[part.name], config)
|
||||
} else if (typeof part === 'string') {
|
||||
parts[part] = part
|
||||
} else throw("Part should be passed as a name of part config object")
|
||||
}
|
||||
config.parts = parts
|
||||
}
|
||||
|
||||
// Ensure all options have a hide() method
|
||||
config.options = optionsWithHide(config.options)
|
||||
|
||||
// A place to store deprecation and other warnings before we even have a pattern instantiated
|
||||
config.warnings = []
|
||||
|
||||
/*
|
||||
* The newer way to initalize a design is to pass one single parameter
|
||||
* The old way passed multiple parameters.
|
||||
* So let's figure out which is which and be backwards compatible
|
||||
*
|
||||
* This mitigation should be removed in v3 when we drop support for the legacy way
|
||||
*/
|
||||
config = migrateConfig(config, plugins, conditionalPlugins)
|
||||
|
||||
const pattern = function (settings) {
|
||||
Pattern.call(this, config)
|
||||
|
||||
// Load plugins
|
||||
if (Array.isArray(plugins)) for (let plugin of plugins) this.use(plugin)
|
||||
else if (plugins) this.use(plugins)
|
||||
if (Array.isArray(config.plugins)) for (const plugin of config.plugins) this.use(plugin)
|
||||
else if (config.plugins) this.use(config.plugins)
|
||||
|
||||
// Load conditional plugins
|
||||
if (Array.isArray(conditionalPlugins))
|
||||
for (let plugin of conditionalPlugins) this.useIf(plugin, settings)
|
||||
else if (conditionalPlugins.plugin && conditionalPlugins.condition)
|
||||
this.useIf(conditionalPlugins, settings)
|
||||
if (Array.isArray(config.conditionalPlugins))
|
||||
for (const plugin of config.conditionalPlugins) this.useIf(plugin, settings)
|
||||
else if (config.conditionalPlugins.plugin && config.conditionalPlugins.condition)
|
||||
this.useIf(config.conditionalPlugins, settings)
|
||||
|
||||
this.apply(settings)
|
||||
|
||||
|
@ -39,3 +65,59 @@ export default function Design(config, plugins = false, conditionalPlugins = fal
|
|||
|
||||
return pattern
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to handle the legacy way of passing configuration
|
||||
* to the design constructor
|
||||
*/
|
||||
const migrateConfig = (config, plugins, conditionalPlugins) => {
|
||||
|
||||
// Migrate plugins
|
||||
if (plugins && config.plugins) config.warnings.push(
|
||||
'Passing plugins to the Design constructor both as a second parameter and in the config is unsupported',
|
||||
'Ignoring plugins passed as parameter. Only config.plugins will be used.'
|
||||
)
|
||||
else if (plugins && !config.plugins) {
|
||||
config.plugins = plugins
|
||||
config.warnings.push(
|
||||
'Passing a plugins parameter to the Design constructure is deprecated',
|
||||
'Please store them in the `plugins` key of the config object that is the first parameter'
|
||||
)
|
||||
} else if (!config.plugins) config.plugins = []
|
||||
|
||||
// Migrate conditional plugins
|
||||
if (conditionalPlugins && config.conditionalPlugins) config.warnings.push(
|
||||
'Passing conditionalPlugins to the Design constructor both as a third parameter and in the config is unsupported.',
|
||||
'Ignoring conditionalPlugins passes as parameter. Only config.conditionalPlugins will be used.',
|
||||
)
|
||||
else if (conditionalPlugins && !config.conditionalPlugins) {
|
||||
config.conditionalPlugins = conditionalPlugins
|
||||
config.warnings.push(
|
||||
'Passing a conditionalPlugins parameter to the Design constructure is deprecated.',
|
||||
'Please store them in the `conditionalPlugins` key of the config object that is the first parameter'
|
||||
)
|
||||
}
|
||||
else if (!config.conditionalPlugins) config.conditionalPlugins = []
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/*
|
||||
* A default hide() method for options that lack it
|
||||
* Since this will always return false, the option will never be hidden
|
||||
*/
|
||||
const hide = () => false // The default hide() method
|
||||
|
||||
/*
|
||||
* Helper method to add the default hide() method to options who lack one
|
||||
*/
|
||||
const optionsWithHide = options => {
|
||||
if (options) {
|
||||
for (const option in options) {
|
||||
if (typeof options[option] === 'object') options[option] = { hide, ...options[option] }
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
|
|
|
@ -180,9 +180,9 @@ Part.prototype.units = function (input) {
|
|||
|
||||
/** Returns an object with shorthand access for pattern design */
|
||||
Part.prototype.shorthand = function () {
|
||||
let complete = this.context.settings.complete ? true : false
|
||||
let paperless = this.context.settings.paperless === true ? true : false
|
||||
let sa = this.context.settings.complete ? this.context.settings.sa || 0 : 0
|
||||
const complete = this.context.settings.complete ? true : false
|
||||
const paperless = this.context.settings.paperless === true ? true : false
|
||||
const sa = this.context.settings.complete ? this.context.settings.sa || 0 : 0
|
||||
const shorthand = {
|
||||
sa,
|
||||
scale: this.context.settings.scale,
|
||||
|
@ -198,154 +198,142 @@ Part.prototype.shorthand = function () {
|
|||
removeCut: this.removeCut,
|
||||
}
|
||||
|
||||
if (this.context.settings.debug) {
|
||||
// We'll need this
|
||||
let self = this
|
||||
// We'll need this
|
||||
let self = this
|
||||
|
||||
// Wrap the Point constructor so objects can raise events
|
||||
shorthand.Point = function (x, y) {
|
||||
Point.apply(this, [x, y, true])
|
||||
Object.defineProperty(this, 'raise', { value: self.context.raise })
|
||||
}
|
||||
shorthand.Point.prototype = Object.create(Point.prototype)
|
||||
// Wrap the Path constructor so objects can raise events
|
||||
shorthand.Path = function () {
|
||||
Path.apply(this, [true])
|
||||
Object.defineProperty(this, 'raise', { value: self.context.raise })
|
||||
}
|
||||
shorthand.Path.prototype = Object.create(Path.prototype)
|
||||
// Wrap the Snippet constructor so objects can raise events
|
||||
shorthand.Snippet = function (def, anchor) {
|
||||
Snippet.apply(this, [def, anchor, true])
|
||||
Snippet.apply(this, arguments)
|
||||
Object.defineProperty(this, 'raise', { value: self.context.raise })
|
||||
}
|
||||
shorthand.Snippet.prototype = Object.create(Snippet.prototype)
|
||||
|
||||
// Proxy the points object
|
||||
const pointsProxy = {
|
||||
get: function () {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (points, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Point !== true)
|
||||
self.context.raise.warning(
|
||||
`\`points.${name}\` was set with a value that is not a \`Point\` object`
|
||||
)
|
||||
if (value.x == null || !utils.isCoord(value.x))
|
||||
self.context.raise.warning(
|
||||
`\`points.${name}\` was set with a \`x\` parameter that is not a \`number\``
|
||||
)
|
||||
if (value.y == null || !utils.isCoord(value.y))
|
||||
self.context.raise.warning(
|
||||
`\`points.${name}\` was set with a \`y\` parameter that is not a \`number\``
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.raise.warning(`Could not set \`name\` property on \`points.${name}\``)
|
||||
}
|
||||
return (self.points[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.points = new Proxy(this.points || {}, pointsProxy)
|
||||
// Proxy the paths object
|
||||
const pathsProxy = {
|
||||
get: function () {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (paths, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Path !== true)
|
||||
self.context.raise.warning(
|
||||
`\`paths.${name}\` was set with a value that is not a \`Path\` object`
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.raise.warning(`Could not set \`name\` property on \`paths.${name}\``)
|
||||
}
|
||||
return (self.paths[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.paths = new Proxy(this.paths || {}, pathsProxy)
|
||||
// Proxy the snippets object
|
||||
const snippetsProxy = {
|
||||
get: function (target, prop, receiver) {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (snippets, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Snippet !== true)
|
||||
self.context.raise.warning(
|
||||
`\`snippets.${name}\` was set with a value that is not a \`Snippet\` object`
|
||||
)
|
||||
if (typeof value.def !== 'string')
|
||||
self.context.raise.warning(
|
||||
`\`snippets.${name}\` was set with a \`def\` parameter that is not a \`string\``
|
||||
)
|
||||
if (value.anchor instanceof Point !== true)
|
||||
self.context.raise.warning(
|
||||
`\`snippets.${name}\` was set with an \`anchor\` parameter that is not a \`Point\``
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.raise.warning(`Could not set \`name\` property on \`snippets.${name}\``)
|
||||
}
|
||||
return (self.snippets[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.snippets = new Proxy(this.snippets || {}, snippetsProxy)
|
||||
// Proxy the measurements object
|
||||
const measurementsProxy = {
|
||||
get: function (measurements, name) {
|
||||
if (typeof measurements[name] === 'undefined')
|
||||
self.context.raise.warning(
|
||||
`Tried to access \`measurements.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (measurements, name, value) => (self.context.settings.measurements[name] = value),
|
||||
}
|
||||
shorthand.measurements = new Proxy(this.context.settings.measurements || {}, measurementsProxy)
|
||||
// Proxy the options object
|
||||
const optionsProxy = {
|
||||
get: function (options, name) {
|
||||
if (typeof options[name] === 'undefined')
|
||||
self.context.raise.warning(`Tried to access \`options.${name}\` but it is \`undefined\``)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (options, name, value) => (self.context.settings.options[name] = value),
|
||||
}
|
||||
shorthand.options = new Proxy(this.context.settings.options || {}, optionsProxy)
|
||||
// Proxy the absoluteOptions object
|
||||
const absoluteOptionsProxy = {
|
||||
get: function (absoluteOptions, name) {
|
||||
if (typeof absoluteOptions[name] === 'undefined')
|
||||
self.context.raise.warning(
|
||||
`Tried to access \`absoluteOptions.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (absoluteOptions, name, value) => (self.context.settings.absoluteOptions[name] = value),
|
||||
}
|
||||
shorthand.absoluteOptions = new Proxy(
|
||||
this.context.settings.absoluteOptions || {},
|
||||
absoluteOptionsProxy
|
||||
)
|
||||
} else {
|
||||
shorthand.Point = Point
|
||||
shorthand.Path = Path
|
||||
shorthand.Snippet = Snippet
|
||||
shorthand.points = this.points || {}
|
||||
shorthand.paths = this.paths || {}
|
||||
shorthand.snippets = this.snippets || {}
|
||||
shorthand.measurements = this.context.settings.measurements || {}
|
||||
shorthand.options = this.context.settings.options || {}
|
||||
shorthand.absoluteOptions = this.context.settings.absoluteOptions || {}
|
||||
// Wrap the Point constructor so objects can raise events
|
||||
shorthand.Point = function (x, y) {
|
||||
Point.apply(this, [x, y, true])
|
||||
Object.defineProperty(this, 'raise', { value: self.context.raise })
|
||||
}
|
||||
shorthand.Point.prototype = Object.create(Point.prototype)
|
||||
// Wrap the Path constructor so objects can raise events
|
||||
shorthand.Path = function () {
|
||||
Path.apply(this, [true])
|
||||
Object.defineProperty(this, 'raise', { value: self.context.raise })
|
||||
}
|
||||
shorthand.Path.prototype = Object.create(Path.prototype)
|
||||
// Wrap the Snippet constructor so objects can raise events
|
||||
shorthand.Snippet = function (def, anchor) {
|
||||
Snippet.apply(this, [def, anchor, true])
|
||||
Snippet.apply(this, arguments)
|
||||
Object.defineProperty(this, 'raise', { value: self.context.raise })
|
||||
}
|
||||
shorthand.Snippet.prototype = Object.create(Snippet.prototype)
|
||||
|
||||
// Proxy the points object
|
||||
const pointsProxy = {
|
||||
get: function () {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (points, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Point !== true)
|
||||
self.context.raise.warning(
|
||||
`\`points.${name}\` was set with a value that is not a \`Point\` object`
|
||||
)
|
||||
if (value.x == null || !utils.isCoord(value.x))
|
||||
self.context.raise.warning(
|
||||
`\`points.${name}\` was set with a \`x\` parameter that is not a \`number\``
|
||||
)
|
||||
if (value.y == null || !utils.isCoord(value.y))
|
||||
self.context.raise.warning(
|
||||
`\`points.${name}\` was set with a \`y\` parameter that is not a \`number\``
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.raise.warning(`Could not set \`name\` property on \`points.${name}\``)
|
||||
}
|
||||
return (self.points[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.points = new Proxy(this.points || {}, pointsProxy)
|
||||
// Proxy the paths object
|
||||
const pathsProxy = {
|
||||
get: function () {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (paths, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Path !== true)
|
||||
self.context.raise.warning(
|
||||
`\`paths.${name}\` was set with a value that is not a \`Path\` object`
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.raise.warning(`Could not set \`name\` property on \`paths.${name}\``)
|
||||
}
|
||||
return (self.paths[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.paths = new Proxy(this.paths || {}, pathsProxy)
|
||||
// Proxy the snippets object
|
||||
const snippetsProxy = {
|
||||
get: function (target, prop, receiver) {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (snippets, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Snippet !== true)
|
||||
self.context.raise.warning(
|
||||
`\`snippets.${name}\` was set with a value that is not a \`Snippet\` object`
|
||||
)
|
||||
if (typeof value.def !== 'string')
|
||||
self.context.raise.warning(
|
||||
`\`snippets.${name}\` was set with a \`def\` parameter that is not a \`string\``
|
||||
)
|
||||
if (value.anchor instanceof Point !== true)
|
||||
self.context.raise.warning(
|
||||
`\`snippets.${name}\` was set with an \`anchor\` parameter that is not a \`Point\``
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.raise.warning(`Could not set \`name\` property on \`snippets.${name}\``)
|
||||
}
|
||||
return (self.snippets[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.snippets = new Proxy(this.snippets || {}, snippetsProxy)
|
||||
// Proxy the measurements object
|
||||
const measurementsProxy = {
|
||||
get: function (measurements, name) {
|
||||
if (typeof measurements[name] === 'undefined')
|
||||
self.context.raise.warning(
|
||||
`Tried to access \`measurements.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (measurements, name, value) => (self.context.settings.measurements[name] = value),
|
||||
}
|
||||
shorthand.measurements = new Proxy(this.context.settings.measurements || {}, measurementsProxy)
|
||||
// Proxy the options object
|
||||
const optionsProxy = {
|
||||
get: function (options, name) {
|
||||
if (typeof options[name] === 'undefined')
|
||||
self.context.raise.warning(`Tried to access \`options.${name}\` but it is \`undefined\``)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (options, name, value) => (self.context.settings.options[name] = value),
|
||||
}
|
||||
shorthand.options = new Proxy(this.context.settings.options || {}, optionsProxy)
|
||||
// Proxy the absoluteOptions object
|
||||
const absoluteOptionsProxy = {
|
||||
get: function (absoluteOptions, name) {
|
||||
if (typeof absoluteOptions[name] === 'undefined')
|
||||
self.context.raise.warning(
|
||||
`Tried to access \`absoluteOptions.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (absoluteOptions, name, value) => (self.context.settings.absoluteOptions[name] = value),
|
||||
}
|
||||
shorthand.absoluteOptions = new Proxy(
|
||||
this.context.settings.absoluteOptions || {},
|
||||
absoluteOptionsProxy
|
||||
)
|
||||
|
||||
return shorthand
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { macroName, sampleStyle, capitalize } from './utils'
|
||||
import {
|
||||
macroName,
|
||||
sampleStyle,
|
||||
capitalize,
|
||||
decoratePartDependency,
|
||||
addPartConfig,
|
||||
mergeDependencies,
|
||||
} from './utils.js'
|
||||
import Part from './part'
|
||||
import Point from './point'
|
||||
import Path from './path'
|
||||
|
@ -11,7 +18,8 @@ import Attributes from './attributes'
|
|||
import pkg from '../package.json'
|
||||
|
||||
export default function Pattern(config = { options: {} }) {
|
||||
// Default settings
|
||||
|
||||
// Apply default settings
|
||||
this.settings = {
|
||||
complete: true,
|
||||
idPrefix: 'fs-',
|
||||
|
@ -25,15 +33,16 @@ export default function Pattern(config = { options: {} }) {
|
|||
absoluteOptions: {},
|
||||
}
|
||||
|
||||
// Events store and raise methods
|
||||
// Object to hold events
|
||||
this.events = {
|
||||
info: [],
|
||||
warning: [],
|
||||
error: [],
|
||||
debug: [],
|
||||
}
|
||||
|
||||
// Raise methods - Make events and settings avialable in them
|
||||
const events = this.events
|
||||
// Make settings available in the raise.debug method
|
||||
const settings = this.settings
|
||||
this.raise = {
|
||||
info: function (data) {
|
||||
|
@ -50,10 +59,13 @@ export default function Pattern(config = { options: {} }) {
|
|||
if (settings.debug) events.debug.push(data)
|
||||
},
|
||||
}
|
||||
|
||||
// Say hi
|
||||
this.raise.info(
|
||||
`New \`@freesewing/${config.name}:${config.version}\` pattern using \`@freesewing/core:${pkg.version}\``
|
||||
)
|
||||
|
||||
// More things that go in a pattern
|
||||
this.config = config // Pattern configuration
|
||||
this.width = 0 // Will be set after render
|
||||
this.height = 0 // Will be set after render
|
||||
|
@ -68,30 +80,17 @@ export default function Pattern(config = { options: {} }) {
|
|||
this.Path = Path // Path constructor
|
||||
this.Snippet = Snippet // Snippet constructor
|
||||
this.Attributes = Attributes // Attributes constructor
|
||||
this.initialized = 0 // Keep track of init calls
|
||||
|
||||
if (typeof this.config.dependencies === 'undefined') this.config.dependencies = {}
|
||||
if (typeof this.config.inject === 'undefined') this.config.inject = {}
|
||||
if (typeof this.config.hide === 'undefined') this.config.hide = []
|
||||
this.config.resolvedDependencies = this.resolveDependencies(this.config.dependencies)
|
||||
this.config.draftOrder = this.draftOrder(this.config.resolvedDependencies)
|
||||
|
||||
// Convert options
|
||||
for (let i in config.options) {
|
||||
let option = config.options[i]
|
||||
if (typeof option === 'object') {
|
||||
if (typeof option.pct !== 'undefined') this.settings.options[i] = option.pct / 100
|
||||
else if (typeof option.mm !== 'undefined') this.settings.options[i] = option.mm
|
||||
else if (typeof option.deg !== 'undefined') this.settings.options[i] = option.deg
|
||||
else if (typeof option.count !== 'undefined') this.settings.options[i] = option.count
|
||||
else if (typeof option.bool !== 'undefined') this.settings.options[i] = option.bool
|
||||
else if (typeof option.dflt !== 'undefined') this.settings.options[i] = option.dflt
|
||||
else {
|
||||
let err = 'Unknown option type: ' + JSON.stringify(option)
|
||||
this.raise.error(err)
|
||||
throw new Error(err)
|
||||
}
|
||||
} else {
|
||||
this.settings.options[i] = option
|
||||
this.addOptions(config.options)
|
||||
if (this.config.parts) {
|
||||
for (const partName in this.config.parts) {
|
||||
if (this.config.parts[partName].options) this.addOptions(this.config.parts[partName].options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,6 +121,68 @@ export default function Pattern(config = { options: {} }) {
|
|||
}
|
||||
}
|
||||
|
||||
// Converts/adds options
|
||||
Pattern.prototype.addOptions = function(options={}) {
|
||||
for (const i in options) {
|
||||
// Add to config
|
||||
const option = options[i]
|
||||
this.config.options[i] = option
|
||||
if (typeof option === 'object') {
|
||||
if (typeof option.pct !== 'undefined') this.settings.options[i] = option.pct / 100
|
||||
else if (typeof option.mm !== 'undefined') this.settings.options[i] = option.mm
|
||||
else if (typeof option.deg !== 'undefined') this.settings.options[i] = option.deg
|
||||
else if (typeof option.count !== 'undefined') this.settings.options[i] = option.count
|
||||
else if (typeof option.bool !== 'undefined') this.settings.options[i] = option.bool
|
||||
else if (typeof option.dflt !== 'undefined') this.settings.options[i] = option.dflt
|
||||
else {
|
||||
let err = 'Unknown option type: ' + JSON.stringify(option)
|
||||
this.raise.error(err)
|
||||
throw new Error(err)
|
||||
}
|
||||
} else {
|
||||
this.settings.options[i] = option
|
||||
}
|
||||
}
|
||||
|
||||
// Make it chainable
|
||||
return this
|
||||
}
|
||||
|
||||
Pattern.prototype.getConfig = function () {
|
||||
this.init()
|
||||
return this.config
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Defer some things that used to happen in the constructor to
|
||||
* facilitate late-stage adding of parts
|
||||
*/
|
||||
Pattern.prototype.init = function () {
|
||||
this.initialized++
|
||||
// Resolve all dependencies
|
||||
this.dependencies = this.config.dependencies
|
||||
this.inject = this.config.inject
|
||||
this.hide = this.config.hide
|
||||
if (typeof this.config.parts === 'object') {
|
||||
this.__parts = this.config.parts
|
||||
this.preresolveDependencies()
|
||||
}
|
||||
this.resolvedDependencies = this.resolveDependencies(this.dependencies)
|
||||
this.config.resolvedDependencies = this.resolvedDependencies
|
||||
this.config.draftOrder = this.draftOrder(this.resolvedDependencies)
|
||||
|
||||
// Make all parts uniform
|
||||
if (this.__parts) {
|
||||
for (const [key, value] of Object.entries(this.__parts)) {
|
||||
this.__parts[key] = decoratePartDependency(value)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
function snappedOption(option, pattern) {
|
||||
const conf = pattern.config.options[option]
|
||||
const abs = conf.toAbs(pattern.settings.options[option], pattern.settings)
|
||||
|
@ -155,7 +216,7 @@ function snappedOption(option, pattern) {
|
|||
// Merges settings object with this.settings
|
||||
Pattern.prototype.apply = function (settings) {
|
||||
if (typeof settings !== 'object') {
|
||||
this.raise.warning('Pattern initialized without any settings')
|
||||
this.raise.warning('Pattern instantiated without any settings')
|
||||
return this
|
||||
}
|
||||
for (let key of Object.keys(settings)) {
|
||||
|
@ -186,10 +247,31 @@ Pattern.prototype.runHooks = function (hookName, data = false) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allows adding a part at run-time
|
||||
*/
|
||||
Pattern.prototype.addPart = function (part, name=false) {
|
||||
if (!part.draft) part = decoratePartDependency(part, name)
|
||||
if (typeof part?.draft === 'function') {
|
||||
if (part.name) {
|
||||
this.config.parts[part.name] = part
|
||||
// Add part-level config to config
|
||||
this.config = addPartConfig(part, this.config)
|
||||
}
|
||||
else this.raise.error(`Part must have a name`)
|
||||
}
|
||||
else this.raise.warning(`Cannot attach part ${name} because it is not a part`)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* The default draft method with pre- and postDraft hooks
|
||||
*/
|
||||
Pattern.prototype.draft = function () {
|
||||
// Late-stage initialization
|
||||
this.init()
|
||||
|
||||
if (this.is !== 'sample') {
|
||||
this.is = 'draft'
|
||||
this.cutList = {}
|
||||
|
@ -207,33 +289,49 @@ Pattern.prototype.draft = function () {
|
|||
}
|
||||
|
||||
this.runHooks('preDraft')
|
||||
for (let partName of this.config.draftOrder) {
|
||||
for (const partName of this.config.draftOrder) {
|
||||
// Create parts
|
||||
this.raise.debug(`Creating part \`${partName}\``)
|
||||
this.parts[partName] = new this.Part(partName)
|
||||
if (typeof this.config.inject[partName] === 'string') {
|
||||
// Handle inject/inheritance
|
||||
if (typeof this.inject[partName] === 'string') {
|
||||
this.raise.debug(
|
||||
`Injecting part \`${this.config.inject[partName]}\` into part \`${partName}\``
|
||||
`Injecting part \`${this.inject[partName]}\` into part \`${partName}\``
|
||||
)
|
||||
try {
|
||||
this.parts[partName].inject(this.parts[this.config.inject[partName]])
|
||||
this.parts[partName].inject(this.parts[this.inject[partName]])
|
||||
} catch (err) {
|
||||
this.raise.error([
|
||||
`Could not inject part \`${this.config.inject[partName]}\` into part \`${partName}\``,
|
||||
`Could not inject part \`${this.inject[partName]}\` into part \`${partName}\``,
|
||||
err,
|
||||
])
|
||||
}
|
||||
}
|
||||
if (this.needs(partName)) {
|
||||
let method = 'draft' + capitalize(partName)
|
||||
if (typeof this[method] !== 'function') {
|
||||
this.raise.error(`Method \`pattern.${method}\` is callable`)
|
||||
throw new Error('Method "' + method + '" on pattern object is not callable')
|
||||
// Draft part
|
||||
const method = 'draft' + capitalize(partName)
|
||||
if (typeof this.__parts?.[partName]?.draft === 'function') {
|
||||
// 2022 way - Part is contained in config
|
||||
try {
|
||||
this.parts[partName] = this.__parts[partName].draft(this.parts[partName])
|
||||
if (this.parts[partName].render) this.cutList[partName] = this.parts[partName].cut
|
||||
} catch (err) {
|
||||
this.raise.error([`Unable to draft part \`${partName}\``, err])
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.parts[partName] = this[method](this.parts[partName])
|
||||
if (this.parts[partName].render ) this.cutList[partName] = this.parts[partName].cut
|
||||
} catch (err) {
|
||||
this.raise.error([`Unable to draft part \`${partName}\``, err])
|
||||
else if (typeof this[method] === 'function') {
|
||||
// Legacy way - Part is attached to the prototype
|
||||
this.raise.warning(`Attaching part methods to the Pattern prototype is deprecated and will be removed in FreeSewing v3 (part: \`${partName}\`)`)
|
||||
try {
|
||||
this.parts[partName] = this[method](this.parts[partName])
|
||||
if (this.parts[partName].render ) this.cutList[partName] = this.parts[partName].cut
|
||||
} catch (err) {
|
||||
this.raise.error([`Unable to draft part \`${partName}\``, err])
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.raise.error(`Unable to draft pattern. Part is not available in iether legacy or 2022`)
|
||||
throw new Error('Method "' + method + '" on pattern object is not callable')
|
||||
}
|
||||
if (typeof this.parts[partName] === 'undefined') {
|
||||
this.raise.error(
|
||||
|
@ -262,6 +360,8 @@ Pattern.prototype.draft = function () {
|
|||
* Handles pattern sampling
|
||||
*/
|
||||
Pattern.prototype.sample = function () {
|
||||
// Late-stage initialization
|
||||
this.init()
|
||||
if (this.settings.sample.type === 'option') {
|
||||
return this.sampleOption(this.settings.sample.option)
|
||||
} else if (this.settings.sample.type === 'measurement') {
|
||||
|
@ -562,7 +662,7 @@ Pattern.prototype.draftOrder = function (graph = this.resolveDependencies()) {
|
|||
Pattern.prototype.resolveDependency = function (
|
||||
seen,
|
||||
part,
|
||||
graph = this.config.dependencies,
|
||||
graph = this.dependencies,
|
||||
deps = []
|
||||
) {
|
||||
if (typeof seen[part] === 'undefined') seen[part] = true
|
||||
|
@ -578,33 +678,86 @@ Pattern.prototype.resolveDependency = function (
|
|||
return deps
|
||||
}
|
||||
|
||||
/** Adds a part as a simple dependency **/
|
||||
Pattern.prototype.addDependency = function (name, part, dep) {
|
||||
this.dependencies[name] = mergeDependencies(dep.name, this.dependencies[name])
|
||||
if (typeof this.__parts[dep.name] === 'undefined') {
|
||||
this.__parts[dep.name] = decoratePartDependency(dep)
|
||||
addPartConfig(this.__parts[dep.name], this.config)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Filter optional measurements out of they are also required measurements */
|
||||
Pattern.prototype.filterOptionalMeasurements = function () {
|
||||
this.config.optionalMeasurements = this.config.optionalMeasurements.filter(
|
||||
m => this.config.measurements.indexOf(m) === -1
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Pre-Resolves part dependencies that are passed in 2022 style */
|
||||
Pattern.prototype.preresolveDependencies = function (count=0) {
|
||||
if (!this.__parts) return
|
||||
for (const [name, part] of Object.entries(this.__parts)) {
|
||||
// Inject (from)
|
||||
if (part.from) {
|
||||
this.inject[name] = part.from.name
|
||||
if (typeof this.__parts[part.from.name] === 'undefined') {
|
||||
this.__parts[part.from.name] = decoratePartDependency(part.from)
|
||||
addPartConfig(this.__parts[part.from.name], this.config)
|
||||
}
|
||||
}
|
||||
// Simple dependency (after)
|
||||
if (part.after) {
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) this.addDependency(name, part, dep)
|
||||
}
|
||||
else this.addDependency(name, part, part.after)
|
||||
}
|
||||
}
|
||||
// Did we discover any new dependencies?
|
||||
const len = Object.keys(this.__parts).length
|
||||
|
||||
if (len > count) return this.preresolveDependencies(len)
|
||||
|
||||
for (const [name, part] of Object.entries(this.__parts)) {
|
||||
addPartConfig(name, this.config)
|
||||
}
|
||||
|
||||
// Weed out doubles
|
||||
return this.filterOptionalMeasurements()
|
||||
}
|
||||
|
||||
/** Resolves part dependencies into a flat array */
|
||||
Pattern.prototype.resolveDependencies = function (graph = this.config.dependencies) {
|
||||
for (let i in this.config.inject) {
|
||||
let dependency = this.config.inject[i]
|
||||
if (typeof this.config.dependencies[i] === 'undefined') this.config.dependencies[i] = dependency
|
||||
else if (this.config.dependencies[i] !== dependency) {
|
||||
if (typeof this.config.dependencies[i] === 'string') {
|
||||
this.config.dependencies[i] = [this.config.dependencies[i], dependency]
|
||||
} else if (Array.isArray(this.config.dependencies[i])) {
|
||||
if (this.config.dependencies[i].indexOf(dependency) === -1)
|
||||
this.config.dependencies[i].push(dependency)
|
||||
Pattern.prototype.resolveDependencies = function (graph = this.dependencies) {
|
||||
for (let i in this.inject) {
|
||||
let 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.raise.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')
|
||||
}
|
||||
}
|
||||
// Parts both in the parts and dependencies array trip up the dependency resolver
|
||||
if (Array.isArray(this.config.parts)) {
|
||||
let pos = this.config.parts.indexOf(this.config.inject[i])
|
||||
if (pos !== -1) this.config.parts.splice(pos, 1)
|
||||
if (Array.isArray(this.__parts)) {
|
||||
let pos = this.__parts.indexOf(this.inject[i])
|
||||
if (pos !== -1) this.__parts.splice(pos, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Include parts outside the dependency graph
|
||||
if (Array.isArray(this.config.parts)) {
|
||||
for (let part of this.config.parts) {
|
||||
if (typeof this.config.dependencies[part] === 'undefined') this.config.dependencies[part] = []
|
||||
if (typeof this.config.parts === 'object') {
|
||||
for (const part of Object.values(this.config.parts)) {
|
||||
if (typeof part === 'string' && typeof this.dependencies[part] === 'undefined') this.dependencies[part] = []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,15 +777,15 @@ Pattern.prototype.needs = function (partName) {
|
|||
if (typeof this.settings.only === 'undefined' || this.settings.only === false) return true
|
||||
else if (typeof this.settings.only === 'string') {
|
||||
if (this.settings.only === partName) return true
|
||||
if (Array.isArray(this.config.resolvedDependencies[this.settings.only])) {
|
||||
for (let dependency of this.config.resolvedDependencies[this.settings.only]) {
|
||||
if (Array.isArray(this.resolvedDependencies[this.settings.only])) {
|
||||
for (let dependency of this.resolvedDependencies[this.settings.only]) {
|
||||
if (dependency === partName) return true
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(this.settings.only)) {
|
||||
for (let part of this.settings.only) {
|
||||
if (part === partName) return true
|
||||
for (let dependency of this.config.resolvedDependencies[part]) {
|
||||
for (let dependency of this.resolvedDependencies[part]) {
|
||||
if (dependency === partName) return true
|
||||
}
|
||||
}
|
||||
|
@ -643,9 +796,11 @@ Pattern.prototype.needs = function (partName) {
|
|||
|
||||
/* Checks whether a part is hidden in the config */
|
||||
Pattern.prototype.isHidden = function (partName) {
|
||||
if (Array.isArray(this.config.hide)) {
|
||||
if (this.config.hide.indexOf(partName) !== -1) return true
|
||||
if (Array.isArray(this.hide)) {
|
||||
if (this.hide.indexOf(partName) !== -1) return true
|
||||
}
|
||||
// 2022 style
|
||||
if (this.__parts?.[partName]?.hide) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -409,3 +409,175 @@ export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => {
|
|||
// 'transform-origin': `${center.x} ${center.y}`
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Makes sure an object passed to be attached as a part it not merely a method
|
||||
*/
|
||||
export const decoratePartDependency = (obj, name) => (typeof obj === 'function') ? { draft: obj, name } : obj
|
||||
|
||||
// Add part-level options
|
||||
const addPartOptions = (part, config) => {
|
||||
if (part.options) {
|
||||
for (const optionName in part.options) {
|
||||
config.options[optionName] = part.options[optionName]
|
||||
}
|
||||
}
|
||||
if (part.from) addPartOptions(part.from, config)
|
||||
if (part.after) {
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) addPartOptions(dep, config)
|
||||
} else addPartOptions(part.after, config)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Helper method for detecting a array with only strings
|
||||
const isStringArray = val => (Array.isArray(val) && val.length > 0)
|
||||
? val.reduce((prev=true, cur) => (prev && typeof cur === 'string'))
|
||||
: false
|
||||
// Helper method for detecting an object
|
||||
const isObject = obj => obj && typeof obj === 'object'
|
||||
|
||||
// Hat-tip to jhildenbiddle => https://stackoverflow.com/a/48218209
|
||||
const mergeOptionSubgroup = (...objects) => objects.reduce((prev, obj) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const pVal = prev[key];
|
||||
const oVal = obj[key];
|
||||
|
||||
if (Array.isArray(pVal) && Array.isArray(oVal)) {
|
||||
prev[key] = pVal.concat(...oVal);
|
||||
}
|
||||
else if (isObject(pVal) && isObject(oVal)) {
|
||||
prev[key] = mergeOptionSubgroup(pVal, oVal);
|
||||
}
|
||||
else {
|
||||
prev[key] = oVal;
|
||||
}
|
||||
})
|
||||
|
||||
return prev
|
||||
}, {})
|
||||
|
||||
const mergeOptionGroups = (cur, add) => {
|
||||
if (isStringArray(cur) && isStringArray(add)) return [...new Set([...cur, ...add])]
|
||||
else if (!Array.isArray(cur) && !Array.isArray(add)) return mergeOptionSubgroup(cur, add)
|
||||
else {
|
||||
const all = [...cur]
|
||||
for (const entry of add) {
|
||||
if (typeof add === 'string' && all.indexOf(entry) === -1) all.push(entry)
|
||||
else all.push(entry)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
return cur
|
||||
}
|
||||
|
||||
// Add part-level optionGroups
|
||||
const addPartOptionGroups = (part, config) => {
|
||||
if (typeof config.optionGroups === 'undefined') {
|
||||
if (part.optionGroups) config.optionGroups = part.optionGroups
|
||||
return config
|
||||
}
|
||||
if (part.optionGroups) {
|
||||
for (const group in part.optionGroups) {
|
||||
if (typeof config.optionGroups[group] === 'undefined') config.optionGroups[group] = part.optionGroups[group]
|
||||
else config.optionGroups[group] = mergeOptionGroups(config.optionGroups[group], part.optionGroups[group])
|
||||
}
|
||||
}
|
||||
if (part.from) addPartOptionGroups(part.from, config)
|
||||
if (part.after) {
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) addPartOptionGroups(dep, config)
|
||||
} else addPartOptionGroups(part.after, config)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Add part-level measurements
|
||||
const addPartMeasurements = (part, config, list=false) => {
|
||||
if (!list) list = config.measurements
|
||||
? [...config.measurements]
|
||||
: []
|
||||
if (part.measurements) {
|
||||
for (const m of part.measurements) list.push(m)
|
||||
}
|
||||
if (part.from) addPartMeasurements(part.from, config, list)
|
||||
if (part.after) {
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) addPartMeasurements(dep, config, list)
|
||||
} else addPartMeasurements(part.after, config, list)
|
||||
}
|
||||
|
||||
// Weed out duplicates
|
||||
config.measurements = [...new Set(list)]
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Add part-level optional measurements
|
||||
const addPartOptionalMeasurements = (part, config, list=false) => {
|
||||
if (!list) list = config.optionalMeasurements
|
||||
? [...config.optionalMeasurements]
|
||||
: []
|
||||
if (part.optionalMeasurements) {
|
||||
for (const m of part.optionalMeasurements) {
|
||||
// Don't add it's a required measurement for another part
|
||||
if (config.measurements.indexOf(m) === -1) list.push(m)
|
||||
}
|
||||
}
|
||||
if (part.from) addPartOptionalMeasurements(part.from, config, list)
|
||||
if (part.after) {
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) addPartOptionalMeasurements(dep, config, list)
|
||||
} else addPartOptionalMeasurements(part.after, config, list)
|
||||
}
|
||||
|
||||
// Weed out duplicates
|
||||
config.optionalMeasurements = [...new Set(list)]
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
export const 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
|
||||
}
|
||||
|
||||
// Add part-level dependencies
|
||||
export const addPartDependencies = (part, config) => {
|
||||
if (part.after) {
|
||||
if (typeof config.dependencies === 'undefined') config.dependencies = {}
|
||||
config.dependencies[part.name] = mergeDependencies(config.dependencies[part.name], part.after)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
export const addPartConfig = (part, config) => {
|
||||
config = addPartOptions(part, config)
|
||||
config = addPartMeasurements(part, config)
|
||||
config = addPartOptionalMeasurements(part, config)
|
||||
config = addPartDependencies(part, config)
|
||||
config = addPartOptionGroups(part, config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue