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 (