Commit b299d32f authored by Jeff Avallone's avatar Jeff Avallone

Adding a layout pass to SVG image components

text nodes are the only elements that need to be "measured". The
dimensions of all other image components can be determined based on the
dimensions of their children. This adds a pre-rendering pass to work out
dimensions so multiple renders don't need to happen
parent 21c39275
Pipeline #44927639 passed with stages
in 4 minutes and 55 seconds
......@@ -22,8 +22,8 @@ exports[`App removing rendered expression 1`] = `
<LoadNamespace(FormActions) />
</LoadNamespace(Form)>
<Render
data="LAYOUT(PARSED(test expression))"
onRender={[Function]}
parsed="PARSED(test expression)"
/>
</Fragment>
`;
......@@ -139,8 +139,8 @@ exports[`App rendering an expression 3`] = `
<LoadNamespace(FormActions) />
</LoadNamespace(Form)>
<Render
data="LAYOUT(PARSED(test expression))"
onRender={[Function]}
parsed="PARSED(test expression)"
/>
</Fragment>
`;
......@@ -167,8 +167,8 @@ exports[`App rendering image details 1`] = `
<LoadNamespace(FormActions) />
</LoadNamespace(Form)>
<Render
data="LAYOUT(PARSED(test expression))"
onRender={[Function]}
parsed="PARSED(test expression)"
/>
</Fragment>
`;
......@@ -201,8 +201,8 @@ exports[`App rendering image details 2`] = `
/>
</LoadNamespace(Form)>
<Render
data="LAYOUT(PARSED(test expression))"
onRender={[Function]}
parsed="PARSED(test expression)"
/>
</Fragment>
`;
......
......@@ -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;
......
......@@ -6,6 +6,7 @@ import { App } from 'components/App';
jest.mock('syntax/js', () => ({
parse: expr => `PARSED(${ expr })`,
layout: parsed => `LAYOUT(${ parsed })`,
Render: () => ''
}));
......
......@@ -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 <div className={ style.render } ref={ this.svgContainer }>
{ render(parsed, { onReflow: this.provideSVGData }) }
{ render(data, { onReflow: this.provideSVGData }) }
</div>;
}
}
......
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;
......@@ -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 <svg { ...svgProps }>
<metadata dangerouslySetInnerHTML={{ __html: metadata }}></metadata>
<g transform={ `translate(${ padding } ${ padding })` }>
{ React.cloneElement(React.Children.only(children), {
onReflow: this.handleReflow
}) }
{ children }
</g>
</svg>;
}
......
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;
......@@ -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 <text { ...textProps }>
......
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(
<svg width="0" height="0" viewBox="0 0 0 0">
<Text { ...data.props }>{ data.children }</Text>
</svg>,
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;
import Render from 'components/Render';
import layout from 'layout';
const parse = expr => {
return {
......@@ -19,5 +20,6 @@ const parse = expr => {
export {
parse,
layout,
Render
};
import Render from 'components/Render';
import layout from 'layout';
const parse = expr => {
return {
......@@ -19,5 +20,6 @@ const parse = expr => {
export {
parse,
layout,
Render
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment