Moving app state management code into App context

This commit is contained in:
Jeff Avallone 2019-01-12 12:12:42 -05:00
parent 1f5da0c690
commit 786cd06cd9
2 changed files with 150 additions and 131 deletions

View File

@ -1,5 +1,149 @@
import React from 'react';
import PropTypes from 'prop-types';
import URLSearchParams from '@ungap/url-search-params';
const AppContext = React.createContext();
const toUrl = params => new URLSearchParams(params).toString();
const readURLHash = () => {
const query = document.location.hash.slice(1);
const params = new URLSearchParams(query);
if (params.get('syntax')) {
return {
syntax: params.get('syntax'),
expr: params.get('expr')
};
} else {
// Assuming old-style URL
return {
syntax: 'js',
expr: query || ''
};
}
};
const createSvgLink = async ({ svg }) => {
try {
const type = 'image/svg+xml';
const blob = new Blob([svg], { type });
return {
url: URL.createObjectURL(blob),
label: 'Download SVG',
filename: 'image.svg',
type
};
}
catch (e) {
console.error(e); // eslint-disable-line no-console
}
};
const createPngLink = async ({ svg, width, height }) => {
try {
const type = 'image/png';
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const loader = new Image();
loader.width = canvas.width = Number(width) * 2;
loader.height = canvas.height = Number(height) * 2;
await new Promise(resolve => {
loader.onload = resolve;
loader.src = 'data:image/svg+xml,' + encodeURIComponent(svg);
});
context.drawImage(loader, 0, 0, loader.width, loader.height);
const blob = await new Promise(resolve => canvas.toBlob(resolve, type));
return {
url: URL.createObjectURL(blob),
label: 'Download PNG',
filename: 'image.png',
type
};
}
catch (e) {
console.error(e); // eslint-disable-line no-console
}
};
class AppContextProvider extends React.PureComponent {
state = {}
mutations = {
svgData: async ({ svg, width, height }) => {
if (svg !== this.state.svg) {
this.setState({
svg,
svgLink: await createSvgLink({ svg }),
pngLink: await createPngLink({ svg, width, height })
});
}
},
renderExpr: ({ syntax, expr }) => {
if (expr) {
document.location.hash = toUrl({ syntax, expr });
}
}
}
componentDidMount() {
// Gatsby doesn't have document.location, so readURLHash can't be called
// until here
this.setState(readURLHash());
window.addEventListener('hashchange', this.handleHashChange);
this.handleHashChange();
}
componentWillUnmount() {
window.removeEventListener('hashchange', this.handleHashChange);
}
handleHashChange = () => {
const { expr, syntax } = readURLHash();
if (!expr) {
return;
}
this.setState({
syntax,
expr,
permalinkUrl: document.location.toString()
});
if (this.props.onExpressionChange) {
this.props.onExpressionChange({ syntax, expr });
}
}
render() {
const { children } = this.props;
const context = {
...this.state,
...this.mutations
};
return <AppContext.Provider value={ context }>
{ children }
</AppContext.Provider>;
}
}
AppContextProvider.propTypes = {
onExpressionChange: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired
};
export { AppContextProvider };
export default AppContext;

View File

@ -1,151 +1,26 @@
import React from 'react';
import URLSearchParams from '@ungap/url-search-params';
import AppContext from 'components/App/context';
import { AppContextProvider } from 'components/App/context';
import Form from 'components/Form';
import Loader from 'components/Loader';
import Message from 'components/Message';
import SVG from 'components/SVG';
const toUrl = params => new URLSearchParams(params).toString();
const readURLHash = () => {
const query = document.location.hash.slice(1);
const params = new URLSearchParams(query);
if (params.get('syntax')) {
return {
syntax: params.get('syntax'),
expr: params.get('expr')
};
} else {
// Assuming old-style URL
return {
syntax: 'js',
expr: query || ''
};
}
};
const createSvgLink = async ({ svg }) => {
try {
const type = 'image/svg+xml';
const blob = new Blob([svg], { type });
return {
url: URL.createObjectURL(blob),
label: 'Download SVG',
filename: 'image.svg',
type
};
}
catch (e) {
console.error(e); // eslint-disable-line no-console
}
};
const createPngLink = async ({ svg, width, height }) => {
try {
const type = 'image/png';
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const loader = new Image();
loader.width = canvas.width = Number(width) * 2;
loader.height = canvas.height = Number(height) * 2;
await new Promise(resolve => {
loader.onload = resolve;
loader.src = 'data:image/svg+xml,' + encodeURIComponent(svg);
});
context.drawImage(loader, 0, 0, loader.width, loader.height);
const blob = await new Promise(resolve => canvas.toBlob(resolve, type));
return {
url: URL.createObjectURL(blob),
label: 'Download PNG',
filename: 'image.png',
type
};
}
catch (e) {
console.error(e); // eslint-disable-line no-console
}
};
class App extends React.PureComponent {
state={}
componentDidMount() {
// Gatsby doesn't have document.location, so readURLHash can't be called
// until here
this.setState(readURLHash());
window.addEventListener('hashchange', this.handleHashChange);
this.handleHashChange();
}
componentWillUnmount() {
window.removeEventListener('hashchange', this.handleHashChange);
}
handleHashChange = () => {
const { expr, syntax } = readURLHash();
if (!expr) {
return;
}
this.setState({
syntax,
expr,
permalinkUrl: document.location.toString()
});
handleRender = ({ syntax, expr }) => {
console.log('Render:', syntax, expr); // eslint-disable-line no-console
this.setState({ syntax, expr });
}
handleRetry = event => {
event.preventDefault();
this.handleHashChange();
}
renderExpr = ({ expr, syntax }) => {
if (expr) {
document.location.hash = toUrl({ syntax, expr });
}
}
svgData = async ({ svg, width, height }) => {
if (svg !== this.state.svg) {
this.setState({
svg,
svgLink: await createSvgLink({ svg }),
pngLink: await createPngLink({ svg, width, height })
});
}
this.handleRender(this.state);
}
render() {
const {
svgLink,
pngLink,
permalinkUrl,
expr,
syntax
} = this.state;
const context = {
svgLink,
pngLink,
permalinkUrl,
expr,
syntax,
renderExpr: this.renderExpr,
svgData: this.svgData
};
return <AppContext.Provider value={ context }>
return <AppContextProvider onExpressionChange={ this.handleRender }>
<Form />
<Loader />
@ -156,7 +31,7 @@ class App extends React.PureComponent {
</Message>
<SVG />
</AppContext.Provider>;
</AppContextProvider>;
}
}