1
0
Fork 0

wip(core): Work on late-stage config resolver

This moves resolving of the config from the pattern constructor to the
init() method. The idea is that adding a part to a pattern is exactly
the same as adding a part to a design. In other words, late-stage adding
of parts would be no different as the config gets resolved after that.

This is currently broken in many different ways, but the unit tests
particular to this new way of resolving the config do pass.
This commit is contained in:
Joost De Cock 2022-09-09 20:20:38 +02:00
parent 58c15f6c81
commit 0cbffd6dc6
32 changed files with 4536 additions and 3384 deletions

View file

@ -356,16 +356,16 @@ export { Bezier }
export function pctBasedOn(measurement) {
return {
toAbs: (val, { measurements }) => measurements[measurement] * val,
fromAbs: (val, { measurements }) => Math.round((10000 * val) / measurements[measurement]) / 10000,
fromAbs: (val, { measurements }) =>
Math.round((10000 * val) / measurements[measurement]) / 10000,
}
}
/** Generates the transform attributes needed for a given part */
export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => {
const transforms = []
let xTotal = x || 0;
let yTotal = y || 0;
let xTotal = x || 0
let yTotal = y || 0
let scaleX = 1
let scaleY = 1
@ -391,8 +391,8 @@ export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => {
if (rotate) {
// we can put the center as the rotation origin, so get the center
const center = {
x: part.topLeft.x + part.width/2,
y: part.topLeft.y + part.height/2,
x: part.topLeft.x + part.width / 2,
y: part.topLeft.y + part.height / 2,
}
// add the rotation around the center to the transforms
@ -400,9 +400,7 @@ export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => {
}
// put the translation before any other transforms to avoid having to make complex calculations once the matrix has been rotated or scaled
if (xTotal !== 0 || yTotal !== 0) transforms.unshift(
`translate(${xTotal} ${yTotal})`
)
if (xTotal !== 0 || yTotal !== 0) transforms.unshift(`translate(${xTotal} ${yTotal})`)
return {
transform: transforms.join(' '),
@ -410,135 +408,7 @@ export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => {
}
}
// 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, raise, 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, raise, list)
if (part.after) {
if (Array.isArray(part.after)) {
for (const dep of part.after) addPartMeasurements(dep, config, raise, list)
} else addPartMeasurements(part.after, config, raise, list)
}
// Weed out duplicates
config.measurements = [...new Set(list)]
return config
}
// Add part-level optional measurements
const addPartOptionalMeasurements = (part, config, raise, 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=[]) => {
export const mergeDependencies = (dep = [], current = []) => {
// Current dependencies
const list = []
if (Array.isArray(current)) list.push(...current)
@ -557,43 +427,129 @@ export const mergeDependencies = (dep=[], current=[]) => {
return deps
}
// Add part-level dependencies
export const addPartDependencies = (part, config) => {
// Decorate an object with a non-enumerable property
export function addNonEnumProp(obj, name, value) {
Object.defineProperty(obj, name, {
enumerable: false,
configurable: false,
writable: true,
value,
})
}
// Add part-level options
const addPartOptions = (part, config, store) => {
if (part.options) {
for (const optionName in part.options) {
store.log.debug(`Config resolver: Option __${optionName}__ in ${part.name}`)
config.options[optionName] = part.options[optionName]
}
}
if (part.from) addPartOptions(part.from, config, store)
if (part.after) {
if (typeof config.dependencies === 'undefined') config.dependencies = {}
config.dependencies[part.name] = mergeDependencies(config.dependencies[part.name], part.after)
if (Array.isArray(part.after)) {
for (const dep of part.after) addPartOptions(dep, config, store)
} else addPartOptions(part.after, config, store)
}
return config
}
// Add part-level measurements
const addPartMeasurements = (part, config, store, list = false) => {
if (!list) list = config.measurements ? [...config.measurements] : []
if (part.measurements) {
for (const m of part.measurements) {
list.push(m)
store.log.debug(`Config resolver: Measurement __${m}__ is required in ${part.name}`)
}
}
if (part.from) addPartMeasurements(part.from, config, store, list)
if (part.after) {
if (Array.isArray(part.after)) {
for (const dep of part.after) addPartMeasurements(dep, config, store, list)
} else addPartMeasurements(part.after, config, store, list)
}
// Weed out duplicates
config.measurements = [...new Set(list)]
return config
}
// Add part-level optional measurements
const addPartOptionalMeasurements = (part, config, store, 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) {
store.log.debug(`Config resolver: Measurement __${m}__ is optional in ${part.name}`)
list.push(m)
}
}
}
if (part.from) addPartOptionalMeasurements(part.from, config, store, list)
if (part.after) {
if (Array.isArray(part.after)) {
for (const dep of part.after) addPartOptionalMeasurements(dep, config, store, list)
} else addPartOptionalMeasurements(part.after, config, store, list)
}
// Weed out duplicates
config.optionalMeasurements = [...new Set(list)]
return config
}
// Add part-level dependencies
//export const addPartDependencies = (part, config, store) => {
// if (part.after) {
// if (typeof config.dependencies === 'undefined') config.dependencies = {}
// config.dependencies[part.name] = mergeDependencies(config.dependencies[part.name], part.after)
// }
//
// return config
//}
// Add part-level plugins
export const addPartPlugins = (part, config, raise) => {
export const addPartPlugins = (part, config, store) => {
const plugins = {}
if (!part.plugins) return config
if (!Array.isArray(part.plugins)) part.plugins = [ part.plugins ]
for (const plugin of config.plugins) plugins[plugin.name] = plugin
if (!Array.isArray(part.plugins)) part.plugins = [part.plugins]
for (const plugin of part.plugins) {
store.log.debug(`Config resolver: Plugin __${plugin.name}__ in ${part.name}`)
// Do not overwrite an existing plugin with a conditional plugin unless it is also conditional
if (plugin.plugin && plugin.condition) {
if (config.plugins[plugin.plugin.name]?.condition) {
raise.info(`Plugin \`${plugin.plugin.name}\` was re-requested conditionally. Overwriting earlier condition.`)
config.plugins[plugin.plugin.name] = plugin
}
else raise.info(`Plugin \`${plugin.plugin.name}\` was requested conditionally, but is already loaded explicitly. Not loading.`)
if (plugins[plugin.plugin.name]?.condition) {
store.log.info(
`Plugin \`${plugin.plugin.name}\` was re-requested conditionally. Overwriting earlier condition.`
)
plugins[plugin.plugin.name] = plugin
} else
store.log.info(
`Plugin \`${plugin.plugin.name}\` was requested conditionally, but is already loaded explicitly. Not loading.`
)
} else {
plugins[plugin.name] = plugin
}
}
else config.plugins[plugin.name] = plugin
return {
...config,
plugins: [...new Set(Object.values(plugins))]
}
}
export const addPartConfig = (part, config, store) => {
// Add parts, using set to keep them unique in the array
config.parts = [...new Set(config.parts).add(part)]
config = addPartOptions(part, config, store)
config = addPartMeasurements(part, config, store)
config = addPartOptionalMeasurements(part, config, store)
//config = addPartDependencies(part, config, store)
config = addPartPlugins(part, config, store)
return config
}
export const addPartConfig = (part, config, raise) => {
config = addPartOptions(part, config, raise)
config = addPartMeasurements(part, config, raise)
config = addPartOptionalMeasurements(part, config, raise)
config = addPartDependencies(part, config, raise)
config = addPartOptionGroups(part, config, raise)
config = addPartPlugins(part, config, raise)
return config
}