1
0
Fork 0

Merge pull request #2827 from freesewing/multiset

feat(core): Adds support for managing multiple sets of settings (aka mutisets)
This commit is contained in:
Joost De Cock 2022-09-18 15:12:15 +02:00 committed by GitHub
commit 026560ef24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 5034 additions and 3428 deletions

1
.gitignore vendored
View file

@ -96,6 +96,7 @@ node_modules
dist
build
export
packages/core/out
# Prebuild files
prebuild/*.json

View file

@ -23,6 +23,7 @@ core:
testci: 'mocha tests/*.test.mjs'
prettier: "npx prettier --write 'src/*.mjs' 'tests/*.mjs'"
lint: "npx eslint 'src/*.mjs' 'tests/*.mjs'"
jsdoc: "jsdoc -c jsdoc.json -r src"
i18n:
prebuild: 'node scripts/prebuilder.mjs'
models:

View file

@ -0,0 +1 @@
jsdoc.json

17
packages/core/jsdoc.json Normal file
View file

@ -0,0 +1,17 @@
{
"plugins": [],
"recurseDepth": 2,
"source": {
"includePattern": ".+\\.mjs?$",
"excludePattern": "(^|\\/|\\\\)_"
},
"sourceType": "module",
"tags": {
"allowUnknownTags": true,
"dictionaries": ["jsdoc","closure"]
},
"templates": {
"cleverLinks": false,
"monospaceLinks": false
}
}

View file

@ -43,6 +43,7 @@
"report": "c8 report",
"testci": "mocha tests/*.test.mjs",
"prettier": "npx prettier --write 'src/*.mjs' 'tests/*.mjs'",
"jsdoc": "jsdoc -c jsdoc.json -r src",
"cibuild_step0": "node build.mjs"
},
"peerDependencies": {},

View file

@ -1,8 +1,30 @@
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for Attributes
*
* @constructor
* @return {Attributes} this - The Attributes instance
*/
export function Attributes() {
this.list = {}
return this
}
/** Adds an attribute */
//////////////////////////////////////////////
// PUBLIC METHODS //
//////////////////////////////////////////////
/**
* Add an attribute
*
* @param {string} name - Name of the attribute to add
* @param {string} value - Value of the attribute to add
* @return {Attributes} this - The Attributes instance
*/
Attributes.prototype.add = function (name, value) {
if (typeof this.list[name] === 'undefined') {
this.list[name] = []
@ -12,75 +34,12 @@ Attributes.prototype.add = function (name, value) {
return this
}
/** Sets an attribute, overwriting existing value */
Attributes.prototype.set = function (name, value) {
this.list[name] = [value]
return this
}
/** Sets an attribute, but only if it's not currently set */
Attributes.prototype.setIfUnset = function (name, value) {
if (typeof this.list[name] === 'undefined') this.list[name] = [value]
return this
}
/** Removes an attribute */
Attributes.prototype.remove = function (name) {
delete this.list[name]
return this
}
/** Retrieves an attribute */
Attributes.prototype.get = function (name) {
if (typeof this.list[name] === 'undefined') return false
else return this.list[name].join(' ')
}
/** Retrieves an attribute as array*/
Attributes.prototype.getAsArray = function (name) {
if (typeof this.list[name] === 'undefined') return false
else return this.list[name]
}
/** Returns SVG code for attributes */
Attributes.prototype.render = function () {
let svg = ''
for (let key in this.list) {
svg += ` ${key}="${this.list[key].join(' ')}"`
}
return svg
}
/** Returns CSS code for attributes */
Attributes.prototype.renderAsCss = function () {
let css = ''
for (let key in this.list) {
css += ` ${key}:${this.list[key].join(' ')};`
}
return css
}
/** Returns SVG code for attributes with a fiven prefix
* typically used for data-text*/
Attributes.prototype.renderIfPrefixIs = function (prefix = '') {
let svg = ''
let prefixLen = prefix.length
for (let key in this.list) {
if (key.substr(0, prefixLen) === prefix) {
svg += ` ${key.substr(prefixLen)}="${this.list[key].join(' ')}"`
}
}
return svg
}
/** Returns a props object for attributes with a fiven prefix
* typically used for data-text*/
/**
* Return a props object for attributes with a fiven prefix (typically used for data-text)
*
* @param {string} prefix - The prefix to filter attributes on
* @return {object} props - The attributes as props
*/
Attributes.prototype.asPropsIfPrefixIs = function (prefix = '') {
let props = {}
let prefixLen = prefix.length
@ -95,10 +54,120 @@ Attributes.prototype.asPropsIfPrefixIs = function (prefix = '') {
return props
}
/** Returns a deep copy of this */
/**
* Return a deep copy of this
*
* @return {object} this - The Attributes instance
*/
Attributes.prototype.clone = function () {
let clone = new Attributes()
clone.list = JSON.parse(JSON.stringify(this.list))
return clone
}
/**
* Retrieve an attribute
*
* @param {string} name - Name of the attribute to get
* @return value - The value under name
*/
Attributes.prototype.get = function (name) {
if (typeof this.list[name] === 'undefined') return false
else return this.list[name].join(' ')
}
/**
* Retrieve an attribute as array
*
* @param {string} name - Name of the attribute to set
* @return {object} this - The Attributes instance
*/
Attributes.prototype.getAsArray = function (name) {
if (typeof this.list[name] === 'undefined') return false
else return this.list[name]
}
/**
* Remove an attribute
*
* @param {string} name - Name of the attribute to set
* @return {object} this - The Attributes instance
*/
Attributes.prototype.remove = function (name) {
delete this.list[name]
return this
}
/**
* Return SVG code for attributes
*
* @return {string} svg - The SVG code
*/
Attributes.prototype.render = function () {
let svg = ''
for (let key in this.list) {
svg += ` ${key}="${this.list[key].join(' ')}"`
}
return svg
}
/**
* Return CSS code for attributes
*
* @return {string} css - The CSS code
*/
Attributes.prototype.renderAsCss = function () {
let css = ''
for (let key in this.list) {
css += ` ${key}:${this.list[key].join(' ')};`
}
return css
}
/**
* Return SVG code for attributes with a fiven prefix (typically used for data-text)
*
* @param {string} prefix - The prefix to filter attributes on
* @return {string} svg - The SVG code
*/
Attributes.prototype.renderIfPrefixIs = function (prefix = '') {
let svg = ''
let prefixLen = prefix.length
for (let key in this.list) {
if (key.substr(0, prefixLen) === prefix) {
svg += ` ${key.substr(prefixLen)}="${this.list[key].join(' ')}"`
}
}
return svg
}
/**
* Set an attribute, overwriting existing value
*
* @param {string} name - Name of the attribute to set
* @param {string} value - Value of the attribute to set
* @return {Attributes} this - The Attributes instance
*/
Attributes.prototype.set = function (name, value) {
this.list[name] = [value]
return this
}
/**
* Sets an attribute, but only if it's not currently set
*
* @param {string} name - Name of the attribute to set
* @param {string} value - Value of the attribute to set
* @return {Attributes} this - The Attributes instance
*/
Attributes.prototype.setIfUnset = function (name, value) {
if (typeof this.list[name] === 'undefined') this.list[name] = [value]
return this
}

View file

@ -1,15 +1,31 @@
export const loadDesignDefaults = () => ({
/**
* Return an object holding the defaults for a design configuration
*
* @function
* @private
* @return {object} defaults - The default design configuration
*/
export const __loadDesignDefaults = () => ({
measurements: [],
optionalMeasurements: [],
options: {},
optionDistance: {},
parts: [],
data: {},
plugins: [],
})
export const loadPatternDefaults = () => ({
/**
* Return an object holding the defaults for pattern settings
*
* @function
* @private
* @return {object} defaults - The default pattern settings
*/
export const __loadPatternDefaults = () => ({
complete: true,
idPrefix: 'fs-',
stackPrefix: '',
locale: 'en',
units: 'metric',
margin: 2,

View file

@ -1,21 +1,28 @@
import { Pattern } from './pattern.mjs'
import { loadDesignDefaults } from './config.mjs'
import { __loadDesignDefaults } from './config.mjs'
/*
* The Design constructor. Returns a Pattern constructor
* So it's sort of a super-constructor
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Return a Pattern constructor (it's a super-constructor)
*
* @constructor
* @param {object} config - The design configuration
* @return {function} pattern - The pattern constructor
*/
export function Design(config) {
// Initialize config with defaults
config = { ...loadDesignDefaults(), ...config }
config = { ...__loadDesignDefaults(), ...config }
// Create the pattern constructor
const pattern = function (settings) {
const pattern = function (...sets) {
// Pass the design config
Pattern.call(this, config)
// Pass the pattern settings
return this.__applySettings(settings)
return this.__applySettings(sets)
}
// Set up inheritance

View file

@ -1,3 +1,9 @@
/**
* Returns an object holding the defaults hooks structure
*
* @constructor
* @return {object} hooks - The default hooks holding structure
*/
export function Hooks() {
return {
preDraft: [],

View file

@ -1,39 +1,37 @@
import { Bezier } from 'bezier-js'
import { Attributes } from './attributes.mjs'
import { Design } from './design.mjs'
import { Pattern } from './pattern.mjs'
import { Part } from './part.mjs'
import { Point } from './point.mjs'
import { Path } from './path.mjs'
import { Snippet } from './snippet.mjs'
import { Store } from './store.mjs'
import {
isCoord,
capitalize,
beamsIntersect,
linesIntersect,
pointOnBeam,
pointOnLine,
pointOnCurve,
splitCurve,
beamIntersectsCircle,
beamIntersectsX,
beamIntersectsY,
units,
lineIntersectsCurve,
beamsIntersect,
capitalize,
circlesIntersect,
curveEdge,
curveIntersectsX,
curveIntersectsY,
curvesIntersect,
circlesIntersect,
beamIntersectsCircle,
lineIntersectsCircle,
curveEdge,
stretchToScale,
round,
sampleStyle,
deg2rad,
rad2deg,
pctBasedOn,
Bezier,
generateStackTransform,
macroName,
lineIntersectsCircle,
lineIntersectsCurve,
linesIntersect,
pctBasedOn,
pointOnBeam,
pointOnCurve,
pointOnLine,
rad2deg,
round,
splitCurve,
stretchToScale,
units,
} from './utils.mjs'
import { version } from '../data.mjs'
@ -44,35 +42,34 @@ export {
Pattern,
Point,
Path,
Part,
Snippet,
Store,
version,
Bezier,
capitalize,
beamsIntersect,
linesIntersect,
pointOnBeam,
pointOnLine,
pointOnCurve,
splitCurve,
// Utils
beamIntersectsCircle,
beamIntersectsX,
beamIntersectsY,
units,
lineIntersectsCurve,
beamsIntersect,
capitalize,
circlesIntersect,
curveEdge,
curveIntersectsX,
curveIntersectsY,
curvesIntersect,
circlesIntersect,
beamIntersectsCircle,
lineIntersectsCircle,
curveEdge,
stretchToScale,
round,
sampleStyle,
deg2rad,
rad2deg,
pctBasedOn,
generateStackTransform,
macroName,
isCoord,
version,
lineIntersectsCircle,
lineIntersectsCurve,
linesIntersect,
pctBasedOn,
pointOnBeam,
pointOnCurve,
pointOnLine,
rad2deg,
round,
splitCurve,
stretchToScale,
units,
}

View file

@ -1,7 +0,0 @@
export function Option(config) {
this.id = config.id
this.config = config
this.val = config.val
return this
}

View file

@ -1,23 +1,33 @@
import { Attributes } from './attributes.mjs'
import * as utils from './utils.mjs'
import { Point } from './point.mjs'
import { Path } from './path.mjs'
import { Snippet } from './snippet.mjs'
import { Point, pointsProxy } from './point.mjs'
import { Path, pathsProxy } from './path.mjs'
import { Snippet, snippetsProxy } from './snippet.mjs'
import { Hooks } from './hooks.mjs'
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for a Part
*
* @constructor
* @return {Part} this - The Part instance
*/
export function Part() {
// Non-enumerable properties
utils.addNonEnumProp(this, 'freeId', 0)
utils.addNonEnumProp(this, 'topLeft', false)
utils.addNonEnumProp(this, 'bottomRight', false)
utils.addNonEnumProp(this, 'width', false)
utils.addNonEnumProp(this, 'height', false)
utils.addNonEnumProp(this, 'utils', utils)
utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
utils.addNonEnumProp(this, 'Point', Point)
utils.addNonEnumProp(this, 'Path', Path)
utils.addNonEnumProp(this, 'Snippet', Snippet)
utils.addNonEnumProp(this, 'hooks', new Hooks())
utils.__addNonEnumProp(this, 'freeId', 0)
utils.__addNonEnumProp(this, 'topLeft', false)
utils.__addNonEnumProp(this, 'bottomRight', false)
utils.__addNonEnumProp(this, 'width', false)
utils.__addNonEnumProp(this, 'height', false)
utils.__addNonEnumProp(this, 'utils', utils)
utils.__addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
utils.__addNonEnumProp(this, 'Point', Point)
utils.__addNonEnumProp(this, 'Path', Path)
utils.__addNonEnumProp(this, 'Snippet', Snippet)
utils.__addNonEnumProp(this, 'hooks', new Hooks())
// Enumerable properties
this.render = true // FIXME: Replace render with hide
@ -31,56 +41,163 @@ export function Part() {
return this
}
Part.prototype.macroClosure = function () {
let self = this
let method = function (key, args) {
let macro = utils.macroName(key)
if (typeof self[macro] === 'function') self[macro](args)
//////////////////////////////////////////////
// PUBLIC METHODS //
//////////////////////////////////////////////
/**
* Adds an attribute in a chainable way
*
* @param {string} name - Name of the attribute to add
* @param {string} value - Value of the attribute to add
* @param {bool} overwrite - Whether to overwrite an existing attrubute or not
* @return {Part} this - The part instance
*/
Part.prototype.attr = function (name, value, overwrite = false) {
if (overwrite) this.attributes.set(name, value)
else this.attributes.add(name, value)
return this
}
return method
}
Part.prototype.runHooks = function (hookName, data = false) {
if (data === false) data = this
let hooks = this.hooks[hookName]
if (hooks && hooks.length > 0) {
for (let hook of hooks) {
hook.method(data, hook.data)
}
}
}
/** Returns an unused ID */
/**
* Returns on unused ID (unused in this part)
*
* @param {string} prefix - An optional prefix to apply to the ID
* @return {string} id - The id
*/
Part.prototype.getId = function (prefix = '') {
this.freeId += 1
return prefix + this.freeId
}
/** Returns a value formatted for units provided in settings */
Part.prototype.unitsClosure = function () {
const self = this
const method = function (value) {
if (typeof value !== 'number')
self.context.store.log.warning(
`Calling \`units(value)\` but \`value\` is not a number (\`${typeof value}\`)`
/** Returns an object with shorthand access for pattern design */
/**
* Returns an object that will be passed to draft method to be destructured
*
* @return {object} short - The so-called shorthand object with what you might need in your draft method
*/
Part.prototype.shorthand = function () {
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 = {
part: this,
sa,
scale: this.context.settings?.scale,
store: this.context.store,
macro: this.__macroClosure(),
units: this.__unitsClosure(),
utils: utils,
complete,
paperless,
events: this.context.events,
log: this.context.store.log,
addCut: this.addCut,
removeCut: this.removeCut,
}
// Add top-level store methods and add a part name parameter
const partName = this.name
for (const [key, method] of Object.entries(this.context.store)) {
if (typeof method === 'function')
shorthand[key] = function (...args) {
return method(partName, ...args)
}
}
// We'll need this
let self = this
// Wrap the Point constructor so objects can log
shorthand.Point = function (x, y) {
Point.apply(this, [x, y])
Object.defineProperty(this, 'log', { value: self.context.store.log })
}
shorthand.Point.prototype = Object.create(Point.prototype)
// Wrap the Path constructor so objects can log
shorthand.Path = function () {
Path.apply(this, [true])
Object.defineProperty(this, 'log', { value: self.context.store.log })
}
shorthand.Path.prototype = Object.create(Path.prototype)
// Wrap the Snippet constructor so objects can log
shorthand.Snippet = function (def, anchor) {
Snippet.apply(this, [def, anchor, true])
Snippet.apply(this, arguments)
Object.defineProperty(this, 'log', { value: self.context.store.log })
}
shorthand.Snippet.prototype = Object.create(Snippet.prototype)
// Proxy points, paths, snippets, measurements, options, and absoluteOptions
shorthand.points = new Proxy(this.points || {}, pointsProxy(self.points, self.context.store.log))
shorthand.paths = new Proxy(this.paths || {}, pathsProxy(self.paths, self.context.store.log))
shorthand.snippets = new Proxy(
this.snippets || {},
snippetsProxy(self.snippets, self.context.store.log)
)
return utils.units(value, self.context.settings.units)
shorthand.measurements = new Proxy(this.context.settings.measurements || {}, {
get: function (measurements, name) {
if (typeof measurements[name] === 'undefined')
self.context.store.log.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.options = new Proxy(this.context.settings.options || {}, {
get: function (options, name) {
if (typeof options[name] === 'undefined')
self.context.store.log.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.absoluteOptions = new Proxy(this.context.settings.absoluteOptions || {}, {
get: function (absoluteOptions, name) {
if (typeof absoluteOptions[name] === 'undefined')
self.context.store.log.warning(
`Tried to access \`absoluteOptions.${name}\` but it is \`undefined\``
)
return Reflect.get(...arguments)
},
set: (absoluteOptions, name, value) => (self.context.settings.absoluteOptions[name] = value),
})
return shorthand
}
return method
/**
* Returns a value formatted for units set in settings
*
* @param {float} input - The value to format
* @return {string} result - The input formatted for the units set in settings
*/
Part.prototype.units = function (input) {
return utils.units(input, this.context.settings.units)
}
/** Calculates the part's bounding box and sets it */
Part.prototype.boundary = function () {
//////////////////////////////////////////////
// PRIVATE METHODS //
//////////////////////////////////////////////
/**
* Calculates the part's bounding box and mutates the part to set it
*
* @private
* @return {Part} this - The part instance
*/
Part.prototype.__boundary = function () {
if (this.topLeft) return this // Cached
let topLeft = new Point(Infinity, Infinity)
let bottomRight = new Point(-Infinity, -Infinity)
for (let key in this.paths) {
try {
let path = this.paths[key].boundary()
let path = this.paths[key].__boundary()
if (path.render) {
if (path.topLeft.x < topLeft.x) topLeft.x = path.topLeft.x
if (path.topLeft.y < topLeft.y) topLeft.y = path.topLeft.y
@ -120,16 +237,14 @@ Part.prototype.boundary = function () {
return this
}
/** Adds an attribute. This is here to make this call chainable in assignment */
Part.prototype.attr = function (name, value, overwrite = false) {
if (overwrite) this.attributes.set(name, value)
else this.attributes.add(name, value)
return this
}
/** Copies point/path/snippet data from part orig into this */
Part.prototype.inject = function (orig) {
/**
* Copies point/path/snippet data from part orig into this
*
* @private
* @param {object} orig - The original part to inject into this
* @return {Part} this - The part instance
*/
Part.prototype.__inject = function (orig) {
const findBasePoint = (p) => {
for (let i in orig.points) {
if (orig.points[i] === p) return i
@ -161,196 +276,37 @@ Part.prototype.inject = function (orig) {
return this
}
Part.prototype.units = function (input) {
return utils.units(input, this.context.settings.units)
}
/** Returns an object with shorthand access for pattern design */
Part.prototype.shorthand = function () {
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 = {
part: this,
sa,
scale: this.context.settings?.scale,
store: this.context.store,
macro: this.macroClosure(),
units: this.unitsClosure(),
utils: utils,
complete,
paperless,
events: this.context.events,
log: this.context.store.log,
addCut: this.addCut,
removeCut: this.removeCut,
}
// Add top-level store methods and add a part name parameter
const partName = this.name
for (const [key, method] of Object.entries(this.context.store)) {
if (typeof method === 'function')
shorthand[key] = function (...args) {
return method(partName, ...args)
}
}
// We'll need this
/**
* Returns a closure holding the macro method
*
* @private
* @return {function} method - The closured macro method
*/
Part.prototype.__macroClosure = function () {
let self = this
// Wrap the Point constructor so objects can log
shorthand.Point = function (x, y) {
Point.apply(this, [x, y, true])
Object.defineProperty(this, 'log', { value: self.context.store.log })
}
shorthand.Point.prototype = Object.create(Point.prototype)
// Wrap the Path constructor so objects can log
shorthand.Path = function () {
Path.apply(this, [true])
Object.defineProperty(this, 'log', { value: self.context.store.log })
}
shorthand.Path.prototype = Object.create(Path.prototype)
// Wrap the Snippet constructor so objects can log
shorthand.Snippet = function (def, anchor) {
Snippet.apply(this, [def, anchor, true])
Snippet.apply(this, arguments)
Object.defineProperty(this, 'log', { value: self.context.store.log })
}
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.store.log.warning(
`\`points.${name}\` was set with a value that is not a \`Point\` object`
)
if (value.x == null || !utils.isCoord(value.x))
self.context.store.log.warning(
`\`points.${name}\` was set with a \`x\` parameter that is not a \`number\``
)
if (value.y == null || !utils.isCoord(value.y))
self.context.store.log.warning(
`\`points.${name}\` was set with a \`y\` parameter that is not a \`number\``
)
try {
value.name = name
} catch (err) {
self.context.store.log.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.store.log.warning(
`\`paths.${name}\` was set with a value that is not a \`Path\` object`
)
try {
value.name = name
} catch (err) {
self.context.store.log.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 (...args) {
return Reflect.get(...args)
},
set: (snippets, name, value) => {
// Constructor checks
if (value instanceof Snippet !== true)
self.context.store.log.warning(
`\`snippets.${name}\` was set with a value that is not a \`Snippet\` object`
)
if (typeof value.def !== 'string')
self.context.store.log.warning(
`\`snippets.${name}\` was set with a \`def\` parameter that is not a \`string\``
)
if (value.anchor instanceof Point !== true)
self.context.store.log.warning(
`\`snippets.${name}\` was set with an \`anchor\` parameter that is not a \`Point\``
)
try {
value.name = name
} catch (err) {
self.context.store.log.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.store.log.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.store.log.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.store.log.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
let method = function (key, args) {
let macro = utils.__macroName(key)
if (typeof self[macro] === 'function') self[macro](args)
}
Part.prototype.isEmpty = function () {
if (Object.keys(this.snippets).length > 0) return false
if (Object.keys(this.paths).length > 0) {
for (const p in this.paths) {
if (this.paths[p].render && this.paths[p].length()) return false
}
return method
}
for (const p in this.points) {
if (this.points[p].attributes.get('data-text')) return false
if (this.points[p].attributes.get('data-circle')) return false
/**
* Returns a method to format values in the units provided in settings
*
* @private
* @return {function} method - The closured units method
*/
Part.prototype.__unitsClosure = function () {
const self = this
const method = function (value) {
if (typeof value !== 'number')
self.context.store.log.warning(
`Calling \`units(value)\` but \`value\` is not a number (\`${typeof value}\`)`
)
return utils.units(value, self.context.settings.units)
}
return true
return method
}
export default Part

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,230 +1,319 @@
import { Attributes } from './attributes.mjs'
import { __isCoord, rad2deg, deg2rad } from './utils.mjs'
export function Point(x, y, debug = false) {
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for a Point
*
* @constructor
* @param {float} x - X-coordinate of the Point
* @param {float} y - Y-coordinate of the Point
* @return {Point} this - The Point instance
*/
export function Point(x, y) {
this.x = x
this.y = y
this.attributes = new Attributes()
Object.defineProperty(this, 'debug', { value: debug, configurable: true })
}
/** Adds the raise method for a path not created through the proxy **/
Point.prototype.withRaise = function (raise = false) {
if (raise) Object.defineProperty(this, 'raise', { value: raise })
//////////////////////////////////////////////
// PUBLIC METHODS //
//////////////////////////////////////////////
return this
/**
* Returns the angle between this Point and that Point
*
* @param {Point} that - The Point instance to calculate the angle with
* @return {float} angle - The angle between this Point and that Point
*/
Point.prototype.angle = function (that) {
let rad = Math.atan2(-1 * this.__check().dy(that.__check()), this.dx(that))
while (rad < 0) rad += 2 * Math.PI
return rad2deg(rad)
}
/** Debug method to validate point data **/
Point.prototype.check = function () {
if (typeof this.x !== 'number') this.raise.warning('X value of `Point` is not a number')
if (typeof this.y !== 'number') this.raise.warning('Y value of `Point` is not a number')
}
/** Radians to degrees */
Point.prototype.rad2deg = function (radians) {
return radians * 57.29577951308232
}
/** Degrees to radians */
Point.prototype.deg2rad = function (degrees) {
return degrees / 57.29577951308232
}
/** Adds an attribute. This is here to make this call chainable in assignment */
/**
* Chainable way to add an attribute to the Point
*
* @param {string} name - Name of the attribute to add
* @param {string} value - Value of the attribute to add
* @param {bool} overwrite - Whether to overwrite an existing attrubute or not
* @return {object} this - The Point instance
*/
Point.prototype.attr = function (name, value, overwrite = false) {
this.check()
if (overwrite) this.attributes.set(name, value)
else this.attributes.add(name, value)
return this
return this.__check()
}
/** Returns the distance between this point and that point */
Point.prototype.dist = function (that) {
this.check()
that.check()
let dx = this.x - that.x
let dy = this.y - that.y
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
}
/** Returns slope of a line made by this point and that point */
Point.prototype.slope = function (that) {
this.check()
that.check()
return (that.y - this.y) / (that.x - this.x)
}
/** Returns the x-delta between this point and that point */
Point.prototype.dx = function (that) {
this.check()
that.check()
return that.x - this.x
}
/** Returns the y-delta between this point and that point */
Point.prototype.dy = function (that) {
this.check()
that.check()
return that.y - this.y
}
/** Returns the angle between this point and that point */
Point.prototype.angle = function (that) {
this.check()
that.check()
let rad = Math.atan2(-1 * this.dy(that), this.dx(that))
while (rad < 0) rad += 2 * Math.PI
return this.rad2deg(rad)
}
/** Rotate this point deg around that point */
Point.prototype.rotate = function (deg, that) {
if (typeof deg !== 'number')
this.raise.warning('Called `Point.rotate(deg,that)` but `deg` is not a number')
if (that instanceof Point !== true)
this.raise.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
this.check()
that.check()
let radius = this.dist(that)
let angle = this.angle(that)
let x = that.x + radius * Math.cos(this.deg2rad(angle + deg)) * -1
let y = that.y + radius * Math.sin(this.deg2rad(angle + deg))
return new Point(x, y, this.debug).withRaise(this.raise)
}
/** returns an identical copy of this point */
Point.prototype.copy = function () {
this.check()
return new Point(this.x, this.y, this.debug).withRaise(this.raise)
}
/** Mirrors this point around X value of that point */
Point.prototype.flipX = function (that = false) {
this.check()
if (that) {
if (that instanceof Point !== true)
this.raise.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
that.check()
}
if (that === false || that.x === 0)
return new Point(this.x * -1, this.y, this.debug).withRaise(this.raise)
else return new Point(that.x + this.dx(that), this.y, this.debug).withRaise(this.raise)
}
/** Mirrors this point around Y value of that point */
Point.prototype.flipY = function (that = false) {
this.check()
if (that) {
if (that instanceof Point !== true)
this.raise.warning('Called `Point.flipY(that)` but `that` is not a `Point` object')
that.check()
}
if (that === false || that.y === 0)
return new Point(this.x, this.y * -1, this.debug).withRaise(this.raise)
else return new Point(this.x, that.y + this.dy(that), this.debug).withRaise(this.raise)
}
/** Shifts this point distance in the deg direction */
Point.prototype.shift = function (deg, distance) {
this.check()
if (typeof deg !== 'number') this.raise.warning('Called `Point.shift` but `deg` is not a number')
if (typeof distance !== 'number')
this.raise.warning('Called `Point.shift` but `distance` is not a number')
let p = this.copy()
p.x += distance
return p.rotate(deg, this)
}
/** Shifts this point distance in the direction of that point */
Point.prototype.shiftTowards = function (that, distance) {
if (typeof distance !== 'number')
this.raise.warning('Called `Point.shiftTowards` but `distance` is not a number')
if (that instanceof Point !== true)
this.raise.warning(
'Called `Point.shiftTowards(that, distance)` but `that` is not a `Point` object'
)
this.check()
that.check()
return this.shift(this.angle(that), distance)
}
/** Checks whether this has the same coordinates as that */
Point.prototype.sitsOn = function (that) {
if (that instanceof Point !== true)
this.raise.warning('Called `Point.sitsOn(that)` but `that` is not a `Point` object')
this.check()
that.check()
if (this.x === that.x && this.y === that.y) return true
else return false
}
/** Checks whether this has roughly the same coordinates as that */
Point.prototype.sitsRoughlyOn = function (that) {
if (that instanceof Point !== true)
this.raise.warning('Called `Point.sitsRoughlyOn(that)` but `that` is not a `Point` object')
this.check()
that.check()
if (Math.round(this.x) === Math.round(that.x) && Math.round(this.y) === Math.round(that.y))
return true
else return false
}
/** Shifts this point fraction of the distance towards that point */
Point.prototype.shiftFractionTowards = function (that, fraction) {
if (that instanceof Point !== true)
this.raise.warning(
'Called `Point.shiftFractionTowards(that, fraction)` but `that` is not a `Point` object'
)
if (typeof fraction !== 'number')
this.raise.warning('Called `Point.shiftFractionTowards` but `fraction` is not a number')
this.check()
that.check()
return this.shiftTowards(that, this.dist(that) * fraction)
}
/** Shifts this point distance beyond that point */
Point.prototype.shiftOutwards = function (that, distance) {
if (that instanceof Point !== true)
this.raise.warning(
'Called `Point.shiftOutwards(that, distance)` but `that` is not a `Point` object'
)
if (typeof distance !== 'number')
this.raise.warning(
'Called `Point.shiftOutwards(that, distance)` but `distance` is not a number'
)
this.check()
that.check()
return this.shiftTowards(that, this.dist(that) + distance)
}
/** Returns a deep copy of this */
/**
* returns an deel clone of this Point (including coordinates)
*
* @return {Point} clone - The cloned Point instance
*/
Point.prototype.clone = function () {
this.check()
const clone = new Point(this.x, this.y, this.debug).withRaise(this.raise)
this.__check()
const clone = new Point(this.x, this.y).__withLog(this.log)
clone.attributes = this.attributes.clone()
return clone
}
/** Applies a translate transform */
/**
* returns an copy of this Point (coordinates only)
*
* @return {Point} copy - The copied Point instance
*/
Point.prototype.copy = function () {
return new Point(this.__check().x, this.y).__withLog(this.log)
}
/**
* Returns the distance between this Point and that Point
*
* @param {Point} that - The Point instance to calculate the distance to
* @return {float} distance - The distance between this Point and that Point
*/
Point.prototype.dist = function (that) {
const dx = this.__check().x - that.__check().x
const dy = this.y - that.y
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
}
/**
* Returns the distance along the X-axis between this Point and that Point (delta X)
*
* @param {Point} that - The Point to which to calcuate the X delta
* @return {float} slote - The X delta
*/
Point.prototype.dx = function (that) {
return that.__check().x - this.__check().x
}
/**
* Returns the distance along the Y-axis between this Point and that Point (delta Y)
*
* @param {Point} that - The Point to which to calcuate the Y delta
* @return {float} slote - The Y delta
*/
Point.prototype.dy = function (that) {
return that.__check().y - this.__check().y
}
/**
* Mirrors this Point around the X value of that Point
*
* @param {Point} that - The Point to flip around
* @return {Point} flopped - The new flipped Point instance
*/
Point.prototype.flipX = function (that = false) {
this.__check()
if (that) {
if (that instanceof Point !== true)
this.log.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
that.__check()
}
if (that === false || that.x === 0) return new Point(this.x * -1, this.y).__withLog(this.log)
else return new Point(that.x + this.dx(that), this.y).__withLog(this.log)
}
/**
* Mirrors this Point around the Y value of that Point
*
* @param {Point} that - The Point to flip around
* @return {Point} flipped - The new flipped Point instance
*/
Point.prototype.flipY = function (that = false) {
this.__check()
if (that) {
if (that instanceof Point !== true)
this.log.warning('Called `Point.flipY(that)` but `that` is not a `Point` object')
that.__check()
}
if (that === false || that.y === 0) return new Point(this.x, this.y * -1).__withLog(this.log)
else return new Point(this.x, that.y + this.dy(that)).__withLog(this.lo)
}
/**
* Rotate this Point deg around that Point
*
* @param {float} deg - The degrees to rotate
* @param {Point} that - The Point instance to rotate around
* @return {Point} rotated - The rotated Point instance
*/
Point.prototype.rotate = function (deg, that) {
if (typeof deg !== 'number')
this.log.warning('Called `Point.rotate(deg,that)` but `deg` is not a number')
if (that instanceof Point !== true)
this.log.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
const radius = this.__check().dist(that.__check())
const angle = this.angle(that)
const x = that.x + radius * Math.cos(deg2rad(angle + deg)) * -1
const y = that.y + radius * Math.sin(deg2rad(angle + deg))
return new Point(x, y).__withLog(this.log)
}
/**
* A chainable way to add a circle at a Point
*
* @param {float} radius - The circle radius
* @param {string} className - The CSS classes to apply to the circle
* @return {Point} this - The Point instance
*/
Point.prototype.setCircle = function (radius = false, className = false) {
if (radius) this.attributes.set('data-circle', radius)
if (className) this.attributes.set('data-circle-class', className)
return this.__check()
}
/**
* A chainable way to add text to a Point
*
* @param {string} text - The text to add to the Point
* @param {string} className - The CSS classes to apply to the text
* @return {Point} this - The Point instance
*/
Point.prototype.setText = function (text = '', className = false) {
this.attributes.set('data-text', text)
if (className) this.attributes.set('data-text-class', className)
return this.__check()
}
/**
* Shifts this Point distance in the deg direction
*
* @param {float} deg - The angle to shift towards
* @param {float} dist - The distance to shift
* @return {Point} shifted - The new shifted Point instance
*/
Point.prototype.shift = function (deg, dist) {
if (typeof deg !== 'number') this.log.warning('Called `Point.shift` but `deg` is not a number')
if (typeof dist !== 'number')
this.log.warning('Called `Point.shift` but `distance` is not a number')
let p = this.__check().copy()
p.x += dist
return p.rotate(deg, this)
}
/**
* Shifts this Point a fraction in the direction of that Point
*
* @param {Point} that - The Point to shift towards
* @param {float} fraction - The fraction to shift
* @return {Point} shifted - The new shifted Point instance
*/
Point.prototype.shiftFractionTowards = function (that, fraction) {
if (that instanceof Point !== true)
this.log.warning(
'Called `Point.shiftFractionTowards(that, fraction)` but `that` is not a `Point` object'
)
if (typeof fraction !== 'number')
this.log.warning('Called `Point.shiftFractionTowards` but `fraction` is not a number')
return this.__check().shiftTowards(that.__check(), this.dist(that) * fraction)
}
/**
* Shifts this Point outwards from that Point
*
* @param {Point} that - The Point to shift outwards from
* @param {float} distance - The distance to shift
* @return {Point} shifted - The new shifted Point instance
*/
Point.prototype.shiftOutwards = function (that, distance) {
if (that instanceof Point !== true)
this.log.warning(
'Called `Point.shiftOutwards(that, distance)` but `that` is not a `Point` object'
)
if (typeof distance !== 'number')
this.log.warning('Called `Point.shiftOutwards(that, distance)` but `distance` is not a number')
this.__check()
that.__check()
return this.__check().shiftTowards(that.__check(), this.dist(that) + distance)
}
/**
* Shifts this Point distance in the direction of that Point
*
* @param {Point} that - The Point to short towards
* @param {float} dist - The distance to shift
* @return {Point} shifted - The new shifted Point instance
*/
Point.prototype.shiftTowards = function (that, dist) {
if (typeof dist !== 'number')
this.log.warning('Called `Point.shiftTowards` but `distance` is not a number')
if (that instanceof Point !== true)
this.log.warning(
'Called `Point.shiftTowards(that, distance)` but `that` is not a `Point` object'
)
return this.__check().shift(this.angle(that.__check()), dist)
}
/**
* Checks whether this Point has the same coordinates as that Point
*
* @param {Point} that - The Point to compare coordinates with
* @return {bool} result - True if the Points' coordinates match, false when they do not
*/
Point.prototype.sitsOn = function (that) {
if (that instanceof Point !== true)
this.log.warning('Called `Point.sitsOn(that)` but `that` is not a `Point` object')
if (this.__check().x === that.__check().x && this.y === that.y) return true
else return false
}
/**
* Checks whether this Point has roughtly the same coordinates as that Point
*
* @param {Point} that - The Point to compare coordinates with
* @return {bool} result - True if the Points' coordinates roughty match, false when they do not
*/
Point.prototype.sitsRoughlyOn = function (that) {
if (that instanceof Point !== true)
this.log.warning('Called `Point.sitsRoughlyOn(that)` but `that` is not a `Point` object')
if (
Math.round(this.__check().x) === Math.round(that.__check().x) &&
Math.round(this.y) === Math.round(that.y)
)
return true
else return false
}
/**
* Returns slope of a line made by this Point and that Point
*
* @param {Point} that - The Point that forms the line together with this Point
* @return {float} slote - The slope of the line made by this Point and that Point
*/
Point.prototype.slope = function (that) {
return (that.__check().y - this.__check().y) / (that.x - this.x)
}
/**
* Returns a Point instance with a translate transform applied
*
* @param {float} x - The X-value of the translate transform
* @param {float} y - The Y-value of the translate transform
* @return {Point} translated - The translated Point instance
*/
Point.prototype.translate = function (x, y) {
this.check()
this.__check()
if (typeof x !== 'number')
this.raise.warning('Called `Point.translate(x,y)` but `x` is not a number')
this.log.warning('Called `Point.translate(x,y)` but `x` is not a number')
if (typeof y !== 'number')
this.raise.warning('Called `Point.translate(x,y)` but `y` is not a number')
this.log.warning('Called `Point.translate(x,y)` but `y` is not a number')
const p = this.copy()
p.x += x
p.y += y
@ -232,18 +321,67 @@ Point.prototype.translate = function (x, y) {
return p
}
/** Chainable way to set the data-text property (and optional class) */
Point.prototype.setText = function (text = '', className = false) {
this.attributes.set('data-text', text)
if (className) this.attributes.set('data-text-class', className)
//////////////////////////////////////////////
// PRIVATE METHODS //
//////////////////////////////////////////////
/**
* Checks the Points coordinates, and raises a warning when they are invalid
*
* @private
* @return {object} this - The Point instance
*/
Point.prototype.__check = function () {
if (typeof this.x !== 'number') this.log.warning('X value of `Point` is not a number')
if (typeof this.y !== 'number') this.log.warning('Y value of `Point` is not a number')
return this
}
/** Chainable way to set the data-circle property (and optional class) */
Point.prototype.setCircle = function (radius = false, className = false) {
if (radius) this.attributes.set('data-circle', radius)
if (className) this.attributes.set('data-circle-class', className)
/**
* Adds a logging instance so the Point can log
*
* @private
* @param {object} log - An object holding the logging methods
* @return {object} this - The Point instance
*/
Point.prototype.__withLog = function (log = false) {
if (log) Object.defineProperty(this, 'log', { value: log })
return this
}
//////////////////////////////////////////////
// PUBLIC STATIC METHODS //
//////////////////////////////////////////////
/**
* Returns a ready-to-proxy that logs when things aren't exactly ok
*
* @private
* @param {object} points - The points object to proxy
* @param {object} log - The logging object
* @return {object} proxy - The object that is ready to be proxied
*/
export function pointsProxy(points, log) {
return {
get: function (...args) {
return Reflect.get(...args)
},
set: (points, name, value) => {
// Constructor checks
if (value instanceof Point !== true)
log.warning(`\`points.${name}\` was set with a value that is not a \`Point\` object`)
if (value.x == null || !__isCoord(value.x))
log.warning(`\`points.${name}\` was set with a \`x\` parameter that is not a \`number\``)
if (value.y == null || !__isCoord(value.y))
log.warning(`\`points.${name}\` was set with a \`y\` parameter that is not a \`number\``)
try {
value.name = name
} catch (err) {
log.warning(`Could not set \`name\` property on \`points.${name}\``)
}
return (points[name] = value)
},
}
}

View file

@ -1,21 +1,38 @@
import { Attributes } from './attributes.mjs'
import { Point } from './point.mjs'
export function Snippet(def, anchor, debug = false) {
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for a Snippet
*
* @constructor
* @param {string} def - The id of the snippet in the SVG defs section
* @param {Point} anchor - The Point to anchor this Snippet on
* @return {Snippet} this - The Snippet instance
*/
export function Snippet(def, anchor) {
this.def = def
this.anchor = anchor
this.attributes = new Attributes()
Object.defineProperty(this, 'debug', { value: debug, configurable: true })
return this
}
/** Adds the raise method for a snippet not created through the proxy **/
Snippet.prototype.withRaise = function (raise = false) {
if (raise) Object.defineProperty(this, 'raise', { value: raise })
//////////////////////////////////////////////
// PUBLIC METHODS //
//////////////////////////////////////////////
return this
}
/** Adds an attribute. This is here to make this call chainable in assignment */
/**
* Chainable way to add an attribute
*
* @param {string} name - Name of the attribute to add
* @param {string} value - Value of the attribute to add
* @param {bool} overwrite - Whether to overwrite an existing attrubute or not
* @return {Snippet} this - The Snippet instance
*/
Snippet.prototype.attr = function (name, value, overwrite = false) {
if (overwrite) this.attributes.set(name, value)
else this.attributes.add(name, value)
@ -23,10 +40,69 @@ Snippet.prototype.attr = function (name, value, overwrite = false) {
return this
}
/** Returns a deep copy of this */
/**
* Returns a deep copy of this snippet
*
* @return {Snippet} clone - A clone of this Snippet instance
*/
Snippet.prototype.clone = function () {
let clone = new Snippet(this.def, this.anchor.clone(), this.debug).withRaise(this.raise)
let clone = new Snippet(this.def, this.anchor.clone()).__withLog(this.log)
clone.attributes = this.attributes.clone()
return clone
}
//////////////////////////////////////////////
// PRIVATE METHODS //
//////////////////////////////////////////////
/**
* Adds the log method for a snippet not created through the proxy
*
* @private
* @return {Snippet} this - The Snippet instance
*/
Snippet.prototype.__withLog = function (log = false) {
if (log) Object.defineProperty(this, 'log', { value: log })
return this
}
//////////////////////////////////////////////
// PUBLIC STATIC METHODS //
//////////////////////////////////////////////
/**
* Returns a ready-to-proxy that logs when things aren't exactly ok
*
* @private
* @param {object} snippets - The snippets object to proxy
* @param {object} log - The logging object
* @return {object} proxy - The object that is ready to be proxied
*/
export function snippetsProxy(snippets, log) {
return {
get: function (...args) {
return Reflect.get(...args)
},
set: (snippets, name, value) => {
// Constructor checks
if (value instanceof Snippet !== true)
log.warning(`\`snippets.${name}\` was set with a value that is not a \`Snippet\` object`)
if (typeof value.def !== 'string')
log.warning(
`\`snippets.${name}\` was set with a \`def\` parameter that is not a \`string\``
)
if (value.anchor instanceof Point !== true)
log.warning(
`\`snippets.${name}\` was set with an \`anchor\` parameter that is not a \`Point\``
)
try {
value.name = name
} catch (err) {
log.warning(`Could not set \`name\` property on \`snippets.${name}\``)
}
return (snippets[name] = value)
},
}
}

View file

@ -4,8 +4,8 @@ import * as utils from './utils.mjs'
export function Stack(name = null) {
// Non-enumerable properties
utils.addNonEnumProp(this, 'freeId', 0)
utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
utils.__addNonEnumProp(this, 'freeId', 0)
utils.__addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
// Enumerable properties
this.attributes = new Attributes()
@ -36,33 +36,13 @@ Stack.prototype.getPartNames = function () {
return [...this.parts].map((p) => p.name)
}
/** Homes the stack so that its top left corner is in (0,0) */
//Stack.prototype.home = function () {
// const parts = this.getPartList()
// if (parts.length < 1) return this
// for (const part of this.getPartList()) {
// part.home()
// }
//
// if (parts.length === 1) {
// this.topLeft = part.topLeft
// this.bottomRigth = part.bottomRight
// this.width = part.width
// this.height = part.height
//
// return this
// }
//
// return this.boundary()
//}
/** Calculates the stack's bounding box and sets it */
Stack.prototype.home = function () {
if (this.topLeft) return this // Cached
this.topLeft = new Point(Infinity, Infinity)
this.bottomRight = new Point(-Infinity, -Infinity)
for (const part of this.getPartList()) {
part.boundary()
part.__boundary()
if (part.topLeft.x < this.topLeft.x) this.topLeft.x = part.topLeft.x
if (part.topLeft.y < this.topLeft.y) this.topLeft.y = part.topLeft.y
if (part.bottomRight.x > this.bottomRight.x) this.bottomRight.x = part.bottomRight.x
@ -76,8 +56,11 @@ Stack.prototype.home = function () {
if (this.bottomRight.y === -Infinity) this.bottomRight.y = 0
// Add margin
let margin = this.context.settings.margin
if (this.context.settings.paperless && margin < 10) margin = 10
let margin = 0
for (const set in this.context.settings) {
if (this.context.settings[set].margin > margin) margin = this.context.settings[set].margin
if (this.context.settings[set].paperless && margin < 10) margin = 10
}
this.topLeft.x -= margin
this.topLeft.y -= margin
this.bottomRight.x += margin
@ -141,4 +124,24 @@ Stack.prototype.generateTransform = function (transforms) {
}
}
/** Homes the stack so that its top left corner is in (0,0) */
//Stack.prototype.home = function () {
// const parts = this.getPartList()
// if (parts.length < 1) return this
// for (const part of this.getPartList()) {
// part.home()
// }
//
// if (parts.length === 1) {
// this.topLeft = part.topLeft
// this.bottomRigth = part.bottomRight
// this.width = part.width
// this.height = part.height
//
// return this
// }
//
// return this.boundary()
//}
export default Stack

View file

@ -2,8 +2,20 @@ import set from 'lodash.set'
import unset from 'lodash.unset'
import get from 'lodash.get'
// Don't allow setting of these top-level keys in the store
const avoid = ['set', 'setIfUnset', 'push', 'unset', 'get', 'extend']
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for a Store
*
* @constructor
* @param {Array} methods - Any methods to add to the store
* @return {Store} this - The Store instance
*/
export function Store(methods = []) {
/*
* Default logging methods
@ -40,7 +52,16 @@ export function Store(methods = []) {
return this
}
/** Extends the store with additional methods */
//////////////////////////////////////////////
// PUBLIC METHODS //
//////////////////////////////////////////////
/**
* Extend the store with additional methods
*
* @param {function} method - Method to add to the store (variadic)
* @return {Store} this - The Store instance
*/
Store.prototype.extend = function (...methods) {
for (const [path, method] of methods) {
if (avoid.indexOf(method[0]) !== -1) {
@ -54,29 +75,29 @@ Store.prototype.extend = function (...methods) {
return this
}
/** Set key at path to value */
Store.prototype.set = function (path, value) {
if (typeof value === 'undefined') {
this.log.warning(`Store.set(value) on key \`${path}\`, but value is undefined`)
}
set(this, path, value)
return this
/**
* Retrieve a key from the store
*
* @param {string|array} path - Path to the key
* @param {mixed} dflt - Default method to return if key is undefined
* @return {mixed} value - The value stored under key
*/
Store.prototype.get = function (path, dflt) {
const val = get(this, path, dflt)
if (typeof val === 'undefined') {
this.log.warning(`Store.get(key) on key \`${path}\`, which is undefined`)
}
/** Set key at path to value, but only if it's not currently set */
Store.prototype.setIfUnset = function (path, value) {
if (typeof value === 'undefined') {
this.log.warning(`Store.setIfUnset(value) on key \`${path}\`, but value is undefined`)
}
if (typeof get(this, path) === 'undefined') {
return set(this, path, value)
return val
}
return this
}
/** Adds a value to an array stored under path */
/**
* Adds a value to an array stored under path
*
* @param {string|array} path - Path to the key
* @param {mixed} values - One or more values to add (variadic)
* @return {Store} this - The Store instance
*/
Store.prototype.push = function (path, ...values) {
const arr = get(this, path)
if (Array.isArray(arr)) {
@ -88,19 +109,49 @@ Store.prototype.push = function (path, ...values) {
return this
}
/** Remove the key at path */
/**
* Set key at path to value
*
* @param {string|array} path - Path to the key
* @param {mixed} value - The value to set
* @return {Store} this - The Store instance
*/
Store.prototype.set = function (path, value) {
if (typeof value === 'undefined') {
this.log.warning(`Store.set(value) on key \`${path}\`, but value is undefined`)
}
set(this, path, value)
return this
}
/**
* Set key at path to value, but only if it's not currently set
*
* @param {string|array} path - Path to the key
* @param {mixed} value - The value to set
* @return {Store} this - The Store instance
*/
Store.prototype.setIfUnset = function (path, value) {
if (typeof value === 'undefined') {
this.log.warning(`Store.setIfUnset(value) on key \`${path}\`, but value is undefined`)
}
if (typeof get(this, path) === 'undefined') {
return set(this, path, value)
}
return this
}
/**
* Remove the key at path
*
* @param {string|array} path - Path to the key
* @param {mixed} value - The value to set
* @return {Store} this - The Store instance
*/
Store.prototype.unset = function (path) {
unset(this, path)
return this
}
/** Retrieve a key */
Store.prototype.get = function (path, dflt) {
const val = get(this, path, dflt)
if (typeof val === 'undefined') {
this.log.warning(`Store.get(key) on key \`${path}\`, which is undefined`)
}
return val
}

View file

@ -1,16 +1,27 @@
import { Attributes } from './attributes.mjs'
import { addNonEnumProp, round } from './utils.mjs'
import { __addNonEnumProp, round } from './utils.mjs'
import { version } from '../data.mjs'
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for an Svg
*
* @constructor
* @param {Patern} pattern - The Pattern object to render
* @return {Svg} this - The Path instance
*/
export function Svg(pattern) {
// Non-enumerable properties
addNonEnumProp(this, 'openGroups', [])
addNonEnumProp(this, 'layout', {})
addNonEnumProp(this, 'freeId', 0)
addNonEnumProp(this, 'body', '')
addNonEnumProp(this, 'style', '')
addNonEnumProp(this, 'defs', '')
addNonEnumProp(this, 'prefix', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
__addNonEnumProp(this, 'openGroups', [])
__addNonEnumProp(this, 'layout', {})
__addNonEnumProp(this, 'freeId', 0)
__addNonEnumProp(this, 'body', '')
__addNonEnumProp(this, 'style', '')
__addNonEnumProp(this, 'defs', '')
__addNonEnumProp(this, 'prefix', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
// Enumerable properties
this.pattern = pattern // Needed to expose pattern to hooks
@ -23,18 +34,107 @@ export function Svg(pattern) {
this.attributes.add('freesewing', version)
}
Svg.prototype.runHooks = function (hookName, data = false) {
if (data === false) data = this
let hooks = this.hooks[hookName]
if (hooks.length > 0) {
for (let hook of hooks) {
hook.method(data, hook.data)
//////////////////////////////////////////////
// PUBLIC METHODS //
//////////////////////////////////////////////
/**
* Renders a drafted Pattern as SVG
*
* @param {Pattern} pattern - The pattern to render
* @return {string} svg - The rendered SVG output
*/
Svg.prototype.render = function (pattern) {
this.idPrefix = pattern.settings.idPrefix
this.__runHooks('preRender')
pattern.__runHooks('postLayout')
if (!pattern.settings.embed) {
this.attributes.add('width', round(pattern.width) + 'mm')
this.attributes.add('height', round(pattern.height) + 'mm')
}
this.attributes.add('viewBox', `0 0 ${pattern.width} ${pattern.height}`)
this.head = this.__renderHead()
this.tail = this.__renderTail()
this.svg = ''
this.layout = {} // Reset layout
for (let partId in pattern.parts) {
let part = pattern.parts[partId]
if (part.render) {
let partSvg = this.__renderPart(part)
this.layout[partId] = {
svg: partSvg,
transform: part.attributes.getAsArray('transform'),
}
this.svg += this.__openGroup(`${this.idPrefix}part-${partId}`, part.attributes)
this.svg += partSvg
this.svg += this.__closeGroup()
}
}
this.svg = this.prefix + this.__renderSvgTag() + this.head + this.svg + this.tail
this.__runHooks('postRender')
return this.svg
}
/** Runs insertText hooks */
Svg.prototype.insertText = function (text) {
//////////////////////////////////////////////
// PRIVATE METHODS //
//////////////////////////////////////////////
/**
* Returns SVG markup to close a group
*
* @private
* @return {string} svg - The SVG markup to open a group
*/
Svg.prototype.__closeGroup = function () {
this.__outdent()
return `${this.__nl()}</g>${this.__nl()}<!-- end of group #${this.openGroups.pop()} -->`
}
/**
* Escapes text for SVG output
*
* @private
* @param {string} text - The text to escape
* @return {string} escaped - The escaped text
*/
Svg.prototype.__escapeText = function (text) {
return text.replace(/"/g, '&#8220;')
}
/**
* Returs an unused ID
*
* @private
* @return {numer} id - The next free ID
*/
Svg.prototype.__getId = function () {
this.freeId += 1
return '' + this.freeId
}
/**
* Increases indentation by 1
*
* @private
* @return {Svg} this - The Svg instance
*/
Svg.prototype.__indent = function () {
this.tabs += 1
return this
}
/**
* Runs the insertText lifecycle hook(s)
*
* @private
* @param {string} text - The text to insert
* @return {Svg} this - The Svg instance
*/
Svg.prototype.__insertText = function (text) {
if (this.hooks.insertText.length > 0) {
for (let hook of this.hooks.insertText)
text = hook.method(this.pattern.settings.locale, text, hook.data)
@ -43,198 +143,188 @@ Svg.prototype.insertText = function (text) {
return text
}
/** Renders a draft object as SVG */
Svg.prototype.render = function (pattern) {
this.idPrefix = pattern.settings.idPrefix
this.runHooks('preRender')
pattern.runHooks('postLayout')
if (!pattern.settings.embed) {
this.attributes.add('width', round(pattern.width) + 'mm')
this.attributes.add('height', round(pattern.height) + 'mm')
}
this.attributes.add('viewBox', `0 0 ${pattern.width} ${pattern.height}`)
this.head = this.renderHead()
this.tail = this.renderTail()
this.svg = ''
this.layout = {} // Reset layout
for (let partId in pattern.parts) {
let part = pattern.parts[partId]
if (part.render) {
let partSvg = this.renderPart(part)
this.layout[partId] = {
svg: partSvg,
transform: part.attributes.getAsArray('transform'),
}
this.svg += this.openGroup(`${this.idPrefix}part-${partId}`, part.attributes)
this.svg += partSvg
this.svg += this.closeGroup()
}
}
this.svg = this.prefix + this.renderSvgTag() + this.head + this.svg + this.tail
this.runHooks('postRender')
return this.svg
/**
* Returns SVG markup for a linebreak + indentation
*
* @private
* @return {string} svg - The Svg markup for a linebreak + indentation
*/
Svg.prototype.__nl = function () {
return '\n' + this.__tab()
}
/** Renders SVG head section */
Svg.prototype.renderHead = function () {
let svg = this.renderStyle()
svg += this.renderScript()
svg += this.renderDefs()
svg += this.openGroup(this.idPrefix + 'container')
/**
* Decreases indentation by 1
*
* @private
* @return {Svg} this - The Svg instance
*/
Svg.prototype.__outdent = function () {
this.tabs -= 1
return this
}
/**
* Returns SVG markup to open a group
*
* @private
* @param {text} id - The group id
* @param {Attributes} attributes - Any other attributes for the group
* @return {string} svg - The SVG markup to open a group
*/
Svg.prototype.__openGroup = function (id, attributes = false) {
let svg = this.__nl() + this.__nl()
svg += `<!-- Start of group #${id} -->`
svg += this.__nl()
svg += `<g id="${id}"`
if (attributes) svg += ` ${attributes.render()}`
svg += '>'
this.__indent()
this.openGroups.push(id)
return svg
}
/** Renders SVG closing section */
Svg.prototype.renderTail = function () {
let svg = ''
svg += this.closeGroup()
svg += this.nl() + '</svg>'
return svg
/**
* Returns SVG markup for a circle
*
* @private
* @param {Point} point - The Point instance that holds the circle data
* @return {string} svg - The SVG markup for the circle
*/
Svg.prototype.__renderCircle = function (point) {
return `<circle cx="${round(point.x)}" cy="${round(point.y)}" r="${point.attributes.get(
'data-circle'
)}" ${point.attributes.renderIfPrefixIs('data-circle-')}></circle>`
}
/** Returns SVG code for the opening SVG tag */
Svg.prototype.renderSvgTag = function () {
let svg = '<svg'
this.indent()
svg += this.nl() + this.attributes.render()
this.outdent()
svg += this.nl() + '>' + this.nl()
return svg
}
/** Returns SVG code for the style block */
Svg.prototype.renderStyle = function () {
let svg = '<style type="text/css"> <![CDATA[ '
this.indent()
svg += this.nl() + this.style
this.outdent()
svg += this.nl() + ']]>' + this.nl() + '</style>' + this.nl()
return svg
}
/** Returns SVG code for the script block */
Svg.prototype.renderScript = function () {
let svg = '<script type="text/javascript"> <![CDATA['
this.indent()
svg += this.nl() + this.script
this.outdent()
svg += this.nl() + ']]>' + this.nl() + '</script>' + this.nl()
return svg
}
/** Returns SVG code for the defs block */
Svg.prototype.renderDefs = function () {
/**
* Returns SVG markup for the defs block
*
* @private
* @return {string} svg - The SVG markup for the defs block
*/
Svg.prototype.__renderDefs = function () {
let svg = '<defs>'
this.indent()
svg += this.nl() + this.defs
this.outdent()
svg += this.nl() + '</defs>' + this.nl()
this.__indent()
svg += this.__nl() + this.defs
this.__outdent()
svg += this.__nl() + '</defs>' + this.__nl()
return svg
}
/** Returns SVG code for a Part object */
Svg.prototype.renderPart = function (part) {
let svg = ''
for (let key in part.paths) {
let path = part.paths[key]
if (path.render) svg += this.renderPath(path)
}
for (let key in part.points) {
if (part.points[key].attributes.get('data-text')) {
svg += this.renderText(part.points[key])
}
if (part.points[key].attributes.get('data-circle')) {
svg += this.renderCircle(part.points[key])
}
}
for (let key in part.snippets) {
let snippet = part.snippets[key]
svg += this.renderSnippet(snippet, part)
}
/**
* Returns SVG markup for the head section
*
* @private
* @return {string} svg - The SVG markup for the head section
*/
Svg.prototype.__renderHead = function () {
let svg = this.__renderStyle()
svg += this.__renderScript()
svg += this.__renderDefs()
svg += this.__openGroup(this.idPrefix + 'container')
return svg
}
/** Returns SVG code for a Path object */
Svg.prototype.renderPath = function (path) {
if (!path.attributes.get('id')) path.attributes.add('id', this.idPrefix + this.getId())
/**
* Returns SVG markup for a Path object
*
* @private
* @param {Path} part - The Path instance to render
* @return {string} svg - The SVG markup for the Path object
*/
Svg.prototype.__renderPath = function (path) {
if (!path.attributes.get('id')) path.attributes.add('id', this.idPrefix + this.__getId())
path.attributes.set('d', path.asPathstring())
return `${this.nl()}<path ${path.attributes.render()} />${this.renderPathText(path)}`
return `${this.__nl()}<path ${path.attributes.render()} />${this.__renderPathText(path)}`
}
Svg.prototype.renderPathText = function (path) {
/**
* Returns SVG markup for the text on a Path object
*
* @private
* @param {Path} path - The Path instance that holds the text render
* @return {string} svg - The SVG markup for the text on a Path object
*/
Svg.prototype.__renderPathText = function (path) {
let text = path.attributes.get('data-text')
if (!text) return ''
else this.text = this.insertText(text)
else this.text = this.__insertText(text)
let attributes = path.attributes.renderIfPrefixIs('data-text-')
// Sadly aligning text along a patch can't be done in CSS only
let offset = ''
let align = path.attributes.get('data-text-class')
if (align && align.indexOf('center') > -1) offset = ' startOffset="50%" '
else if (align && align.indexOf('right') > -1) offset = ' startOffset="100%" '
let svg = this.nl() + '<text>'
this.indent()
let svg = this.__nl() + '<text>'
this.__indent()
svg += `<textPath xlink:href="#${path.attributes.get(
'id'
)}" ${offset}><tspan ${attributes}>${this.escapeText(this.text)}</tspan></textPath>`
this.outdent()
svg += this.nl() + '</text>'
)}" ${offset}><tspan ${attributes}>${this.__escapeText(this.text)}</tspan></textPath>`
this.__outdent()
svg += this.__nl() + '</text>'
return svg
}
Svg.prototype.renderText = function (point) {
let text = point.attributes.getAsArray('data-text')
if (text !== false) {
let joint = ''
for (let string of text) {
this.text = this.insertText(string)
joint += this.text + ' '
/**
* Returns SVG markup for a Part object
*
* @private
* @param {Part} part - The Part instance to render
* @return {string} svg - The SVG markup for the Part object
*/
Svg.prototype.__renderPart = function (part) {
let svg = ''
for (let key in part.paths) {
let path = part.paths[key]
if (path.render) svg += this.__renderPath(path)
}
this.text = this.insertText(joint)
for (let key in part.points) {
if (part.points[key].attributes.get('data-text')) {
svg += this.__renderText(part.points[key])
}
point.attributes.set('data-text-x', round(point.x))
point.attributes.set('data-text-y', round(point.y))
let lineHeight =
point.attributes.get('data-text-lineheight') || 6 * (this.pattern.settings.scale || 1)
point.attributes.remove('data-text-lineheight')
let svg = `${this.nl()}<text ${point.attributes.renderIfPrefixIs('data-text-')}>`
this.indent()
// Multi-line text?
if (this.text.indexOf('\n') !== -1) {
let lines = this.text.split('\n')
svg += `<tspan>${lines.shift()}</tspan>`
for (let line of lines) {
svg += `<tspan x="${round(point.x)}" dy="${lineHeight}">${line}</tspan>`
if (part.points[key].attributes.get('data-circle')) {
svg += this.__renderCircle(part.points[key])
}
} else {
svg += `<tspan>${this.escapeText(this.text)}</tspan>`
}
this.outdent()
svg += this.nl() + '</text>'
for (let key in part.snippets) {
let snippet = part.snippets[key]
svg += this.__renderSnippet(snippet, part)
}
return svg
}
Svg.prototype.escapeText = function (text) {
return text.replace(/"/g, '&#8220;')
/**
* Returns SVG markup for the script block
*
* @private
* @return {string} svg - The SVG markup for the script block
*/
Svg.prototype.__renderScript = function () {
let svg = '<script type="text/javascript"> <![CDATA['
this.__indent()
svg += this.__nl() + this.script
this.__outdent()
svg += this.__nl() + ']]>' + this.__nl() + '</script>' + this.__nl()
return svg
}
Svg.prototype.renderCircle = function (point) {
return `<circle cx="${round(point.x)}" cy="${round(point.y)}" r="${point.attributes.get(
'data-circle'
)}" ${point.attributes.renderIfPrefixIs('data-circle-')}></circle>`
}
/** Returns SVG code for a snippet */
Svg.prototype.renderSnippet = function (snippet) {
/**
* Returns SVG markup for a snippet
*
* @private
* @param {Snippet} snippet - The Snippet instance to render
* @return {string} svg - The SVG markup for the snippet
*/
Svg.prototype.__renderSnippet = function (snippet) {
let x = round(snippet.anchor.x)
let y = round(snippet.anchor.y)
let scale = snippet.attributes.get('data-scale') || 1
@ -248,7 +338,7 @@ Svg.prototype.renderSnippet = function (snippet) {
if (rotate) {
snippet.attributes.add('transform', `rotate(${rotate}, ${x}, ${y})`)
}
let svg = this.nl()
let svg = this.__nl()
svg += `<use x="${x}" y="${y}" `
svg += `xlink:href="#${snippet.def}" ${snippet.attributes.render()}>`
svg += '</use>'
@ -256,34 +346,116 @@ Svg.prototype.renderSnippet = function (snippet) {
return svg
}
/** Returns SVG code to open a group */
Svg.prototype.openGroup = function (id, attributes = false) {
let svg = this.nl() + this.nl()
svg += `<!-- Start of group #${id} -->`
svg += this.nl()
svg += `<g id="${id}"`
if (attributes) svg += ` ${attributes.render()}`
svg += '>'
this.indent()
this.openGroups.push(id)
/**
* Returns SVG markup for the style block
*
* @private
* @return {string} svg - The SVG markup for the style block
*/
Svg.prototype.__renderStyle = function () {
let svg = '<style type="text/css"> <![CDATA[ '
this.__indent()
svg += this.__nl() + this.style
this.__outdent()
svg += this.__nl() + ']]>' + this.__nl() + '</style>' + this.__nl()
return svg
}
/**
* Returns SVG markup for the opening SVG tag
*
* @private
* @return {string} svg - The SVG markup for the SVG tag
*/
Svg.prototype.__renderSvgTag = function () {
let svg = '<svg'
this.__indent()
svg += this.__nl() + this.attributes.render()
this.__outdent()
svg += this.__nl() + '>' + this.__nl()
return svg
}
/** Returns SVG code to close a group */
Svg.prototype.closeGroup = function () {
this.outdent()
/**
* Returns SVG markup for the closing section
*
* @private
* @return {string} svg - The SVG markup for the closing section
*/
Svg.prototype.__renderTail = function () {
let svg = ''
svg += this.__closeGroup()
svg += this.__nl() + '</svg>'
return `${this.nl()}</g>${this.nl()}<!-- end of group #${this.openGroups.pop()} -->`
return svg
}
/** Returns a linebreak + identation */
Svg.prototype.nl = function () {
return '\n' + this.tab()
/**
* Returns SVG markup for text
*
* @private
* @param {Point} point - The Point instance that holds the text render
* @return {string} svg - The SVG markup for text
*/
Svg.prototype.__renderText = function (point) {
let text = point.attributes.getAsArray('data-text')
if (text !== false) {
let joint = ''
for (let string of text) {
this.text = this.__insertText(string)
joint += this.text + ' '
}
this.text = this.__insertText(joint)
}
point.attributes.set('data-text-x', round(point.x))
point.attributes.set('data-text-y', round(point.y))
let lineHeight =
point.attributes.get('data-text-lineheight') || 6 * (this.pattern.settings.scale || 1)
point.attributes.remove('data-text-lineheight')
let svg = `${this.__nl()}<text ${point.attributes.renderIfPrefixIs('data-text-')}>`
this.__indent()
// Multi-line text?
if (this.text.indexOf('\n') !== -1) {
let lines = this.text.split('\n')
svg += `<tspan>${lines.shift()}</tspan>`
for (let line of lines) {
svg += `<tspan x="${round(point.x)}" dy="${lineHeight}">${line}</tspan>`
}
} else {
svg += `<tspan>${this.__escapeText(this.text)}</tspan>`
}
this.__outdent()
svg += this.__nl() + '</text>'
return svg
}
/** Returns indentation */
Svg.prototype.tab = function () {
/**
* Runs SVG lifecycle hooks
*
* @private
* @param {string} hookName - The lifecycle hook to run
* @param {mixed} data - Any data to pass to the hook method
* @return {string} svg - The SVG markup for the indentation
*/
Svg.prototype.__runHooks = function (hookName, data = false) {
if (data === false) data = this
let hooks = this.hooks[hookName]
if (hooks.length > 0) {
for (let hook of hooks) {
hook.method(data, hook.data)
}
}
}
/**
* Returns SVG markup for indentation
*
* @private
* @return {string} svg - The SVG markup for the indentation
*/
Svg.prototype.__tab = function () {
let space = ''
for (let i = 0; i < this.tabs; i++) {
space += ' '
@ -291,20 +463,3 @@ Svg.prototype.tab = function () {
return space
}
/** Increases indentation by 1 */
Svg.prototype.indent = function () {
this.tabs += 1
}
/** Decreases indentation by 1 */
Svg.prototype.outdent = function () {
this.tabs -= 1
}
/** Returns an unused ID */
Svg.prototype.getId = function () {
this.freeId += 1
return '' + this.freeId
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
import chai from 'chai'
import { Design } from '../src/index.mjs'
const expect = chai.expect
if (!expect) console.log('shut up eslint REMOVE')
describe('Multisets', () => {
describe('FIXME', () => {
const partA = {
name: 'test.partA',
measurements: ['head'],
options: {
size: { pct: 40, min: 20, max: 80 },
},
draft: ({ points, Point, paths, Path, part, store, measurements, options }) => {
store.set('size', measurements.head * options.size)
points.from = new Point(0, 0)
points.to = new Point(0, store.get('size'))
paths.line = new Path().move(points.from).line(points.to)
return part
},
//stack: 'box',
}
const partB = {
name: 'test.partB',
measurements: ['head'],
after: partA,
draft: ({ points, Point, paths, Path, part, store }) => {
points.from = new Point(0, store.get('size'))
points.to = new Point(store.get('size'), store.get('size'))
paths.line = new Path().move(points.from).line(points.to)
return part
},
//stack: 'box',
}
const partC = {
name: 'test.partC',
after: partB,
draft: ({ points, Point, paths, Path, part, store }) => {
points.from = new Point(store.get('size'), store.get('size'))
points.to = new Point(store.get('size'), 0)
paths.line = new Path().move(points.from).line(points.to)
return part
},
//stack: 'box',
}
const partD = {
name: 'test.partD',
after: partC,
draft: ({ points, Point, paths, Path, part, store }) => {
points.from = new Point(store.get('size'), 0)
points.to = new Point(0, 0)
paths.line = new Path().move(points.from).line(points.to)
return part
},
// stack: 'box',
}
const Pattern = new Design({
data: {
name: 'test',
version: '1.2.3',
},
parts: [partD],
})
const pattern = new Pattern([
{
measurements: { head: 400 },
},
{
measurements: { head: 400 },
},
])
pattern.draft()
})
})

View file

@ -1,5 +1,5 @@
import chai from 'chai'
import { Design, Pattern } from '../src/index.mjs'
import { Design, Part } from '../src/index.mjs'
const expect = chai.expect
@ -20,21 +20,25 @@ describe('Part', () => {
expect(typeof dp.context).to.equal('object')
})
it('Should return a function from macroClosure', () => {
const pattern = new Pattern()
const part = pattern.__createPartWithContext()
expect(typeof part.macroClosure()).to.equal('function')
it('Should return a function from __macroClosure', () => {
const part = new Part()
expect(typeof part.__macroClosure()).to.equal('function')
})
it('Should not run an unknown macro', () => {
const pattern = new Pattern()
const part = pattern.__createPartWithContext()
const macro = part.macroClosure()
const part = new Part()
const macro = part.__macroClosure()
expect(macro('unknown')).to.equal(undefined)
})
it('Should register and run a macro', () => {
const pattern = new Pattern()
const part = {
name: 'test',
draft: ({ part, macro }) => {
macro('test', { x: 123, y: 456 })
return part
},
}
const plugin = {
name: 'test',
version: '0.1-test',
@ -45,39 +49,34 @@ describe('Part', () => {
},
},
}
pattern.use(plugin)
const part = pattern.__createPartWithContext()
const macro = part.macroClosure()
macro('test', { x: 123, y: 456 })
expect(part.points.macro.x).to.equal(123)
expect(part.points.macro.y).to.equal(456)
const design = new Design({ parts: [part], plugins: [plugin] })
const pattern = new design()
pattern.draft()
expect(pattern.parts[0].test.points.macro.x).to.equal(123)
expect(pattern.parts[0].test.points.macro.y).to.equal(456)
})
it('Should return a free ID', () => {
const pattern = new Pattern()
const part = pattern.__createPartWithContext()
const part = new Part()
const free = part.getId()
expect(part.getId()).to.equal('' + (parseInt(free) + 1))
})
it('Should return a function from unitsClosure', () => {
const pattern = new Pattern()
const part = pattern.__createPartWithContext()
expect(typeof part.unitsClosure()).to.equal('function')
it('Should return a function from __unitsClosure', () => {
const part = new Part()
expect(typeof part.__unitsClosure()).to.equal('function')
})
it('Should convert units', () => {
const design = new Design()
const pattern = new design()
const part = pattern.__createPartWithContext()
const units = part.unitsClosure()
const part = new Part()
part.context = { settings: { units: 'metric' } }
const units = part.__unitsClosure()
expect(units(123.456)).to.equal('12.35cm')
expect(units(123.456)).to.equal('12.35cm')
expect(part.units(123.456)).to.equal('12.35cm')
})
it('Should set part attributes', () => {
const pattern = new Pattern()
const part = pattern.__createPartWithContext()
const part = new Part()
part.attr('foo', 'bar')
expect(part.attributes.get('foo')).to.equal('bar')
part.attr('foo', 'baz')
@ -87,60 +86,72 @@ describe('Part', () => {
})
it('Should raise a warning when setting a non-Point value in points', () => {
const design = new Design()
const pattern = new design()
const part = pattern.__createPartWithContext()
pattern.init()
const { points } = part.shorthand()
const part = {
name: 'test',
draft: ({ points, part }) => {
points.a = 'banana'
expect(pattern.store.logs.warning.length).to.equal(4)
expect(pattern.store.logs.warning[0]).to.equal(
return part
},
}
const design = new Design({ parts: [part] })
const pattern = new design()
pattern.draft()
expect(pattern.stores[0].logs.warning.length).to.equal(4)
expect(pattern.stores[0].logs.warning[0]).to.equal(
'`points.a` was set with a value that is not a `Point` object'
)
expect(pattern.store.logs.warning[1]).to.equal(
expect(pattern.stores[0].logs.warning[1]).to.equal(
'`points.a` was set with a `x` parameter that is not a `number`'
)
expect(pattern.store.logs.warning[2]).to.equal(
expect(pattern.stores[0].logs.warning[2]).to.equal(
'`points.a` was set with a `y` parameter that is not a `number`'
)
})
it('Should raise a warning when setting a non-Snippet value in snippets', () => {
const design = new Design()
const pattern = new design()
const part = pattern.__createPartWithContext()
pattern.init()
const { snippets } = part.shorthand()
const part = {
name: 'test',
draft: ({ snippets, part }) => {
snippets.a = 'banana'
expect(pattern.store.logs.warning.length).to.equal(4)
expect(pattern.store.logs.warning[0]).to.equal(
return part
},
}
const design = new Design({ parts: [part] })
const pattern = new design()
pattern.draft()
expect(pattern.stores[0].logs.warning.length).to.equal(4)
expect(pattern.stores[0].logs.warning[0]).to.equal(
'`snippets.a` was set with a value that is not a `Snippet` object'
)
expect(pattern.store.logs.warning[1]).to.equal(
expect(pattern.stores[0].logs.warning[1]).to.equal(
'`snippets.a` was set with a `def` parameter that is not a `string`'
)
expect(pattern.store.logs.warning[2]).to.equal(
expect(pattern.stores[0].logs.warning[2]).to.equal(
'`snippets.a` was set with an `anchor` parameter that is not a `Point`'
)
})
it('Should calculate the part boundary', () => {
const design = new Design()
const part = {
name: 'test',
draft: ({ points, Point, paths, Path, part }) => {
points.from = new Point(123, 456)
points.to = new Point(19, 76)
paths.test = new Path().move(points.from).line(points.to)
return part
},
}
const design = new Design({ parts: [part] })
const pattern = new design()
const part = pattern.__createPartWithContext()
pattern.init()
const short = part.shorthand()
part.points.from = new short.Point(123, 456)
part.points.to = new short.Point(19, 76)
part.paths.test = new short.Path().move(part.points.from).line(part.points.to)
let boundary = part.boundary()
expect(boundary.topLeft.x).to.equal(19)
expect(boundary.topLeft.y).to.equal(76)
expect(boundary.bottomRight.x).to.equal(123)
expect(boundary.bottomRight.y).to.equal(456)
boundary = part.boundary()
expect(boundary.width).to.equal(104)
expect(boundary.height).to.equal(380)
pattern.draft()
const boundary = pattern.parts[0].test.__boundary()
const { topLeft, bottomRight, width, height } = boundary
expect(topLeft.x).to.equal(19)
expect(topLeft.y).to.equal(76)
expect(bottomRight.x).to.equal(123)
expect(bottomRight.y).to.equal(456)
expect(width).to.equal(104)
expect(height).to.equal(380)
})
/*
@ -176,7 +187,6 @@ describe('Part', () => {
part.home()
expect(part.attributes.get('transform')).to.equal(false)
})
*/
it('Should run hooks', () => {
let count = 0
const design = new Design()
@ -192,20 +202,25 @@ describe('Part', () => {
part.runHooks('preDraft')
expect(count).to.equal(1)
})
*/
it('Should get the units closure to raise a debug when passing a non-number', () => {
const design = new Design()
const pattern = new design({ margin: 5 })
const part = pattern.__createPartWithContext()
pattern.init()
const short = part.shorthand()
short.units('a')
expect(pattern.store.logs.warning.length).to.equal(1)
expect(pattern.store.logs.warning[0]).to.equal(
it('Units closure should log a warning when passing a non-number', () => {
const part = {
name: 'test',
draft: ({ units, part }) => {
units('a')
return part
},
}
const design = new Design({ parts: [part] })
const pattern = new design()
pattern.draft()
expect(pattern.stores[0].logs.warning.length).to.equal(1)
expect(pattern.stores[0].logs.warning[0]).to.equal(
'Calling `units(value)` but `value` is not a number (`string`)'
)
})
/*
describe('isEmpty', () => {
it('Should return true if the part has no paths or snippets', () => {
const design = new Design()
@ -272,4 +287,5 @@ describe('Part', () => {
expect(part.isEmpty()).to.be.false
})
})
*/
})

File diff suppressed because it is too large Load diff

View file

@ -78,9 +78,9 @@ describe('Pattern', () => {
only: ['test.partB'],
})
pattern.init()
expect(pattern.needs('test.partA')).to.equal(true)
expect(pattern.needs('test.partB')).to.equal(true)
expect(pattern.needs('test.partC')).to.equal(false)
expect(pattern.__needs('test.partA')).to.equal(true)
expect(pattern.__needs('test.partB')).to.equal(true)
expect(pattern.__needs('test.partC')).to.equal(false)
})
it('Should check whether a part is wanted', () => {
@ -127,9 +127,9 @@ describe('Pattern', () => {
only: ['test.partB'],
})
pattern.init()
expect(pattern.wants('test.partA')).to.equal(false)
expect(pattern.wants('test.partB')).to.equal(true)
expect(pattern.wants('test.partC')).to.equal(false)
expect(pattern.__wants('test.partA')).to.equal(false)
expect(pattern.__wants('test.partB')).to.equal(true)
expect(pattern.__wants('test.partC')).to.equal(false)
})
/*

View file

@ -14,11 +14,11 @@ describe('Pattern', () => {
it('Pattern constructor should add enumerable properties', () => {
const Pattern = new Design()
const pattern = new Pattern()
expect(typeof pattern.settings).to.equal('object')
expect(Array.isArray(pattern.settings)).to.equal(true)
expect(Array.isArray(pattern.stores)).to.equal(true)
expect(typeof pattern.config).to.equal('object')
expect(typeof pattern.parts).to.equal('object')
expect(typeof pattern.store).to.equal('object')
expect(Object.keys(pattern).length).to.equal(5)
expect(typeof pattern.store).to.equal('undefined')
expect(Object.keys(pattern).length).to.equal(4)
})
it('Pattern constructor should add non-enumerable properties', () => {
@ -60,7 +60,7 @@ describe('Pattern', () => {
}
for (const [key, value] of Object.entries(dflts)) {
if (typeof value === 'object') expect(Object.keys(value).length).to.equal(0)
else expect(pattern.settings[key]).to.equal(value)
else expect(pattern.settings[0][key]).to.equal(value)
}
})
})
@ -154,8 +154,8 @@ describe('Pattern', () => {
})
it('Pattern.init() should set config data in the store', () => {
expect(pattern.store.get('data.name')).to.equal('test')
expect(pattern.store.get('data.version')).to.equal('1.2.3')
expect(pattern.stores[0].get('data.name')).to.equal('test')
expect(pattern.stores[0].get('data.version')).to.equal('1.2.3')
})
it('Pattern.init() should resolve dependencies', () => {
@ -282,58 +282,58 @@ describe('Pattern', () => {
expect(pattern.config.draftOrder[2]).to.equal('partC')
expect(pattern.config.draftOrder[3]).to.equal('partR')
// Points
expect(pattern.parts.partA.points.a1.x).to.equal(1)
expect(pattern.parts.partA.points.a1.y).to.equal(1)
expect(pattern.parts.partA.points.a2.x).to.equal(11)
expect(pattern.parts.partA.points.a2.y).to.equal(11)
expect(pattern.parts.partB.points.b1.x).to.equal(2)
expect(pattern.parts.partB.points.b1.y).to.equal(2)
expect(pattern.parts.partB.points.b2.x).to.equal(22)
expect(pattern.parts.partB.points.b2.y).to.equal(22)
expect(pattern.parts.partC.points.c1.x).to.equal(3)
expect(pattern.parts.partC.points.c1.y).to.equal(3)
expect(pattern.parts.partC.points.c2.x).to.equal(33)
expect(pattern.parts.partC.points.c2.y).to.equal(33)
expect(pattern.parts.partR.points.r1.x).to.equal(4)
expect(pattern.parts.partR.points.r1.y).to.equal(4)
expect(pattern.parts.partR.points.r2.x).to.equal(44)
expect(pattern.parts.partR.points.r2.y).to.equal(44)
expect(pattern.parts[0].partA.points.a1.x).to.equal(1)
expect(pattern.parts[0].partA.points.a1.y).to.equal(1)
expect(pattern.parts[0].partA.points.a2.x).to.equal(11)
expect(pattern.parts[0].partA.points.a2.y).to.equal(11)
expect(pattern.parts[0].partB.points.b1.x).to.equal(2)
expect(pattern.parts[0].partB.points.b1.y).to.equal(2)
expect(pattern.parts[0].partB.points.b2.x).to.equal(22)
expect(pattern.parts[0].partB.points.b2.y).to.equal(22)
expect(pattern.parts[0].partC.points.c1.x).to.equal(3)
expect(pattern.parts[0].partC.points.c1.y).to.equal(3)
expect(pattern.parts[0].partC.points.c2.x).to.equal(33)
expect(pattern.parts[0].partC.points.c2.y).to.equal(33)
expect(pattern.parts[0].partR.points.r1.x).to.equal(4)
expect(pattern.parts[0].partR.points.r1.y).to.equal(4)
expect(pattern.parts[0].partR.points.r2.x).to.equal(44)
expect(pattern.parts[0].partR.points.r2.y).to.equal(44)
// Paths in partA
expect(pattern.parts.partA.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts.partA.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts.partA.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts.partA.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts[0].partA.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts[0].partA.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts[0].partA.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts[0].partA.paths.a.ops[1].to.y).to.equal(11)
// Paths in partB
expect(pattern.parts.partB.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts.partB.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts.partB.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts.partB.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts.partB.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts.partB.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts.partB.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts.partB.paths.b.ops[1].to.y).to.equal(22)
expect(pattern.parts[0].partB.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts[0].partB.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts[0].partB.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts[0].partB.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts[0].partB.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts[0].partB.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts[0].partB.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts[0].partB.paths.b.ops[1].to.y).to.equal(22)
// Paths in partC
expect(pattern.parts.partC.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts.partC.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts.partC.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts.partC.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts.partC.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts.partC.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts.partC.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts.partC.paths.b.ops[1].to.y).to.equal(22)
expect(pattern.parts.partC.paths.c.ops[0].to.x).to.equal(3)
expect(pattern.parts.partC.paths.c.ops[0].to.y).to.equal(3)
expect(pattern.parts.partC.paths.c.ops[1].to.x).to.equal(33)
expect(pattern.parts.partC.paths.c.ops[1].to.y).to.equal(33)
expect(pattern.parts[0].partC.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts[0].partC.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts[0].partC.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts[0].partC.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts[0].partC.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts[0].partC.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts[0].partC.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts[0].partC.paths.b.ops[1].to.y).to.equal(22)
expect(pattern.parts[0].partC.paths.c.ops[0].to.x).to.equal(3)
expect(pattern.parts[0].partC.paths.c.ops[0].to.y).to.equal(3)
expect(pattern.parts[0].partC.paths.c.ops[1].to.x).to.equal(33)
expect(pattern.parts[0].partC.paths.c.ops[1].to.y).to.equal(33)
// Paths in partR
expect(pattern.parts.partC.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts.partC.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts.partC.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts.partC.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts.partR.paths.r.ops[0].to.x).to.equal(4)
expect(pattern.parts.partR.paths.r.ops[0].to.y).to.equal(4)
expect(pattern.parts.partR.paths.r.ops[1].to.x).to.equal(44)
expect(pattern.parts.partR.paths.r.ops[1].to.y).to.equal(44)
expect(pattern.parts[0].partC.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts[0].partC.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts[0].partC.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts[0].partC.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts[0].partR.paths.r.ops[0].to.x).to.equal(4)
expect(pattern.parts[0].partR.paths.r.ops[0].to.y).to.equal(4)
expect(pattern.parts[0].partR.paths.r.ops[1].to.x).to.equal(44)
expect(pattern.parts[0].partR.paths.r.ops[1].to.y).to.equal(44)
})
it('Pattern.init() should resolve nested dependencies', () => {
@ -428,54 +428,54 @@ describe('Pattern', () => {
expect(pattern.config.draftOrder[2]).to.equal('partC')
expect(pattern.config.draftOrder[3]).to.equal('partD')
// Points
expect(pattern.parts.partA.points.a1.x).to.equal(1)
expect(pattern.parts.partA.points.a1.y).to.equal(1)
expect(pattern.parts.partA.points.a2.x).to.equal(11)
expect(pattern.parts.partA.points.a2.y).to.equal(11)
expect(pattern.parts.partB.points.b1.x).to.equal(2)
expect(pattern.parts.partB.points.b1.y).to.equal(2)
expect(pattern.parts.partB.points.b2.x).to.equal(22)
expect(pattern.parts.partB.points.b2.y).to.equal(22)
expect(pattern.parts.partC.points.c1.x).to.equal(3)
expect(pattern.parts.partC.points.c1.y).to.equal(3)
expect(pattern.parts.partC.points.c2.x).to.equal(33)
expect(pattern.parts.partC.points.c2.y).to.equal(33)
expect(pattern.parts.partD.points.d1.x).to.equal(4)
expect(pattern.parts.partD.points.d1.y).to.equal(4)
expect(pattern.parts.partD.points.d2.x).to.equal(44)
expect(pattern.parts.partD.points.d2.y).to.equal(44)
expect(pattern.parts[0].partA.points.a1.x).to.equal(1)
expect(pattern.parts[0].partA.points.a1.y).to.equal(1)
expect(pattern.parts[0].partA.points.a2.x).to.equal(11)
expect(pattern.parts[0].partA.points.a2.y).to.equal(11)
expect(pattern.parts[0].partB.points.b1.x).to.equal(2)
expect(pattern.parts[0].partB.points.b1.y).to.equal(2)
expect(pattern.parts[0].partB.points.b2.x).to.equal(22)
expect(pattern.parts[0].partB.points.b2.y).to.equal(22)
expect(pattern.parts[0].partC.points.c1.x).to.equal(3)
expect(pattern.parts[0].partC.points.c1.y).to.equal(3)
expect(pattern.parts[0].partC.points.c2.x).to.equal(33)
expect(pattern.parts[0].partC.points.c2.y).to.equal(33)
expect(pattern.parts[0].partD.points.d1.x).to.equal(4)
expect(pattern.parts[0].partD.points.d1.y).to.equal(4)
expect(pattern.parts[0].partD.points.d2.x).to.equal(44)
expect(pattern.parts[0].partD.points.d2.y).to.equal(44)
// Paths in partA
expect(pattern.parts.partA.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts.partA.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts.partA.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts.partA.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts[0].partA.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts[0].partA.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts[0].partA.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts[0].partA.paths.a.ops[1].to.y).to.equal(11)
// Paths in partB
expect(pattern.parts.partB.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts.partB.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts.partB.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts.partB.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts.partB.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts.partB.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts.partB.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts.partB.paths.b.ops[1].to.y).to.equal(22)
expect(pattern.parts[0].partB.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts[0].partB.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts[0].partB.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts[0].partB.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts[0].partB.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts[0].partB.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts[0].partB.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts[0].partB.paths.b.ops[1].to.y).to.equal(22)
// Paths in partC
expect(pattern.parts.partC.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts.partC.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts.partC.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts.partC.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts.partC.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts.partC.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts.partC.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts.partC.paths.b.ops[1].to.y).to.equal(22)
expect(pattern.parts.partC.paths.c.ops[0].to.x).to.equal(3)
expect(pattern.parts.partC.paths.c.ops[0].to.y).to.equal(3)
expect(pattern.parts.partC.paths.c.ops[1].to.x).to.equal(33)
expect(pattern.parts.partC.paths.c.ops[1].to.y).to.equal(33)
expect(pattern.parts[0].partC.paths.a.ops[0].to.x).to.equal(1)
expect(pattern.parts[0].partC.paths.a.ops[0].to.y).to.equal(1)
expect(pattern.parts[0].partC.paths.a.ops[1].to.x).to.equal(11)
expect(pattern.parts[0].partC.paths.a.ops[1].to.y).to.equal(11)
expect(pattern.parts[0].partC.paths.b.ops[0].to.x).to.equal(2)
expect(pattern.parts[0].partC.paths.b.ops[0].to.y).to.equal(2)
expect(pattern.parts[0].partC.paths.b.ops[1].to.x).to.equal(22)
expect(pattern.parts[0].partC.paths.b.ops[1].to.y).to.equal(22)
expect(pattern.parts[0].partC.paths.c.ops[0].to.x).to.equal(3)
expect(pattern.parts[0].partC.paths.c.ops[0].to.y).to.equal(3)
expect(pattern.parts[0].partC.paths.c.ops[1].to.x).to.equal(33)
expect(pattern.parts[0].partC.paths.c.ops[1].to.y).to.equal(33)
// Paths in partR
expect(pattern.parts.partD.paths.d.ops[0].to.x).to.equal(4)
expect(pattern.parts.partD.paths.d.ops[0].to.y).to.equal(4)
expect(pattern.parts.partD.paths.d.ops[1].to.x).to.equal(44)
expect(pattern.parts.partD.paths.d.ops[1].to.y).to.equal(44)
expect(pattern.parts[0].partD.paths.d.ops[0].to.x).to.equal(4)
expect(pattern.parts[0].partD.paths.d.ops[0].to.y).to.equal(4)
expect(pattern.parts[0].partD.paths.d.ops[1].to.x).to.equal(44)
expect(pattern.parts[0].partD.paths.d.ops[1].to.y).to.equal(44)
})
it('Pattern.init() should load a single plugin', () => {
@ -635,30 +635,35 @@ describe('Pattern', () => {
})
it('Should check whether created parts get the pattern context', () => {
const Pattern = new Design()
const part = {
name: 'test',
draft: ({ part }) => part,
}
const Pattern = new Design({ parts: [part] })
const pattern = new Pattern()
const part = pattern.__createPartWithContext('test')
expect(typeof part.context).to.equal('object')
expect(typeof part.context.parts).to.equal('object')
expect(typeof part.context.config).to.equal('object')
expect(typeof part.context.config.options).to.equal('object')
expect(typeof part.context.config.data).to.equal('object')
expect(Array.isArray(part.context.config.measurements)).to.equal(true)
expect(Array.isArray(part.context.config.optionalMeasurements)).to.equal(true)
expect(Array.isArray(part.context.config.parts)).to.equal(true)
expect(Array.isArray(part.context.config.plugins)).to.equal(true)
expect(part.context.settings).to.equal(pattern.settings)
expect(typeof part.context.store).to.equal('object')
expect(typeof part.context.store.log).to.equal('object')
expect(typeof part.context.store.log.debug).to.equal('function')
expect(typeof part.context.store.log.info).to.equal('function')
expect(typeof part.context.store.log.warning).to.equal('function')
expect(typeof part.context.store.log.error).to.equal('function')
expect(typeof part.context.store.logs).to.equal('object')
expect(Array.isArray(part.context.store.logs.debug)).to.equal(true)
expect(Array.isArray(part.context.store.logs.info)).to.equal(true)
expect(Array.isArray(part.context.store.logs.warning)).to.equal(true)
expect(Array.isArray(part.context.store.logs.error)).to.equal(true)
pattern.draft()
const context = pattern.parts[0].test.context
expect(typeof context).to.equal('object')
expect(typeof context.parts).to.equal('object')
expect(typeof context.config).to.equal('object')
expect(typeof context.config.options).to.equal('object')
expect(typeof pattern.parts[0].test.context.config.data).to.equal('object')
expect(Array.isArray(context.config.measurements)).to.equal(true)
expect(Array.isArray(context.config.optionalMeasurements)).to.equal(true)
expect(Array.isArray(context.config.parts)).to.equal(true)
expect(Array.isArray(context.config.plugins)).to.equal(true)
expect(context.settings).to.equal(pattern.settings[0])
expect(typeof context.store).to.equal('object')
expect(typeof context.store.log).to.equal('object')
expect(typeof context.store.log.debug).to.equal('function')
expect(typeof context.store.log.info).to.equal('function')
expect(typeof context.store.log.warning).to.equal('function')
expect(typeof context.store.log.error).to.equal('function')
expect(typeof context.store.logs).to.equal('object')
expect(Array.isArray(context.store.logs.debug)).to.equal(true)
expect(Array.isArray(context.store.logs.info)).to.equal(true)
expect(Array.isArray(context.store.logs.warning)).to.equal(true)
expect(Array.isArray(context.store.logs.error)).to.equal(true)
})
})
@ -690,27 +695,27 @@ describe('Pattern', () => {
pattern.init()
it('Pattern settings should contain percentage options', () => {
expect(pattern.settings.options.pct).to.equal(0.3)
expect(pattern.settings[0].options.pct).to.equal(0.3)
})
it('Pattern settings should contain millimeter options', () => {
expect(pattern.settings.options.mm).to.equal(12)
expect(pattern.settings[0].options.mm).to.equal(12)
})
it('Pattern settings should contain degree options', () => {
expect(pattern.settings.options.deg).to.equal(2)
expect(pattern.settings[0].options.deg).to.equal(2)
})
it('Pattern settings should contain list options', () => {
expect(pattern.settings.options.list).to.equal('d')
expect(pattern.settings[0].options.list).to.equal('d')
})
it('Pattern settings should contain count options', () => {
expect(pattern.settings.options.count).to.equal(4)
expect(pattern.settings[0].options.count).to.equal(4)
})
it('Pattern settings should contain bool options', () => {
expect(pattern.settings.options.bool).to.equal(false)
expect(pattern.settings[0].options.bool).to.equal(false)
})
it('Pattern should throw an error for an unknown option', () => {

View file

@ -227,40 +227,40 @@ describe('Point', () => {
expect(p2.y).to.equal(70)
})
it('Should add raise methods to a point', () => {
const raise = () => 'hello'
const p1 = new Point(10, 20).withRaise(raise)
expect(p1.raise()).to.equal('hello')
it('Should add log methods to a point', () => {
const log = () => 'hello'
const p1 = new Point(10, 20).__withLog(log)
expect(p1.log()).to.equal('hello')
})
it('Should raise a warning on invalid point coordinates', () => {
it('Should log a warning on invalid point coordinates', () => {
const invalid = { x: false, y: false }
const raiseX = { warning: () => (invalid.x = true) }
const raiseY = { warning: () => (invalid.y = true) }
const p1 = new Point('a', 10).withRaise(raiseX)
const p2 = new Point(20, 'b').withRaise(raiseY)
const logX = { warning: () => (invalid.x = true) }
const logY = { warning: () => (invalid.y = true) }
const p1 = new Point('a', 10).__withLog(logX)
const p2 = new Point(20, 'b').__withLog(logY)
expect(invalid.x).to.equal(false)
expect(invalid.y).to.equal(false)
p1.check()
p2.check()
p1.__check()
p2.__check()
expect(invalid.x).to.equal(true)
expect(invalid.y).to.equal(true)
})
it('Should raise a warning if rotation is not a number', () => {
it('Should log a warning if rotation is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const p2 = new Point(20, 20).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false)
p1.rotate('a', p2)
expect(invalid).to.equal(true)
})
it('Should raise a warning if rotating around what is not a point', () => {
it('Should log a warning if rotating around what is not a point', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.rotate(45, 'a')
@ -270,10 +270,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it("Should raise a warning when flipX'ing around what is not a point", () => {
it("Should log a warning when flipX'ing around what is not a point", () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.flipX('a')
@ -283,10 +283,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it("Should raise a warning when flipY'ing around what is not a point", () => {
it("Should log a warning when flipY'ing around what is not a point", () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.flipY('a')
@ -296,10 +296,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting with a distance that is not a number', () => {
it('Should log a warning when shifting with a distance that is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shift(0, 'a')
@ -309,10 +309,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting with an angle that is not a number', () => {
it('Should log a warning when shifting with an angle that is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shift('a', 12)
@ -322,11 +322,11 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting towards with a distance that is not a number', () => {
it('Should log a warning when shifting towards with a distance that is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const p2 = new Point(20, 20).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shiftTowards(p2, 'a')
@ -336,10 +336,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting towards with a target that is not a point', () => {
it('Should log a warning when shifting towards with a target that is not a point', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shiftTowards('a', 10)
@ -349,11 +349,11 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting fraction towards with a distance that is not a number', () => {
it('Should log a warning when shifting fraction towards with a distance that is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const p2 = new Point(20, 20).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shiftFractionTowards(p2, 'a')
@ -363,10 +363,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting a fraction towards with a target that is not a point', () => {
it('Should log a warning when shifting a fraction towards with a target that is not a point', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shiftFractionTowards('a', 0.1)
@ -376,11 +376,11 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting outowards with a distance that is not a number', () => {
it('Should log a warning when shifting outowards with a distance that is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const p2 = new Point(20, 20).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shiftOutwards(p2, 'a')
@ -390,10 +390,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when shifting a outowards with a target that is not a point', () => {
it('Should log a warning when shifting a outowards with a target that is not a point', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.shiftOutwards('a', 0.1)
@ -403,10 +403,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when translating with an X-delta that is not a number', () => {
it('Should log a warning when translating with an X-delta that is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.translate('a', 10)
@ -416,10 +416,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when translating with an Y-delta that is not a number', () => {
it('Should log a warning when translating with an Y-delta that is not a number', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.translate(10, 'a')
@ -429,10 +429,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when sitsOn receives a non-point', () => {
it('Should log a warning when sitsOn receives a non-point', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.sitsOn('a')
@ -442,10 +442,10 @@ describe('Point', () => {
expect(invalid).to.equal(true)
})
it('Should raise a warning when sitsRoughlyOn receives a non-point', () => {
it('Should log a warning when sitsRoughlyOn receives a non-point', () => {
let invalid = false
const raise = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise)
const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false)
try {
p1.sitsRoughlyOn('a')

View file

@ -10,6 +10,7 @@ describe('Snapped options', () => {
it('Should snap a percentage options to equal steps', () => {
const part = {
name: 'test',
measurements: ['head'],
options: {
test: { pct: 30, min: 0, max: 100, snap: 12, toAbs },
},
@ -17,13 +18,13 @@ describe('Snapped options', () => {
const design = new Design({ parts: [part] })
const patternA = new design({ options: { test: 0.13 }, measurements }).draft()
const patternB = new design({ options: { test: 0.27 }, measurements }).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(60)
expect(patternB.settings.absoluteOptions.test).to.equal(108)
expect(patternA.settings[0].absoluteOptions.test).to.equal(60)
expect(patternB.settings[0].absoluteOptions.test).to.equal(108)
})
it('Should snap a percentage options to the Fibonacci sequence', () => {
const part = {
name: 'test',
measurements: ['head'],
options: {
test: {
pct: 30,
@ -38,9 +39,9 @@ describe('Snapped options', () => {
const patternA = new design({ options: { test: 0.13 }, measurements }).draft()
const patternB = new design({ options: { test: 0.27 }, measurements }).draft()
const patternC = new design({ options: { test: 0.97 }, measurements }).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(55)
expect(patternB.settings.absoluteOptions.test).to.equal(89)
expect(patternC.settings.absoluteOptions.test).to.equal(388)
expect(patternA.settings[0].absoluteOptions.test).to.equal(55)
expect(patternB.settings[0].absoluteOptions.test).to.equal(89)
expect(patternC.settings[0].absoluteOptions.test).to.equal(388)
})
it('Should snap a percentage options to imperial snaps', () => {
@ -64,10 +65,10 @@ describe('Snapped options', () => {
const patternB = new design({ options: { test: 0.27 }, measurements, units: 'metric' }).draft()
const patternC = new design({ options: { test: 0.97 }, measurements, units: 'metric' }).draft()
const patternD = new design({ options: { test: 0.01 }, measurements, units: 'metric' }).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(50)
expect(patternB.settings.absoluteOptions.test).to.equal(100)
expect(patternC.settings.absoluteOptions.test).to.equal(388)
expect(patternD.settings.absoluteOptions.test).to.equal(4)
expect(patternA.settings[0].absoluteOptions.test).to.equal(50)
expect(patternB.settings[0].absoluteOptions.test).to.equal(100)
expect(patternC.settings[0].absoluteOptions.test).to.equal(388)
expect(patternD.settings[0].absoluteOptions.test).to.equal(4)
})
it('Should snap a percentage options to metrics snaps', () => {
@ -107,9 +108,9 @@ describe('Snapped options', () => {
measurements,
units: 'imperial',
}).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(50.8)
expect(patternB.settings.absoluteOptions.test).to.equal(101.6)
expect(patternC.settings.absoluteOptions.test).to.equal(388)
expect(patternD.settings.absoluteOptions.test).to.equal(4)
expect(patternA.settings[0].absoluteOptions.test).to.equal(50.8)
expect(patternB.settings[0].absoluteOptions.test).to.equal(101.6)
expect(patternC.settings[0].absoluteOptions.test).to.equal(388)
expect(patternD.settings[0].absoluteOptions.test).to.equal(4)
})
})

View file

@ -69,8 +69,8 @@ describe('Store', () => {
const Test = new Design({ plugins: [plugin], parts: [part] })
const pattern = new Test()
pattern.draft()
expect(pattern.store.get('test.message.warning')).to.equal('hello warning')
expect(pattern.store.get('test.message.info')).to.equal('hello info')
expect(pattern.stores[0].get('test.message.warning')).to.equal('hello warning')
expect(pattern.stores[0].get('test.message.info')).to.equal('hello info')
})
it('Should make top-level plugin methods available via shorthand', () => {
@ -103,7 +103,7 @@ describe('Store', () => {
const Test = new Design({ plugins: [plugin], parts: [part] })
const pattern = new Test()
pattern.draft()
expect(pattern.store.get('test.example_part.a')).to.equal('hello A')
expect(pattern.store.get('test.example_part.b')).to.equal('hello B')
expect(pattern.stores[0].get('test.example_part.a')).to.equal('hello A')
expect(pattern.stores[0].get('test.example_part.b')).to.equal('hello B')
})
})

View file

@ -1,7 +1,6 @@
import chai from 'chai'
import {
Point,
isCoord,
capitalize,
beamsIntersect,
linesIntersect,
@ -21,20 +20,14 @@ import {
lineIntersectsCircle,
stretchToScale,
round,
sampleStyle,
deg2rad,
rad2deg,
pctBasedOn,
macroName,
} from '../src/index.mjs'
const { expect } = chai
describe('Utils', () => {
it('Should return the correct macro name', () => {
expect(macroName('test')).to.equal('_macro_test')
})
it('Should find the intersection of two endless line segments', () => {
let a = new Point(10, 20)
let b = new Point(20, 24)
@ -463,34 +456,6 @@ describe('Utils', () => {
expect(round(i.y)).to.equal(400)
})
it('Should check for valid coordinate', () => {
expect(isCoord(23423.23)).to.equal(true)
expect(isCoord(0)).to.equal(true)
expect(isCoord()).to.equal(false)
expect(isCoord(null)).to.equal(false)
expect(isCoord('hi')).to.equal(false)
expect(isCoord(NaN)).to.equal(false)
})
it('Should return the correct sample style', () => {
expect(sampleStyle(0, 5)).to.equal('stroke: hsl(-66, 100%, 35%);')
expect(sampleStyle(1, 5)).to.equal('stroke: hsl(0, 100%, 35%);')
expect(sampleStyle(2, 5)).to.equal('stroke: hsl(66, 100%, 35%);')
expect(sampleStyle(3, 5)).to.equal('stroke: hsl(132, 100%, 35%);')
expect(sampleStyle(4, 5)).to.equal('stroke: hsl(198, 100%, 35%);')
})
it('Should return the correct sample styles', () => {
const styles = [
'stroke: red;',
'stroke: blue;',
'stroke: green;',
'stroke: pink;',
'stroke: orange;',
]
for (let i = 0; i < 5; i++) expect(sampleStyle(i, 5, styles)).to.equal(styles[i])
})
it('Should convert degrees to radians', () => {
expect(deg2rad(0)).to.equal(0)
expect(round(deg2rad(69))).to.equal(1.2)

View file

@ -0,0 +1,4 @@
themes/*
styles/*
.eslintignore
*.css

View file

@ -0,0 +1,18 @@
env:
browser: true
es2021: true
extends:
- eslint:recommended
- plugin:react/recommended
overrides: []
parserOptions:
ecmaVersion: latest
sourceType: module
plugins:
- react
rules:
react/prop-types: off
react/react-in-jsx-scope: off
globals:
module: readonly

View file

@ -1,37 +1,41 @@
import React from 'react';
import React from 'react'
import ResetButtons from './reset-buttons'
import {EventGroup} from 'shared/components/workbench/events'
import DefaultErrorView from './view';
import { LogGroup } from 'shared/components/workbench/logs'
import DefaultErrorView from './view'
const ErrorView = (props) => {
if (props.children) return props.children
const inspectChildrenProps = {
type: 'error',
events: [props.error],
units: props.gist?.units
logs: [props.error],
units: props.gist?.units,
}
const inspectChildren = (<EventGroup {...inspectChildrenProps}></EventGroup>)
return (props.children || (<DefaultErrorView inspectChildren={inspectChildren}>
const inspectChildren = <LogGroup {...inspectChildrenProps}></LogGroup>
return (
props.children || (
<DefaultErrorView inspectChildren={inspectChildren}>
<h4>If you think your last action caused this error, you can: </h4>
<ResetButtons undoGist={props.undoGist} resetGist={props.resetGist} />
</DefaultErrorView>))
</DefaultErrorView>
)
)
}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error };
return { hasError: true, error }
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.log(error, errorInfo);
console.log(error, errorInfo)
}
componentDidUpdate(prevProps) {
@ -43,13 +47,21 @@ class ErrorBoundary extends React.Component {
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <ErrorView {...this.props} error={this.state.error}>{this.errorView}</ErrorView>
return (
<ErrorView {...this.props} error={this.state.error}>
{this.errorView}
</ErrorView>
)
}
try {
return this.props.children;
return this.props.children
} catch (e) {
return <ErrorView {...this.props} error={e}>{this.errorView}</ErrorView>;
return (
<ErrorView {...this.props} error={e}>
{this.errorView}
</ErrorView>
)
}
}
}

View file

@ -1,18 +1,30 @@
import DefaultErrorView from 'shared/components/error/view';
import DefaultErrorView from 'shared/components/error/view'
const Error = ({ draft, patternProps, error, updateGist }) => {
const inspectChildren = (<ul className="list-disc list-inside ml-4 text-xl">
const Error = ({ logs=[], updateGist }) => {
let errors = 0
let warnings = 0
for (const log of logs) {
errors += log.errors.length
warnings += log.warnings.length
}
const inspectChildren = (
<ul className="list-disc list-inside ml-4 text-xl">
<li>
Check the <button className="btn-link" onClick={() => updateGist(['_state', 'view'], 'events')}>
<strong>{patternProps?.events?.error?.length} errors</strong> and <strong>
{patternProps?.events?.warning?.length} warnings</strong></button>
Check the{' '}
<button className="btn-link" onClick={() => updateGist(['_state', 'view'], 'logs')}>
<strong>{errors.length} errors</strong> and <strong>{warnings.length} warnings</strong>
</button>
</li>
<li>Check the partially rendered pattern below to see which areas are problematic</li>
</ul>)
</ul>
)
return (<DefaultErrorView inspectChildren={inspectChildren}>
<p>Don't be alarmed, but we ran into some trouble while drafting this pattern.</p>
</DefaultErrorView>)
return (
<DefaultErrorView inspectChildren={inspectChildren}>
<p>No need to be alarmed, but we ran into some trouble while drafting this pattern.</p>
</DefaultErrorView>
)
}
export default Error

View file

@ -1,16 +1,18 @@
import SvgWrapper from './svg-wrapper'
import Error from './error.js'
const LabDraft = props => {
const { app, draft, gist, updateGist, unsetGist, showInfo, feedback, hasRequiredMeasurements } = props
const LabDraft = (props) => {
const { app, draft, gist, updateGist, unsetGist, showInfo, feedback, hasRequiredMeasurements } =
props
if (!draft || !hasRequiredMeasurements) return null
// Render as SVG
if (gist?.renderer === 'svg') {
let svg
try { svg = draft.render() }
catch(error) {
try {
svg = draft.render()
} catch (error) {
console.log('Failed to render design', error)
return <Error error={error} {...props} />
}
@ -19,19 +21,26 @@ const LabDraft = props => {
// Render as React
let patternProps = {}
try { patternProps = draft.getRenderProps() }
catch(error) {
try {
patternProps = draft.getRenderProps()
} catch (error) {
console.log('Failed to get render props for design', error)
return <Error error={error} {...props} />
}
const errors = []
for (const logs of patternProps.logs) {
errors.push(...logs.error)
}
return (
<>
{(!patternProps || patternProps.logs?.error?.length > 0)
? <Error {...{ draft, patternProps, updateGist }} />
: null
}
<SvgWrapper {...{ draft, patternProps, gist, updateGist, unsetGist, showInfo, app, feedback }} />
{!patternProps || errors.length > 0 ? (
<Error {...{ draft, patternProps, updateGist }} />
) : null}
<SvgWrapper
{...{ draft, patternProps, gist, updateGist, unsetGist, showInfo, app, feedback }}
/>
</>
)
}

View file

@ -88,7 +88,7 @@ const XrayPart = props => {
export const PartInner = forwardRef((props, ref) => {
const { partName, part, gist } = props
const grid = gist.paperless ? (
const Grid = gist.paperless ? (
<rect
x={part.topLeft.x}
y={part.topLeft.y}
@ -100,7 +100,7 @@ export const PartInner = forwardRef((props, ref) => {
) : null
return (<g ref={ref}>
{grid}
{Grid}
{
gist._state?.xray?.enabled &&
<XrayPart {...props} />
@ -140,7 +140,7 @@ const Part = props => {
const { partName, part} = props
return (
<g {...getProps(part)} id={`part-${partName}`}>
<g {...getProps(part)} id={`${part.context.settings.idPrefix || ''}part-${partName}`} className={part.context.settings.idPrefix || ''}>
<PartInner {...props}/>
</g>
)

View file

@ -2,10 +2,10 @@ import Part from './part'
import { getProps } from './utils'
const Stack = props => {
const { stackName, stack, gist, app, updateGist, unsetGist, showInfo } = props
const { stack, gist, app, updateGist, unsetGist, showInfo } = props
return (
<g {...getProps(stack)} id={`stack-${stackName}`}>
<g {...getProps(stack)}>
{[...stack.parts].map((part) => (
<Part {...{ app, gist, updateGist, unsetGist, showInfo }}
key={part.name}

View file

@ -26,7 +26,9 @@ import Stack from './stack'
*/
const SvgWrapper = props => {
const { patternProps, gist, app, updateGist, unsetGist, showInfo } = props
const { patternProps=false, gist, app, updateGist, unsetGist, showInfo } = props
if (!patternProps) return null
return <SizeMe>{({ size }) => (
<TransformWrapper

View file

@ -1,79 +0,0 @@
import Markdown from 'react-markdown'
import { formatMm } from 'shared/utils'
export const Error = ({err}) => (
<pre>
{err.stack.split(/\n/g).slice(0, 5).map((l, i) => (<code key={`error-${i}`} className={'block whitespace-pre-wrap' + (i > 0 ? ' break-all' : '')}>{l}</code>))}
</pre>
)
// Markdown wrapper to suppress creation of P tags
const Md = ({ children }) => <Markdown components={{ p: props => props.children }}>{children}</Markdown>
const Event = ({ evt, units }) => {
if (Array.isArray(evt)) {
if (evt[1]?.mm) return <span dangerouslySetInnerHTML={{
__html: `${evt[0]}: <strong>${formatMm(evt[1].mm, units, 'html')}</strong>`
}}/>
else return evt.map(e => <Event evt={e} key={e} />)
}
else if (evt.message) return <Error err={evt} />
else if (typeof evt === 'string') return <Md>{evt}</Md>
return <Md>Note a recognized event: {JSON.stringify(evt, null ,2)}</Md>
}
export const EventGroup = ({ type='info', events=[], units='metric' }) => events.length > 0 ? (
<div className="">
<h3 className="capitalize" id={`events-${type}`}>{type}</h3>
<table className="table w-full mdx">
<thead>
<tr>
<th className="text-right w-16">#</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{events.map((evt, i) => (
<tr key={i} className="leading-1 hover:bg-base-200 hover:bg-opacity-40">
<td className="text-right p-1 pr-4 font-bold opacity-80 text-accent">{i}</td>
<td className="p-1 pl-4"><Event evt={evt} units={units}/></td>
</tr>
))}
</tbody>
</table>
</div>
) : null
const order = [
'error',
'warning',
'info',
'debug'
]
const Events = props => props?.draft?.store.logs
? (
<div className="max-w-screen-xl m-auto">
<div className="flex flex-col">
<ul className="flex flex-row row-wrap">
{order.map(type => (props.draft.store.logs[type].length > 0)
? (
<li key={type} className="">
<a href={`#events-${type}`} className={`text-secondary font-bold capitalize text-xl`}>{type}</a>
{type === 'debug' ? '' : <span className="px-2 font-bold">|</span>}
</li>
) : (
<li key={type} className="text-base-content font-bold capitalize text-xl">
<span className="opacity-50">{type}</span>
{type === 'debug' ? '' : <span className="px-2 font-bold">|</span>}
</li>
)
)}
</ul>
{order.map(type => <EventGroup type={type} events={props.draft.store.logs[type]} units={props.gist.units}/>)}
</div>
</div>
) : null
export default Events

View file

@ -0,0 +1,112 @@
import Markdown from 'react-markdown'
import { formatMm } from 'shared/utils'
import { Tab, Tabs } from '../mdx/tabs.js'
export const Error = ({ err }) => (
<pre>
{err.stack
.split(/\n/g)
.slice(0, 5)
.map((l, i) => (
<code
key={`error-${i}`}
className={'block whitespace-pre-wrap' + (i > 0 ? ' break-all' : '')}
>
{l}
</code>
))}
</pre>
)
// Markdown wrapper to suppress creation of P tags
const Md = ({ children }) => (
<Markdown components={{ p: (props) => props.children }}>{children}</Markdown>
)
const Log = ({ log, units }) => {
if (Array.isArray(log)) {
if (log[1]?.mm)
return (
<span
dangerouslySetInnerHTML={{
__html: `${log[0]}: <strong>${formatMm(log[1].mm, units, 'html')}</strong>`,
}}
/>
)
else return log.map((l) => <Log log={l} key={l} />)
} else if (log.message) return <Error err={log} />
else if (typeof log === 'string') return <Md>{log}</Md>
return <Md>Unrecognized log: {JSON.stringify(log, null, 2)}</Md>
}
export const LogGroup = ({ type = 'info', logs = [], units = 'metric' }) =>
logs.length > 0 ? (
<div className="">
<h3 className="capitalize" id={`logs-${type}`}>
{type}
</h3>
<table className="table w-full mdx">
<thead>
<tr>
<th className="text-right w-16">#</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{logs.map((log, i) => (
<tr key={i} className="leading-1 hover:bg-base-200 hover:bg-opacity-40">
<td className="text-right p-1 pr-4 font-bold opacity-80 text-accent">{i}</td>
<td className="p-1 pl-4">
<Log log={log} units={units} />
</td>
</tr>
))}
</tbody>
</table>
</div>
) : null
const order = ['error', 'warning', 'info', 'debug']
const StoreLogs = ({ logs, units }) => (
<div className="max-w-screen-xl m-auto">
<div className="flex flex-col">
<ul className="flex flex-row row-wrap">
{order.map((type) =>
logs[type].length > 0 ? (
<li key={type} className="">
<a href={`#logs-${type}`} className={`text-secondary font-bold capitalize text-xl`}>
{type}
</a>
{type === 'debug' ? '' : <span className="px-2 font-bold">|</span>}
</li>
) : (
<li key={type} className="text-base-content font-bold capitalize text-xl">
<span className="opacity-50">{type}</span>
{type === 'debug' ? '' : <span className="px-2 font-bold">|</span>}
</li>
)
)}
</ul>
{order.map((type) => (
<LogGroup type={type} logs={logs[type]} units={units} key={type} />
))}
</div>
</div>
)
const Logs = (props) =>
props.draft.stores.length === 1 ? (
<StoreLogs logs={props.draft.stores[0].logs} units={props.gist.units} />
) : (
<Tabs tabs={props.draft.stores.map((store, i) => `Set ${i}`).join(',')}>
{props.draft.stores.map((store, i) => (
<Tab key={i}>
<StoreLogs logs={store.logs} units={props.gist.units} />
</Tab>
))}
</Tabs>
)
export default Logs

View file

@ -3,88 +3,90 @@ import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import { useTranslation } from 'next-i18next'
import { defaultGist } from 'shared/hooks/useGist'
const View = props => {
const View = (props) => {
const { t } = useTranslation(['app'])
const entries = [
{
name: 'measurements',
title: t('measurements'),
onClick: () => props.updateGist(['_state', 'view'], 'measurements', true)
onClick: () => props.updateGist(['_state', 'view'], 'measurements', true),
},
{
name: 'draft',
title: t('draftDesign', { design: props.design.config.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'draft', true)
onClick: () => props.updateGist(['_state', 'view'], 'draft', true),
},
{
name: 'test',
title: t('testDesign', { design: props.design.config.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'test', true)
onClick: () => props.updateGist(['_state', 'view'], 'test', true),
},
{
name: 'printingLayout',
title: t('layoutThing', { thing: props.design.config.data.name })
+ ': ' + t('forPrinting'),
onClick: () => props.updateGist(['_state', 'view'], 'printingLayout', true)
title: t('layoutThing', { thing: props.design.config.data.name }) + ': ' + t('forPrinting'),
onClick: () => props.updateGist(['_state', 'view'], 'printingLayout', true),
},
{
name: 'cuttingLayout',
title: t('layoutThing', { thing: props.design.config.data.name })
+ ': ' + t('forCutting'),
onClick: () => props.updateGist(['_state', 'view'], 'cuttingLayout', true)
title: t('layoutThing', { thing: props.design.config.data.name }) + ': ' + t('forCutting'),
onClick: () => props.updateGist(['_state', 'view'], 'cuttingLayout', true),
},
{
name: 'export',
title: t('exportThing', { thing: props.design.config.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'export', true)
onClick: () => props.updateGist(['_state', 'view'], 'export', true),
},
{
name: 'events',
title: t('events'),
onClick: () => props.updateGist(['_state', 'view'], 'events', true)
name: 'logs',
title: t('logs'),
onClick: () => props.updateGist(['_state', 'view'], 'logs', true),
},
{
name: 'yaml',
title: t('YAML'),
onClick: () => props.updateGist(['_state', 'view'], 'yaml', true)
onClick: () => props.updateGist(['_state', 'view'], 'yaml', true),
},
{
name: 'json',
title: t('JSON'),
onClick: () => props.updateGist(['_state', 'view'], 'json', true)
onClick: () => props.updateGist(['_state', 'view'], 'json', true),
},
{
name: 'edit',
title: t('editThing', { thing: 'YAML' }),
onClick: () => props.updateGist(['_state', 'view'], 'edit', true)
onClick: () => props.updateGist(['_state', 'view'], 'edit', true),
},
{
name: 'clear',
title: t('clearThing', { thing: 'YAML' }),
onClick: () => props.setGist(defaultGist(props.design, props.gist.locale))
onClick: () => props.setGist(defaultGist(props.design, props.gist.locale)),
},
]
return (
<details className='py-1' open>
<summary className={`
<details className="py-1" open>
<summary
className={`
flex flex-row uppercase gap-4 font-bold text-lg
hover:cursor-row-resize
p-2
text-base-content
sm:text-base-content
items-center
`}>
<span className="text-secondary-focus mr-4"><MenuIcon /></span>
<span className={`grow ${linkClasses} hover:cursor-resize`}>
{t('view')}
`}
>
<span className="text-secondary-focus mr-4">
<MenuIcon />
</span>
<span className={`grow ${linkClasses} hover:cursor-resize`}>{t('view')}</span>
<Chevron />
</summary>
<ul className="pl-5 list-inside">
{entries.map(entry => (
{entries.map((entry) => (
<li key={entry.title} className="flex flex-row">
<button title={entry.title} className={`
<button
title={entry.title}
className={`
grow pl-2 border-l-2
${linkClasses}
hover:cursor-pointer
@ -92,18 +94,24 @@ const View = props => {
sm:hover:border-secondary-focus
text-left
capitalize
${entry.name === props.gist?._state?.view
${
entry.name === props.gist?._state?.view
? 'text-secondary border-secondary sm:text-secondary-focus sm:border-secondary-focus'
: 'text-base-content sm:text-base-content'
}
`} onClick={entry.onClick}>
<span className={`
`}
onClick={entry.onClick}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
${entry.name === props.gist?._state?.view
${
entry.name === props.gist?._state?.view
? 'text-secondary sm:text-secondary-focus translate-y-1 font-bold'
: 'translate-y-3'
}
`}>
`}
>
{entry.name === props.gist?._state?.view ? <>&bull;</> : <>&deg;</>}
</span>
<span className={entry.name === props.gist?._state?.view ? 'font-bold' : ''}>

View file

@ -1,18 +1,24 @@
import SvgWrapper from './draft/svg-wrapper'
import Error from './draft/error.js'
import { svgattrPlugin } from '@freesewing/plugin-svgattr'
import { useTranslation } from 'next-i18next'
const LabSample = ({ gist, draft }) => {
const LabSample = ({ gist, draft, updateGist, unsetGist, showInfo, app, feedback }) => {
const { t } = useTranslation(['workbench'])
let svg
let title = ''
let patternProps
const errors = []
if (gist.sample) {
try {
draft.use(svgattrPlugin, {
class: 'freesewing pattern max-h-screen'
})
draft.sample()
svg = draft.render()
draft = draft.sample()
// Render as React
patternProps = draft.getRenderProps()
for (const logs of patternProps.logs) errors.push(...logs.error)
}
catch(err) {
console.log(err)
@ -27,6 +33,12 @@ const LabSample = ({ gist, draft }) => {
return (
<>
<h2>{title}</h2>
{!patternProps || errors.length > 0 ? (
<Error {...{ draft, patternProps, updateGist }} />
) : null}
<SvgWrapper
{...{ draft, patternProps, gist, updateGist, unsetGist, showInfo, app, feedback }}
/>
<div className="freesewing pattern" dangerouslySetInnerHTML={{__html: svg}} />
</>
)

View file

@ -1,4 +1,4 @@
import { useEffect, useState, useMemo,} from 'react'
import { useEffect, useState, useMemo } from 'react'
import { useGist } from 'shared/hooks/useGist'
import Layout from 'shared/components/layouts/default'
import Menu from 'shared/components/workbench/menu/index.js'
@ -14,11 +14,11 @@ import LabSample from 'shared/components/workbench/sample.js'
import ExportDraft from 'shared/components/workbench/exporting/index.js'
import GistAsJson from 'shared/components/workbench/gist-as-json.js'
import GistAsYaml from 'shared/components/workbench/yaml.js'
import DraftEvents from 'shared/components/workbench/events.js'
import DraftLogs from 'shared/components/workbench/logs.js'
import CutLayout from 'shared/components/workbench/layout/cut'
import PrintingLayout from 'shared/components/workbench/layout/print'
import ErrorBoundary from 'shared/components/error/error-boundary';
import ErrorBoundary from 'shared/components/error/error-boundary'
const views = {
measurements: Measurements,
@ -27,7 +27,7 @@ const views = {
printingLayout: PrintingLayout,
cuttingLayout: CutLayout,
export: ExportDraft,
events: DraftEvents,
logs: DraftLogs,
yaml: GistAsYaml,
json: GistAsJson,
welcome: () => <p>TODO</p>,
@ -55,14 +55,15 @@ const doPreload = async (preload, from, design, gist, setGist, setPreloaded) =>
* to all workbench subcomponents
*/
const WorkbenchWrapper = ({ app, design, preload = false, from = false, layout = false }) => {
// State for gist
const {gist, setGist, unsetGist, updateGist, gistReady, undoGist, resetGist} = useGist(design, app);
const { gist, setGist, unsetGist, updateGist, gistReady, undoGist, resetGist } = useGist(
design,
app
)
const [messages, setMessages] = useState([])
const [popup, setPopup] = useState(false)
const [preloaded, setPreloaded] = useState(false)
// We'll use this in more than one location
const hasRequiredMeasurements = hasRequiredMeasurementsMethod(design, gist)
@ -70,34 +71,31 @@ const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false
// force view to measurements
useEffect(() => {
if (!gistReady) return
if (gist._state?.view !== 'measurements'
&& !hasRequiredMeasurements
) updateGist(['_state', 'view'], 'measurements')
if (gist._state?.view !== 'measurements' && !hasRequiredMeasurements)
updateGist(['_state', 'view'], 'measurements')
}, [gistReady, gist._state?.view, hasRequiredMeasurements])
// If we need to preload the gist, do so
useEffect(() => {
if (
preload &&
preload !== preloaded &&
from &&
preloaders[from]
) {
if (preload && preload !== preloaded && from && preloaders[from]) {
doPreload(preload, from, design, gist, setGist, setPreloaded)
}
}, [preload, preloaded, from, design])
// Helper methods to manage the gist state
const updateWBGist = useMemo(() => (path, value, closeNav=false, addToHistory=true) => {
const updateWBGist = useMemo(
() =>
(path, value, closeNav = false, addToHistory = true) => {
updateGist(path, value, addToHistory)
// Force close of menu on mobile if it is open
if (closeNav && app.primaryMenu) app.setPrimaryMenu(false)
}, [app])
},
[app]
)
// Helper methods to handle messages
const feedback = {
add: msg => {
add: (msg) => {
const newMsgs = [...messages]
if (Array.isArray(msg)) newMsgs.push(...msg)
else newMsgs.push(msg)
@ -108,11 +106,13 @@ const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false
}
// don't do anything until the gist is ready
if (!gistReady) {return null}
if (!gistReady) {
return null
}
// Generate the draft here so we can pass it down to both the view and the options menu
let draft = false
if (['draft', 'events', 'test', 'printingLayout'].indexOf(gist._state?.view) !== -1) {
if (['draft', 'logs', 'test', 'printingLayout'].indexOf(gist._state?.view) !== -1) {
gist.embed = true
// get the appropriate layout for the view
const layout = gist.layouts?.[gist._state.view] || gist.layout || true
@ -125,9 +125,8 @@ const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false
// draft it for draft and event views. Other views may add plugins, etc and we don't want to draft twice
try {
if (['draft', 'events'].indexOf(gist._state.view) > -1) draft.draft()
}
catch(error) {
if (['draft', 'logs'].indexOf(gist._state.view) > -1) draft.draft()
} catch (error) {
console.log('Failed to draft design', error)
return <DraftError error={error} app={app} draft={draft} at={'draft'} />
}
@ -159,26 +158,23 @@ const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false
const errorProps = {
undoGist,
resetGist,
gist
gist,
}
// Layout to use
const LayoutComponent = layout
? layout
: Layout
const LayoutComponent = layout ? layout : Layout
const Component = views[gist._state?.view]
? views[gist._state.view]
: views.welcome
const Component = views[gist._state?.view] ? views[gist._state.view] : views.welcome
return <LayoutComponent {...layoutProps}>
return (
<LayoutComponent {...layoutProps}>
{messages}
<ErrorBoundary {...errorProps}>
<Component {...componentProps} />
{popup && <Modal cancel={() => setPopup(false)}>{popup}</Modal>}
</ErrorBoundary>
</LayoutComponent>
)
}
export default WorkbenchWrapper

View file

@ -1,4 +1,4 @@
// Can't seem to make this work as ESM
module.exports = {
plugins: ['tailwindcss/nesting', 'tailwindcss', 'autoprefixer'],
plugins: ['tailwindcss/nesting', 'tailwindcss', 'autoprefixer', 'postcss-for'],
}

View file

@ -28,6 +28,7 @@
"lodash.unset": "^4.5.2",
"mdast-util-toc": "^6.1.0",
"pdfkit": "^0.13.0",
"postcss-for": "^2.1.1",
"react-markdown": "^8.0.0",
"react-sizeme": "^3.0.2",
"react-timeago": "^7.1.0",
@ -44,6 +45,8 @@
},
"devDependencies": {
"autoprefixer": "^10.4.0",
"eslint": "^8.23.1",
"eslint-plugin-react": "^7.31.8",
"lodash.set": "^4.3.2",
"postcss": "^8.4.4",
"tailwindcss": "^3.0.1",

View file

@ -161,6 +161,20 @@ svg.freesewing.pattern {
}
}
/* Styling for v3 sampling */
@for $i from 1 to 10 {
svg.freesewing.pattern g.sample-$i path.fabric,
svg.freesewing.pattern g.sample-$i path.lining,
svg.freesewing.pattern g.sample-$i path.interfacing {
stroke: var(--pattern-sample-$i);
fill: var(--pattern-sample-$i);
fill-opacity: 0.01;
}
}
/* Styling the shadow DOM is hard to do
* This is for styling FreeSewing snippets
* TODO: Update snippets to use inline styles with CSS vars
@ -257,3 +271,6 @@ figure.develop.example div.develop {
}
}

View file

@ -257,4 +257,17 @@ module.exports = {
'--pattern-stroke-6xl': "16px",
// Pattern 7xl stroke width
'--pattern-stroke-7xl': "20px",
// Pattern sampling styles
'--pattern-sample-1': colors.red["500"],
'--pattern-sample-2': colors.orange["500"],
'--pattern-sample-3': colors.yellow["500"],
'--pattern-sample-4': colors.lime["500"],
'--pattern-sample-5': colors.emerald["500"],
'--pattern-sample-6': colors.cyan["500"],
'--pattern-sample-7': colors.blue["500"],
'--pattern-sample-8': colors.violet["500"],
'--pattern-sample-9': colors.fuchsia["500"],
'--pattern-sample-10': colors.rose["500"],
}

187
yarn.lock
View file

@ -378,6 +378,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
"@babel/parser@^7.9.4":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c"
integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@ -3481,6 +3486,19 @@
dependencies:
"@types/node" "*"
"@types/linkify-it@*":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
"@types/markdown-it@^12.2.3":
version "12.2.3"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51"
integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==
dependencies:
"@types/linkify-it" "*"
"@types/mdurl" "*"
"@types/mdast@^3.0.0":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
@ -3488,7 +3506,7 @@
dependencies:
"@types/unist" "*"
"@types/mdurl@^1.0.0":
"@types/mdurl@*", "@types/mdurl@^1.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
@ -4915,7 +4933,7 @@ bl@^5.0.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
bluebird@^3.5.5:
bluebird@^3.5.5, bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@ -5450,6 +5468,13 @@ capture-stack-trace@^1.0.0:
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
catharsis@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121"
integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==
dependencies:
lodash "^4.17.15"
cbor@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5"
@ -7502,6 +7527,11 @@ entities@^4.2.0, entities@^4.3.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.1.tgz#c34062a94c865c322f9d67b4384e4169bcede6a4"
integrity sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==
entities@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@ -7941,6 +7971,26 @@ eslint-plugin-react@^7.28.0, eslint-plugin-react@^7.29.4:
semver "^6.3.0"
string.prototype.matchall "^4.0.7"
eslint-plugin-react@^7.31.8:
version "7.31.8"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.8.tgz#3a4f80c10be1bcbc8197be9e8b641b2a3ef219bf"
integrity sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==
dependencies:
array-includes "^3.1.5"
array.prototype.flatmap "^1.3.0"
doctrine "^2.1.0"
estraverse "^5.3.0"
jsx-ast-utils "^2.4.1 || ^3.0.0"
minimatch "^3.1.2"
object.entries "^1.1.5"
object.fromentries "^2.0.5"
object.hasown "^1.1.1"
object.values "^1.1.5"
prop-types "^15.8.1"
resolve "^2.0.0-next.3"
semver "^6.3.0"
string.prototype.matchall "^4.0.7"
eslint-plugin-yaml@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-yaml/-/eslint-plugin-yaml-0.5.0.tgz#8c79d9d6389b67cbcf58ef6f970c4c086665a63a"
@ -9506,7 +9556,7 @@ got@^9.6.0:
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@ -9562,6 +9612,11 @@ has-bigints@^1.0.1, has-bigints@^1.0.2:
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
integrity sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -11131,6 +11186,11 @@ jest-validate@^27.3.1, jest-validate@^27.4.2:
leven "^3.1.0"
pretty-format "^27.5.1"
js-base64@^2.1.9:
version "2.6.4"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
js-sdsl@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.4.tgz#78793c90f80e8430b7d8dc94515b6c77d98a26a6"
@ -11168,6 +11228,34 @@ js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
js2xmlparser@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a"
integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==
dependencies:
xmlcreate "^2.0.4"
jsdoc@^3.6.11:
version "3.6.11"
resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce"
integrity sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==
dependencies:
"@babel/parser" "^7.9.4"
"@types/markdown-it" "^12.2.3"
bluebird "^3.7.2"
catharsis "^0.9.0"
escape-string-regexp "^2.0.0"
js2xmlparser "^4.0.2"
klaw "^3.0.0"
markdown-it "^12.3.2"
markdown-it-anchor "^8.4.1"
marked "^4.0.10"
mkdirp "^1.0.4"
requizzle "^0.2.3"
strip-json-comments "^3.1.0"
taffydb "2.6.2"
underscore "~1.13.2"
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@ -11427,6 +11515,13 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
klaw@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146"
integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==
dependencies:
graceful-fs "^4.1.9"
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@ -11587,6 +11682,13 @@ lines-and-columns@^2.0.2:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b"
integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==
linkify-it@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==
dependencies:
uc.micro "^1.0.1"
lint-staged@^13.0.3:
version "13.0.3"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.3.tgz#d7cdf03a3830b327a2b63c6aec953d71d9dc48c6"
@ -12182,11 +12284,32 @@ markdown-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3"
integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==
markdown-it-anchor@^8.4.1:
version "8.6.5"
resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz#30c4bc5bbff327f15ce3c429010ec7ba75e7b5f8"
integrity sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==
markdown-it@^12.3.2:
version "12.3.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90"
integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==
dependencies:
argparse "^2.0.1"
entities "~2.1.0"
linkify-it "^3.0.1"
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-table@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c"
integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==
marked@^4.0.10:
version "4.1.0"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.1.0.tgz#3fc6e7485f21c1ca5d6ec4a39de820e146954796"
integrity sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==
matcher@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/matcher/-/matcher-5.0.0.tgz#cd82f1c7ae7ee472a9eeaf8ec7cac45e0fe0da62"
@ -12537,7 +12660,7 @@ mdast-util-toc@^6.1.0:
unist-util-is "^5.0.0"
unist-util-visit "^3.0.0"
mdurl@^1.0.0:
mdurl@^1.0.0, mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
@ -15353,6 +15476,14 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==
postcss-for@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/postcss-for/-/postcss-for-2.1.1.tgz#841378c0ef909d50e1980d5aa71e6a340e728fcd"
integrity sha512-X0R84FCyr5cqzW4+/g4Dvz2OUe1iwC3G/atIrwEpiRstZlBBpknV+ETlIneSTnw/iXgUnEoTRaO2qXY62YWLhQ==
dependencies:
postcss "^5.0.0"
postcss-simple-vars "^2.0.0"
postcss-import@^14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0"
@ -15392,6 +15523,13 @@ postcss-selector-parser@6.0.10, postcss-selector-parser@^6.0.10, postcss-selecto
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-simple-vars@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-2.0.0.tgz#d0a1091b0da22b79507028f7b22b976c0a60b8d5"
integrity sha512-HllLaKKCBOdKudyzqrw/ve5rWouM9cDL+WHaSF9q4CkBEPjdTdiKNw1xF2dAz5rUKrxVmnUmOYxamwy37dnq2Q==
dependencies:
postcss "^5.0.21"
postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
@ -15415,6 +15553,16 @@ postcss@8.4.14:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^5.0.0, postcss@^5.0.21:
version "5.2.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
integrity sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==
dependencies:
chalk "^1.1.3"
js-base64 "^2.1.9"
source-map "^0.5.6"
supports-color "^3.2.3"
postcss@^8.4.12, postcss@^8.4.14, postcss@^8.4.4, postcss@^8.4.5:
version "8.4.16"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
@ -16933,6 +17081,13 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
requizzle@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.3.tgz#4675c90aacafb2c036bd39ba2daa4a1cb777fded"
integrity sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==
dependencies:
lodash "^4.17.14"
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@ -18195,6 +18350,13 @@ supports-color@^2.0.0:
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
supports-color@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
integrity sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==
dependencies:
has-flag "^1.0.0"
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -18251,6 +18413,11 @@ tabtab@^3.0.2:
mkdirp "^0.5.1"
untildify "^3.0.3"
taffydb@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
integrity sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==
tailwindcss-open-variant@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tailwindcss-open-variant/-/tailwindcss-open-variant-1.0.0.tgz#e4555c0a0ec2a82801e563ed36b1b23e4dd04a3b"
@ -18846,6 +19013,11 @@ typical@^4.0.0:
resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uglify-js@^3.1.4:
version "3.16.2"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.2.tgz#0481e1dbeed343ad1c2ddf3c6d42e89b7a6d4def"
@ -18881,7 +19053,7 @@ undefsafe@^2.0.2:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
underscore@^1.5.0:
underscore@^1.5.0, underscore@~1.13.2:
version "1.13.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee"
integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==
@ -19950,6 +20122,11 @@ xml-js@^1.6.11:
dependencies:
sax "^1.2.4"
xmlcreate@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be"
integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==
xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"