From 786cd06cd94cb3d702bc3247769da42a56fb40b0 Mon Sep 17 00:00:00 2001 From: Jeff Avallone Date: Sat, 12 Jan 2019 12:12:42 -0500 Subject: [PATCH] Moving app state management code into App context --- src/components/App/context.js | 144 ++++++++++++++++++++++++++++++++++ src/components/App/index.js | 137 ++------------------------------ 2 files changed, 150 insertions(+), 131 deletions(-) diff --git a/src/components/App/context.js b/src/components/App/context.js index e4dab10..0207df5 100644 --- a/src/components/App/context.js +++ b/src/components/App/context.js @@ -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 + { children } + ; + } +} + +AppContextProvider.propTypes = { + onExpressionChange: PropTypes.func, + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node + ]).isRequired +}; + +export { AppContextProvider }; export default AppContext; diff --git a/src/components/App/index.js b/src/components/App/index.js index 009c171..724ce63 100644 --- a/src/components/App/index.js +++ b/src/components/App/index.js @@ -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 + return
@@ -156,7 +31,7 @@ class App extends React.PureComponent { - ; + ; } }