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 dist
build build
export export
packages/core/out
# Prebuild files # Prebuild files
prebuild/*.json prebuild/*.json

View file

@ -23,6 +23,7 @@ core:
testci: 'mocha tests/*.test.mjs' testci: 'mocha tests/*.test.mjs'
prettier: "npx prettier --write 'src/*.mjs' 'tests/*.mjs'" prettier: "npx prettier --write 'src/*.mjs' 'tests/*.mjs'"
lint: "npx eslint 'src/*.mjs' 'tests/*.mjs'" lint: "npx eslint 'src/*.mjs' 'tests/*.mjs'"
jsdoc: "jsdoc -c jsdoc.json -r src"
i18n: i18n:
prebuild: 'node scripts/prebuilder.mjs' prebuild: 'node scripts/prebuilder.mjs'
models: 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", "report": "c8 report",
"testci": "mocha tests/*.test.mjs", "testci": "mocha tests/*.test.mjs",
"prettier": "npx prettier --write 'src/*.mjs' 'tests/*.mjs'", "prettier": "npx prettier --write 'src/*.mjs' 'tests/*.mjs'",
"jsdoc": "jsdoc -c jsdoc.json -r src",
"cibuild_step0": "node build.mjs" "cibuild_step0": "node build.mjs"
}, },
"peerDependencies": {}, "peerDependencies": {},

View file

@ -1,8 +1,30 @@
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for Attributes
*
* @constructor
* @return {Attributes} this - The Attributes instance
*/
export function Attributes() { export function Attributes() {
this.list = {} 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) { Attributes.prototype.add = function (name, value) {
if (typeof this.list[name] === 'undefined') { if (typeof this.list[name] === 'undefined') {
this.list[name] = [] this.list[name] = []
@ -12,75 +34,12 @@ Attributes.prototype.add = function (name, value) {
return this return this
} }
/** Sets an attribute, overwriting existing value */ /**
Attributes.prototype.set = function (name, value) { * Return a props object for attributes with a fiven prefix (typically used for data-text)
this.list[name] = [value] *
* @param {string} prefix - The prefix to filter attributes on
return this * @return {object} props - The attributes as props
} */
/** 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*/
Attributes.prototype.asPropsIfPrefixIs = function (prefix = '') { Attributes.prototype.asPropsIfPrefixIs = function (prefix = '') {
let props = {} let props = {}
let prefixLen = prefix.length let prefixLen = prefix.length
@ -95,10 +54,120 @@ Attributes.prototype.asPropsIfPrefixIs = function (prefix = '') {
return props return props
} }
/** Returns a deep copy of this */ /**
* Return a deep copy of this
*
* @return {object} this - The Attributes instance
*/
Attributes.prototype.clone = function () { Attributes.prototype.clone = function () {
let clone = new Attributes() let clone = new Attributes()
clone.list = JSON.parse(JSON.stringify(this.list)) clone.list = JSON.parse(JSON.stringify(this.list))
return clone 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: [], measurements: [],
optionalMeasurements: [], optionalMeasurements: [],
options: {}, options: {},
optionDistance: {},
parts: [], parts: [],
data: {}, data: {},
plugins: [], 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, complete: true,
idPrefix: 'fs-', idPrefix: 'fs-',
stackPrefix: '',
locale: 'en', locale: 'en',
units: 'metric', units: 'metric',
margin: 2, margin: 2,

View file

@ -1,21 +1,28 @@
import { Pattern } from './pattern.mjs' import { Pattern } from './pattern.mjs'
import { loadDesignDefaults } from './config.mjs' import { __loadDesignDefaults } from './config.mjs'
/* //////////////////////////////////////////////
* The Design constructor. Returns a Pattern constructor // CONSTRUCTOR //
* So it's sort of a super-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) { export function Design(config) {
// Initialize config with defaults // Initialize config with defaults
config = { ...loadDesignDefaults(), ...config } config = { ...__loadDesignDefaults(), ...config }
// Create the pattern constructor // Create the pattern constructor
const pattern = function (settings) { const pattern = function (...sets) {
// Pass the design config // Pass the design config
Pattern.call(this, config) Pattern.call(this, config)
// Pass the pattern settings // Pass the pattern settings
return this.__applySettings(settings) return this.__applySettings(sets)
} }
// Set up inheritance // 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() { export function Hooks() {
return { return {
preDraft: [], preDraft: [],

View file

@ -1,39 +1,37 @@
import { Bezier } from 'bezier-js'
import { Attributes } from './attributes.mjs' import { Attributes } from './attributes.mjs'
import { Design } from './design.mjs' import { Design } from './design.mjs'
import { Pattern } from './pattern.mjs' import { Pattern } from './pattern.mjs'
import { Part } from './part.mjs'
import { Point } from './point.mjs' import { Point } from './point.mjs'
import { Path } from './path.mjs' import { Path } from './path.mjs'
import { Snippet } from './snippet.mjs' import { Snippet } from './snippet.mjs'
import { Store } from './store.mjs' import { Store } from './store.mjs'
import { import {
isCoord, beamIntersectsCircle,
capitalize,
beamsIntersect,
linesIntersect,
pointOnBeam,
pointOnLine,
pointOnCurve,
splitCurve,
beamIntersectsX, beamIntersectsX,
beamIntersectsY, beamIntersectsY,
units, beamsIntersect,
lineIntersectsCurve, capitalize,
circlesIntersect,
curveEdge,
curveIntersectsX, curveIntersectsX,
curveIntersectsY, curveIntersectsY,
curvesIntersect, curvesIntersect,
circlesIntersect,
beamIntersectsCircle,
lineIntersectsCircle,
curveEdge,
stretchToScale,
round,
sampleStyle,
deg2rad, deg2rad,
rad2deg,
pctBasedOn,
Bezier,
generateStackTransform, generateStackTransform,
macroName, lineIntersectsCircle,
lineIntersectsCurve,
linesIntersect,
pctBasedOn,
pointOnBeam,
pointOnCurve,
pointOnLine,
rad2deg,
round,
splitCurve,
stretchToScale,
units,
} from './utils.mjs' } from './utils.mjs'
import { version } from '../data.mjs' import { version } from '../data.mjs'
@ -44,35 +42,34 @@ export {
Pattern, Pattern,
Point, Point,
Path, Path,
Part,
Snippet, Snippet,
Store, Store,
version,
Bezier, Bezier,
capitalize, // Utils
beamsIntersect, beamIntersectsCircle,
linesIntersect,
pointOnBeam,
pointOnLine,
pointOnCurve,
splitCurve,
beamIntersectsX, beamIntersectsX,
beamIntersectsY, beamIntersectsY,
units, beamsIntersect,
lineIntersectsCurve, capitalize,
circlesIntersect,
curveEdge,
curveIntersectsX, curveIntersectsX,
curveIntersectsY, curveIntersectsY,
curvesIntersect, curvesIntersect,
circlesIntersect,
beamIntersectsCircle,
lineIntersectsCircle,
curveEdge,
stretchToScale,
round,
sampleStyle,
deg2rad, deg2rad,
rad2deg,
pctBasedOn,
generateStackTransform, generateStackTransform,
macroName, lineIntersectsCircle,
isCoord, lineIntersectsCurve,
version, 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 { Attributes } from './attributes.mjs'
import * as utils from './utils.mjs' import * as utils from './utils.mjs'
import { Point } from './point.mjs' import { Point, pointsProxy } from './point.mjs'
import { Path } from './path.mjs' import { Path, pathsProxy } from './path.mjs'
import { Snippet } from './snippet.mjs' import { Snippet, snippetsProxy } from './snippet.mjs'
import { Hooks } from './hooks.mjs' import { Hooks } from './hooks.mjs'
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
/**
* Constructor for a Part
*
* @constructor
* @return {Part} this - The Part instance
*/
export function Part() { export function Part() {
// Non-enumerable properties // Non-enumerable properties
utils.addNonEnumProp(this, 'freeId', 0) utils.__addNonEnumProp(this, 'freeId', 0)
utils.addNonEnumProp(this, 'topLeft', false) utils.__addNonEnumProp(this, 'topLeft', false)
utils.addNonEnumProp(this, 'bottomRight', false) utils.__addNonEnumProp(this, 'bottomRight', false)
utils.addNonEnumProp(this, 'width', false) utils.__addNonEnumProp(this, 'width', false)
utils.addNonEnumProp(this, 'height', false) utils.__addNonEnumProp(this, 'height', false)
utils.addNonEnumProp(this, 'utils', utils) utils.__addNonEnumProp(this, 'utils', utils)
utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } }) utils.__addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
utils.addNonEnumProp(this, 'Point', Point) utils.__addNonEnumProp(this, 'Point', Point)
utils.addNonEnumProp(this, 'Path', Path) utils.__addNonEnumProp(this, 'Path', Path)
utils.addNonEnumProp(this, 'Snippet', Snippet) utils.__addNonEnumProp(this, 'Snippet', Snippet)
utils.addNonEnumProp(this, 'hooks', new Hooks()) utils.__addNonEnumProp(this, 'hooks', new Hooks())
// Enumerable properties // Enumerable properties
this.render = true // FIXME: Replace render with hide this.render = true // FIXME: Replace render with hide
@ -31,56 +41,163 @@ export function Part() {
return this return this
} }
Part.prototype.macroClosure = function () { //////////////////////////////////////////////
let self = this // PUBLIC METHODS //
let method = function (key, args) { //////////////////////////////////////////////
let macro = utils.macroName(key)
if (typeof self[macro] === 'function') self[macro](args)
}
return method /**
* 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
} }
Part.prototype.runHooks = function (hookName, data = false) { /**
if (data === false) data = this * Returns on unused ID (unused in this part)
let hooks = this.hooks[hookName] *
if (hooks && hooks.length > 0) { * @param {string} prefix - An optional prefix to apply to the ID
for (let hook of hooks) { * @return {string} id - The id
hook.method(data, hook.data) */
}
}
}
/** Returns an unused ID */
Part.prototype.getId = function (prefix = '') { Part.prototype.getId = function (prefix = '') {
this.freeId += 1 this.freeId += 1
return prefix + this.freeId return prefix + this.freeId
} }
/** Returns a value formatted for units provided in settings */ /** Returns an object with shorthand access for pattern design */
Part.prototype.unitsClosure = function () { /**
const self = this * Returns an object that will be passed to draft method to be destructured
const method = function (value) { *
if (typeof value !== 'number') * @return {object} short - The so-called shorthand object with what you might need in your draft method
self.context.store.log.warning( */
`Calling \`units(value)\` but \`value\` is not a number (\`${typeof value}\`)` Part.prototype.shorthand = function () {
) const complete = this.context.settings?.complete ? true : false
return utils.units(value, self.context.settings.units) 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)
}
} }
return method // 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)
)
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
} }
/** Calculates the part's bounding box and sets it */ /**
Part.prototype.boundary = function () { * 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)
}
//////////////////////////////////////////////
// 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 if (this.topLeft) return this // Cached
let topLeft = new Point(Infinity, Infinity) let topLeft = new Point(Infinity, Infinity)
let bottomRight = new Point(-Infinity, -Infinity) let bottomRight = new Point(-Infinity, -Infinity)
for (let key in this.paths) { for (let key in this.paths) {
try { try {
let path = this.paths[key].boundary() let path = this.paths[key].__boundary()
if (path.render) { if (path.render) {
if (path.topLeft.x < topLeft.x) topLeft.x = path.topLeft.x if (path.topLeft.x < topLeft.x) topLeft.x = path.topLeft.x
if (path.topLeft.y < topLeft.y) topLeft.y = path.topLeft.y if (path.topLeft.y < topLeft.y) topLeft.y = path.topLeft.y
@ -120,16 +237,14 @@ Part.prototype.boundary = function () {
return this return this
} }
/** Adds an attribute. This is here to make this call chainable in assignment */ /**
Part.prototype.attr = function (name, value, overwrite = false) { * Copies point/path/snippet data from part orig into this
if (overwrite) this.attributes.set(name, value) *
else this.attributes.add(name, value) * @private
* @param {object} orig - The original part to inject into this
return this * @return {Part} this - The part instance
} */
Part.prototype.__inject = function (orig) {
/** Copies point/path/snippet data from part orig into this */
Part.prototype.inject = function (orig) {
const findBasePoint = (p) => { const findBasePoint = (p) => {
for (let i in orig.points) { for (let i in orig.points) {
if (orig.points[i] === p) return i if (orig.points[i] === p) return i
@ -161,196 +276,37 @@ Part.prototype.inject = function (orig) {
return this return this
} }
Part.prototype.units = function (input) { /**
return utils.units(input, this.context.settings.units) * Returns a closure holding the macro method
} *
* @private
/** Returns an object with shorthand access for pattern design */ * @return {function} method - The closured macro method
Part.prototype.shorthand = function () { */
const complete = this.context.settings?.complete ? true : false Part.prototype.__macroClosure = function () {
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 let self = this
let method = function (key, args) {
let macro = utils.__macroName(key)
if (typeof self[macro] === 'function') self[macro](args)
}
// Wrap the Point constructor so objects can log return method
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
} }
Part.prototype.isEmpty = function () { /**
if (Object.keys(this.snippets).length > 0) return false * Returns a method to format values in the units provided in settings
*
if (Object.keys(this.paths).length > 0) { * @private
for (const p in this.paths) { * @return {function} method - The closured units method
if (this.paths[p].render && this.paths[p].length()) return false */
} 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)
} }
for (const p in this.points) { return method
if (this.points[p].attributes.get('data-text')) return false
if (this.points[p].attributes.get('data-circle')) return false
}
return true
} }
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 { 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.x = x
this.y = y this.y = y
this.attributes = new Attributes() 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) { // PUBLIC METHODS //
if (raise) Object.defineProperty(this, 'raise', { value: raise }) //////////////////////////////////////////////
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 () { * Chainable way to add an attribute to the Point
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') * @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
/** Radians to degrees */ * @return {object} this - The Point instance
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 */
Point.prototype.attr = function (name, value, overwrite = false) { Point.prototype.attr = function (name, value, overwrite = false) {
this.check()
if (overwrite) this.attributes.set(name, value) if (overwrite) this.attributes.set(name, value)
else this.attributes.add(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) { * returns an deel clone of this Point (including coordinates)
this.check() *
that.check() * @return {Point} clone - The cloned Point instance
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 */
Point.prototype.clone = function () { Point.prototype.clone = function () {
this.check() this.__check()
const clone = new Point(this.x, this.y, this.debug).withRaise(this.raise) const clone = new Point(this.x, this.y).__withLog(this.log)
clone.attributes = this.attributes.clone() clone.attributes = this.attributes.clone()
return 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) { Point.prototype.translate = function (x, y) {
this.check() this.__check()
if (typeof x !== 'number') 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') 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() const p = this.copy()
p.x += x p.x += x
p.y += y p.y += y
@ -232,18 +321,67 @@ Point.prototype.translate = function (x, y) {
return p return p
} }
/** Chainable way to set the data-text property (and optional class) */ //////////////////////////////////////////////
Point.prototype.setText = function (text = '', className = false) { // PRIVATE METHODS //
this.attributes.set('data-text', text) //////////////////////////////////////////////
if (className) this.attributes.set('data-text-class', className)
/**
* 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 return this
} }
/** Chainable way to set the data-circle property (and optional class) */ /**
Point.prototype.setCircle = function (radius = false, className = false) { * Adds a logging instance so the Point can log
if (radius) this.attributes.set('data-circle', radius) *
if (className) this.attributes.set('data-circle-class', className) * @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 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 { 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.def = def
this.anchor = anchor this.anchor = anchor
this.attributes = new Attributes() this.attributes = new Attributes()
Object.defineProperty(this, 'debug', { value: debug, configurable: true })
return this return this
} }
/** Adds the raise method for a snippet not created through the proxy **/ //////////////////////////////////////////////
Snippet.prototype.withRaise = function (raise = false) { // PUBLIC METHODS //
if (raise) Object.defineProperty(this, 'raise', { value: raise }) //////////////////////////////////////////////
return this /**
} * Chainable way to add an attribute
/** Adds an attribute. This is here to make this call chainable in assignment */ *
* @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) { Snippet.prototype.attr = function (name, value, overwrite = false) {
if (overwrite) this.attributes.set(name, value) if (overwrite) this.attributes.set(name, value)
else this.attributes.add(name, value) else this.attributes.add(name, value)
@ -23,10 +40,69 @@ Snippet.prototype.attr = function (name, value, overwrite = false) {
return this 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 () { 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() clone.attributes = this.attributes.clone()
return 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) { export function Stack(name = null) {
// Non-enumerable properties // Non-enumerable properties
utils.addNonEnumProp(this, 'freeId', 0) utils.__addNonEnumProp(this, 'freeId', 0)
utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } }) utils.__addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
// Enumerable properties // Enumerable properties
this.attributes = new Attributes() this.attributes = new Attributes()
@ -36,33 +36,13 @@ Stack.prototype.getPartNames = function () {
return [...this.parts].map((p) => p.name) 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 */ /** Calculates the stack's bounding box and sets it */
Stack.prototype.home = function () { Stack.prototype.home = function () {
if (this.topLeft) return this // Cached if (this.topLeft) return this // Cached
this.topLeft = new Point(Infinity, Infinity) this.topLeft = new Point(Infinity, Infinity)
this.bottomRight = new Point(-Infinity, -Infinity) this.bottomRight = new Point(-Infinity, -Infinity)
for (const part of this.getPartList()) { 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.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.topLeft.y < this.topLeft.y) this.topLeft.y = part.topLeft.y
if (part.bottomRight.x > this.bottomRight.x) this.bottomRight.x = part.bottomRight.x 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 if (this.bottomRight.y === -Infinity) this.bottomRight.y = 0
// Add margin // Add margin
let margin = this.context.settings.margin let margin = 0
if (this.context.settings.paperless && margin < 10) margin = 10 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.x -= margin
this.topLeft.y -= margin this.topLeft.y -= margin
this.bottomRight.x += 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 export default Stack

View file

@ -2,8 +2,20 @@ import set from 'lodash.set'
import unset from 'lodash.unset' import unset from 'lodash.unset'
import get from 'lodash.get' 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'] 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 = []) { export function Store(methods = []) {
/* /*
* Default logging methods * Default logging methods
@ -40,7 +52,16 @@ export function Store(methods = []) {
return this 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) { Store.prototype.extend = function (...methods) {
for (const [path, method] of methods) { for (const [path, method] of methods) {
if (avoid.indexOf(method[0]) !== -1) { if (avoid.indexOf(method[0]) !== -1) {
@ -54,29 +75,29 @@ Store.prototype.extend = function (...methods) {
return this return this
} }
/** Set key at path to value */ /**
Store.prototype.set = function (path, value) { * Retrieve a key from the store
if (typeof value === 'undefined') { *
this.log.warning(`Store.set(value) on key \`${path}\`, but value is undefined`) * @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(this, path, value)
return this return val
} }
/** Set key at path to value, but only if it's not currently set */ /**
Store.prototype.setIfUnset = function (path, value) { * Adds a value to an array stored under path
if (typeof value === 'undefined') { *
this.log.warning(`Store.setIfUnset(value) on key \`${path}\`, but value is undefined`) * @param {string|array} path - Path to the key
} * @param {mixed} values - One or more values to add (variadic)
if (typeof get(this, path) === 'undefined') { * @return {Store} this - The Store instance
return set(this, path, value) */
}
return this
}
/** Adds a value to an array stored under path */
Store.prototype.push = function (path, ...values) { Store.prototype.push = function (path, ...values) {
const arr = get(this, path) const arr = get(this, path)
if (Array.isArray(arr)) { if (Array.isArray(arr)) {
@ -88,19 +109,49 @@ Store.prototype.push = function (path, ...values) {
return this 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) { Store.prototype.unset = function (path) {
unset(this, path) unset(this, path)
return this 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 { Attributes } from './attributes.mjs'
import { addNonEnumProp, round } from './utils.mjs' import { __addNonEnumProp, round } from './utils.mjs'
import { version } from '../data.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) { export function Svg(pattern) {
// Non-enumerable properties // Non-enumerable properties
addNonEnumProp(this, 'openGroups', []) __addNonEnumProp(this, 'openGroups', [])
addNonEnumProp(this, 'layout', {}) __addNonEnumProp(this, 'layout', {})
addNonEnumProp(this, 'freeId', 0) __addNonEnumProp(this, 'freeId', 0)
addNonEnumProp(this, 'body', '') __addNonEnumProp(this, 'body', '')
addNonEnumProp(this, 'style', '') __addNonEnumProp(this, 'style', '')
addNonEnumProp(this, 'defs', '') __addNonEnumProp(this, 'defs', '')
addNonEnumProp(this, 'prefix', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>') __addNonEnumProp(this, 'prefix', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
// Enumerable properties // Enumerable properties
this.pattern = pattern // Needed to expose pattern to hooks this.pattern = pattern // Needed to expose pattern to hooks
@ -23,18 +34,107 @@ export function Svg(pattern) {
this.attributes.add('freesewing', version) this.attributes.add('freesewing', version)
} }
Svg.prototype.runHooks = function (hookName, data = false) { //////////////////////////////////////////////
if (data === false) data = this // PUBLIC METHODS //
let hooks = this.hooks[hookName] //////////////////////////////////////////////
if (hooks.length > 0) {
for (let hook of hooks) { /**
hook.method(data, hook.data) * 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) { if (this.hooks.insertText.length > 0) {
for (let hook of this.hooks.insertText) for (let hook of this.hooks.insertText)
text = hook.method(this.pattern.settings.locale, text, hook.data) text = hook.method(this.pattern.settings.locale, text, hook.data)
@ -43,198 +143,188 @@ Svg.prototype.insertText = function (text) {
return text return text
} }
/** Renders a draft object as SVG */ /**
Svg.prototype.render = function (pattern) { * Returns SVG markup for a linebreak + indentation
this.idPrefix = pattern.settings.idPrefix *
this.runHooks('preRender') * @private
pattern.runHooks('postLayout') * @return {string} svg - The Svg markup for a linebreak + indentation
if (!pattern.settings.embed) { */
this.attributes.add('width', round(pattern.width) + 'mm') Svg.prototype.__nl = function () {
this.attributes.add('height', round(pattern.height) + 'mm') return '\n' + this.__tab()
}
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
} }
/** Renders SVG head section */ /**
Svg.prototype.renderHead = function () { * Decreases indentation by 1
let svg = this.renderStyle() *
svg += this.renderScript() * @private
svg += this.renderDefs() * @return {Svg} this - The Svg instance
svg += this.openGroup(this.idPrefix + 'container') */
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 return svg
} }
/** Renders SVG closing section */ /**
Svg.prototype.renderTail = function () { * Returns SVG markup for a circle
let svg = '' *
svg += this.closeGroup() * @private
svg += this.nl() + '</svg>' * @param {Point} point - The Point instance that holds the circle data
* @return {string} svg - The SVG markup for the circle
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 the opening SVG tag */ /**
Svg.prototype.renderSvgTag = function () { * Returns SVG markup for the defs block
let svg = '<svg' *
this.indent() * @private
svg += this.nl() + this.attributes.render() * @return {string} svg - The SVG markup for the defs block
this.outdent() */
svg += this.nl() + '>' + this.nl() Svg.prototype.__renderDefs = function () {
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 () {
let svg = '<defs>' let svg = '<defs>'
this.indent() this.__indent()
svg += this.nl() + this.defs svg += this.__nl() + this.defs
this.outdent() this.__outdent()
svg += this.nl() + '</defs>' + this.nl() svg += this.__nl() + '</defs>' + this.__nl()
return svg return svg
} }
/** Returns SVG code for a Part object */ /**
Svg.prototype.renderPart = function (part) { * Returns SVG markup for the head section
let svg = '' *
for (let key in part.paths) { * @private
let path = part.paths[key] * @return {string} svg - The SVG markup for the head section
if (path.render) svg += this.renderPath(path) */
} Svg.prototype.__renderHead = function () {
for (let key in part.points) { let svg = this.__renderStyle()
if (part.points[key].attributes.get('data-text')) { svg += this.__renderScript()
svg += this.renderText(part.points[key]) svg += this.__renderDefs()
} svg += this.__openGroup(this.idPrefix + 'container')
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)
}
return svg return svg
} }
/** Returns SVG code for a Path object */ /**
Svg.prototype.renderPath = function (path) { * Returns SVG markup for a Path object
if (!path.attributes.get('id')) path.attributes.add('id', this.idPrefix + this.getId()) *
* @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()) 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') let text = path.attributes.get('data-text')
if (!text) return '' if (!text) return ''
else this.text = this.insertText(text) else this.text = this.__insertText(text)
let attributes = path.attributes.renderIfPrefixIs('data-text-') let attributes = path.attributes.renderIfPrefixIs('data-text-')
// Sadly aligning text along a patch can't be done in CSS only // Sadly aligning text along a patch can't be done in CSS only
let offset = '' let offset = ''
let align = path.attributes.get('data-text-class') let align = path.attributes.get('data-text-class')
if (align && align.indexOf('center') > -1) offset = ' startOffset="50%" ' if (align && align.indexOf('center') > -1) offset = ' startOffset="50%" '
else if (align && align.indexOf('right') > -1) offset = ' startOffset="100%" ' else if (align && align.indexOf('right') > -1) offset = ' startOffset="100%" '
let svg = this.nl() + '<text>' let svg = this.__nl() + '<text>'
this.indent() this.__indent()
svg += `<textPath xlink:href="#${path.attributes.get( svg += `<textPath xlink:href="#${path.attributes.get(
'id' 'id'
)}" ${offset}><tspan ${attributes}>${this.escapeText(this.text)}</tspan></textPath>` )}" ${offset}><tspan ${attributes}>${this.__escapeText(this.text)}</tspan></textPath>`
this.outdent() this.__outdent()
svg += this.nl() + '</text>' svg += this.__nl() + '</text>'
return svg return svg
} }
Svg.prototype.renderText = function (point) { /**
let text = point.attributes.getAsArray('data-text') * Returns SVG markup for a Part object
if (text !== false) { *
let joint = '' * @private
for (let string of text) { * @param {Part} part - The Part instance to render
this.text = this.insertText(string) * @return {string} svg - The SVG markup for the Part object
joint += this.text + ' ' */
} Svg.prototype.__renderPart = function (part) {
this.text = this.insertText(joint) let svg = ''
for (let key in part.paths) {
let path = part.paths[key]
if (path.render) svg += this.__renderPath(path)
} }
point.attributes.set('data-text-x', round(point.x)) for (let key in part.points) {
point.attributes.set('data-text-y', round(point.y)) if (part.points[key].attributes.get('data-text')) {
let lineHeight = svg += this.__renderText(part.points[key])
point.attributes.get('data-text-lineheight') || 6 * (this.pattern.settings.scale || 1) }
point.attributes.remove('data-text-lineheight') if (part.points[key].attributes.get('data-circle')) {
let svg = `${this.nl()}<text ${point.attributes.renderIfPrefixIs('data-text-')}>` svg += this.__renderCircle(part.points[key])
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() for (let key in part.snippets) {
svg += this.nl() + '</text>' let snippet = part.snippets[key]
svg += this.__renderSnippet(snippet, part)
}
return svg 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( * Returns SVG markup for a snippet
'data-circle' *
)}" ${point.attributes.renderIfPrefixIs('data-circle-')}></circle>` * @private
} * @param {Snippet} snippet - The Snippet instance to render
* @return {string} svg - The SVG markup for the snippet
/** Returns SVG code for a snippet */ */
Svg.prototype.renderSnippet = function (snippet) { Svg.prototype.__renderSnippet = function (snippet) {
let x = round(snippet.anchor.x) let x = round(snippet.anchor.x)
let y = round(snippet.anchor.y) let y = round(snippet.anchor.y)
let scale = snippet.attributes.get('data-scale') || 1 let scale = snippet.attributes.get('data-scale') || 1
@ -248,7 +338,7 @@ Svg.prototype.renderSnippet = function (snippet) {
if (rotate) { if (rotate) {
snippet.attributes.add('transform', `rotate(${rotate}, ${x}, ${y})`) snippet.attributes.add('transform', `rotate(${rotate}, ${x}, ${y})`)
} }
let svg = this.nl() let svg = this.__nl()
svg += `<use x="${x}" y="${y}" ` svg += `<use x="${x}" y="${y}" `
svg += `xlink:href="#${snippet.def}" ${snippet.attributes.render()}>` svg += `xlink:href="#${snippet.def}" ${snippet.attributes.render()}>`
svg += '</use>' svg += '</use>'
@ -256,34 +346,116 @@ Svg.prototype.renderSnippet = function (snippet) {
return svg return svg
} }
/** Returns SVG code to open a group */ /**
Svg.prototype.openGroup = function (id, attributes = false) { * Returns SVG markup for the style block
let svg = this.nl() + this.nl() *
svg += `<!-- Start of group #${id} -->` * @private
svg += this.nl() * @return {string} svg - The SVG markup for the style block
svg += `<g id="${id}"` */
if (attributes) svg += ` ${attributes.render()}` Svg.prototype.__renderStyle = function () {
svg += '>' let svg = '<style type="text/css"> <![CDATA[ '
this.indent() this.__indent()
this.openGroups.push(id) 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 return svg
} }
/** Returns SVG code to close a group */ /**
Svg.prototype.closeGroup = function () { * Returns SVG markup for the closing section
this.outdent() *
* @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 () { * Returns SVG markup for text
return '\n' + this.tab() *
* @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 = '' let space = ''
for (let i = 0; i < this.tabs; i++) { for (let i = 0; i < this.tabs; i++) {
space += ' ' space += ' '
@ -291,20 +463,3 @@ Svg.prototype.tab = function () {
return space 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 chai from 'chai'
import { Design, Pattern } from '../src/index.mjs' import { Design, Part } from '../src/index.mjs'
const expect = chai.expect const expect = chai.expect
@ -20,21 +20,25 @@ describe('Part', () => {
expect(typeof dp.context).to.equal('object') expect(typeof dp.context).to.equal('object')
}) })
it('Should return a function from macroClosure', () => { it('Should return a function from __macroClosure', () => {
const pattern = new Pattern() const part = new Part()
const part = pattern.__createPartWithContext() expect(typeof part.__macroClosure()).to.equal('function')
expect(typeof part.macroClosure()).to.equal('function')
}) })
it('Should not run an unknown macro', () => { it('Should not run an unknown macro', () => {
const pattern = new Pattern() const part = new Part()
const part = pattern.__createPartWithContext() const macro = part.__macroClosure()
const macro = part.macroClosure()
expect(macro('unknown')).to.equal(undefined) expect(macro('unknown')).to.equal(undefined)
}) })
it('Should register and run a macro', () => { 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 = { const plugin = {
name: 'test', name: 'test',
version: '0.1-test', version: '0.1-test',
@ -45,39 +49,34 @@ describe('Part', () => {
}, },
}, },
} }
pattern.use(plugin) const design = new Design({ parts: [part], plugins: [plugin] })
const part = pattern.__createPartWithContext() const pattern = new design()
const macro = part.macroClosure() pattern.draft()
macro('test', { x: 123, y: 456 }) expect(pattern.parts[0].test.points.macro.x).to.equal(123)
expect(part.points.macro.x).to.equal(123) expect(pattern.parts[0].test.points.macro.y).to.equal(456)
expect(part.points.macro.y).to.equal(456)
}) })
it('Should return a free ID', () => { it('Should return a free ID', () => {
const pattern = new Pattern() const part = new Part()
const part = pattern.__createPartWithContext()
const free = part.getId() const free = part.getId()
expect(part.getId()).to.equal('' + (parseInt(free) + 1)) expect(part.getId()).to.equal('' + (parseInt(free) + 1))
}) })
it('Should return a function from unitsClosure', () => { it('Should return a function from __unitsClosure', () => {
const pattern = new Pattern() const part = new Part()
const part = pattern.__createPartWithContext() expect(typeof part.__unitsClosure()).to.equal('function')
expect(typeof part.unitsClosure()).to.equal('function')
}) })
it('Should convert units', () => { it('Should convert units', () => {
const design = new Design() const part = new Part()
const pattern = new design() part.context = { settings: { units: 'metric' } }
const part = pattern.__createPartWithContext() const units = part.__unitsClosure()
const units = part.unitsClosure() expect(units(123.456)).to.equal('12.35cm')
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', () => { it('Should set part attributes', () => {
const pattern = new Pattern() const part = new Part()
const part = pattern.__createPartWithContext()
part.attr('foo', 'bar') part.attr('foo', 'bar')
expect(part.attributes.get('foo')).to.equal('bar') expect(part.attributes.get('foo')).to.equal('bar')
part.attr('foo', 'baz') part.attr('foo', 'baz')
@ -87,60 +86,72 @@ describe('Part', () => {
}) })
it('Should raise a warning when setting a non-Point value in points', () => { it('Should raise a warning when setting a non-Point value in points', () => {
const design = new Design() const part = {
name: 'test',
draft: ({ points, part }) => {
points.a = 'banana'
return part
},
}
const design = new Design({ parts: [part] })
const pattern = new design() const pattern = new design()
const part = pattern.__createPartWithContext() pattern.draft()
pattern.init() expect(pattern.stores[0].logs.warning.length).to.equal(4)
const { points } = part.shorthand() expect(pattern.stores[0].logs.warning[0]).to.equal(
points.a = 'banana'
expect(pattern.store.logs.warning.length).to.equal(4)
expect(pattern.store.logs.warning[0]).to.equal(
'`points.a` was set with a value that is not a `Point` object' '`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`' '`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`' '`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', () => { it('Should raise a warning when setting a non-Snippet value in snippets', () => {
const design = new Design() const part = {
name: 'test',
draft: ({ snippets, part }) => {
snippets.a = 'banana'
return part
},
}
const design = new Design({ parts: [part] })
const pattern = new design() const pattern = new design()
const part = pattern.__createPartWithContext() pattern.draft()
pattern.init() expect(pattern.stores[0].logs.warning.length).to.equal(4)
const { snippets } = part.shorthand() expect(pattern.stores[0].logs.warning[0]).to.equal(
snippets.a = 'banana'
expect(pattern.store.logs.warning.length).to.equal(4)
expect(pattern.store.logs.warning[0]).to.equal(
'`snippets.a` was set with a value that is not a `Snippet` object' '`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`' '`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`' '`snippets.a` was set with an `anchor` parameter that is not a `Point`'
) )
}) })
it('Should calculate the part boundary', () => { 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 pattern = new design()
const part = pattern.__createPartWithContext() pattern.draft()
pattern.init() const boundary = pattern.parts[0].test.__boundary()
const short = part.shorthand() const { topLeft, bottomRight, width, height } = boundary
part.points.from = new short.Point(123, 456) expect(topLeft.x).to.equal(19)
part.points.to = new short.Point(19, 76) expect(topLeft.y).to.equal(76)
part.paths.test = new short.Path().move(part.points.from).line(part.points.to) expect(bottomRight.x).to.equal(123)
let boundary = part.boundary() expect(bottomRight.y).to.equal(456)
expect(boundary.topLeft.x).to.equal(19) expect(width).to.equal(104)
expect(boundary.topLeft.y).to.equal(76) expect(height).to.equal(380)
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)
}) })
/* /*
@ -176,7 +187,6 @@ describe('Part', () => {
part.home() part.home()
expect(part.attributes.get('transform')).to.equal(false) expect(part.attributes.get('transform')).to.equal(false)
}) })
*/
it('Should run hooks', () => { it('Should run hooks', () => {
let count = 0 let count = 0
const design = new Design() const design = new Design()
@ -192,20 +202,25 @@ describe('Part', () => {
part.runHooks('preDraft') part.runHooks('preDraft')
expect(count).to.equal(1) expect(count).to.equal(1)
}) })
*/
it('Should get the units closure to raise a debug when passing a non-number', () => { it('Units closure should log a warning when passing a non-number', () => {
const design = new Design() const part = {
const pattern = new design({ margin: 5 }) name: 'test',
const part = pattern.__createPartWithContext() draft: ({ units, part }) => {
pattern.init() units('a')
const short = part.shorthand() return part
short.units('a') },
expect(pattern.store.logs.warning.length).to.equal(1) }
expect(pattern.store.logs.warning[0]).to.equal( 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`)' 'Calling `units(value)` but `value` is not a number (`string`)'
) )
}) })
/*
describe('isEmpty', () => { describe('isEmpty', () => {
it('Should return true if the part has no paths or snippets', () => { it('Should return true if the part has no paths or snippets', () => {
const design = new Design() const design = new Design()
@ -272,4 +287,5 @@ describe('Part', () => {
expect(part.isEmpty()).to.be.false 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'], only: ['test.partB'],
}) })
pattern.init() pattern.init()
expect(pattern.needs('test.partA')).to.equal(true) expect(pattern.__needs('test.partA')).to.equal(true)
expect(pattern.needs('test.partB')).to.equal(true) expect(pattern.__needs('test.partB')).to.equal(true)
expect(pattern.needs('test.partC')).to.equal(false) expect(pattern.__needs('test.partC')).to.equal(false)
}) })
it('Should check whether a part is wanted', () => { it('Should check whether a part is wanted', () => {
@ -127,9 +127,9 @@ describe('Pattern', () => {
only: ['test.partB'], only: ['test.partB'],
}) })
pattern.init() pattern.init()
expect(pattern.wants('test.partA')).to.equal(false) expect(pattern.__wants('test.partA')).to.equal(false)
expect(pattern.wants('test.partB')).to.equal(true) expect(pattern.__wants('test.partB')).to.equal(true)
expect(pattern.wants('test.partC')).to.equal(false) expect(pattern.__wants('test.partC')).to.equal(false)
}) })
/* /*

View file

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

View file

@ -227,40 +227,40 @@ describe('Point', () => {
expect(p2.y).to.equal(70) expect(p2.y).to.equal(70)
}) })
it('Should add raise methods to a point', () => { it('Should add log methods to a point', () => {
const raise = () => 'hello' const log = () => 'hello'
const p1 = new Point(10, 20).withRaise(raise) const p1 = new Point(10, 20).__withLog(log)
expect(p1.raise()).to.equal('hello') 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 invalid = { x: false, y: false }
const raiseX = { warning: () => (invalid.x = true) } const logX = { warning: () => (invalid.x = true) }
const raiseY = { warning: () => (invalid.y = true) } const logY = { warning: () => (invalid.y = true) }
const p1 = new Point('a', 10).withRaise(raiseX) const p1 = new Point('a', 10).__withLog(logX)
const p2 = new Point(20, 'b').withRaise(raiseY) const p2 = new Point(20, 'b').__withLog(logY)
expect(invalid.x).to.equal(false) expect(invalid.x).to.equal(false)
expect(invalid.y).to.equal(false) expect(invalid.y).to.equal(false)
p1.check() p1.__check()
p2.check() p2.__check()
expect(invalid.x).to.equal(true) expect(invalid.x).to.equal(true)
expect(invalid.y).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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).withRaise(raise) const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
p1.rotate('a', p2) p1.rotate('a', p2)
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.rotate(45, 'a') p1.rotate(45, 'a')
@ -270,10 +270,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.flipX('a') p1.flipX('a')
@ -283,10 +283,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.flipY('a') p1.flipY('a')
@ -296,10 +296,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shift(0, 'a') p1.shift(0, 'a')
@ -309,10 +309,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shift('a', 12) p1.shift('a', 12)
@ -322,11 +322,11 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).withRaise(raise) const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shiftTowards(p2, 'a') p1.shiftTowards(p2, 'a')
@ -336,10 +336,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shiftTowards('a', 10) p1.shiftTowards('a', 10)
@ -349,11 +349,11 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).withRaise(raise) const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shiftFractionTowards(p2, 'a') p1.shiftFractionTowards(p2, 'a')
@ -363,10 +363,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shiftFractionTowards('a', 0.1) p1.shiftFractionTowards('a', 0.1)
@ -376,11 +376,11 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
const p2 = new Point(20, 20).withRaise(raise) const p2 = new Point(20, 20).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shiftOutwards(p2, 'a') p1.shiftOutwards(p2, 'a')
@ -390,10 +390,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.shiftOutwards('a', 0.1) p1.shiftOutwards('a', 0.1)
@ -403,10 +403,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.translate('a', 10) p1.translate('a', 10)
@ -416,10 +416,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.translate(10, 'a') p1.translate(10, 'a')
@ -429,10 +429,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.sitsOn('a') p1.sitsOn('a')
@ -442,10 +442,10 @@ describe('Point', () => {
expect(invalid).to.equal(true) 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 let invalid = false
const raise = { warning: () => (invalid = true) } const log = { warning: () => (invalid = true) }
const p1 = new Point(10, 10).withRaise(raise) const p1 = new Point(10, 10).__withLog(log)
expect(invalid).to.equal(false) expect(invalid).to.equal(false)
try { try {
p1.sitsRoughlyOn('a') p1.sitsRoughlyOn('a')

View file

@ -10,6 +10,7 @@ describe('Snapped options', () => {
it('Should snap a percentage options to equal steps', () => { it('Should snap a percentage options to equal steps', () => {
const part = { const part = {
name: 'test', name: 'test',
measurements: ['head'],
options: { options: {
test: { pct: 30, min: 0, max: 100, snap: 12, toAbs }, test: { pct: 30, min: 0, max: 100, snap: 12, toAbs },
}, },
@ -17,13 +18,13 @@ describe('Snapped options', () => {
const design = new Design({ parts: [part] }) const design = new Design({ parts: [part] })
const patternA = new design({ options: { test: 0.13 }, measurements }).draft() const patternA = new design({ options: { test: 0.13 }, measurements }).draft()
const patternB = new design({ options: { test: 0.27 }, measurements }).draft() const patternB = new design({ options: { test: 0.27 }, measurements }).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(60) expect(patternA.settings[0].absoluteOptions.test).to.equal(60)
expect(patternB.settings.absoluteOptions.test).to.equal(108) expect(patternB.settings[0].absoluteOptions.test).to.equal(108)
}) })
it('Should snap a percentage options to the Fibonacci sequence', () => { it('Should snap a percentage options to the Fibonacci sequence', () => {
const part = { const part = {
name: 'test', name: 'test',
measurements: ['head'],
options: { options: {
test: { test: {
pct: 30, pct: 30,
@ -38,9 +39,9 @@ describe('Snapped options', () => {
const patternA = new design({ options: { test: 0.13 }, measurements }).draft() const patternA = new design({ options: { test: 0.13 }, measurements }).draft()
const patternB = new design({ options: { test: 0.27 }, measurements }).draft() const patternB = new design({ options: { test: 0.27 }, measurements }).draft()
const patternC = new design({ options: { test: 0.97 }, measurements }).draft() const patternC = new design({ options: { test: 0.97 }, measurements }).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(55) expect(patternA.settings[0].absoluteOptions.test).to.equal(55)
expect(patternB.settings.absoluteOptions.test).to.equal(89) expect(patternB.settings[0].absoluteOptions.test).to.equal(89)
expect(patternC.settings.absoluteOptions.test).to.equal(388) expect(patternC.settings[0].absoluteOptions.test).to.equal(388)
}) })
it('Should snap a percentage options to imperial snaps', () => { 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 patternB = new design({ options: { test: 0.27 }, measurements, units: 'metric' }).draft()
const patternC = new design({ options: { test: 0.97 }, 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() const patternD = new design({ options: { test: 0.01 }, measurements, units: 'metric' }).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(50) expect(patternA.settings[0].absoluteOptions.test).to.equal(50)
expect(patternB.settings.absoluteOptions.test).to.equal(100) expect(patternB.settings[0].absoluteOptions.test).to.equal(100)
expect(patternC.settings.absoluteOptions.test).to.equal(388) expect(patternC.settings[0].absoluteOptions.test).to.equal(388)
expect(patternD.settings.absoluteOptions.test).to.equal(4) expect(patternD.settings[0].absoluteOptions.test).to.equal(4)
}) })
it('Should snap a percentage options to metrics snaps', () => { it('Should snap a percentage options to metrics snaps', () => {
@ -107,9 +108,9 @@ describe('Snapped options', () => {
measurements, measurements,
units: 'imperial', units: 'imperial',
}).draft() }).draft()
expect(patternA.settings.absoluteOptions.test).to.equal(50.8) expect(patternA.settings[0].absoluteOptions.test).to.equal(50.8)
expect(patternB.settings.absoluteOptions.test).to.equal(101.6) expect(patternB.settings[0].absoluteOptions.test).to.equal(101.6)
expect(patternC.settings.absoluteOptions.test).to.equal(388) expect(patternC.settings[0].absoluteOptions.test).to.equal(388)
expect(patternD.settings.absoluteOptions.test).to.equal(4) 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 Test = new Design({ plugins: [plugin], parts: [part] })
const pattern = new Test() const pattern = new Test()
pattern.draft() pattern.draft()
expect(pattern.store.get('test.message.warning')).to.equal('hello warning') expect(pattern.stores[0].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.info')).to.equal('hello info')
}) })
it('Should make top-level plugin methods available via shorthand', () => { 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 Test = new Design({ plugins: [plugin], parts: [part] })
const pattern = new Test() const pattern = new Test()
pattern.draft() pattern.draft()
expect(pattern.store.get('test.example_part.a')).to.equal('hello A') expect(pattern.stores[0].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.b')).to.equal('hello B')
}) })
}) })

View file

@ -1,7 +1,6 @@
import chai from 'chai' import chai from 'chai'
import { import {
Point, Point,
isCoord,
capitalize, capitalize,
beamsIntersect, beamsIntersect,
linesIntersect, linesIntersect,
@ -21,20 +20,14 @@ import {
lineIntersectsCircle, lineIntersectsCircle,
stretchToScale, stretchToScale,
round, round,
sampleStyle,
deg2rad, deg2rad,
rad2deg, rad2deg,
pctBasedOn, pctBasedOn,
macroName,
} from '../src/index.mjs' } from '../src/index.mjs'
const { expect } = chai const { expect } = chai
describe('Utils', () => { 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', () => { it('Should find the intersection of two endless line segments', () => {
let a = new Point(10, 20) let a = new Point(10, 20)
let b = new Point(20, 24) let b = new Point(20, 24)
@ -463,34 +456,6 @@ describe('Utils', () => {
expect(round(i.y)).to.equal(400) 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', () => { it('Should convert degrees to radians', () => {
expect(deg2rad(0)).to.equal(0) expect(deg2rad(0)).to.equal(0)
expect(round(deg2rad(69))).to.equal(1.2) 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,55 +1,67 @@
import React from 'react'; import React from 'react'
import ResetButtons from './reset-buttons' import ResetButtons from './reset-buttons'
import {EventGroup} from 'shared/components/workbench/events' import { LogGroup } from 'shared/components/workbench/logs'
import DefaultErrorView from './view'; import DefaultErrorView from './view'
const ErrorView = (props) => { const ErrorView = (props) => {
if (props.children) return props.children if (props.children) return props.children
const inspectChildrenProps = { const inspectChildrenProps = {
type: 'error', type: 'error',
events: [props.error], logs: [props.error],
units: props.gist?.units units: props.gist?.units,
} }
const inspectChildren = (<EventGroup {...inspectChildrenProps}></EventGroup>) const inspectChildren = <LogGroup {...inspectChildrenProps}></LogGroup>
return (props.children || (<DefaultErrorView inspectChildren={inspectChildren}> return (
props.children || (
<DefaultErrorView inspectChildren={inspectChildren}>
<h4>If you think your last action caused this error, you can: </h4> <h4>If you think your last action caused this error, you can: </h4>
<ResetButtons undoGist={props.undoGist} resetGist={props.resetGist} /> <ResetButtons undoGist={props.undoGist} resetGist={props.resetGist} />
</DefaultErrorView>)) </DefaultErrorView>
)
)
} }
class ErrorBoundary extends React.Component { class ErrorBoundary extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props)
this.state = { hasError: false }; this.state = { hasError: false }
} }
static getDerivedStateFromError(error) { static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI. // Update state so the next render will show the fallback UI.
return { hasError: true, error }; return { hasError: true, error }
} }
componentDidCatch(error, errorInfo) { componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service // You can also log the error to an error reporting service
console.log(error, errorInfo); console.log(error, errorInfo)
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.gist !== prevProps.gist) { if (this.props.gist !== prevProps.gist) {
this.setState({hasError: false}) this.setState({ hasError: false })
} }
} }
render() { render() {
if (this.state.hasError) { if (this.state.hasError) {
// You can render any custom fallback UI // 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 { try {
return this.props.children; return this.props.children
} catch(e) { } 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 Error = ({ logs=[], updateGist }) => {
const inspectChildren = (<ul className="list-disc list-inside ml-4 text-xl"> 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> <li>
Check the <button className="btn-link" onClick={() => updateGist(['_state', 'view'], 'events')}> Check the{' '}
<strong>{patternProps?.events?.error?.length} errors</strong> and <strong> <button className="btn-link" onClick={() => updateGist(['_state', 'view'], 'logs')}>
{patternProps?.events?.warning?.length} warnings</strong></button> <strong>{errors.length} errors</strong> and <strong>{warnings.length} warnings</strong>
</button>
</li> </li>
<li>Check the partially rendered pattern below to see which areas are problematic</li> <li>Check the partially rendered pattern below to see which areas are problematic</li>
</ul>) </ul>
)
return (<DefaultErrorView inspectChildren={inspectChildren}> return (
<p>Don't be alarmed, but we ran into some trouble while drafting this pattern.</p> <DefaultErrorView inspectChildren={inspectChildren}>
</DefaultErrorView>) <p>No need to be alarmed, but we ran into some trouble while drafting this pattern.</p>
</DefaultErrorView>
)
} }
export default Error export default Error

View file

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

View file

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

View file

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

View file

@ -26,7 +26,9 @@ import Stack from './stack'
*/ */
const SvgWrapper = props => { 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 }) => ( return <SizeMe>{({ size }) => (
<TransformWrapper <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

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

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

View file

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

View file

@ -1,4 +1,4 @@
// Can't seem to make this work as ESM // Can't seem to make this work as ESM
module.exports = { 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", "lodash.unset": "^4.5.2",
"mdast-util-toc": "^6.1.0", "mdast-util-toc": "^6.1.0",
"pdfkit": "^0.13.0", "pdfkit": "^0.13.0",
"postcss-for": "^2.1.1",
"react-markdown": "^8.0.0", "react-markdown": "^8.0.0",
"react-sizeme": "^3.0.2", "react-sizeme": "^3.0.2",
"react-timeago": "^7.1.0", "react-timeago": "^7.1.0",
@ -44,6 +45,8 @@
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
"eslint": "^8.23.1",
"eslint-plugin-react": "^7.31.8",
"lodash.set": "^4.3.2", "lodash.set": "^4.3.2",
"postcss": "^8.4.4", "postcss": "^8.4.4",
"tailwindcss": "^3.0.1", "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 /* Styling the shadow DOM is hard to do
* This is for styling FreeSewing snippets * This is for styling FreeSewing snippets
* TODO: Update snippets to use inline styles with CSS vars * 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-stroke-6xl': "16px",
// Pattern 7xl stroke width // Pattern 7xl stroke width
'--pattern-stroke-7xl': "20px", '--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" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg== 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": "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "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" 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: dependencies:
"@types/node" "*" "@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": "@types/mdast@^3.0.0":
version "3.0.10" version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
@ -3488,7 +3506,7 @@
dependencies: dependencies:
"@types/unist" "*" "@types/unist" "*"
"@types/mdurl@^1.0.0": "@types/mdurl@*", "@types/mdurl@^1.0.0":
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
@ -4915,7 +4933,7 @@ bl@^5.0.0:
inherits "^2.0.4" inherits "^2.0.4"
readable-stream "^3.4.0" readable-stream "^3.4.0"
bluebird@^3.5.5: bluebird@^3.5.5, bluebird@^3.7.2:
version "3.7.2" version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== 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" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== 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: cbor@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5" 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" resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.1.tgz#c34062a94c865c322f9d67b4384e4169bcede6a4"
integrity sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg== 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: env-paths@^2.2.0:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" 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" semver "^6.3.0"
string.prototype.matchall "^4.0.7" 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: eslint-plugin-yaml@^0.5.0:
version "0.5.0" version "0.5.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-yaml/-/eslint-plugin-yaml-0.5.0.tgz#8c79d9d6389b67cbcf58ef6f970c4c086665a63a" 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" to-readable-stream "^1.0.0"
url-parse-lax "^3.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" version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== 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" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== 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: has-flag@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 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" leven "^3.1.0"
pretty-format "^27.5.1" 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: js-sdsl@^4.1.4:
version "4.1.4" version "4.1.4"
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.4.tgz#78793c90f80e8430b7d8dc94515b6c77d98a26a6" 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: dependencies:
argparse "^2.0.1" 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: jsesc@^2.5.1:
version "2.5.2" version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 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" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== 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: kleur@^3.0.3:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" 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" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b"
integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w== 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: lint-staged@^13.0.3:
version "13.0.3" version "13.0.3"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.3.tgz#d7cdf03a3830b327a2b63c6aec953d71d9dc48c6" 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" resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3"
integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q== 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: markdown-table@^3.0.0:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c"
integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA== 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: matcher@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/matcher/-/matcher-5.0.0.tgz#cd82f1c7ae7ee472a9eeaf8ec7cac45e0fe0da62" 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-is "^5.0.0"
unist-util-visit "^3.0.0" unist-util-visit "^3.0.0"
mdurl@^1.0.0: mdurl@^1.0.0, mdurl@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== 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" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== 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: postcss-import@^14.1.0:
version "14.1.0" version "14.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" 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" cssesc "^3.0.0"
util-deprecate "^1.0.2" 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: postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" 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" picocolors "^1.0.0"
source-map-js "^1.0.2" 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: postcss@^8.4.12, postcss@^8.4.14, postcss@^8.4.4, postcss@^8.4.5:
version "8.4.16" version "8.4.16"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" 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" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== 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: resolve-cwd@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" 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" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== 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: supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 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" mkdirp "^0.5.1"
untildify "^3.0.3" 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: tailwindcss-open-variant@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/tailwindcss-open-variant/-/tailwindcss-open-variant-1.0.0.tgz#e4555c0a0ec2a82801e563ed36b1b23e4dd04a3b" 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" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== 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: uglify-js@^3.1.4:
version "3.16.2" version "3.16.2"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.2.tgz#0481e1dbeed343ad1c2ddf3c6d42e89b7a6d4def" 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" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
underscore@^1.5.0: underscore@^1.5.0, underscore@~1.13.2:
version "1.13.4" version "1.13.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee"
integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==
@ -19950,6 +20122,11 @@ xml-js@^1.6.11:
dependencies: dependencies:
sax "^1.2.4" 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: xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"