diff --git a/src/components/App/__snapshots__/test.js.snap b/src/components/App/__snapshots__/test.js.snap index c84d246..6304af7 100644 --- a/src/components/App/__snapshots__/test.js.snap +++ b/src/components/App/__snapshots__/test.js.snap @@ -22,8 +22,8 @@ exports[`App removing rendered expression 1`] = ` `; @@ -139,8 +139,8 @@ exports[`App rendering an expression 3`] = ` `; @@ -167,8 +167,8 @@ exports[`App rendering image details 1`] = ` `; @@ -201,8 +201,8 @@ exports[`App rendering image details 2`] = ` /> `; diff --git a/src/components/App/index.js b/src/components/App/index.js index df11c78..95c3683 100644 --- a/src/components/App/index.js +++ b/src/components/App/index.js @@ -73,13 +73,13 @@ class App extends React.PureComponent { `syntax/${ syntax }` ); - const parsed = syntaxModule.parse(expr); + const exprData = syntaxModule.layout(syntaxModule.parse(expr)); this.setState({ loading: false, render: { syntax, - parsed, + exprData, Component: syntaxModule.Render } }); @@ -119,7 +119,7 @@ class App extends React.PureComponent { imageDetails, render: { syntax: renderSyntax, - parsed, + exprData, Component } } = this.state; @@ -137,7 +137,7 @@ class App extends React.PureComponent { }; const renderProps = { onRender: this.handleSvg, - parsed + data: exprData }; const doRender = renderSyntax === syntax; diff --git a/src/components/App/test.js b/src/components/App/test.js index 3d66b22..982ff1c 100644 --- a/src/components/App/test.js +++ b/src/components/App/test.js @@ -6,6 +6,7 @@ import { App } from 'components/App'; jest.mock('syntax/js', () => ({ parse: expr => `PARSED(${ expr })`, + layout: parsed => `LAYOUT(${ parsed })`, Render: () => '' })); diff --git a/src/components/Render/index.js b/src/components/Render/index.js index 1e1c790..73ac638 100644 --- a/src/components/Render/index.js +++ b/src/components/Render/index.js @@ -28,13 +28,21 @@ const render = (data, extraProps) => { class Render extends React.PureComponent { static propTypes = { - parsed: PropTypes.object.isRequired, + data: PropTypes.object.isRequired, onRender: PropTypes.func.isRequired } svgContainer = React.createRef() - provideSVGData = () => { + componentDidMount() { + this.provideSVGData(); + } + + componentDidUpdate() { + this.provideSVGData(); + } + + provideSVGData() { if (!this.svgContainer.current) { return; } @@ -48,10 +56,10 @@ class Render extends React.PureComponent { } render() { - const { parsed } = this.props; + const { data } = this.props; return
- { render(parsed, { onReflow: this.provideSVGData }) } + { render(data, { onReflow: this.provideSVGData }) }
; } } diff --git a/src/layout.js b/src/layout.js new file mode 100644 index 0000000..6b05543 --- /dev/null +++ b/src/layout.js @@ -0,0 +1,22 @@ +import SVG from 'rendering/SVG/layout'; +import Text from 'rendering/Text/layout'; + +const nodeTypes = { + SVG, + Text +}; + +const layout = data => { + if (typeof data == 'string') { + return data; + } + + const { type } = data; + + return nodeTypes[type]({ + props: {}, + ...data + }); +}; + +export default layout; diff --git a/src/rendering/SVG/index.js b/src/rendering/SVG/index.js index 4db45d6..1eb24bd 100644 --- a/src/rendering/SVG/index.js +++ b/src/rendering/SVG/index.js @@ -24,7 +24,9 @@ class SVG extends React.PureComponent { static propTypes = { onReflow: PropTypes.func, children: PropTypes.node, - padding: PropTypes.number + padding: PropTypes.number, + innerWidth: PropTypes.number, + innerHeight: PropTypes.number } static defaultProps = { @@ -36,18 +38,11 @@ class SVG extends React.PureComponent { height: 0 } - handleReflow = box => { - const { padding } = this.props; - - this.setState({ - width: Math.round(box.width + 2 * padding), - height: Math.round(box.height + 2 * padding) - }, () => this.props.onReflow(this)); - } - render() { - const { width, height } = this.state; - const { padding, children } = this.props; + const { padding, innerWidth, innerHeight, children } = this.props; + + const width = Math.round(innerWidth + 2 * padding); + const height = Math.round(innerHeight + 2 * padding); const svgProps = { width, @@ -60,9 +55,7 @@ class SVG extends React.PureComponent { return - { React.cloneElement(React.Children.only(children), { - onReflow: this.handleReflow - }) } + { children } ; } diff --git a/src/rendering/SVG/layout.js b/src/rendering/SVG/layout.js new file mode 100644 index 0000000..1caf46d --- /dev/null +++ b/src/rendering/SVG/layout.js @@ -0,0 +1,12 @@ +import layout from 'layout'; + +const layoutSVG = data => { + const child = layout(data.children[0]); + + data.props.innerWidth = child.box.width; + data.props.innerHeight = child.box.height; + + return data; +}; + +export default layoutSVG; diff --git a/src/rendering/Text/index.js b/src/rendering/Text/index.js index 0b446fd..118ebcb 100644 --- a/src/rendering/Text/index.js +++ b/src/rendering/Text/index.js @@ -6,6 +6,7 @@ import * as style from './style'; class Text extends React.PureComponent { static propTypes = { quoted: PropTypes.bool, + transform: PropTypes.string, onReflow: PropTypes.func, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), @@ -13,31 +14,6 @@ class Text extends React.PureComponent { ]).isRequired } - state = { - transform: '' - } - - textRef = React.createRef() - - componentDidMount() { - this.reflow(); - } - - componentDidUpdate() { - this.reflow(); - } - - reflow() { - const box = this.textRef.current.getBBox(); - const transform = `translate(${ -box.x } ${ -box.y })`; - - if (transform === this.state.transform) { - return; // No update required - } - - this.setState({ transform }, () => this.props.onReflow(box)); - } - renderContent() { const { children, quoted } = this.props; @@ -53,12 +29,11 @@ class Text extends React.PureComponent { } render() { - const { transform } = this.state; + const { transform } = this.props; const textProps = { style: style.text, - transform, - ref: this.textRef + transform }; return diff --git a/src/rendering/Text/layout.js b/src/rendering/Text/layout.js new file mode 100644 index 0000000..cfe82e1 --- /dev/null +++ b/src/rendering/Text/layout.js @@ -0,0 +1,27 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import Text from 'rendering/Text'; + +const layoutText = data => { + const container = document.createElement('div'); + document.body.appendChild(container); + + ReactDOM.render( + + { data.children } + , + container); + + const box = container.querySelector('svg > text').getBBox(); + document.body.removeChild(container); + + data.box = { + width: box.width, + height: box.height + }; + data.props.transform = `translate(${ -box.x } ${ -box.y })`; + return data; +}; + +export default layoutText; diff --git a/src/syntax/js.js b/src/syntax/js.js index 7214bd3..a3c68bd 100644 --- a/src/syntax/js.js +++ b/src/syntax/js.js @@ -1,4 +1,5 @@ import Render from 'components/Render'; +import layout from 'layout'; const parse = expr => { return { @@ -19,5 +20,6 @@ const parse = expr => { export { parse, + layout, Render }; diff --git a/src/syntax/pcre.js b/src/syntax/pcre.js index a078762..997046a 100644 --- a/src/syntax/pcre.js +++ b/src/syntax/pcre.js @@ -1,4 +1,5 @@ import Render from 'components/Render'; +import layout from 'layout'; const parse = expr => { return { @@ -19,5 +20,6 @@ const parse = expr => { export { parse, + layout, Render };