1
0
Fork 0

wip(core): Support for 2022 style Design constructor

This is the first commit to tackle some exploratory work
in the context of discussion #2538 that deals with a number
of things, such as:

- Making it easier to attach parts to designs
- Making it easier to attach parts at run-time
- Simplify part inheritance into other designs
- Find ways to handle dependenices across designs
- Find ways to keep the part-specific config with the part

In this initial commit, I've update the Design constructor to
handle two different ways of calling it:

- legacy: new Design(config, plugins, conditionalPlugins)
- 2022: new Design(config)

I didn't want to call this the `new` way because that doesn't
age well, so I went with `legacy` and `2022` and this is how I
will refer to them from now on.

This is very much a work in progress and while I will create a PR
to keep on eye on the tests, I don't expect to merge this as-is.
This commit is contained in:
joostdecock 2022-08-07 17:29:33 +02:00
parent 6e2a0a33d9
commit ac9b616b99
4 changed files with 207 additions and 27 deletions

View file

@ -1,29 +1,38 @@
import Pattern from './pattern' import Pattern from './pattern'
// 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) { export default function Design(config, plugins = false, conditionalPlugins = false) {
// Add default hide() method to config.options
for (const option in config.options) { // Ensure all options have a hide() method
if (typeof config.options[option] === 'object') { config.options = optionsWithHide(config.options)
config.options[option] = {
hide, // A place to store deprecation and other warnings before we even have a pattern instantiated
...config.options[option], 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) { const pattern = function (settings) {
Pattern.call(this, config) Pattern.call(this, config)
// Load plugins // Load plugins
if (Array.isArray(plugins)) for (let plugin of plugins) this.use(plugin) if (Array.isArray(config.plugins)) for (const plugin of config.plugins) this.use(plugin)
else if (plugins) this.use(plugins) else if (config.plugins) this.use(config.plugins)
// Load conditional plugins // Load conditional plugins
if (Array.isArray(conditionalPlugins)) if (Array.isArray(config.conditionalPlugins))
for (let plugin of conditionalPlugins) this.useIf(plugin, settings) for (const plugin of config.conditionalPlugins) this.useIf(plugin, settings)
else if (conditionalPlugins.plugin && conditionalPlugins.condition) else if (config.conditionalPlugins.plugin && config.conditionalPlugins.condition)
this.useIf(conditionalPlugins, settings) this.useIf(config.conditionalPlugins, settings)
this.apply(settings) this.apply(settings)
@ -39,3 +48,59 @@ export default function Design(config, plugins = false, conditionalPlugins = fal
return pattern 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
}

View file

@ -11,7 +11,8 @@ import Attributes from './attributes'
import pkg from '../package.json' import pkg from '../package.json'
export default function Pattern(config = { options: {} }) { export default function Pattern(config = { options: {} }) {
// Default settings
// Apply default settings
this.settings = { this.settings = {
complete: true, complete: true,
idPrefix: 'fs-', idPrefix: 'fs-',
@ -25,15 +26,16 @@ export default function Pattern(config = { options: {} }) {
absoluteOptions: {}, absoluteOptions: {},
} }
// Events store and raise methods // Object to hold events
this.events = { this.events = {
info: [], info: [],
warning: [], warning: [],
error: [], error: [],
debug: [], debug: [],
} }
// Raise methods - Make events and settings avialable in them
const events = this.events const events = this.events
// Make settings available in the raise.debug method
const settings = this.settings const settings = this.settings
this.raise = { this.raise = {
info: function (data) { info: function (data) {
@ -50,10 +52,13 @@ export default function Pattern(config = { options: {} }) {
if (settings.debug) events.debug.push(data) if (settings.debug) events.debug.push(data)
}, },
} }
// Say hi
this.raise.info( 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:${pkg.version}\``
) )
// More things that go in a pattern
this.config = config // Pattern configuration this.config = config // Pattern configuration
this.width = 0 // Will be set after render this.width = 0 // Will be set after render
this.height = 0 // Will be set after render this.height = 0 // Will be set after render

View file

@ -21,7 +21,7 @@ it("Design constructor should return pattern constructor", () => {
expect(pattern.settings.options.percentage).to.equal(0.3); expect(pattern.settings.options.percentage).to.equal(0.3);
}); });
it("Design constructor should load single plugin", () => { it("Design constructor should load single plugin (legacy)", () => {
let plugin = { let plugin = {
name: "example", name: "example",
version: 1, version: 1,
@ -37,7 +37,23 @@ it("Design constructor should load single plugin", () => {
expect(pattern.hooks.preRender.length).to.equal(1); expect(pattern.hooks.preRender.length).to.equal(1);
}); });
it("Design constructor should load array of plugins", () => { it("Design constructor should load single plugin (2022)", () => {
let plugin = {
name: "example",
version: 1,
hooks: {
preRender: function(svg, attributes) {
svg.attributes.add("freesewing:plugin-example", version);
}
}
};
let design = new freesewing.Design({plugins: plugin});
let pattern = new design();
expect(pattern.hooks.preRender.length).to.equal(1);
});
it("Design constructor should load array of plugins (legacy)", () => {
let plugin1 = { let plugin1 = {
name: "example1", name: "example1",
version: 1, version: 1,
@ -62,7 +78,32 @@ it("Design constructor should load array of plugins", () => {
expect(pattern.hooks.preRender.length).to.equal(2); expect(pattern.hooks.preRender.length).to.equal(2);
}); });
it("Design constructor should load conditional plugin", () => { it("Design constructor should load array of plugins (2022)", () => {
let plugin1 = {
name: "example1",
version: 1,
hooks: {
preRender: function(svg, attributes) {
svg.attributes.add("freesewing:plugin-example1", version);
}
}
};
let plugin2 = {
name: "example2",
version: 2,
hooks: {
preRender: function(svg, attributes) {
svg.attributes.add("freesewing:plugin-example2", version);
}
}
};
let design = new freesewing.Design( { plugins: [plugin1, plugin2] });
let pattern = new design();
expect(pattern.hooks.preRender.length).to.equal(2);
});
it("Design constructor should load conditional plugin (legacy)", () => {
const plugin = { const plugin = {
name: "example", name: "example",
version: 1, version: 1,
@ -78,7 +119,23 @@ it("Design constructor should load conditional plugin", () => {
expect(pattern.hooks.preRender.length).to.equal(1); expect(pattern.hooks.preRender.length).to.equal(1);
}); });
it("Design constructor should not load conditional plugin", () => { it("Design constructor should load conditional plugin (2022)", () => {
const plugin = {
name: "example",
version: 1,
hooks: {
preRender: function(svg, attributes) {
svg.attributes.add("freesewing:plugin-example", version);
}
}
};
const condition = () => true
const design = new freesewing.Design({ conditionalPlugins: { plugin, condition } });
const pattern = new design();
expect(pattern.hooks.preRender.length).to.equal(1);
});
it("Design constructor should not load conditional plugin (legacy)", () => {
const plugin = { const plugin = {
name: "example", name: "example",
version: 1, version: 1,
@ -94,7 +151,23 @@ it("Design constructor should not load conditional plugin", () => {
expect(pattern.hooks.preRender.length).to.equal(0); expect(pattern.hooks.preRender.length).to.equal(0);
}); });
it("Design constructor should load multiple conditional plugins", () => { it("Design constructor should not load conditional plugin (2022)", () => {
const plugin = {
name: "example",
version: 1,
hooks: {
preRender: function(svg, attributes) {
svg.attributes.add("freesewing:plugin-example", version);
}
}
};
const condition = () => false
const design = new freesewing.Design({ conditionalPlugins: { plugin, condition } });
const pattern = new design();
expect(pattern.hooks.preRender.length).to.equal(0);
});
it("Design constructor should load multiple conditional plugins (legacy)", () => {
const plugin = { const plugin = {
name: "example", name: "example",
version: 1, version: 1,
@ -114,6 +187,26 @@ it("Design constructor should load multiple conditional plugins", () => {
expect(pattern.hooks.preRender.length).to.equal(1); expect(pattern.hooks.preRender.length).to.equal(1);
}); });
it("Design constructor should load multiple conditional plugins (2022)", () => {
const plugin = {
name: "example",
version: 1,
hooks: {
preRender: function(svg, attributes) {
svg.attributes.add("freesewing:plugin-example", version);
}
}
};
const condition1 = () => true
const condition2 = () => false
const design = new freesewing.Design({ conditionalPlugins: [
{ plugin, condition: condition1 },
{ plugin, condition: condition2 },
]});
const pattern = new design();
expect(pattern.hooks.preRender.length).to.equal(1);
});
it("Design constructor should construct basic part order", () => { it("Design constructor should construct basic part order", () => {
let design = new freesewing.Design({ let design = new freesewing.Design({
dependencies: { step4: "step3" }, dependencies: { step4: "step3" },
@ -209,7 +302,7 @@ it("Design constructor should handle Simon", () => {
let pattern = new design(); let pattern = new design();
}); });
it("Design constructor should add default hide() method to options", () => { it("Pattern constructor should add default hide() method to options", () => {
const design = new freesewing.Design({ const design = new freesewing.Design({
foo: "bar", foo: "bar",
options: { options: {
@ -233,3 +326,19 @@ it("Design constructor should add default hide() method to options", () => {
expect(pattern.config.options.degree.hide()).to.be.false expect(pattern.config.options.degree.hide()).to.be.false
expect(pattern.config.options.withHide.hide(pattern.settings)).to.be.true expect(pattern.config.options.withHide.hide(pattern.settings)).to.be.true
}) })
it("Should warn when passing plugins both as parameter and in the config", () => {
const design = new freesewing.Design({plugins: [{}]}, {});
expect(design.config.warnings.length).to.equal(2)
expect(design.config.warnings[0]).to.equal('Passing plugins to the Design constructor both as a second parameter and in the config is unsupported')
expect(design.config.warnings[1]).to.equal('Ignoring plugins passed as parameter. Only config.plugins will be used.')
})
it("Should warn when passing conditionalPlugins both as parameter and in the config", () => {
const design = new freesewing.Design({conditionalPlugins: [{}]}, false, {});
expect(design.config.warnings.length).to.equal(2)
expect(design.config.warnings[0]).to.equal('Passing conditionalPlugins to the Design constructor both as a third parameter and in the config is unsupported.')
expect(design.config.warnings[1]).to.equal('Ignoring conditionalPlugins passes as parameter. Only config.conditionalPlugins will be used.')
})

View file

@ -19,6 +19,7 @@ it("Pattern constructor should initialize object", () => {
expect(pattern.settings.options.percentage).to.equal(0.3); expect(pattern.settings.options.percentage).to.equal(0.3);
}); });
it("Should load percentage options", () => { it("Should load percentage options", () => {
let pattern = new freesewing.Pattern({ let pattern = new freesewing.Pattern({
options: { options: {