refactor (core) move packing and rendering to its own class
This commit is contained in:
parent
c5baaab234
commit
9e8db66c94
3 changed files with 200 additions and 155 deletions
|
@ -15,6 +15,7 @@ import { PatternConfig } from './pattern-config.mjs'
|
||||||
import { PatternDraftQueue } from './pattern-draft-queue.mjs'
|
import { PatternDraftQueue } from './pattern-draft-queue.mjs'
|
||||||
import { PatternSampler } from './pattern-sampler.mjs'
|
import { PatternSampler } from './pattern-sampler.mjs'
|
||||||
import { PatternPlugins, getPluginName } from './pattern-plugins.mjs'
|
import { PatternPlugins, getPluginName } from './pattern-plugins.mjs'
|
||||||
|
import { PatternRenderer } from './pattern-renderer.mjs'
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
@ -194,60 +195,22 @@ Pattern.prototype.getConfig = function () {
|
||||||
return this.__init().config
|
return this.__init().config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the pattern to SVG
|
||||||
|
*
|
||||||
|
* @return {string} svg - The rendered SVG
|
||||||
|
*/
|
||||||
|
Pattern.prototype.render = function () {
|
||||||
|
return new PatternRenderer(this).render()
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns props required to render this pattern through
|
/** Returns props required to render this pattern through
|
||||||
* an external renderer (eg. a React component)
|
* an external renderer (eg. a React component)
|
||||||
*
|
*
|
||||||
* @return {object} this - The Pattern instance
|
* @return {object} this - The Pattern instance
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.getRenderProps = function () {
|
Pattern.prototype.getRenderProps = function () {
|
||||||
this.store.log.info('Gathering render props')
|
return new PatternRenderer(this).getRenderProps()
|
||||||
// Run pre-render hook
|
|
||||||
let svg = new Svg(this)
|
|
||||||
svg.hooks = this.plugins.hooks
|
|
||||||
|
|
||||||
this.__pack()
|
|
||||||
svg.__runHooks('preRender')
|
|
||||||
|
|
||||||
let props = { svg }
|
|
||||||
props.width = this.width
|
|
||||||
props.height = this.height
|
|
||||||
props.autoLayout = this.autoLayout
|
|
||||||
props.settings = this.settings
|
|
||||||
props.parts = []
|
|
||||||
for (const set of this.parts) {
|
|
||||||
const setParts = {}
|
|
||||||
for (let p in set) {
|
|
||||||
if (!set[p].hidden) {
|
|
||||||
setParts[p] = {
|
|
||||||
...set[p].asProps(),
|
|
||||||
store: this.setStores[set[p].set],
|
|
||||||
}
|
|
||||||
} else if (this.setStores[set.set]) {
|
|
||||||
this.setStores[set.set].log.info(
|
|
||||||
`Part${p} is hidden in set ${set.set}. Not adding to render props`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
props.parts.push(setParts)
|
|
||||||
}
|
|
||||||
props.stacks = {}
|
|
||||||
for (let s in this.stacks) {
|
|
||||||
if (!this.__isStackHidden(s)) {
|
|
||||||
props.stacks[s] = this.stacks[s].asProps()
|
|
||||||
} else this.store.log.info(`Stack ${s} is hidden. Skipping in render props.`)
|
|
||||||
}
|
|
||||||
props.logs = {
|
|
||||||
pattern: this.store.logs,
|
|
||||||
sets: this.setStores.map((store) => ({
|
|
||||||
debug: store.logs.debug,
|
|
||||||
info: store.logs.info,
|
|
||||||
error: store.logs.error,
|
|
||||||
warning: store.logs.warning,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
|
|
||||||
svg.__runHooks('postRender')
|
|
||||||
return props
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -300,18 +263,6 @@ Pattern.prototype.on = function (hook, method, data) {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the pattern to SVG
|
|
||||||
*
|
|
||||||
* @return {string} svg - The rendered SVG
|
|
||||||
*/
|
|
||||||
Pattern.prototype.render = function () {
|
|
||||||
this.svg = new Svg(this)
|
|
||||||
this.svg.hooks = this.plugins.hooks
|
|
||||||
|
|
||||||
return this.__pack().svg.render()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a plugin
|
* Loads a plugin
|
||||||
*
|
*
|
||||||
|
@ -405,26 +356,6 @@ Pattern.prototype.__createPartWithContext = function (name, set) {
|
||||||
return part
|
return part
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new Stack instance and populates it with the pattern context
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {string} name - The name of the stack
|
|
||||||
* @return {Stack} stack - The instantiated Stack
|
|
||||||
*/
|
|
||||||
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,
|
|
||||||
setStores: this.setStores,
|
|
||||||
}
|
|
||||||
|
|
||||||
return stack
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the pattern coniguration and settings
|
* Initializes the pattern coniguration and settings
|
||||||
*
|
*
|
||||||
|
@ -601,79 +532,6 @@ Pattern.prototype.__needs = function (partName, set = 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Packs stacks in a 2D space and sets pattern size
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @return {Pattern} this - The Pattern instance
|
|
||||||
*/
|
|
||||||
Pattern.prototype.__pack = function () {
|
|
||||||
this.__runHooks('preLayout')
|
|
||||||
for (const set in this.settings) {
|
|
||||||
if (this.setStores[set].logs.error.length > 0) {
|
|
||||||
this.setStores[set].log.warning(`One or more errors occured. Not packing pattern parts`)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// First, create all stacks
|
|
||||||
this.stacks = {}
|
|
||||||
for (const set in this.settings) {
|
|
||||||
for (const [name, part] of Object.entries(this.parts[set])) {
|
|
||||||
const stackName =
|
|
||||||
this.settings[set].stackPrefix +
|
|
||||||
(typeof part.stack === 'function' ? part.stack(this.settings[set], name) : part.stack)
|
|
||||||
if (typeof this.stacks[stackName] === 'undefined')
|
|
||||||
this.stacks[stackName] = this.__createStackWithContext(stackName, set)
|
|
||||||
this.stacks[stackName].addPart(part)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let bins = []
|
|
||||||
for (const [key, stack] of Object.entries(this.stacks)) {
|
|
||||||
// Avoid multiple render calls to cause addition of transforms
|
|
||||||
stack.attributes.remove('transform')
|
|
||||||
if (!this.__isStackHidden(key)) {
|
|
||||||
stack.home()
|
|
||||||
if (this.settings[0].layout === true)
|
|
||||||
bins.push({ id: key, width: stack.width, height: stack.height })
|
|
||||||
else {
|
|
||||||
if (this.width < stack.width) this.width = stack.width
|
|
||||||
if (this.height < stack.height) this.height = stack.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.settings[0].layout === true) {
|
|
||||||
// some plugins will add a width constraint to the settings, but we can safely pass undefined if not
|
|
||||||
let size = pack(bins, { inPlace: true, maxWidth: this.settings[0].maxWidth })
|
|
||||||
for (let bin of bins) {
|
|
||||||
this.autoLayout.stacks[bin.id] = { move: {} }
|
|
||||||
let stack = this.stacks[bin.id]
|
|
||||||
if (bin.x !== 0 || bin.y !== 0) {
|
|
||||||
stack.attr('transform', `translate(${bin.x}, ${bin.y})`)
|
|
||||||
}
|
|
||||||
this.autoLayout.stacks[bin.id].move = {
|
|
||||||
x: bin.x + stack.layout.move.x,
|
|
||||||
y: bin.y + stack.layout.move.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.width = size.width
|
|
||||||
this.height = size.height
|
|
||||||
} else if (typeof this.settings[0].layout === 'object') {
|
|
||||||
this.width = this.settings[0].layout.width
|
|
||||||
this.height = this.settings[0].layout.height
|
|
||||||
for (let stackId of Object.keys(this.settings[0].layout.stacks)) {
|
|
||||||
// Some parts are added by late-stage plugins
|
|
||||||
if (this.stacks[stackId]) {
|
|
||||||
let transforms = this.settings[this.activeStack || 0].layout.stacks[stackId]
|
|
||||||
this.stacks[stackId].generateTransform(transforms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.__runHooks('postLayout')
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the configuration for the config resolver and sets it on the pattern
|
* Gets the configuration for the config resolver and sets it on the pattern
|
||||||
* @private
|
* @private
|
||||||
|
|
187
packages/core/src/pattern/pattern-renderer.mjs
Normal file
187
packages/core/src/pattern/pattern-renderer.mjs
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
import { Svg } from '../svg.mjs'
|
||||||
|
import { Stack } from '../stack.mjs'
|
||||||
|
import pack from 'bin-pack-with-constraints'
|
||||||
|
|
||||||
|
export function PatternRenderer(pattern) {
|
||||||
|
this.pattern = pattern
|
||||||
|
this.autoLayout = pattern.autoLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the pattern to SVG
|
||||||
|
*
|
||||||
|
* @return {string} svg - The rendered SVG
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.render = function () {
|
||||||
|
this.__startRender()
|
||||||
|
this.pattern.svg = this.svg
|
||||||
|
return this.svg.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns props required to render this pattern through
|
||||||
|
* an external renderer (eg. a React component)
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.getRenderProps = function () {
|
||||||
|
this.pattern.store.log.info('Gathering render props')
|
||||||
|
// Run pre-render hook
|
||||||
|
this.__startRender()
|
||||||
|
this.svg.__runHooks('preRender')
|
||||||
|
|
||||||
|
let props = {
|
||||||
|
svg: this.svg,
|
||||||
|
width: this.pattern.width,
|
||||||
|
height: this.pattern.height,
|
||||||
|
autoLayout: this.pattern.autoLayout,
|
||||||
|
settings: this.pattern.settings,
|
||||||
|
parts: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const set of this.pattern.parts) {
|
||||||
|
const setParts = {}
|
||||||
|
for (let p in set) {
|
||||||
|
if (!set[p].hidden) {
|
||||||
|
setParts[p] = {
|
||||||
|
...set[p].asProps(),
|
||||||
|
store: this.pattern.setStores[set[p].set],
|
||||||
|
}
|
||||||
|
} else if (this.pattern.setStores[set.set]) {
|
||||||
|
this.pattern.setStores[set.set].log.info(
|
||||||
|
`Part${p} is hidden in set ${set.set}. Not adding to render props`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.parts.push(setParts)
|
||||||
|
}
|
||||||
|
props.stacks = {}
|
||||||
|
for (let s in this.pattern.stacks) {
|
||||||
|
if (!this.pattern.__isStackHidden(s)) {
|
||||||
|
props.stacks[s] = this.pattern.stacks[s].asProps()
|
||||||
|
} else this.pattern.store.log.info(`Stack ${s} is hidden. Skipping in render props.`)
|
||||||
|
}
|
||||||
|
props.logs = {
|
||||||
|
pattern: this.pattern.store.logs,
|
||||||
|
sets: this.pattern.setStores.map((store) => ({
|
||||||
|
debug: store.logs.debug,
|
||||||
|
info: store.logs.info,
|
||||||
|
error: store.logs.error,
|
||||||
|
warning: store.logs.warning,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.svg.__runHooks('postRender')
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternRenderer.prototype.__startRender = function () {
|
||||||
|
this.svg = new Svg(this.pattern)
|
||||||
|
this.svg.hooks = this.pattern.plugins.hooks
|
||||||
|
this.__pack()
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternRenderer.prototype.__stack = function () {
|
||||||
|
// First, create all stacks
|
||||||
|
this.stacks = {}
|
||||||
|
const settings = this.pattern.settings
|
||||||
|
for (const set in settings) {
|
||||||
|
for (const [name, part] of Object.entries(this.pattern.parts[set])) {
|
||||||
|
const stackName =
|
||||||
|
settings[set].stackPrefix +
|
||||||
|
(typeof part.stack === 'function' ? part.stack(settings[set], name) : part.stack)
|
||||||
|
if (typeof this.stacks[stackName] === 'undefined')
|
||||||
|
this.stacks[stackName] = this.__createStackWithContext(stackName, set)
|
||||||
|
this.stacks[stackName].addPart(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pattern.stacks = this.stacks
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Packs stacks in a 2D space and sets pattern size
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.__pack = function () {
|
||||||
|
this.pattern.__runHooks('preLayout')
|
||||||
|
const { settings, setStores, parts } = this.pattern
|
||||||
|
for (const set in settings) {
|
||||||
|
if (setStores[set].logs.error.length > 0) {
|
||||||
|
setStores[set].log.warning(`One or more errors occured. Not packing pattern parts`)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__stack()
|
||||||
|
|
||||||
|
let bins = []
|
||||||
|
for (const [key, stack] of Object.entries(this.stacks)) {
|
||||||
|
// Avoid multiple render calls to cause addition of transforms
|
||||||
|
stack.attributes.remove('transform')
|
||||||
|
if (!this.pattern.__isStackHidden(key)) {
|
||||||
|
stack.home()
|
||||||
|
if (settings[0].layout === true)
|
||||||
|
bins.push({ id: key, width: stack.width, height: stack.height })
|
||||||
|
else {
|
||||||
|
if (this.width < stack.width) this.width = stack.width
|
||||||
|
if (this.height < stack.height) this.height = stack.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (settings[0].layout === true) {
|
||||||
|
// some plugins will add a width constraint to the settings, but we can safely pass undefined if not
|
||||||
|
let size = pack(bins, { inPlace: true, maxWidth: settings[0].maxWidth })
|
||||||
|
for (let bin of bins) {
|
||||||
|
this.autoLayout.stacks[bin.id] = { move: {} }
|
||||||
|
let stack = this.stacks[bin.id]
|
||||||
|
if (bin.x !== 0 || bin.y !== 0) {
|
||||||
|
stack.attr('transform', `translate(${bin.x}, ${bin.y})`)
|
||||||
|
}
|
||||||
|
this.autoLayout.stacks[bin.id].move = {
|
||||||
|
x: bin.x + stack.layout.move.x,
|
||||||
|
y: bin.y + stack.layout.move.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.width = size.width
|
||||||
|
this.height = size.height
|
||||||
|
} else if (typeof settings[0].layout === 'object') {
|
||||||
|
this.width = settings[0].layout.width
|
||||||
|
this.height = settings[0].layout.height
|
||||||
|
for (let stackId of Object.keys(settings[0].layout.stacks)) {
|
||||||
|
// Some parts are added by late-stage plugins
|
||||||
|
if (this.stacks[stackId]) {
|
||||||
|
let transforms = settings[this.activeStack || 0].layout.stacks[stackId]
|
||||||
|
this.stacks[stackId].generateTransform(transforms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pattern.width = this.width
|
||||||
|
this.pattern.height = this.height
|
||||||
|
this.pattern.autoLayout = this.autoLayout
|
||||||
|
this.pattern.__runHooks('postLayout')
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new Stack instance and populates it with the pattern context
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} name - The name of the stack
|
||||||
|
* @return {Stack} stack - The instantiated Stack
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.__createStackWithContext = function (name) {
|
||||||
|
// Context object to add to Stack closure
|
||||||
|
const stack = new Stack()
|
||||||
|
stack.name = name
|
||||||
|
stack.context = {
|
||||||
|
config: this.pattern.config,
|
||||||
|
settings: this.pattern.settings,
|
||||||
|
setStores: this.pattern.setStores,
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack
|
||||||
|
}
|
|
@ -175,7 +175,7 @@ describe('Pattern', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Pattern.__pack()', () => {
|
describe('PatternRenderer.__pack()', () => {
|
||||||
it('should get a part stack name from a function that uses settings', () => {
|
it('should get a part stack name from a function that uses settings', () => {
|
||||||
const expectedName = 'namedStack'
|
const expectedName = 'namedStack'
|
||||||
const front = {
|
const front = {
|
||||||
|
@ -201,7 +201,7 @@ describe('Pattern', () => {
|
||||||
|
|
||||||
const pattern = new Test()
|
const pattern = new Test()
|
||||||
pattern.draft()
|
pattern.draft()
|
||||||
pattern.__pack()
|
pattern.getRenderProps()
|
||||||
|
|
||||||
const stackNames = Object.keys(pattern.stacks)
|
const stackNames = Object.keys(pattern.stacks)
|
||||||
expect(stackNames).to.include(expectedName)
|
expect(stackNames).to.include(expectedName)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue