1
0
Fork 0

chore(core): Refactor v3 code

This commit is contained in:
Joost De Cock 2022-09-18 15:11:10 +02:00
parent f882a26408
commit 200cebf582
27 changed files with 3961 additions and 2633 deletions

1
.gitignore vendored
View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

@ -1,4 +1,11 @@
export const loadDesignDefaults = () => ({
/**
* Return an object holding the defaults for a design configuration
*
* @function
* @private
* @return {object} defaults - The default design configuration
*/
export const __loadDesignDefaults = () => ({
measurements: [],
optionalMeasurements: [],
options: {},
@ -8,7 +15,14 @@ export const loadDesignDefaults = () => ({
plugins: [],
})
export const loadPatternDefaults = () => ({
/**
* Return an object holding the defaults for pattern settings
*
* @function
* @private
* @return {object} defaults - The default pattern settings
*/
export const __loadPatternDefaults = () => ({
complete: true,
idPrefix: 'fs-',
stackPrefix: '',

View file

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

View file

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

View file

@ -1,3 +1,4 @@
import { Bezier } from 'bezier-js'
import { Attributes } from './attributes.mjs'
import { Design } from './design.mjs'
import { Pattern } from './pattern.mjs'
@ -7,34 +8,30 @@ import { Path } from './path.mjs'
import { Snippet } from './snippet.mjs'
import { Store } from './store.mjs'
import {
isCoord,
capitalize,
beamsIntersect,
linesIntersect,
pointOnBeam,
pointOnLine,
pointOnCurve,
splitCurve,
beamIntersectsCircle,
beamIntersectsX,
beamIntersectsY,
units,
lineIntersectsCurve,
beamsIntersect,
capitalize,
circlesIntersect,
curveEdge,
curveIntersectsX,
curveIntersectsY,
curvesIntersect,
circlesIntersect,
beamIntersectsCircle,
lineIntersectsCircle,
curveEdge,
stretchToScale,
round,
sampleStyle,
deg2rad,
rad2deg,
pctBasedOn,
Bezier,
generateStackTransform,
macroName,
lineIntersectsCircle,
lineIntersectsCurve,
linesIntersect,
pctBasedOn,
pointOnBeam,
pointOnCurve,
pointOnLine,
rad2deg,
round,
splitCurve,
stretchToScale,
units,
} from './utils.mjs'
import { version } from '../data.mjs'
@ -48,33 +45,31 @@ export {
Part,
Snippet,
Store,
version,
Bezier,
capitalize,
beamsIntersect,
linesIntersect,
pointOnBeam,
pointOnLine,
pointOnCurve,
splitCurve,
// Utils
beamIntersectsCircle,
beamIntersectsX,
beamIntersectsY,
units,
lineIntersectsCurve,
beamsIntersect,
capitalize,
circlesIntersect,
curveEdge,
curveIntersectsX,
curveIntersectsY,
curvesIntersect,
circlesIntersect,
beamIntersectsCircle,
lineIntersectsCircle,
curveEdge,
stretchToScale,
round,
sampleStyle,
deg2rad,
rad2deg,
pctBasedOn,
generateStackTransform,
macroName,
isCoord,
version,
lineIntersectsCircle,
lineIntersectsCurve,
linesIntersect,
pctBasedOn,
pointOnBeam,
pointOnCurve,
pointOnLine,
rad2deg,
round,
splitCurve,
stretchToScale,
units,
}

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -73,8 +73,5 @@ describe('Multisets', () => {
},
])
pattern.draft()
console.log(pattern)
console.log(pattern.render())
//pattern.render()
})
})

View file

@ -20,14 +20,14 @@ describe('Part', () => {
expect(typeof dp.context).to.equal('object')
})
it('Should return a function from macroClosure', () => {
it('Should return a function from __macroClosure', () => {
const part = new Part()
expect(typeof part.macroClosure()).to.equal('function')
expect(typeof part.__macroClosure()).to.equal('function')
})
it('Should not run an unknown macro', () => {
const part = new Part()
const macro = part.macroClosure()
const macro = part.__macroClosure()
expect(macro('unknown')).to.equal(undefined)
})
@ -62,15 +62,15 @@ describe('Part', () => {
expect(part.getId()).to.equal('' + (parseInt(free) + 1))
})
it('Should return a function from unitsClosure', () => {
it('Should return a function from __unitsClosure', () => {
const part = new Part()
expect(typeof part.unitsClosure()).to.equal('function')
expect(typeof part.__unitsClosure()).to.equal('function')
})
it('Should convert units', () => {
const part = new Part()
part.context = { settings: { units: 'metric' } }
const units = part.unitsClosure()
const units = part.__unitsClosure()
expect(units(123.456)).to.equal('12.35cm')
expect(units(123.456)).to.equal('12.35cm')
})
@ -144,7 +144,7 @@ describe('Part', () => {
const design = new Design({ parts: [part] })
const pattern = new design()
pattern.draft()
const boundary = pattern.parts[0].test.boundary()
const boundary = pattern.parts[0].test.__boundary()
const { topLeft, bottomRight, width, height } = boundary
expect(topLeft.x).to.equal(19)
expect(topLeft.y).to.equal(76)

View file

@ -51,7 +51,7 @@ describe('Path', () => {
const pattern = new design()
pattern.draft().render()
expect(round(pattern.parts[0].test.paths.offset.bottomRight.x)).to.equal(72.63)
expect(round(pattern.parts[0].test.paths.offset.bottomRight.y)).to.equal(26.48)
expect(round(pattern.parts[0].test.paths.offset.bottomRight.y)).to.equal(26.47)
})
it('Should offset a curve where cp2 = end', () => {
@ -70,26 +70,6 @@ describe('Path', () => {
expect(round(pattern.parts[0].test.paths.offset.bottomRight.y)).to.equal(43.27)
})
/*
it('Should throw error when offsetting line that is no line', () => {
const part = {
name: 'test',
draft: ({ paths, Path, Point }) => {
paths.line = new Path()
.move(new Point(0, 40))
.line(new Point(0, 40))
paths.offset = paths.line.offset(10)
return part
}
}
const design = new Design({ parts: [ part ] })
const pattern = new design()
pattern.draft().render()
console.log(pattern.store.logs)
expect(() => pattern.draft().render()).to.throw()
})
*/
it('Should return the length of a line', () => {
const part = {
name: 'test',
@ -121,6 +101,22 @@ describe('Path', () => {
expect(round(pattern.parts[0].test.paths.curve.length())).to.equal(145.11)
})
it('Should return the rough length of a curve', () => {
const part = {
name: 'test',
draft: ({ paths, Path, Point, part }) => {
paths.curve = new Path()
.move(new Point(0, 0))
.curve(new Point(0, 50), new Point(100, 50), new Point(100, 0))
return part
},
}
const design = new Design({ parts: [part] })
const pattern = new design()
pattern.draft().render()
expect(round(pattern.parts[0].test.paths.curve.roughLength())).to.equal(200)
})
it('Should return the path start point', () => {
const part = {
name: 'test',
@ -161,7 +157,7 @@ describe('Path', () => {
const curve = new Path()
.move(new Point(123, 456))
.curve(new Point(0, 40), new Point(123, 34), new Point(230, 4))
curve.boundary()
curve.__boundary()
expect(curve.topLeft.x).to.equal(71.6413460920667)
expect(curve.topLeft.y).to.equal(4)
expect(curve.bottomRight.x).to.equal(230)
@ -173,7 +169,7 @@ describe('Path', () => {
.move(new Point(123, 456))
.curve(new Point(0, 40), new Point(123, 34), new Point(230, 4))
let b = curve.clone()
b.boundary()
b.__boundary()
expect(b.topLeft.x).to.equal(71.6413460920667)
expect(b.topLeft.y).to.equal(4)
b = b.clone()
@ -236,7 +232,7 @@ describe('Path', () => {
.curve(new Point(123, 123), new Point(-123, 456), new Point(456, -123))
const a = test.shiftAlong(100)
const b = test.reverse().shiftAlong(test.length() - 100)
expect(a.dist(b)).to.below(0.05)
expect(a.dist(b)).to.below(0.2)
})
it('Should shift fraction with sufficient precision', () => {
@ -245,7 +241,7 @@ describe('Path', () => {
.curve(new Point(123, 123), new Point(-123, 456), new Point(456, -123))
const a = test.shiftFractionAlong(0.5)
const b = test.reverse().shiftFractionAlong(0.5)
expect(a.dist(b)).to.below(0.05)
expect(a.dist(b)).to.below(0.2)
})
it('Should shift a fraction along a line', () => {
@ -541,10 +537,10 @@ describe('Path', () => {
expect(curve.type).to.equal('curve')
expect(round(curve.cp1.x)).to.equal(35.08)
expect(round(curve.cp1.y)).to.equal(21.64)
expect(round(curve.cp2.x)).to.equal(46.19)
expect(round(curve.cp2.y)).to.equal(-14.69)
expect(round(curve.to.x)).to.equal(72.53)
expect(round(curve.to.y)).to.equal(8.71)
expect(round(curve.cp2.x)).to.equal(46.18)
expect(round(curve.cp2.y)).to.equal(-14.67)
expect(round(curve.to.x)).to.equal(72.51)
expect(round(curve.to.y)).to.equal(8.69)
})
it('Should split a path on a line', () => {
@ -683,13 +679,13 @@ describe('Path', () => {
it('Should add log methods to a path', () => {
const log = () => 'hello'
const p1 = new Path(10, 20).withLog(log)
const p1 = new Path(10, 20).__withLog(log)
expect(p1.log()).to.equal('hello')
})
it('Should add log methods to a path', () => {
const log = () => 'hello'
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
expect(p1.log()).to.equal('hello')
})
@ -707,7 +703,7 @@ describe('Path', () => {
it('Should log a warning when moving to a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
expect(invalid).to.equal(false)
try {
p1.move('a')
@ -720,7 +716,7 @@ describe('Path', () => {
it('Should log a warning when drawing a line to a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
expect(invalid).to.equal(false)
try {
p1.line('a')
@ -733,7 +729,7 @@ describe('Path', () => {
it('Should log a warning when drawing a curve to a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const a = new Point(0, 0)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
@ -748,7 +744,7 @@ describe('Path', () => {
it('Should log a warning when drawing a curve with a Cp1 that is a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const a = new Point(0, 0)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
@ -763,7 +759,7 @@ describe('Path', () => {
it('Should log a warning when drawing a curve with a Cp1 that is a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
try {
@ -777,7 +773,7 @@ describe('Path', () => {
it('Should log a warning when drawing a curve with a Cp2 that is a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
try {
@ -791,7 +787,7 @@ describe('Path', () => {
it('Should log a warning when drawing a _curve with a To that is a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
try {
@ -805,7 +801,7 @@ describe('Path', () => {
it('Should log a warning when drawing a _curve with a Cp2 that is a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
try {
@ -819,7 +815,7 @@ describe('Path', () => {
it('Should log a warning when drawing a curve_ with a To that is a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
try {
@ -833,7 +829,7 @@ describe('Path', () => {
it('Should log a warning when drawing a curve_ with a Cp2 that is a non-point', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
const p1 = new Path().withLog(log)
const p1 = new Path().__withLog(log)
const b = new Point(10, 10)
expect(invalid).to.equal(false)
try {
@ -867,7 +863,7 @@ describe('Path', () => {
const b = new Point(10, 10)
const p1 = new Path().move(a).line(b)
expect(invalid).to.equal(false)
new Path().withLog(log).noop('test').insop(false, p1)
new Path().__withLog(log).noop('test').insop(false, p1)
expect(invalid).to.equal(true)
})
@ -879,7 +875,7 @@ describe('Path', () => {
new Path().move(a).line(b)
expect(invalid).to.equal(false)
try {
new Path().withLog(log).noop('test').insop('test')
new Path().__withLog(log).noop('test').insop('test')
} catch (err) {
expect('' + err).to.contain("Cannot read properties of undefined (reading 'ops')")
}
@ -890,7 +886,7 @@ describe('Path', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
expect(invalid).to.equal(false)
new Path().withLog(log).attr()
new Path().__withLog(log).attr()
expect(invalid).to.equal(true)
})
@ -898,7 +894,7 @@ describe('Path', () => {
let invalid = false
const log = { warning: () => (invalid = true) }
expect(invalid).to.equal(false)
new Path().withLog(log).attr('test')
new Path().__withLog(log).attr('test')
expect(invalid).to.equal(true)
})
@ -944,7 +940,7 @@ describe('Path', () => {
const log = { error: () => (invalid = true) }
expect(invalid).to.equal(false)
try {
new Path().withLog(log).start()
new Path().__withLog(log).start()
} catch (err) {
expect('' + err).to.contain("Cannot read properties of undefined (reading 'to')")
}
@ -956,7 +952,7 @@ describe('Path', () => {
const log = { error: () => (invalid = true) }
expect(invalid).to.equal(false)
try {
new Path().withLog(log).end()
new Path().__withLog(log).end()
} catch (err) {
expect('' + err).to.contain("Cannot read properties of undefined (reading 'type')")
}
@ -967,7 +963,7 @@ describe('Path', () => {
let invalid = false
const log = { error: () => (invalid = true) }
expect(invalid).to.equal(false)
new Path().withLog(log).move(new Point(0, 0)).line(new Point(10, 10)).shiftAlong()
new Path().__withLog(log).move(new Point(0, 0)).line(new Point(10, 10)).shiftAlong()
expect(invalid).to.equal(true)
})

View file

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

View file

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

View file

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

125
yarn.lock
View file

@ -378,6 +378,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
"@babel/parser@^7.9.4":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c"
integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@ -3474,6 +3479,19 @@
dependencies:
"@types/node" "*"
"@types/linkify-it@*":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
"@types/markdown-it@^12.2.3":
version "12.2.3"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51"
integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==
dependencies:
"@types/linkify-it" "*"
"@types/mdurl" "*"
"@types/mdast@^3.0.0":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
@ -3481,7 +3499,7 @@
dependencies:
"@types/unist" "*"
"@types/mdurl@^1.0.0":
"@types/mdurl@*", "@types/mdurl@^1.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
@ -4908,7 +4926,7 @@ bl@^5.0.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
bluebird@^3.5.5:
bluebird@^3.5.5, bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@ -5443,6 +5461,13 @@ capture-stack-trace@^1.0.0:
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
catharsis@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121"
integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==
dependencies:
lodash "^4.17.15"
cbor@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5"
@ -7495,6 +7520,11 @@ entities@^4.2.0, entities@^4.3.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.1.tgz#c34062a94c865c322f9d67b4384e4169bcede6a4"
integrity sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==
entities@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@ -9519,7 +9549,7 @@ got@^9.6.0:
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@ -11191,6 +11221,34 @@ js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
js2xmlparser@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a"
integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==
dependencies:
xmlcreate "^2.0.4"
jsdoc@^3.6.11:
version "3.6.11"
resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce"
integrity sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==
dependencies:
"@babel/parser" "^7.9.4"
"@types/markdown-it" "^12.2.3"
bluebird "^3.7.2"
catharsis "^0.9.0"
escape-string-regexp "^2.0.0"
js2xmlparser "^4.0.2"
klaw "^3.0.0"
markdown-it "^12.3.2"
markdown-it-anchor "^8.4.1"
marked "^4.0.10"
mkdirp "^1.0.4"
requizzle "^0.2.3"
strip-json-comments "^3.1.0"
taffydb "2.6.2"
underscore "~1.13.2"
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@ -11450,6 +11508,13 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
klaw@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146"
integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==
dependencies:
graceful-fs "^4.1.9"
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@ -11610,6 +11675,13 @@ lines-and-columns@^2.0.2:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b"
integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==
linkify-it@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==
dependencies:
uc.micro "^1.0.1"
lint-staged@^13.0.3:
version "13.0.3"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.3.tgz#d7cdf03a3830b327a2b63c6aec953d71d9dc48c6"
@ -12205,11 +12277,32 @@ markdown-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3"
integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==
markdown-it-anchor@^8.4.1:
version "8.6.5"
resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz#30c4bc5bbff327f15ce3c429010ec7ba75e7b5f8"
integrity sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==
markdown-it@^12.3.2:
version "12.3.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90"
integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==
dependencies:
argparse "^2.0.1"
entities "~2.1.0"
linkify-it "^3.0.1"
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-table@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c"
integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==
marked@^4.0.10:
version "4.1.0"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.1.0.tgz#3fc6e7485f21c1ca5d6ec4a39de820e146954796"
integrity sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==
matcher@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/matcher/-/matcher-5.0.0.tgz#cd82f1c7ae7ee472a9eeaf8ec7cac45e0fe0da62"
@ -12560,7 +12653,7 @@ mdast-util-toc@^6.1.0:
unist-util-is "^5.0.0"
unist-util-visit "^3.0.0"
mdurl@^1.0.0:
mdurl@^1.0.0, mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
@ -16981,6 +17074,13 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
requizzle@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.3.tgz#4675c90aacafb2c036bd39ba2daa4a1cb777fded"
integrity sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==
dependencies:
lodash "^4.17.14"
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@ -18306,6 +18406,11 @@ tabtab@^3.0.2:
mkdirp "^0.5.1"
untildify "^3.0.3"
taffydb@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
integrity sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==
tailwindcss-open-variant@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tailwindcss-open-variant/-/tailwindcss-open-variant-1.0.0.tgz#e4555c0a0ec2a82801e563ed36b1b23e4dd04a3b"
@ -18901,6 +19006,11 @@ typical@^4.0.0:
resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uglify-js@^3.1.4:
version "3.16.2"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.2.tgz#0481e1dbeed343ad1c2ddf3c6d42e89b7a6d4def"
@ -18936,7 +19046,7 @@ undefsafe@^2.0.2:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
underscore@^1.5.0:
underscore@^1.5.0, underscore@~1.13.2:
version "1.13.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee"
integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==
@ -20000,6 +20110,11 @@ xml-js@^1.6.11:
dependencies:
sax "^1.2.4"
xmlcreate@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be"
integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==
xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"