From 33d4f8f88542273649e23082154d2f13f5a69542 Mon Sep 17 00:00:00 2001 From: Przemek Wiech Date: Thu, 28 Feb 2019 23:19:27 +0100 Subject: [PATCH] Merged ChartView component into App to push state up. Hide "print" and "download" buttons when not displaying a chart. --- src/app.tsx | 183 ++++++++++++++++++++++++++++++++++++++++----- src/chart_view.tsx | 168 ----------------------------------------- src/index.tsx | 5 +- src/top_bar.tsx | 51 +++++++------ 4 files changed, 196 insertions(+), 211 deletions(-) delete mode 100644 src/chart_view.tsx diff --git a/src/app.tsx b/src/app.tsx index 49aa7a0..8f3a0c4 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,42 +1,185 @@ +import * as queryString from 'query-string'; import * as React from 'react'; -import {ChartView} from './chart_view'; -import {HashRouter as Router, Route, RouteComponentProps, Switch} from 'react-router-dom'; +import {Chart} from './chart'; +import {getSelection, loadFromUrl, loadGedcom} from './load_data'; +import {IndiInfo, JsonGedcomData} from 'topola'; import {Intro} from './intro'; +import {Loader, Message} from 'semantic-ui-react'; +import {Route, RouteComponentProps, Switch, Redirect} from 'react-router-dom'; import {TopBar} from './top_bar'; -export class App extends React.Component<{}, {}> { - chartViewRef?: ChartView; +/** Shows an error message. */ +export function ErrorMessage(props: {message: string}) { + return ( + + Failed to load file +

{props.message}

+
+ ); +} + +interface State { + /** Loaded data. */ + data?: JsonGedcomData; + /** Selected individual. */ + selection?: IndiInfo; + /** Hash of the GEDCOM contents. */ + hash?: string; + /** Error to display. */ + error?: string; + /** True if currently loading. */ + loading: boolean; + /** URL of the data that is loaded or is being loaded. */ + url?: string; +} + +export class App extends React.Component { + state: State = {loading: false}; + chartRef: Chart | null = null; + + private isNewData( + hash: string | undefined, + url: string | undefined, + ): boolean { + return ( + !!(hash && hash !== this.state.hash) || !!(url && this.state.url !== url) + ); + } + + componentDidMount() { + this.componentDidUpdate(); + } + + componentDidUpdate() { + if (this.props.location.pathname !== '/view') { + return; + } + const gedcom = this.props.location.state && this.props.location.state.data; + const search = queryString.parse(this.props.location.search); + const getParam = (name: string) => { + const value = search[name]; + return typeof value === 'string' ? value : undefined; + }; + const url = getParam('url'); + const indi = getParam('indi'); + const parsedGen = Number(getParam('gen')); + const generation = !isNaN(parsedGen) ? parsedGen : undefined; + const hash = getParam('file'); + const handleCors = getParam('handleCors') !== 'false'; + + if (!url && !hash) { + this.props.history.replace({pathname: '/'}); + } else if (this.isNewData(hash, url)) { + const loadedData = hash + ? loadGedcom(hash, gedcom) + : loadFromUrl(url!, handleCors); + loadedData.then( + (data) => { + // Set state with data. + this.setState( + Object.assign({}, this.state, { + data, + hash, + selection: getSelection(data, indi, generation), + error: undefined, + loading: false, + url, + }), + ); + }, + (error) => { + // Set error state. + this.setState( + Object.assign({}, this.state, { + error: error.message, + loading: false, + }), + ); + }, + ); + // Set loading state. + this.setState( + Object.assign({}, this.state, { + data: undefined, + selection: undefined, + hash, + error: undefined, + loading: true, + url, + }), + ); + } else if (this.state.data && this.state.selection) { + // Update selection if it has changed in the URL. + const selection = getSelection(this.state.data, indi, generation); + if ( + this.state.selection.id !== selection.id || + this.state.selection.generation !== selection.generation + ) { + this.setState( + Object.assign({}, this.state, { + selection, + }), + ); + } + } + } + + /** + * Called when the user clicks an individual box in the chart. + * Updates the browser URL. + */ + private onSelection = (selection: IndiInfo) => { + const location = this.props.location; + const search = queryString.parse(location.search); + search.indi = selection.id; + search.gen = String(selection.generation); + location.search = queryString.stringify(search); + this.props.history.push(location); + }; + + private renderMainArea = () => { + if (this.state.data && this.state.selection) { + return ( + (this.chartRef = ref)} + /> + ); + } + if (this.state.error) { + return ; + } + return ; + }; render() { return ( - <> ( + render={(props: RouteComponentProps) => ( this.chartViewRef && this.chartViewRef.print()} - onDownloadSvg={() => - this.chartViewRef && this.chartViewRef.downloadSvg() - } - onDownloadPng={() => - this.chartViewRef && this.chartViewRef.downloadPng() + showingChart={ + !!( + this.props.history.location.pathname === '/view' && + this.state.data && + this.state.selection + ) } + onPrint={() => this.chartRef && this.chartRef.print()} + onDownloadSvg={() => this.chartRef && this.chartRef.downloadSvg()} + onDownloadPng={() => this.chartRef && this.chartRef.downloadPng()} /> )} /> - ( - (this.chartViewRef = ref!)} /> - )} - /> + + - ); } } diff --git a/src/chart_view.tsx b/src/chart_view.tsx deleted file mode 100644 index 4f82bb7..0000000 --- a/src/chart_view.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import * as queryString from 'query-string'; -import * as React from 'react'; -import {Chart} from './chart'; -import {IndiInfo, JsonGedcomData} from 'topola'; -import {Loader, Message} from 'semantic-ui-react'; -import {RouteComponentProps} from 'react-router-dom'; -import {getSelection, loadFromUrl, loadGedcom} from './load_data'; - -/** Shows an error message. */ -export function ErrorMessage(props: {message: string}) { - return ( - - Failed to load file -

{props.message}

-
- ); -} - -interface State { - /** Loaded data. */ - data?: JsonGedcomData; - /** Selected individual. */ - selection?: IndiInfo; - /** Hash of the GEDCOM contents. */ - hash?: string; - /** Error to display. */ - error?: string; - /** True if currently loading. */ - loading: boolean; - /** URL of the data that is loaded or is being loaded. */ - url?: string; -} - -/** The main area of the application dedicated for rendering the family chart. */ -export class ChartView extends React.Component { - state: State = {loading: false}; - chartRef: Chart | null = null; - - /** - * Called when the user clicks an individual box in the chart. - * Updates the browser URL. - */ - onSelection = (selection: IndiInfo) => { - const location = this.props.location; - const search = queryString.parse(location.search); - search.indi = selection.id; - search.gen = String(selection.generation); - location.search = queryString.stringify(search); - this.props.history.push(location); - }; - - isNewData(hash: string | undefined, url: string | undefined): boolean { - return ( - !!(hash && hash !== this.state.hash) || !!(url && this.state.url !== url) - ); - } - - componentDidMount() { - this.componentDidUpdate(); - } - - componentDidUpdate() { - const gedcom = this.props.location.state && this.props.location.state.data; - const search = queryString.parse(this.props.location.search); - const getParam = (name: string) => { - const value = search[name]; - return typeof value === 'string' ? value : undefined; - }; - const url = getParam('url'); - const indi = getParam('indi'); - const parsedGen = Number(getParam('gen')); - const generation = !isNaN(parsedGen) ? parsedGen : undefined; - const hash = getParam('file'); - const handleCors = getParam('handleCors') !== 'false'; - - if (!url && !hash) { - this.props.history.replace({pathname: '/'}); - } else if (this.isNewData(hash, url)) { - const loadedData = hash - ? loadGedcom(hash, gedcom) - : loadFromUrl(url!, handleCors); - loadedData.then( - (data) => { - // Set state with data. - this.setState( - Object.assign({}, this.state, { - data, - hash, - selection: getSelection(data, indi, generation), - error: undefined, - loading: false, - url, - }), - ); - }, - (error) => { - // Set error state. - this.setState( - Object.assign({}, this.state, { - error: error.message, - loading: false, - }), - ); - }, - ); - // Set loading state. - this.setState( - Object.assign({}, this.state, { - data: undefined, - selection: undefined, - hash, - error: undefined, - loading: true, - url, - }), - ); - } else if (this.state.data && this.state.selection) { - // Update selection if it has changed in the URL. - const selection = getSelection(this.state.data, indi, generation); - if ( - this.state.selection.id !== selection.id || - this.state.selection.generation !== selection.generation - ) { - this.setState( - Object.assign({}, this.state, { - selection, - }), - ); - } - } - } - - render() { - if (this.state.data && this.state.selection) { - return ( - (this.chartRef = ref)} - /> - ); - } - if (this.state.error) { - return ; - } - return ; - } - - /** Shows the print dialog to print the currently displayed chart. */ - print() { - if (this.chartRef) { - this.chartRef.print(); - } - } - - downloadSvg() { - if (this.chartRef) { - this.chartRef.downloadSvg(); - } - } - - downloadPng() { - if (this.chartRef) { - this.chartRef.downloadPng(); - } - } -} diff --git a/src/index.tsx b/src/index.tsx index 2693de1..b030b1e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,6 +6,7 @@ import messages_pl from './translations/pl.json'; import {addLocaleData} from 'react-intl'; import {App} from './app'; import {detect} from 'detect-browser'; +import {HashRouter as Router, Route} from 'react-router-dom'; import {IntlProvider} from 'react-intl'; import './index.css'; import 'semantic-ui-css/semantic.min.css'; @@ -30,7 +31,9 @@ if (browser && browser.name === 'ie') { } else { ReactDOM.render( - + + + , document.querySelector('#root'), ); diff --git a/src/top_bar.tsx b/src/top_bar.tsx index 768b9e6..95f0380 100644 --- a/src/top_bar.tsx +++ b/src/top_bar.tsx @@ -22,6 +22,7 @@ interface State { } interface Props { + showingChart: boolean; onPrint: () => void; onDownloadSvg: () => void; onDownloadPng: () => void; @@ -144,6 +145,33 @@ export class TopBar extends React.Component< ); + const chartMenus = this.props.showingChart ? ( + <> + this.props.onPrint()}> + + + + + + + + } + className="item" + > + + this.props.onDownloadPng()}> + + + this.props.onDownloadSvg()}> + + + + + + ) : null; + return ( @@ -174,28 +202,7 @@ export class TopBar extends React.Component< /> - this.props.onPrint()}> - - - - - - - - } - className="item" - > - - this.props.onDownloadPng()}> - - - this.props.onDownloadSvg()}> - - - - + {chartMenus}