diff --git a/packages/core/src/design.mjs b/packages/core/src/design.mjs index 74c19cf3934..bb2c8a774dc 100644 --- a/packages/core/src/design.mjs +++ b/packages/core/src/design.mjs @@ -1,43 +1,40 @@ import Pattern from './pattern.mjs' import { addPartConfig } from './utils.mjs' + /* * 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 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 - } +export default function Design(config) { - // Ensure all options have a hide() method - config.options = optionsWithHide(config.options) + // Merge config with defaults + config = { + parts: [], + options: {}, + measurements: [], + optionalMeasurements: [], + plugins: [], + conditionalPlugins: [], + ...config + } + const parts = {} + for (const part of config.parts) { + if (typeof part === 'object') { + parts[part.name] = part + config = addPartConfig(parts[part.name], config) + } + else throw("Invalid part configuration. Part is not an object") + } + // Replace config.parts with the resolved config + config.parts = parts + + // Ensure all options have a hide() method and menu property + config.options = completeOptions(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) @@ -66,55 +63,22 @@ 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 + * As this always return false, the option will never be hidden */ -const hide = () => false // The default hide() method +const hide = () => false /* * Helper method to add the default hide() method to options who lack one + * as well as set the `menu` property to false (if it's missing) */ -const optionsWithHide = options => { +const completeOptions = options => { if (options) { for (const option in options) { - if (typeof options[option] === 'object') options[option] = { hide, ...options[option] } + if (typeof options[option] === 'object') { + options[option] = { hide, menu: false, ...options[option] } + } } } diff --git a/packages/core/src/part.mjs b/packages/core/src/part.mjs index 78265d05d88..e7b5882970d 100644 --- a/packages/core/src/part.mjs +++ b/packages/core/src/part.mjs @@ -57,8 +57,8 @@ Part.prototype.getId = function (prefix = '') { /** Returns a value formatted for units provided in settings */ Part.prototype.unitsClosure = function (value) { - let self = this - let method = function (value) { + const self = this + const method = function (value) { if (self.context.settings.debug && typeof value !== 'number') self.context.raise.debug( `Calling \`units(value)\` but \`value\` is not a number (\`${typeof value}\`)` diff --git a/packages/core/src/pattern.mjs b/packages/core/src/pattern.mjs index 32b296b3b65..81aac7d24d9 100644 --- a/packages/core/src/pattern.mjs +++ b/packages/core/src/pattern.mjs @@ -15,7 +15,7 @@ import Svg from './svg.mjs' import Store from './store.mjs' import Hooks from './hooks.mjs' import Attributes from './attributes.mjs' -import pkg from '../package.json' +import { version } from '../package.json' assert { type: 'json' } export default function Pattern(config = { options: {} }) { @@ -28,41 +28,45 @@ export default function Pattern(config = { options: {} }) { margin: 2, scale: 1, layout: true, - debug: true, + debug: false, options: {}, absoluteOptions: {}, } // Object to hold events this.events = { - info: [], - warning: [], - error: [], debug: [], + error: [], + info: [], + suggestion: [], + warning: [], } // Raise methods - Make events and settings avialable in them const events = this.events const settings = this.settings this.raise = { + debug: function (data) { + // Debug only if debug is active + if (settings.debug) events.debug.push(data) + }, + error: function (data) { + events.error.push(data) + }, info: function (data) { events.info.push(data) }, + suggestion: function (data) { + events.info.push(data) + }, warning: function (data) { events.warning.push(data) }, - error: function (data) { - events.error.push(data) - }, - debug: function (data) { - // Debug only if debug is active - if (settings.debug) events.debug.push(data) - }, } // Say hi this.raise.info( - `New \`@freesewing/${config.name}:${config.version}\` pattern using \`@freesewing/core:${pkg.version}\`` + `New \`@freesewing/${config.name}:${config.version}\` pattern using \`@freesewing/core:${version}\`` ) // More things that go in a pattern @@ -72,7 +76,6 @@ export default function Pattern(config = { options: {} }) { this.is = '' // Will be set when drafting/sampling this.autoLayout = { parts: {} } // Will hold auto-generated layout this.cutList = {} // Will hold the cutlist - this.store = new Store(this.raise) // Store for sharing data across parts this.parts = {} // Parts container this.hooks = new Hooks() // Hooks container @@ -81,6 +84,7 @@ export default function Pattern(config = { options: {} }) { this.Snippet = Snippet // Snippet constructor this.Attributes = Attributes // Attributes constructor this.initialized = 0 // Keep track of init calls + this.macros = {} // Macros if (typeof this.config.dependencies === 'undefined') this.config.dependencies = {} if (typeof this.config.inject === 'undefined') this.config.inject = {} @@ -94,9 +98,6 @@ export default function Pattern(config = { options: {} }) { } } - // Macros - this.macros = {} - // Context object to add to Part closure const context = { parts: this.parts, @@ -148,11 +149,18 @@ Pattern.prototype.addOptions = function(options={}) { return this } +/* Utility method to get the (initialized) config */ Pattern.prototype.getConfig = function () { this.init() return this.config } +/* Utility method to get the (initialized) part list */ +Pattern.prototype.getPartList = function () { + this.init() + return Object.keys(this.config.parts) || [] +} + /* * Defer some things that used to happen in the constructor to @@ -163,11 +171,8 @@ Pattern.prototype.init = function () { // 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.__parts = this.config.parts + this.preresolveDependencies() this.resolvedDependencies = this.resolveDependencies(this.dependencies) this.config.resolvedDependencies = this.resolvedDependencies this.config.draftOrder = this.draftOrder(this.resolvedDependencies) @@ -250,8 +255,7 @@ 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) +Pattern.prototype.addPart = function (part) { if (typeof part?.draft === 'function') { if (part.name) { this.config.parts[part.name] = part @@ -260,7 +264,7 @@ Pattern.prototype.addPart = function (part, name=false) { } else this.raise.error(`Part must have a name`) } - else this.raise.warning(`Cannot attach part ${name} because it is not a part`) + else this.raise.error(`Part must have a draft() method`) return this } @@ -309,7 +313,6 @@ Pattern.prototype.draft = function () { } if (this.needs(partName)) { // Draft part - const method = 'draft' + capitalize(partName) if (typeof this.__parts?.[partName]?.draft === 'function') { // 2022 way - Part is contained in config try { @@ -319,25 +322,7 @@ Pattern.prototype.draft = function () { 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( - `Result of \`pattern.${method}\` was \`undefined\`. Did you forget to return the \`Part\` object?` - ) - } + else this.raise.error(`Unable to draft pattern. Part.draft() is not callable`) try { this.parts[partName].render = this.parts[partName].render === false ? false : this.wants(partName) @@ -689,7 +674,7 @@ Pattern.prototype.addDependency = function (name, part, dep) { return this } -/** Filter optional measurements out of they are also required measurements */ +/** Filter optional measurements out if they are also required measurements */ Pattern.prototype.filterOptionalMeasurements = function () { this.config.optionalMeasurements = this.config.optionalMeasurements.filter( m => this.config.measurements.indexOf(m) === -1 @@ -755,11 +740,11 @@ Pattern.prototype.resolveDependencies = function (graph = this.dependencies) { } // Include parts outside the dependency graph - 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] = [] - } - } + //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] = [] + // } + //} let resolved = {} let seen = {} @@ -796,26 +781,19 @@ Pattern.prototype.needs = function (partName) { /* Checks whether a part is hidden in the config */ Pattern.prototype.isHidden = function (partName) { - if (Array.isArray(this.hide)) { - if (this.hide.indexOf(partName) !== -1) return true - } - // 2022 style - if (this.__parts?.[partName]?.hide) return true - - return false + return (this.__parts?.[partName]?.hide) ? true : false } /** Determines whether a part is wanted by the user * This depends on the 'only' setting */ Pattern.prototype.wants = function (partName) { - if (typeof this.settings.only === 'undefined' || this.settings.only === false) { + if (!this.settings?.only === 'undefined' || this.settings.only === false) { if (this.isHidden(partName)) return false - } else if (typeof this.settings.only === 'string') { - if (this.settings.only === partName) return true - return false - } else if (Array.isArray(this.settings.only)) { - for (let part of this.settings.only) { + } + else if (typeof this.settings.only === 'string') return (this.settings.only === partName) + else if (Array.isArray(this.settings.only)) { + for (const part of this.settings.only) { if (part === partName) return true } return false @@ -851,8 +829,8 @@ Pattern.prototype.getRenderProps = function () { props.events = { debug: this.events.debug, info: this.events.info, - warning: this.events.warning, error: this.events.error, + warning: this.events.warning, } props.cutList = this.cutList props.parts = {}