2022-12-12 18:45:51 +01:00
|
|
|
import { Bezier } from 'bezier-js'
|
2022-08-28 02:14:39 +02:00
|
|
|
import { Attributes } from './attributes.mjs'
|
2022-08-26 18:51:02 +02:00
|
|
|
import * as utils from './utils.mjs'
|
2022-09-18 15:11:10 +02:00
|
|
|
import { Point, pointsProxy } from './point.mjs'
|
|
|
|
import { Path, pathsProxy } from './path.mjs'
|
|
|
|
import { Snippet, snippetsProxy } from './snippet.mjs'
|
2022-08-28 02:14:39 +02:00
|
|
|
import { Hooks } from './hooks.mjs'
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
//////////////////////////////////////////////
|
|
|
|
// CONSTRUCTOR //
|
|
|
|
//////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor for a Part
|
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
* @return {Part} this - The Part instance
|
|
|
|
*/
|
2022-08-28 02:14:39 +02:00
|
|
|
export function Part() {
|
2022-09-10 16:00:43 +02:00
|
|
|
// Non-enumerable properties
|
2022-09-18 15:11:10 +02:00
|
|
|
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())
|
2022-09-10 16:00:43 +02:00
|
|
|
|
|
|
|
// Enumerable properties
|
2022-09-18 17:01:19 +02:00
|
|
|
this.hidden = false
|
2019-08-03 15:03:33 +02:00
|
|
|
this.attributes = new Attributes()
|
|
|
|
this.points = {}
|
|
|
|
this.paths = {}
|
|
|
|
this.snippets = {}
|
2022-09-12 20:10:22 +02:00
|
|
|
this.name = null
|
2018-12-18 15:35:07 +01:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
2018-07-23 20:14:32 +02:00
|
|
|
}
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
//////////////////////////////////////////////
|
|
|
|
// PUBLIC METHODS //
|
|
|
|
//////////////////////////////////////////////
|
2018-07-23 17:35:06 +00:00
|
|
|
|
2022-09-24 18:37:49 +02:00
|
|
|
/**
|
|
|
|
* Returns a part as an object suitable for inclusion in renderprops
|
|
|
|
*
|
|
|
|
* @return {object} part - A plain object representing the part
|
|
|
|
*/
|
feat(core): Added Pattern.getLogs() and updated Pattern.getRenderProps()
The data returned by `Pattern.getRenderProps()` was not serializable as
we were returning `this` all over the place, thereby including marcors,
log methods, cyclic object references, and so on.
This commit changes that by implementing a `.asRenderProp()` method on
all of the various objects (stack, part, path, point, snippet,
attributes, svg) and only including data that can be serialized.
In addition, we no longer include the logs in the renderProps because
they are not related to rendering the pattern.
Instead, the new method `Pattern.getLogs()` gives you the logs.
2023-06-01 16:45:13 +02:00
|
|
|
Part.prototype.asRenderProps = function () {
|
|
|
|
const paths = {}
|
|
|
|
for (const i in this.paths) paths[i] = this.paths[i].asRenderProps()
|
|
|
|
const points = {}
|
|
|
|
for (const i in this.points) points[i] = this.points[i].asRenderProps()
|
|
|
|
const snippets = {}
|
|
|
|
for (const i in this.snippets) snippets[i] = this.snippets[i].asRenderProps()
|
|
|
|
|
2022-09-24 18:37:49 +02:00
|
|
|
return {
|
feat(core): Added Pattern.getLogs() and updated Pattern.getRenderProps()
The data returned by `Pattern.getRenderProps()` was not serializable as
we were returning `this` all over the place, thereby including marcors,
log methods, cyclic object references, and so on.
This commit changes that by implementing a `.asRenderProp()` method on
all of the various objects (stack, part, path, point, snippet,
attributes, svg) and only including data that can be serialized.
In addition, we no longer include the logs in the renderProps because
they are not related to rendering the pattern.
Instead, the new method `Pattern.getLogs()` gives you the logs.
2023-06-01 16:45:13 +02:00
|
|
|
paths,
|
|
|
|
points,
|
|
|
|
snippets,
|
|
|
|
attributes: this.attributes.asRenderProps(),
|
2022-09-24 18:37:49 +02:00
|
|
|
height: this.height,
|
|
|
|
width: this.width,
|
feat(core): Added Pattern.getLogs() and updated Pattern.getRenderProps()
The data returned by `Pattern.getRenderProps()` was not serializable as
we were returning `this` all over the place, thereby including marcors,
log methods, cyclic object references, and so on.
This commit changes that by implementing a `.asRenderProp()` method on
all of the various objects (stack, part, path, point, snippet,
attributes, svg) and only including data that can be serialized.
In addition, we no longer include the logs in the renderProps because
they are not related to rendering the pattern.
Instead, the new method `Pattern.getLogs()` gives you the logs.
2023-06-01 16:45:13 +02:00
|
|
|
bottomRight: this.bottomRight.asRenderProps(),
|
|
|
|
topLeft: this.topLeft.asRenderProps(),
|
2022-09-24 18:37:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
/**
|
|
|
|
* 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)
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
return this
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-12-18 15:35:07 +01:00
|
|
|
|
2022-12-22 19:35:14 +01:00
|
|
|
/**
|
|
|
|
* Gets a free ID to use in the part
|
|
|
|
*
|
2022-12-23 15:29:44 +01:00
|
|
|
* @param {string} prefix - An optional prefix to apply to the ID
|
2022-12-22 19:35:14 +01:00
|
|
|
* @return {string} id - A free ID to use
|
|
|
|
*/
|
|
|
|
Part.prototype.getId = function (prefix = '') {
|
2022-12-23 15:36:08 +01:00
|
|
|
return this.__getIdClosure()(prefix)
|
2022-12-22 19:35:14 +01:00
|
|
|
}
|
|
|
|
|
2022-09-18 17:01:19 +02:00
|
|
|
/**
|
|
|
|
* Hide the part
|
|
|
|
*
|
|
|
|
* @return {Part} part - The Part instance
|
|
|
|
*/
|
|
|
|
Part.prototype.hide = function () {
|
|
|
|
this.hidden = true
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the hidden attribute
|
|
|
|
*
|
|
|
|
* @param {boolean} hidden - The value to set the hidden property to
|
|
|
|
* @return {Part} this - The Part instance
|
|
|
|
*/
|
|
|
|
Part.prototype.setHidden = function (hidden = false) {
|
|
|
|
if (hidden) this.hidden = true
|
|
|
|
else this.hidden = false
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
/**
|
|
|
|
* 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
|
2023-06-05 18:39:50 +02:00
|
|
|
const paperless = this.context.settings?.paperless ? true : false
|
2022-09-18 15:11:10 +02:00
|
|
|
const sa = this.context.settings?.complete ? this.context.settings?.sa || 0 : 0
|
|
|
|
const shorthand = {
|
2022-09-18 17:01:19 +02:00
|
|
|
complete,
|
2022-09-24 18:37:49 +02:00
|
|
|
context: this.context,
|
2022-09-25 16:40:09 +02:00
|
|
|
getId: this.__getIdClosure(),
|
2022-09-18 17:01:19 +02:00
|
|
|
log: this.context.store.log,
|
|
|
|
paperless,
|
2022-09-18 15:11:10 +02:00
|
|
|
part: this,
|
|
|
|
sa,
|
|
|
|
scale: this.context.settings?.scale,
|
|
|
|
store: this.context.store,
|
|
|
|
units: this.__unitsClosure(),
|
|
|
|
utils: utils,
|
2022-12-12 18:45:51 +01:00
|
|
|
Bezier: Bezier,
|
2022-09-18 15:11:10 +02:00
|
|
|
}
|
|
|
|
// 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
|
2022-09-19 18:04:47 +02:00
|
|
|
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))
|
2022-09-18 15:11:10 +02:00
|
|
|
shorthand.snippets = new Proxy(
|
2022-09-19 18:04:47 +02:00
|
|
|
this.snippets,
|
2022-09-18 15:11:10 +02:00
|
|
|
snippetsProxy(self.snippets, self.context.store.log)
|
|
|
|
)
|
2022-09-19 18:04:47 +02:00
|
|
|
shorthand.measurements = new Proxy(this.context.settings.measurements, {
|
2022-09-18 15:11:10 +02:00
|
|
|
get: function (measurements, name) {
|
|
|
|
if (typeof measurements[name] === 'undefined')
|
|
|
|
self.context.store.log.warning(
|
2023-06-20 18:18:39 -05:00
|
|
|
`${self.name} tried to access \`measurements.${name}\` but it is \`undefined\``
|
2022-09-18 15:11:10 +02:00
|
|
|
)
|
|
|
|
return Reflect.get(...arguments)
|
|
|
|
},
|
|
|
|
set: (measurements, name, value) => (self.context.settings.measurements[name] = value),
|
|
|
|
})
|
2022-09-19 18:04:47 +02:00
|
|
|
shorthand.options = new Proxy(this.context.settings.options, {
|
2022-09-18 15:11:10 +02:00
|
|
|
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),
|
|
|
|
})
|
2022-09-19 18:04:47 +02:00
|
|
|
shorthand.absoluteOptions = new Proxy(this.context.settings.absoluteOptions, {
|
2022-09-18 15:11:10 +02:00
|
|
|
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),
|
|
|
|
})
|
|
|
|
|
2022-09-25 15:00:10 +02:00
|
|
|
// Macro closure at the end as it includes the shorthand object
|
|
|
|
shorthand.macro = this.__macroClosure(shorthand)
|
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
return shorthand
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-08-01 14:55:54 +02:00
|
|
|
|
2022-09-18 17:01:19 +02:00
|
|
|
/**
|
2022-09-18 23:01:10 +02:00
|
|
|
* Unhide the part
|
2022-09-18 17:01:19 +02:00
|
|
|
*
|
|
|
|
* @return {Part} part - The Part instance
|
|
|
|
*/
|
|
|
|
Part.prototype.unhide = function () {
|
|
|
|
this.hidden = false
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
/**
|
|
|
|
* 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 () {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (this.topLeft) return this // Cached
|
2018-08-01 18:18:29 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
let topLeft = new Point(Infinity, Infinity)
|
|
|
|
let bottomRight = new Point(-Infinity, -Infinity)
|
2018-08-01 18:18:29 +02:00
|
|
|
for (let key in this.paths) {
|
2020-07-19 13:01:01 +02:00
|
|
|
try {
|
2022-09-18 15:11:10 +02:00
|
|
|
let path = this.paths[key].__boundary()
|
2022-09-18 17:01:19 +02:00
|
|
|
if (!path.hidden) {
|
2020-07-19 13:01:01 +02:00
|
|
|
if (path.topLeft.x < topLeft.x) topLeft.x = path.topLeft.x
|
|
|
|
if (path.topLeft.y < topLeft.y) topLeft.y = path.topLeft.y
|
|
|
|
if (path.bottomRight.x > bottomRight.x) bottomRight.x = path.bottomRight.x
|
|
|
|
if (path.bottomRight.y > bottomRight.y) bottomRight.y = path.bottomRight.y
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2022-09-10 16:00:43 +02:00
|
|
|
this.context.store.log.error(`Could not calculate boundary of \`paths.${key}\``)
|
2020-07-19 13:01:01 +02:00
|
|
|
return false
|
2018-08-01 18:18:29 +02:00
|
|
|
}
|
|
|
|
}
|
2018-08-22 11:34:08 +02:00
|
|
|
for (let key in this.points) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let point = this.points[key]
|
|
|
|
let radius = point.attributes.get('data-circle')
|
2018-08-22 11:34:08 +02:00
|
|
|
if (radius) {
|
2019-08-03 15:03:33 +02:00
|
|
|
radius = parseFloat(radius)
|
|
|
|
if (point.x - radius < topLeft.x) topLeft.x = point.x - radius
|
|
|
|
if (point.y - radius < topLeft.y) topLeft.y = point.y - radius
|
|
|
|
if (point.x + radius > bottomRight.x) bottomRight.x = point.x + radius
|
|
|
|
if (point.y + radius > bottomRight.y) bottomRight.y = point.y + radius
|
2018-08-22 11:34:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Fix infinity if part has no paths
|
2019-08-03 15:03:33 +02:00
|
|
|
if (topLeft.x === Infinity) topLeft.x = 0
|
|
|
|
if (topLeft.y === Infinity) topLeft.y = 0
|
|
|
|
if (bottomRight.x === -Infinity) bottomRight.x = 0
|
|
|
|
if (bottomRight.y === -Infinity) bottomRight.y = 0
|
2022-09-13 17:56:01 +02:00
|
|
|
|
|
|
|
this.topLeft = topLeft
|
|
|
|
this.bottomRight = bottomRight
|
2019-08-03 15:03:33 +02:00
|
|
|
this.width = this.bottomRight.x - this.topLeft.x
|
|
|
|
this.height = this.bottomRight.y - this.topLeft.y
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
2018-08-01 18:18:29 +02:00
|
|
|
|
2022-09-25 16:40:09 +02:00
|
|
|
/**
|
|
|
|
* Returns a closure holding a getId method (returns an ID unused in this part)
|
|
|
|
*
|
|
|
|
* @return {function} getId - The getId function
|
|
|
|
*/
|
|
|
|
Part.prototype.__getIdClosure = function () {
|
|
|
|
const self = this
|
|
|
|
const method = function (prefix = '') {
|
|
|
|
self.freeId += 1
|
|
|
|
|
|
|
|
return prefix + self.freeId
|
|
|
|
}
|
|
|
|
|
|
|
|
return method
|
|
|
|
}
|
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
/**
|
|
|
|
* Copies point/path/snippet data from part orig into this
|
2023-04-15 17:54:56 +02:00
|
|
|
* Also sets the freeId
|
2022-09-18 15:11:10 +02:00
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {object} orig - The original part to inject into this
|
|
|
|
* @return {Part} this - The part instance
|
|
|
|
*/
|
|
|
|
Part.prototype.__inject = function (orig) {
|
2020-07-11 15:15:02 +02:00
|
|
|
const findBasePoint = (p) => {
|
2019-05-10 17:38:47 +02:00
|
|
|
for (let i in orig.points) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (orig.points[i] === p) return i
|
2019-05-10 17:38:47 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2019-05-10 17:38:47 +02:00
|
|
|
|
2023-04-15 17:54:56 +02:00
|
|
|
this.freeId = orig.freeId
|
2019-08-03 15:03:33 +02:00
|
|
|
for (let i in orig.points) this.points[i] = orig.points[i].clone()
|
2019-05-10 17:38:47 +02:00
|
|
|
for (let i in orig.paths) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.paths[i] = orig.paths[i].clone()
|
2019-05-10 17:38:47 +02:00
|
|
|
// Keep link between points and path ops where possible
|
|
|
|
for (let j in orig.paths[i].ops) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let op = orig.paths[i].ops[j]
|
|
|
|
if (op.type !== 'close') {
|
|
|
|
let toPoint = findBasePoint(op.to)
|
|
|
|
if (toPoint) this.paths[i].ops[j].to = this.points[toPoint]
|
2019-05-10 17:38:47 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
if (op.type === 'curve') {
|
|
|
|
let cp1Point = findBasePoint(op.cp1)
|
|
|
|
if (cp1Point) this.paths[i].ops[j].cp1 = this.points[cp1Point]
|
|
|
|
let cp2Point = findBasePoint(op.cp2)
|
|
|
|
if (cp2Point) this.paths[i].ops[j].cp2 = this.points[cp2Point]
|
2018-08-05 15:52:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-10 17:38:47 +02:00
|
|
|
for (let i in orig.snippets) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.snippets[i] = orig.snippets[i].clone()
|
2019-05-10 17:38:47 +02:00
|
|
|
}
|
2018-08-05 15:52:37 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-08-05 15:52:37 +02:00
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
/**
|
|
|
|
* Returns a closure holding the macro method
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @return {function} method - The closured macro method
|
|
|
|
*/
|
2022-09-25 15:00:10 +02:00
|
|
|
Part.prototype.__macroClosure = function (props) {
|
|
|
|
const self = this
|
|
|
|
const method = function (key, args) {
|
|
|
|
const macro = utils.__macroName(key)
|
|
|
|
if (typeof self[macro] === 'function') self[macro](args, props)
|
2023-05-07 13:51:59 -07:00
|
|
|
else if ('context' in self)
|
|
|
|
self.context.store.log.warning('Unknown macro `' + key + '` used in ' + self.name)
|
2020-07-18 16:48:29 +02:00
|
|
|
}
|
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
return method
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-08-08 14:02:06 +02:00
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
}
|
2022-07-23 14:52:56 +02:00
|
|
|
|
2022-09-18 15:11:10 +02:00
|
|
|
return method
|
|
|
|
}
|