Starting to add SVG components
This commit is contained in:
parent
57dbea8c40
commit
3378c68aed
@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import PlaceholderIcon from 'react-feather/dist/icons/file-text';
|
import SVG from 'rendering/SVG';
|
||||||
|
import Text from 'rendering/Text';
|
||||||
|
|
||||||
import style from './style.module.css';
|
import style from './style.module.css';
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ class Render extends React.PureComponent {
|
|||||||
|
|
||||||
svgContainer = React.createRef()
|
svgContainer = React.createRef()
|
||||||
|
|
||||||
provideSVGData() {
|
provideSVGData = () => {
|
||||||
if (!this.svgContainer.current) {
|
if (!this.svgContainer.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -26,23 +27,14 @@ class Render extends React.PureComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.provideSVGData();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.provideSVGData();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { expr } = this.props;
|
const { expr } = this.props;
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('Render:', this.constructor.name, expr);
|
|
||||||
|
|
||||||
// Demo rendering for now
|
// Demo rendering for now
|
||||||
return <div className={ style.render } ref={ this.svgContainer }>
|
return <div className={ style.render } ref={ this.svgContainer }>
|
||||||
<PlaceholderIcon />
|
<SVG onReflow={ this.provideSVGData }>
|
||||||
|
<Text>{ this.constructor.name } => { expr }</Text>
|
||||||
|
</SVG>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
71
src/rendering/SVG/index.js
Normal file
71
src/rendering/SVG/index.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import * as style from './style';
|
||||||
|
|
||||||
|
const namespaceProps = {
|
||||||
|
'xmlns': 'http://www.w3.org/2000/svg',
|
||||||
|
'xmlns:cc': 'http://creativecommons.org/ns#',
|
||||||
|
'xmlns:rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
||||||
|
};
|
||||||
|
/* eslint-disable max-len */
|
||||||
|
const metadata = `<rdf:rdf>
|
||||||
|
<cc:license rdf:about="http://creativecommons.org/licenses/by/3.0/">
|
||||||
|
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"></cc:permits>
|
||||||
|
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"></cc:permits>
|
||||||
|
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"></cc:requires>
|
||||||
|
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"></cc:requires>
|
||||||
|
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"></cc:permits>
|
||||||
|
</cc:license>
|
||||||
|
</rdf:rdf>`;
|
||||||
|
/* eslint-enable max-len */
|
||||||
|
|
||||||
|
class SVG extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
onReflow: PropTypes.func,
|
||||||
|
children: PropTypes.node,
|
||||||
|
padding: PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
padding: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
width: 0,
|
||||||
|
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 svgProps = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
viewBox: [0, 0, width, height].join(' '),
|
||||||
|
style: style.image,
|
||||||
|
...namespaceProps
|
||||||
|
};
|
||||||
|
|
||||||
|
return <svg { ...svgProps }>
|
||||||
|
<metadata dangerouslySetInnerHTML={{ __html: metadata }}></metadata>
|
||||||
|
<g transform={ `translate(${ padding } ${ padding })` }>
|
||||||
|
{ React.cloneElement(React.Children.only(children), {
|
||||||
|
onReflow: this.handleReflow
|
||||||
|
}) }
|
||||||
|
</g>
|
||||||
|
</svg>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SVG;
|
5
src/rendering/SVG/style.js
Normal file
5
src/rendering/SVG/style.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { white } from 'rendering/style';
|
||||||
|
|
||||||
|
export const image = {
|
||||||
|
backgroundColor: white
|
||||||
|
};
|
70
src/rendering/Text/index.js
Normal file
70
src/rendering/Text/index.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import * as style from './style';
|
||||||
|
|
||||||
|
class Text extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
quoted: PropTypes.bool,
|
||||||
|
onReflow: PropTypes.func,
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node
|
||||||
|
]).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;
|
||||||
|
|
||||||
|
if (!quoted) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<tspan style={ style.quote }>“</tspan>
|
||||||
|
<tspan>{ children }</tspan>
|
||||||
|
<tspan style={ style.quote }>”</tspan>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { transform } = this.state;
|
||||||
|
|
||||||
|
const textProps = {
|
||||||
|
style: style.text,
|
||||||
|
transform,
|
||||||
|
ref: this.textRef
|
||||||
|
};
|
||||||
|
|
||||||
|
return <text { ...textProps }>
|
||||||
|
{ this.renderContent() }
|
||||||
|
</text>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Text;
|
8
src/rendering/Text/style.js
Normal file
8
src/rendering/Text/style.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { fontSize, fontFamily, grey } from 'rendering/style';
|
||||||
|
|
||||||
|
export const text = {
|
||||||
|
fontSize, fontFamily
|
||||||
|
};
|
||||||
|
export const quote = {
|
||||||
|
fill: grey
|
||||||
|
};
|
20
src/rendering/style.js
Normal file
20
src/rendering/style.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Styles are in JS instead of CSS so they will be inlined as attributes
|
||||||
|
// instead of served as a CSS file. This is so styles are included in
|
||||||
|
// downloaded SVG files.
|
||||||
|
|
||||||
|
export const green = '#bada55';
|
||||||
|
export const brown = '#6b6659';
|
||||||
|
export const tan = '#cbcbba';
|
||||||
|
export const black = '#000';
|
||||||
|
export const grey = '#908c83';
|
||||||
|
export const white = '#fff';
|
||||||
|
export const blue = '#dae9e5';
|
||||||
|
|
||||||
|
export const fontFamily = 'Arial';
|
||||||
|
export const fontSize = '16px';
|
||||||
|
export const fontSizeSmall = '12px';
|
||||||
|
|
||||||
|
export const strokeBase = {
|
||||||
|
strokeWidth: '2px',
|
||||||
|
stroke: black
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user