1
0
Fork 0

feat(core): Added support for stacks in layout

This commit is contained in:
Joost De Cock 2022-09-13 17:56:01 +02:00
parent 85748db201
commit 7ac5a88dff
7 changed files with 114 additions and 45 deletions

View file

@ -111,31 +111,15 @@ Part.prototype.boundary = function () {
if (topLeft.y === Infinity) topLeft.y = 0 if (topLeft.y === Infinity) topLeft.y = 0
if (bottomRight.x === -Infinity) bottomRight.x = 0 if (bottomRight.x === -Infinity) bottomRight.x = 0
if (bottomRight.y === -Infinity) bottomRight.y = 0 if (bottomRight.y === -Infinity) bottomRight.y = 0
// Add margin
let margin = this.context.settings.margin this.topLeft = topLeft
if (this.context.settings.paperless && margin < 10) margin = 10 this.bottomRight = bottomRight
this.topLeft = new Point(topLeft.x - margin, topLeft.y - margin)
this.bottomRight = new Point(bottomRight.x + margin, bottomRight.y + margin)
this.width = this.bottomRight.x - this.topLeft.x this.width = this.bottomRight.x - this.topLeft.x
this.height = this.bottomRight.y - this.topLeft.y this.height = this.bottomRight.y - this.topLeft.y
return this return this
} }
/** Homes part so that its top left corner is in (0,0) */
Part.prototype.home = function () {
if (this.topLeft !== false) return this
else this.boundary()
if (this.topLeft.x == 0 && this.topLeft.y == 0) return this
else {
this.attr('transform', `translate(${this.topLeft.x * -1}, ${this.topLeft.y * -1})`)
this.layout.move.x = this.topLeft.x * -1
this.layout.move.y = this.topLeft.y * -1
}
return this
}
/** Adds an attribute. This is here to make this call chainable in assignment */ /** Adds an attribute. This is here to make this call chainable in assignment */
Part.prototype.attr = function (name, value, overwrite = false) { Part.prototype.attr = function (name, value, overwrite = false) {
if (overwrite) this.attributes.set(name, value) if (overwrite) this.attributes.set(name, value)

View file

@ -104,6 +104,19 @@ Pattern.prototype.__createPartWithContext = function (name) {
return part return part
} }
Pattern.prototype.__createStackWithContext = function (name) {
// Context object to add to Stack closure
const stack = new Stack()
stack.name = name
stack.context = {
config: this.config,
settings: this.settings,
store: this.store,
}
return stack
}
// Merges default for options with user-provided options // Merges default for options with user-provided options
Pattern.prototype.__loadOptionDefaults = function () { Pattern.prototype.__loadOptionDefaults = function () {
if (Object.keys(this.config.options).length < 1) return this if (Object.keys(this.config.options).length < 1) return this
@ -548,7 +561,8 @@ Pattern.prototype.pack = function () {
// First, create all stacks // First, create all stacks
this.stacks = {} this.stacks = {}
for (const [name, part] of Object.entries(this.parts)) { for (const [name, part] of Object.entries(this.parts)) {
if (typeof this.stacks[part.stack] === 'undefined') this.stacks[part.stack] = new Stack(part.stack) if (typeof this.stacks[part.stack] === 'undefined')
this.stacks[part.stack] = this.__createStackWithContext(part.stack)
this.stacks[part.stack].addPart(part) this.stacks[part.stack].addPart(part)
} }
@ -558,7 +572,8 @@ Pattern.prototype.pack = function () {
stack.attributes.remove('transform') stack.attributes.remove('transform')
if (!this.isStackHidden(key)) { if (!this.isStackHidden(key)) {
stack.home() stack.home()
if (this.settings.layout === true) bins.push({ id: key, width: stack.width, height: stack.height }) if (this.settings.layout === true)
bins.push({ id: key, width: stack.width, height: stack.height })
else { else {
if (this.width < stack.width) this.width = stack.width if (this.width < stack.width) this.width = stack.width
if (this.height < stack.height) this.height = stack.height if (this.height < stack.height) this.height = stack.height
@ -573,7 +588,6 @@ Pattern.prototype.pack = function () {
if (bin.x !== 0 || bin.y !== 0) { if (bin.x !== 0 || bin.y !== 0) {
stack.attr('transform', `translate(${bin.x}, ${bin.y})`) stack.attr('transform', `translate(${bin.x}, ${bin.y})`)
} }
this.autoLayout.stacks[bin.id].move = { this.autoLayout.stacks[bin.id].move = {
x: bin.x + stack.layout.move.x, x: bin.x + stack.layout.move.x,
y: bin.y + stack.layout.move.y, y: bin.y + stack.layout.move.y,
@ -845,6 +859,12 @@ Pattern.prototype.getRenderProps = function () {
} }
} }
} }
props.stacks = {}
for (let s in this.stacks) {
if (!this.isStackHidden(s)) {
props.stacks[s] = this.stacks[s]
}
}
return props return props
} }

View file

@ -2,7 +2,7 @@ import { Attributes } from './attributes.mjs'
import { Point } from './point.mjs' import { Point } from './point.mjs'
import * as utils from './utils.mjs' import * as utils from './utils.mjs'
export function Stack(name=null) { export function Stack(name = null) {
// Non-enumerable properties // Non-enumerable properties
utils.addNonEnumProp(this, 'freeId', 0) utils.addNonEnumProp(this, 'freeId', 0)
utils.addNonEnumProp(this, 'topLeft', false) utils.addNonEnumProp(this, 'topLeft', false)
@ -20,20 +20,20 @@ export function Stack(name=null) {
} }
/* Adds a part to the stack */ /* Adds a part to the stack */
Stack.prototype.addPart = function(part) { Stack.prototype.addPart = function (part) {
if (part) this.parts.add(part) if (part) this.parts.add(part)
return this return this
} }
/* Returns a list of parts in this stack */ /* Returns a list of parts in this stack */
Stack.prototype.getPartList = function(part) { Stack.prototype.getPartList = function (part) {
return [...this.parts] return [...this.parts]
} }
/* Returns a list of names of parts in this stack */ /* Returns a list of names of parts in this stack */
Stack.prototype.getPartNames = function(part) { Stack.prototype.getPartNames = function (part) {
return [...this.parts].map(p => p.name) return [...this.parts].map((p) => p.name)
} }
/** Homes the stack so that its top left corner is in (0,0) */ /** Homes the stack so that its top left corner is in (0,0) */
@ -58,9 +58,11 @@ Stack.prototype.home = function () {
/** Calculates the stack's bounding box and sets it */ /** Calculates the stack's bounding box and sets it */
Stack.prototype.home = function () { Stack.prototype.home = function () {
if (this.topLeft) return this // Cached
this.topLeft = new Point(Infinity, Infinity) this.topLeft = new Point(Infinity, Infinity)
this.bottomRight = new Point(-Infinity, -Infinity) this.bottomRight = new Point(-Infinity, -Infinity)
for (const part of this.getPartList()) { for (const part of this.getPartList()) {
part.boundary()
if (part.topLeft.x < this.topLeft.x) this.topLeft.x = part.topLeft.x if (part.topLeft.x < this.topLeft.x) this.topLeft.x = part.topLeft.x
if (part.topLeft.y < this.topLeft.y) this.topLeft.y = part.topLeft.y if (part.topLeft.y < this.topLeft.y) this.topLeft.y = part.topLeft.y
if (part.bottomRight.x > this.bottomRight.x) this.bottomRight.x = part.bottomRight.x if (part.bottomRight.x > this.bottomRight.x) this.bottomRight.x = part.bottomRight.x
@ -73,8 +75,53 @@ Stack.prototype.home = function () {
if (this.bottomRight.x === -Infinity) this.bottomRight.x = 0 if (this.bottomRight.x === -Infinity) this.bottomRight.x = 0
if (this.bottomRight.y === -Infinity) this.bottomRight.y = 0 if (this.bottomRight.y === -Infinity) this.bottomRight.y = 0
// Add margin
let margin = this.context.settings.margin
if (this.context.settings.paperless && margin < 10) margin = 10
this.topLeft.x -= margin
this.topLeft.y -= margin
this.bottomRight.x += margin
this.bottomRight.y += margin
// Set dimensions
this.width = this.bottomRight.x - this.topLeft.x this.width = this.bottomRight.x - this.topLeft.x
this.height = this.bottomRight.y - this.topLeft.y this.height = this.bottomRight.y - this.topLeft.y
this.width = this.bottomRight.x - this.topLeft.x
this.height = this.bottomRight.y - this.topLeft.y
// Add transform
this.anchor = this.getAnchor()
if (this.topLeft.x === this.anchor.x && this.topLeft.y === this.anchor.y) return this
else {
this.attr('transform', `translate(${this.anchor.x - this.topLeft.x}, ${this.anchor.y - this.topLeft.y})`)
this.layout.move.x = this.anchor.x - this.topLeft.x
this.layout.move.y = this.anchor.y - this.topLeft.y
}
return this
}
/** Finds the anchor to aling parts in this stack */
Stack.prototype.getAnchor = function() {
let anchorPoint = true
let gridAnchorPoint = true
const parts = this.getPartList()
for (const part of parts) {
if (typeof part.points.anchor === 'undefined') anchorPoint = false
if (typeof part.points.gridAnchor === 'undefined') gridAnchorPoint = false
}
if (anchorPoint) return parts[0].points.anchor
if (gridAnchorPoint) return parts[0].points.gridAnchor
return new Point(0,0)
}
/** Adds an attribute. This is here to make this call chainable in assignment */
Stack.prototype.attr = function (name, value, overwrite = false) {
if (overwrite) this.attributes.set(name, value)
else this.attributes.add(name, value)
return this return this
} }

View file

@ -4,7 +4,6 @@ import { round, Design, Point, pctBasedOn } from '../src/index.mjs'
const expect = chai.expect const expect = chai.expect
describe('Stacks', () => { describe('Stacks', () => {
describe('Pattern.init()', () => { describe('Pattern.init()', () => {
const partA = { const partA = {
name: 'test.partA', name: 'test.partA',
@ -12,9 +11,9 @@ describe('Stacks', () => {
options: { options: {
size: { pct: 40, min: 20, max: 80 }, size: { pct: 40, min: 20, max: 80 },
}, },
draft: ({ points, Point, paths, Path, part, store, measurements, options}) => { draft: ({ points, Point, paths, Path, part, store, measurements, options }) => {
store.set('size', measurements.head * options.size) store.set('size', measurements.head * options.size)
points.from = new Point(0,0) points.from = new Point(0, 0)
points.to = new Point(0, store.get('size')) points.to = new Point(0, store.get('size'))
paths.line = new Path().move(points.from).line(points.to) paths.line = new Path().move(points.from).line(points.to)
return part return part
@ -25,8 +24,8 @@ describe('Stacks', () => {
name: 'test.partB', name: 'test.partB',
measurements: ['head'], measurements: ['head'],
after: partA, after: partA,
draft: ({ points, Point, paths, Path, part, store}) => { draft: ({ points, Point, paths, Path, part, store }) => {
points.from = new Point(0,store.get('size')) points.from = new Point(0, store.get('size'))
points.to = new Point(store.get('size'), store.get('size')) points.to = new Point(store.get('size'), store.get('size'))
paths.line = new Path().move(points.from).line(points.to) paths.line = new Path().move(points.from).line(points.to)
return part return part
@ -36,7 +35,7 @@ describe('Stacks', () => {
const partC = { const partC = {
name: 'test.partC', name: 'test.partC',
after: partB, after: partB,
draft: ({ points, Point, paths, Path, part, store}) => { draft: ({ points, Point, paths, Path, part, store }) => {
points.from = new Point(store.get('size'), store.get('size')) points.from = new Point(store.get('size'), store.get('size'))
points.to = new Point(store.get('size'), 0) points.to = new Point(store.get('size'), 0)
paths.line = new Path().move(points.from).line(points.to) paths.line = new Path().move(points.from).line(points.to)
@ -47,7 +46,7 @@ describe('Stacks', () => {
const partD = { const partD = {
name: 'test.partD', name: 'test.partD',
after: partC, after: partC,
draft: ({ points, Point, paths, Path, part, store}) => { draft: ({ points, Point, paths, Path, part, store }) => {
points.from = new Point(store.get('size'), 0) points.from = new Point(store.get('size'), 0)
points.to = new Point(0, 0) points.to = new Point(0, 0)
paths.line = new Path().move(points.from).line(points.to) paths.line = new Path().move(points.from).line(points.to)
@ -65,8 +64,8 @@ describe('Stacks', () => {
}) })
const pattern = new Pattern({ const pattern = new Pattern({
measurements: { measurements: {
head: 400 head: 400,
} },
}) })
pattern.draft() pattern.draft()
console.log(pattern.store.logs) console.log(pattern.store.logs)
@ -90,6 +89,5 @@ describe('Stacks', () => {
pattern.config.resolvedDependencies['test.partC'].indexOf('test.partB') !== -1 pattern.config.resolvedDependencies['test.partC'].indexOf('test.partB') !== -1
).to.equal(true) ).to.equal(true)
}) })
}) })
}) })

View file

@ -27,7 +27,7 @@ const LabDraft = props => {
return ( return (
<> <>
{(!patternProps || patternProps.events?.error?.length > 0) {(!patternProps || patternProps.logs?.error?.length > 0)
? <Error {...{ draft, patternProps, updateGist }} /> ? <Error {...{ draft, patternProps, updateGist }} />
: null : null
} }

View file

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

View file

@ -2,7 +2,7 @@ import { SizeMe } from 'react-sizeme'
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch" import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"
import Svg from './svg' import Svg from './svg'
import Defs from './defs' import Defs from './defs'
import Part from './part' import Stack from './stack'
/* What's with all the wrapping? /* What's with all the wrapping?
* *
@ -40,11 +40,11 @@ const SvgWrapper = props => {
<Defs {...patternProps} /> <Defs {...patternProps} />
<style>{`:root { --pattern-scale: ${gist.scale || 1}} ${patternProps.svg.style}`}</style> <style>{`:root { --pattern-scale: ${gist.scale || 1}} ${patternProps.svg.style}`}</style>
<g> <g>
{Object.keys(patternProps.parts).map((name) => ( {Object.keys(patternProps.stacks).map((stackName) => (
<Part {...{ app, gist, updateGist, unsetGist, showInfo }} <Stack {...{ app, gist, updateGist, unsetGist, showInfo, patternProps }}
key={name} key={stackName}
partName={name} stackName={stackName}
part={patternProps.parts[name]} stack={patternProps.stacks[stackName]}
/> />
))} ))}
</g> </g>