diff --git a/package.json b/package.json index 76b5597..192fd66 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "transform-runtime", "transform-class-properties", "transform-object-rest-spread", + "transform-decorators-legacy", "syntax-dynamic-import" ], "env": { @@ -59,6 +60,7 @@ "transform-runtime", "transform-class-properties", "transform-object-rest-spread", + "transform-decorators-legacy", "syntax-dynamic-import" ] } @@ -145,6 +147,7 @@ "babel-loader": "^7.1.2", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.6.1", diff --git a/src/__mocks__/SVGElement.js b/src/__mocks__/SVGElement.js index 9dc3596..4010658 100644 --- a/src/__mocks__/SVGElement.js +++ b/src/__mocks__/SVGElement.js @@ -1,8 +1,11 @@ +/* eslint-disable react/prop-types */ + import React from 'react'; -import Base from 'components/SVG/Base'; +import reflowable from 'components/SVG/reflowable'; -class SVGElement extends Base { +@reflowable +class SVGElement extends React.PureComponent { reflow() { return this.setBBox(this.props.bbox); } diff --git a/src/components/SVG/Base.js b/src/components/SVG/Base.js deleted file mode 100644 index 7be6cc6..0000000 --- a/src/components/SVG/Base.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import { Map } from 'immutable'; - -class Base extends React.PureComponent { - _currentBBox() { - return this.tempBBox ? this.tempBBox : (this.state || {}).bbox; - } - - setStateAsync(state) { - return new Promise(resolve => { - this.setState(state, resolve); - }); - } - - async setBBox(box, recalculate = {}) { - const bbox = (this._currentBBox() || Map({ width: 0, height: 0})).withMutations(bbox => { - bbox.merge(box); - - if (!bbox.has('axisY') || recalculate.axisY) { - bbox.set('axisY', bbox.get('height') / 2); - } - - if (!bbox.has('axisX1') || recalculate.axisX1) { - bbox.set('axisX1', 0); - } - - if (!bbox.has('axisX2') || recalculate.axisX2) { - bbox.set('axisX2', bbox.get('width')); - } - }); - - this.tempBBox = bbox; // Want to get the updated bbox while setState is pending - await this.setStateAsync({ bbox }); - this.tempBBox = null; - } - - getBBox() { - const bbox = this._currentBBox() || Map(); - return { - width: 0, - height: 0, - axisY: 0, - axisX1: 0, - axisX2: 0, - ...bbox.toJS() - }; - } - - async reflowChildren() { - // No child components - if (this.children === undefined) { - return true; - } - - const reflowed = await Promise.all(this.children.map(c => c.doReflow())); - - return reflowed.reduce((memo, value) => memo || value, false); - } - - async doReflow() { - const oldBBox = this._currentBBox(); - const shouldReflow = await this.reflowChildren(); - - if (shouldReflow) { - this.reflow(); - } - - return this._currentBBox() !== oldBBox; - } - - reflow() { - // Implemented in subclasses - } -} - -export default Base; diff --git a/src/components/SVG/Box.js b/src/components/SVG/Box.js index 478f97d..4b452c3 100644 --- a/src/components/SVG/Box.js +++ b/src/components/SVG/Box.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Base from './Base'; import style from './style'; -/** @extends React.PureComponent */ -class Box extends Base { +import reflowable from './reflowable'; + +@reflowable +class Box extends React.PureComponent { static defaultProps = { padding: 5, radius: 3 diff --git a/src/components/SVG/HorizontalLayout.js b/src/components/SVG/HorizontalLayout.js index 688ce88..5a8fb4a 100644 --- a/src/components/SVG/HorizontalLayout.js +++ b/src/components/SVG/HorizontalLayout.js @@ -2,12 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { List } from 'immutable'; -import Base from './Base'; import style from './style'; + +import reflowable from './reflowable'; import Path from './path'; -/** @extends React.PureComponent */ -class HorizontalLayout extends Base { +@reflowable +class HorizontalLayout extends React.PureComponent { static defaultProps = { withConnectors: false, spacing: 10 diff --git a/src/components/SVG/Image.js b/src/components/SVG/Image.js index 0dd7344..a7c61fa 100644 --- a/src/components/SVG/Image.js +++ b/src/components/SVG/Image.js @@ -1,9 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Base from './Base'; import style from './style'; +import reflowable from './reflowable'; + const namespaceProps = { 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:cc': 'http://creativecommons.org/ns#', @@ -19,8 +20,8 @@ const metadata = ` `; -/** @extends React.PureComponent */ -class Image extends Base { +@reflowable +class Image extends React.PureComponent { static defaultProps = { padding: 10 } diff --git a/src/components/SVG/Loop.js b/src/components/SVG/Loop.js index 7ffd882..048809d 100644 --- a/src/components/SVG/Loop.js +++ b/src/components/SVG/Loop.js @@ -1,8 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Base from './Base'; import style from './style'; + +import reflowable from './reflowable'; import Path from './path'; const skipPath = (box, greedy) => { @@ -54,8 +55,8 @@ const repeatPath = (box, greedy) => { .quadraticCurveTo({ cx: 0, cy: -10, x: -10, y: -10 }); }; -/** @extends React.PureComponent */ -class Loop extends Base { +@reflowable +class Loop extends React.PureComponent { get contentOffset() { const { skip, repeat } = this.props; diff --git a/src/components/SVG/Pin.js b/src/components/SVG/Pin.js index 0c39c56..69dcb3c 100644 --- a/src/components/SVG/Pin.js +++ b/src/components/SVG/Pin.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Base from './Base'; import style from './style'; -/** @extends React.PureComponent */ -class Pin extends Base { +import reflowable from './reflowable'; + +@reflowable +class Pin extends React.PureComponent { static defaultProps = { radius: 5 } diff --git a/src/components/SVG/Text.js b/src/components/SVG/Text.js index 53e5447..bc4c09d 100644 --- a/src/components/SVG/Text.js +++ b/src/components/SVG/Text.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Base from './Base'; import style from './style'; -/** @extends React.PureComponent */ -class Text extends Base { +import reflowable from './reflowable'; + +@reflowable +class Text extends React.PureComponent { reflow() { const box = this.text.getBBox(); diff --git a/src/components/SVG/VerticalLayout.js b/src/components/SVG/VerticalLayout.js index 3b43892..0c781f1 100644 --- a/src/components/SVG/VerticalLayout.js +++ b/src/components/SVG/VerticalLayout.js @@ -2,14 +2,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import { List } from 'immutable'; -import Base from './Base'; import style from './style'; + +import reflowable from './reflowable'; import Path from './path'; const connectorMargin = 20; -/** @extends React.PureComponent */ -class VerticalLayout extends Base { +@reflowable +class VerticalLayout extends React.PureComponent { static defaultProps = { withConnectors: false, spacing: 10 diff --git a/src/components/SVG/reflowable.js b/src/components/SVG/reflowable.js new file mode 100644 index 0000000..7ddb9b5 --- /dev/null +++ b/src/components/SVG/reflowable.js @@ -0,0 +1,77 @@ +import { Map } from 'immutable'; + +const reflowable = Component => { + Object.assign(Component.prototype, { + _currentBBox() { + return this.tempBBox ? this.tempBBox : (this.state || {}).bbox; + }, + + setStateAsync(state) { + return new Promise(resolve => { + this.setState(state, resolve); + }); + }, + + async setBBox(box, recalculate = {}) { + const bbox = (this._currentBBox() || Map({ width: 0, height: 0})).withMutations(bbox => { + bbox.merge(box); + + if (!bbox.has('axisY') || recalculate.axisY) { + bbox.set('axisY', bbox.get('height') / 2); + } + + if (!bbox.has('axisX1') || recalculate.axisX1) { + bbox.set('axisX1', 0); + } + + if (!bbox.has('axisX2') || recalculate.axisX2) { + bbox.set('axisX2', bbox.get('width')); + } + }); + + this.tempBBox = bbox; // Want to get the updated bbox while setState is pending + await this.setStateAsync({ bbox }); + this.tempBBox = null; + }, + + getBBox() { + const bbox = this._currentBBox() || Map(); + return { + width: 0, + height: 0, + axisY: 0, + axisX1: 0, + axisX2: 0, + ...bbox.toJS() + }; + }, + + async reflowChildren() { + // No child components + if (this.children === undefined) { + return true; + } + + const reflowed = await Promise.all(this.children.map(c => c.doReflow())); + + return reflowed.reduce((memo, value) => memo || value, false); + }, + + async doReflow() { + const oldBBox = this._currentBBox(); + const shouldReflow = await this.reflowChildren(); + + if (shouldReflow) { + this.reflow(); + } + + return this._currentBBox() !== oldBBox; + }, + + ...Component.prototype + }); + + return Component; +}; + +export default reflowable; diff --git a/yarn.lock b/yarn.lock index fa32320..e81b266 100644 --- a/yarn.lock +++ b/yarn.lock @@ -634,6 +634,10 @@ babel-plugin-syntax-class-properties@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" +babel-plugin-syntax-decorators@^6.1.18: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" + babel-plugin-syntax-dynamic-import@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" @@ -675,6 +679,14 @@ babel-plugin-transform-class-properties@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" +babel-plugin-transform-decorators-legacy@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz#741b58f6c5bce9e6027e0882d9c994f04f366925" + dependencies: + babel-plugin-syntax-decorators "^6.1.18" + babel-runtime "^6.2.0" + babel-template "^6.3.0" + babel-plugin-transform-es2015-arrow-functions@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" @@ -983,14 +995,14 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: +babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: