diff --git a/src/app.tsx b/src/app.tsx
index 3d45076..f39e80a 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -3,24 +3,57 @@ import * as React from 'react';
import {analyticsEvent} from './analytics';
import {Chart, ChartType} from './chart';
import {Details} from './details';
+import {FormattedMessage} from 'react-intl';
import {getSelection, loadFromUrl, loadGedcom} from './load_data';
+import {getSoftware, TopolaData} from './gedcom_util';
import {IndiInfo} from 'topola';
+import {intlShape} from 'react-intl';
import {Intro} from './intro';
-import {Loader, Message, Responsive} from 'semantic-ui-react';
+import {Loader, Message, Portal, Responsive} from 'semantic-ui-react';
import {Redirect, Route, RouteComponentProps, Switch} from 'react-router-dom';
import {TopBar} from './top_bar';
-import {TopolaData, getSoftware} from './gedcom_util';
-/** Shows an error message. */
-export function ErrorMessage(props: {message: string}) {
+/** Shows an error message in the middle of the screen. */
+function ErrorMessage(props: {message?: string}) {
return (
- Failed to load file
+
+
+
{props.message}
);
}
+interface ErrorPopupProps {
+ message?: string;
+ open: boolean;
+ onDismiss: () => void;
+}
+
+/**
+ * Shows a dismissable error message in the bottom left corner of the screen.
+ */
+function ErrorPopup(props: ErrorPopupProps) {
+ return (
+
+
+
+
+
+ {props.message}
+
+
+ );
+}
+
/**
* Message types used in embedded mode.
* When the parent is ready to receive messages, it sends PARENT_READY.
@@ -65,6 +98,8 @@ interface State {
standalone: boolean;
/** Type of displayed chart. */
chartType: ChartType;
+ /** Whether to show the error popup. */
+ showErrorPopup: boolean;
}
export class App extends React.Component {
@@ -73,9 +108,15 @@ export class App extends React.Component {
embedded: false,
standalone: true,
chartType: ChartType.Hourglass,
+ showErrorPopup: false,
};
chartRef: Chart | null = null;
+ /** Make intl appear in this.context. */
+ static contextTypes = {
+ intl: intlShape,
+ };
+
private isNewData(
hash: string | undefined,
url: string | undefined,
@@ -277,14 +318,45 @@ export class App extends React.Component {
this.chartRef && this.chartRef.print();
};
- private onDownloadPdf = () => {
+ private showErrorPopup(message: string) {
+ this.setState(
+ Object.assign({}, this.state, {
+ showErrorPopup: true,
+ error: message,
+ }),
+ );
+ }
+
+ private onDownloadPdf = async () => {
analyticsEvent('download_pdf');
- this.chartRef && this.chartRef.downloadPdf();
+ try {
+ this.chartRef && (await this.chartRef.downloadPdf());
+ } catch (e) {
+ this.showErrorPopup(
+ this.context.intl.formatMessage({
+ id: 'error.failed_pdf',
+ defaultMessage:
+ 'Failed to generate PDF file.' +
+ ' Please try with a smaller diagram or download an SVG file.',
+ }),
+ );
+ }
};
- private onDownloadPng = () => {
+ private onDownloadPng = async () => {
analyticsEvent('download_png');
- this.chartRef && this.chartRef.downloadPng();
+ try {
+ this.chartRef && (await this.chartRef.downloadPng());
+ } catch (e) {
+ this.showErrorPopup(
+ this.context.intl.formatMessage({
+ id: 'error.failed_png',
+ defaultMessage:
+ 'Failed to generate PNG file.' +
+ ' Please try with a smaller diagram or download an SVG file.',
+ }),
+ );
+ }
};
private onDownloadSvg = () => {
@@ -292,10 +364,23 @@ export class App extends React.Component {
this.chartRef && this.chartRef.downloadSvg();
};
+ onDismissErrorPopup = () => {
+ this.setState(
+ Object.assign({}, this.state, {
+ showErrorPopup: false,
+ }),
+ );
+ };
+
private renderMainArea = () => {
if (this.state.data && this.state.selection) {
return (
+