mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-05-26 15:16:14 +00:00
Refactoring: slowly detaching data loading code from the main app
This commit is contained in:
318
src/app.tsx
318
src/app.tsx
@@ -83,37 +83,80 @@ interface GedcomMessage extends EmbeddedMessage {
|
|||||||
gedcom?: string;
|
gedcom?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Supported data sources. */
|
||||||
|
enum DataSourceEnum {
|
||||||
|
UPLOADED,
|
||||||
|
GEDCOM_URL,
|
||||||
|
WIKITREE,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UploadSourceSpec {
|
||||||
|
source: DataSourceEnum.UPLOADED;
|
||||||
|
gedcom?: string;
|
||||||
|
/** Hash of the GEDCOM contents. */
|
||||||
|
hash: string;
|
||||||
|
images?: Map<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UrlSourceSpec {
|
||||||
|
source: DataSourceEnum.GEDCOM_URL;
|
||||||
|
/** URL of the data that is loaded or is being loaded. */
|
||||||
|
url: string;
|
||||||
|
handleCors: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WikiTreeSourceSpec {
|
||||||
|
source: DataSourceEnum.WIKITREE;
|
||||||
|
authcode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataSourceSpec = UrlSourceSpec | UploadSourceSpec | WikiTreeSourceSpec;
|
||||||
|
|
||||||
|
/** Source specification together with individual selection. */
|
||||||
|
interface SourceSelection<SourceSpecT> {
|
||||||
|
spec: SourceSpecT;
|
||||||
|
selection?: IndiInfo;
|
||||||
|
}
|
||||||
|
|
||||||
/** Interface encapsulating functions specific for a data source. */
|
/** Interface encapsulating functions specific for a data source. */
|
||||||
interface DataSource {
|
interface DataSource<SourceSpecT> {
|
||||||
/**
|
/**
|
||||||
* Returns true if the application is now loading a completely new data set
|
* Returns true if the application is now loading a completely new data set
|
||||||
* and the existing one should be wiped.
|
* and the existing one should be wiped.
|
||||||
*/
|
*/
|
||||||
isNewData(args: Arguments, state: State): boolean;
|
isNewData(
|
||||||
|
newSource: SourceSelection<SourceSpecT>,
|
||||||
|
oldSource: SourceSelection<SourceSpecT>,
|
||||||
|
data?: TopolaData,
|
||||||
|
): boolean;
|
||||||
/** Loads data from the data source. */
|
/** Loads data from the data source. */
|
||||||
loadData(args: Arguments): Promise<TopolaData>;
|
loadData(spec: SourceSelection<SourceSpecT>): Promise<TopolaData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Files opened from the local computer. */
|
/** Files opened from the local computer. */
|
||||||
class UploadedDataSource implements DataSource {
|
class UploadedDataSource implements DataSource<UploadSourceSpec> {
|
||||||
isNewData(args: Arguments, state: State): boolean {
|
// isNewData(args: Arguments, state: State): boolean {
|
||||||
return (
|
isNewData(
|
||||||
args.hash !== state.hash ||
|
newSource: SourceSelection<UploadSourceSpec>,
|
||||||
!!(
|
oldSource: SourceSelection<UploadSourceSpec>,
|
||||||
args.gedcom &&
|
data?: TopolaData,
|
||||||
state.state !== AppState.LOADING &&
|
): boolean {
|
||||||
state.state !== AppState.SHOWING_CHART
|
return newSource.spec.hash !== oldSource.spec.hash;
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData(args: Arguments): Promise<TopolaData> {
|
async loadData(
|
||||||
|
source: SourceSelection<UploadSourceSpec>,
|
||||||
|
): Promise<TopolaData> {
|
||||||
try {
|
try {
|
||||||
const data = await loadGedcom(args.hash!, args.gedcom, args.images);
|
const data = await loadGedcom(
|
||||||
|
source.spec.hash,
|
||||||
|
source.spec.gedcom,
|
||||||
|
source.spec.images,
|
||||||
|
);
|
||||||
const software = getSoftware(data.gedcom.head);
|
const software = getSoftware(data.gedcom.head);
|
||||||
analyticsEvent('upload_file_loaded', {
|
analyticsEvent('upload_file_loaded', {
|
||||||
event_label: software,
|
event_label: software,
|
||||||
event_value: (args.images && args.images.size) || 0,
|
event_value: (source.spec.images && source.spec.images.size) || 0,
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -124,14 +167,18 @@ class UploadedDataSource implements DataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** GEDCOM file loaded by pointing to a URL. */
|
/** GEDCOM file loaded by pointing to a URL. */
|
||||||
class GedcomUrlDataSource implements DataSource {
|
class GedcomUrlDataSource implements DataSource<UrlSourceSpec> {
|
||||||
isNewData(args: Arguments, state: State): boolean {
|
isNewData(
|
||||||
return args.url !== state.url;
|
newSource: SourceSelection<UrlSourceSpec>,
|
||||||
|
oldSource: SourceSelection<UrlSourceSpec>,
|
||||||
|
data?: TopolaData,
|
||||||
|
): boolean {
|
||||||
|
return newSource.spec.url !== oldSource.spec.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData(args: Arguments): Promise<TopolaData> {
|
async loadData(source: SourceSelection<UrlSourceSpec>): Promise<TopolaData> {
|
||||||
try {
|
try {
|
||||||
const data = await loadFromUrl(args.url!, args.handleCors);
|
const data = await loadFromUrl(source.spec.url, source.spec.handleCors);
|
||||||
const software = getSoftware(data.gedcom.head);
|
const software = getSoftware(data.gedcom.head);
|
||||||
analyticsEvent('upload_file_loaded', {event_label: software});
|
analyticsEvent('upload_file_loaded', {event_label: software});
|
||||||
return data;
|
return data;
|
||||||
@@ -143,17 +190,24 @@ class GedcomUrlDataSource implements DataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Loading data from the WikiTree API. */
|
/** Loading data from the WikiTree API. */
|
||||||
class WikiTreeDataSource implements DataSource {
|
class WikiTreeDataSource implements DataSource<WikiTreeSourceSpec> {
|
||||||
constructor(private intl: InjectedIntl) {}
|
constructor(private intl: InjectedIntl) {}
|
||||||
|
|
||||||
isNewData(args: Arguments, state: State): boolean {
|
isNewData(
|
||||||
if (state.selection && state.selection.id === args.indi) {
|
newSource: SourceSelection<WikiTreeSourceSpec>,
|
||||||
|
oldSource: SourceSelection<WikiTreeSourceSpec>,
|
||||||
|
data?: TopolaData,
|
||||||
|
): boolean {
|
||||||
|
if (!newSource.selection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (oldSource.selection?.id === newSource.selection.id) {
|
||||||
// Selection unchanged -> don't reload.
|
// Selection unchanged -> don't reload.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
state.data &&
|
data &&
|
||||||
state.data.chartData.indis.some((indi) => indi.id === args.indi)
|
data.chartData.indis.some((indi) => indi.id === newSource.selection?.id)
|
||||||
) {
|
) {
|
||||||
// New selection exists in current view -> animate instead of reloading.
|
// New selection exists in current view -> animate instead of reloading.
|
||||||
return false;
|
return false;
|
||||||
@@ -161,9 +215,18 @@ class WikiTreeDataSource implements DataSource {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData(args: Arguments): Promise<TopolaData> {
|
async loadData(
|
||||||
|
source: SourceSelection<WikiTreeSourceSpec>,
|
||||||
|
): Promise<TopolaData> {
|
||||||
|
if (!source.selection) {
|
||||||
|
throw new Error('WikiTree id needs to be provided');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const data = await loadWikiTree(args.indi!, this.intl, args.authcode);
|
const data = await loadWikiTree(
|
||||||
|
source.selection.id,
|
||||||
|
this.intl,
|
||||||
|
source.spec.authcode,
|
||||||
|
);
|
||||||
analyticsEvent('wikitree_loaded');
|
analyticsEvent('wikitree_loaded');
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -173,29 +236,15 @@ class WikiTreeDataSource implements DataSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Supported data sources. */
|
|
||||||
enum DataSourceEnum {
|
|
||||||
UPLOADED,
|
|
||||||
GEDCOM_URL,
|
|
||||||
WIKITREE,
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Arguments passed to the application, primarily through URL parameters. */
|
/** Arguments passed to the application, primarily through URL parameters. */
|
||||||
interface Arguments {
|
interface Arguments {
|
||||||
showSidePanel: boolean;
|
sourceSpec?: DataSourceSpec;
|
||||||
embedded: boolean;
|
selection?: IndiInfo;
|
||||||
url?: string;
|
|
||||||
indi?: string;
|
|
||||||
generation?: number;
|
|
||||||
hash?: string;
|
|
||||||
handleCors: boolean;
|
|
||||||
standalone: boolean;
|
|
||||||
source?: DataSourceEnum;
|
|
||||||
authcode?: string;
|
|
||||||
chartType: ChartType;
|
chartType: ChartType;
|
||||||
gedcom?: string;
|
embedded: boolean;
|
||||||
images?: Map<string, string>;
|
standalone: boolean;
|
||||||
freezeAnimation?: boolean;
|
freezeAnimation?: boolean;
|
||||||
|
showSidePanel: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,40 +258,51 @@ function getArguments(location: H.Location<any>): Arguments {
|
|||||||
return typeof value === 'string' ? value : undefined;
|
return typeof value === 'string' ? value : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsedGen = Number(getParam('gen'));
|
|
||||||
const view = getParam('view');
|
const view = getParam('view');
|
||||||
const chartTypes = new Map<string | undefined, ChartType>([
|
const chartTypes = new Map<string | undefined, ChartType>([
|
||||||
['relatives', ChartType.Relatives],
|
['relatives', ChartType.Relatives],
|
||||||
['fancy', ChartType.Fancy],
|
['fancy', ChartType.Fancy],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hash = getParam('file');
|
const hash = getParam('file');
|
||||||
const url = getParam('url');
|
const url = getParam('url');
|
||||||
const source =
|
var sourceSpec: DataSourceSpec | undefined = undefined;
|
||||||
getParam('source') === 'wikitree'
|
if (getParam('source') === 'wikitree') {
|
||||||
? DataSourceEnum.WIKITREE
|
sourceSpec = {
|
||||||
: hash
|
source: DataSourceEnum.WIKITREE,
|
||||||
? DataSourceEnum.UPLOADED
|
authcode: getParam('?authcode'),
|
||||||
: url
|
};
|
||||||
? DataSourceEnum.GEDCOM_URL
|
} else if (hash) {
|
||||||
: undefined;
|
sourceSpec = {
|
||||||
return {
|
source: DataSourceEnum.UPLOADED,
|
||||||
showSidePanel: getParam('sidePanel') !== 'false', // True by default.
|
hash,
|
||||||
embedded: getParam('embedded') === 'true', // False by default.
|
gedcom: location.state && location.state.data,
|
||||||
url,
|
images: location.state && location.state.images,
|
||||||
indi: getParam('indi'),
|
};
|
||||||
generation: !isNaN(parsedGen) ? parsedGen : undefined,
|
} else if (url) {
|
||||||
hash,
|
sourceSpec = {
|
||||||
handleCors: getParam('handleCors') !== 'false', // True by default.
|
source: DataSourceEnum.GEDCOM_URL,
|
||||||
standalone: getParam('standalone') !== 'false', // True by default.
|
url,
|
||||||
source,
|
handleCors: getParam('handleCors') !== 'false', // True by default.
|
||||||
authcode: getParam('?authcode'),
|
};
|
||||||
freezeAnimation: getParam('freeze') === 'true', // False by default
|
}
|
||||||
|
|
||||||
|
const indi = getParam('indi');
|
||||||
|
const parsedGen = Number(getParam('gen'));
|
||||||
|
const selection = indi
|
||||||
|
? {id: indi, generation: !isNaN(parsedGen) ? parsedGen : 0}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceSpec,
|
||||||
|
selection,
|
||||||
// Hourglass is the default view.
|
// Hourglass is the default view.
|
||||||
chartType: chartTypes.get(view) || ChartType.Hourglass,
|
chartType: chartTypes.get(view) || ChartType.Hourglass,
|
||||||
|
|
||||||
gedcom: location.state && location.state.data,
|
showSidePanel: getParam('sidePanel') !== 'false', // True by default.
|
||||||
images: location.state && location.state.images,
|
embedded: getParam('embedded') === 'true', // False by default.
|
||||||
|
standalone: getParam('standalone') !== 'false', // True by default.
|
||||||
|
freezeAnimation: getParam('freeze') === 'true', // False by default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,12 +323,8 @@ interface State {
|
|||||||
data?: TopolaData;
|
data?: TopolaData;
|
||||||
/** Selected individual. */
|
/** Selected individual. */
|
||||||
selection?: IndiInfo;
|
selection?: IndiInfo;
|
||||||
/** Hash of the GEDCOM contents. */
|
|
||||||
hash?: string;
|
|
||||||
/** Error to display. */
|
/** Error to display. */
|
||||||
error?: string;
|
error?: string;
|
||||||
/** URL of the data that is loaded or is being loaded. */
|
|
||||||
url?: string;
|
|
||||||
/** Whether the side panel is shown. */
|
/** Whether the side panel is shown. */
|
||||||
showSidePanel?: boolean;
|
showSidePanel?: boolean;
|
||||||
/** Whether the app is in embedded mode, i.e. embedded in an iframe. */
|
/** Whether the app is in embedded mode, i.e. embedded in an iframe. */
|
||||||
@@ -279,8 +335,8 @@ interface State {
|
|||||||
chartType: ChartType;
|
chartType: ChartType;
|
||||||
/** Whether to show the error popup. */
|
/** Whether to show the error popup. */
|
||||||
showErrorPopup: boolean;
|
showErrorPopup: boolean;
|
||||||
/** Source of the data. */
|
/** Specification of the source of the data. */
|
||||||
source?: DataSourceEnum;
|
sourceSpec?: DataSourceSpec;
|
||||||
/** Freeze animations after initial chart render. */
|
/** Freeze animations after initial chart render. */
|
||||||
freezeAnimation?: boolean;
|
freezeAnimation?: boolean;
|
||||||
}
|
}
|
||||||
@@ -300,13 +356,6 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
intl: intlShape,
|
intl: intlShape,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Mapping from data source identifier to data source handler functions. */
|
|
||||||
private readonly dataSources = new Map([
|
|
||||||
[DataSourceEnum.UPLOADED, new UploadedDataSource()],
|
|
||||||
[DataSourceEnum.GEDCOM_URL, new GedcomUrlDataSource()],
|
|
||||||
[DataSourceEnum.WIKITREE, new WikiTreeDataSource(this.context.intl)],
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** Sets the state with a new individual selection and chart type. */
|
/** Sets the state with a new individual selection and chart type. */
|
||||||
private updateDisplay(
|
private updateDisplay(
|
||||||
selection: IndiInfo,
|
selection: IndiInfo,
|
||||||
@@ -368,6 +417,58 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
this.componentDidUpdate();
|
this.componentDidUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly uploadedDataSource = new UploadedDataSource();
|
||||||
|
private readonly gedcomUrlDataSource = new GedcomUrlDataSource();
|
||||||
|
private readonly wikiTreeDataSource = new WikiTreeDataSource(
|
||||||
|
this.context.intl,
|
||||||
|
);
|
||||||
|
|
||||||
|
private isNewData(sourceSpec: DataSourceSpec, selection?: IndiInfo) {
|
||||||
|
if (
|
||||||
|
!this.state.sourceSpec ||
|
||||||
|
this.state.sourceSpec.source !== sourceSpec.source
|
||||||
|
) {
|
||||||
|
// New data source means new data.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const newSource = {spec: sourceSpec, selection};
|
||||||
|
const oldSouce = {
|
||||||
|
spec: this.state.sourceSpec,
|
||||||
|
selection: this.state.selection,
|
||||||
|
};
|
||||||
|
switch (newSource.spec.source) {
|
||||||
|
case DataSourceEnum.UPLOADED:
|
||||||
|
return this.uploadedDataSource.isNewData(
|
||||||
|
newSource as SourceSelection<UploadSourceSpec>,
|
||||||
|
oldSouce as SourceSelection<UploadSourceSpec>,
|
||||||
|
this.state.data,
|
||||||
|
);
|
||||||
|
case DataSourceEnum.GEDCOM_URL:
|
||||||
|
return this.gedcomUrlDataSource.isNewData(
|
||||||
|
newSource as SourceSelection<UrlSourceSpec>,
|
||||||
|
oldSouce as SourceSelection<UrlSourceSpec>,
|
||||||
|
this.state.data,
|
||||||
|
);
|
||||||
|
case DataSourceEnum.WIKITREE:
|
||||||
|
return this.wikiTreeDataSource.isNewData(
|
||||||
|
newSource as SourceSelection<WikiTreeSourceSpec>,
|
||||||
|
oldSouce as SourceSelection<WikiTreeSourceSpec>,
|
||||||
|
this.state.data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadData(sourceSpec: DataSourceSpec, selection?: IndiInfo) {
|
||||||
|
switch (sourceSpec.source) {
|
||||||
|
case DataSourceEnum.UPLOADED:
|
||||||
|
return this.uploadedDataSource.loadData({spec: sourceSpec, selection});
|
||||||
|
case DataSourceEnum.GEDCOM_URL:
|
||||||
|
return this.gedcomUrlDataSource.loadData({spec: sourceSpec, selection});
|
||||||
|
case DataSourceEnum.WIKITREE:
|
||||||
|
return this.wikiTreeDataSource.loadData({spec: sourceSpec, selection});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async componentDidUpdate() {
|
async componentDidUpdate() {
|
||||||
if (this.props.location.pathname !== '/view') {
|
if (this.props.location.pathname !== '/view') {
|
||||||
if (this.state.state !== AppState.INITIAL) {
|
if (this.state.state !== AppState.INITIAL) {
|
||||||
@@ -397,43 +498,30 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataSource = this.dataSources.get(args.source!);
|
if (!args.sourceSpec) {
|
||||||
|
|
||||||
if (!dataSource) {
|
|
||||||
this.props.history.replace({pathname: '/'});
|
this.props.history.replace({pathname: '/'});
|
||||||
} else if (
|
} else if (
|
||||||
this.state.state === AppState.INITIAL ||
|
this.state.state === AppState.INITIAL ||
|
||||||
args.source !== this.state.source ||
|
this.isNewData(args.sourceSpec, args.selection)
|
||||||
dataSource.isNewData(args, this.state)
|
|
||||||
) {
|
) {
|
||||||
// Set loading state.
|
// Set loading state.
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
state: AppState.LOADING,
|
state: AppState.LOADING,
|
||||||
selection: {id: args.indi},
|
sourceSpec: args.sourceSpec,
|
||||||
hash: args.hash,
|
selection: args.selection,
|
||||||
url: args.url,
|
|
||||||
standalone: args.standalone,
|
standalone: args.standalone,
|
||||||
chartType: args.chartType,
|
chartType: args.chartType,
|
||||||
source: args.source,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const data = await dataSource.loadData(args);
|
const data = await this.loadData(args.sourceSpec, args.selection);
|
||||||
|
|
||||||
// Set state with data.
|
// Set state with data.
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
state: AppState.SHOWING_CHART,
|
state: AppState.SHOWING_CHART,
|
||||||
data,
|
data,
|
||||||
hash: args.hash,
|
selection: getSelection(data.chartData, args.selection),
|
||||||
selection: getSelection(data.chartData, args.indi, args.generation),
|
|
||||||
url: args.url,
|
|
||||||
showSidePanel: args.showSidePanel,
|
|
||||||
standalone: args.standalone,
|
|
||||||
chartType: args.chartType,
|
|
||||||
source: args.source,
|
|
||||||
freezeAnimation: args.freezeAnimation,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -446,11 +534,10 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
// Update selection if it has changed in the URL.
|
// Update selection if it has changed in the URL.
|
||||||
const selection = getSelection(
|
const selection = getSelection(
|
||||||
this.state.data!.chartData,
|
this.state.data!.chartData,
|
||||||
args.indi,
|
args.selection,
|
||||||
args.generation,
|
|
||||||
);
|
);
|
||||||
const loadMoreFromWikitree =
|
const loadMoreFromWikitree =
|
||||||
args.source === DataSourceEnum.WIKITREE &&
|
args.sourceSpec.source === DataSourceEnum.WIKITREE &&
|
||||||
(!this.state.selection || this.state.selection.id !== selection.id);
|
(!this.state.selection || this.state.selection.id !== selection.id);
|
||||||
this.updateDisplay(selection, {
|
this.updateDisplay(selection, {
|
||||||
chartType: args.chartType,
|
chartType: args.chartType,
|
||||||
@@ -460,23 +547,16 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
});
|
});
|
||||||
if (loadMoreFromWikitree) {
|
if (loadMoreFromWikitree) {
|
||||||
try {
|
try {
|
||||||
const data = await loadWikiTree(args.indi!, this.context.intl);
|
const data = await loadWikiTree(
|
||||||
const selection = getSelection(
|
args.selection!.id,
|
||||||
data.chartData,
|
this.context.intl,
|
||||||
args.indi,
|
|
||||||
args.generation,
|
|
||||||
);
|
);
|
||||||
|
const selection = getSelection(data.chartData, args.selection);
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
state: AppState.SHOWING_CHART,
|
state: AppState.SHOWING_CHART,
|
||||||
data,
|
data,
|
||||||
hash: args.hash,
|
|
||||||
selection,
|
selection,
|
||||||
url: args.url,
|
|
||||||
showSidePanel: args.showSidePanel,
|
|
||||||
standalone: args.standalone,
|
|
||||||
chartType: args.chartType,
|
|
||||||
source: args.source,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -633,7 +713,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
{...props}
|
{...props}
|
||||||
data={this.state.data && this.state.data.chartData}
|
data={this.state.data && this.state.data.chartData}
|
||||||
allowAllRelativesChart={
|
allowAllRelativesChart={
|
||||||
this.state.source !== DataSourceEnum.WIKITREE
|
this.state.sourceSpec?.source !== DataSourceEnum.WIKITREE
|
||||||
}
|
}
|
||||||
showingChart={
|
showingChart={
|
||||||
this.props.history.location.pathname === '/view' &&
|
this.props.history.location.pathname === '/view' &&
|
||||||
@@ -648,7 +728,9 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
onDownloadPng: this.onDownloadPng,
|
onDownloadPng: this.onDownloadPng,
|
||||||
onDownloadSvg: this.onDownloadSvg,
|
onDownloadSvg: this.onDownloadSvg,
|
||||||
}}
|
}}
|
||||||
showWikiTreeMenus={this.state.source === DataSourceEnum.WIKITREE}
|
showWikiTreeMenus={
|
||||||
|
this.state.sourceSpec?.source === DataSourceEnum.WIKITREE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ import {IndiInfo, JsonGedcomData} from 'topola';
|
|||||||
*/
|
*/
|
||||||
export function getSelection(
|
export function getSelection(
|
||||||
data: JsonGedcomData,
|
data: JsonGedcomData,
|
||||||
indi?: string,
|
selection?: IndiInfo,
|
||||||
generation?: number,
|
|
||||||
): IndiInfo {
|
): IndiInfo {
|
||||||
// If ID is not given or it doesn't exist in the data, use the first ID in
|
// If ID is not given or it doesn't exist in the data, use the first ID in
|
||||||
// the data.
|
// the data.
|
||||||
const id =
|
const id =
|
||||||
indi && data.indis.some((i) => i.id === indi) ? indi : data.indis[0].id;
|
selection && data.indis.some((i) => i.id === selection.id)
|
||||||
return {id, generation: generation || 0};
|
? selection.id
|
||||||
|
: data.indis[0].id;
|
||||||
|
return {id, generation: selection?.generation || 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareData(
|
function prepareData(
|
||||||
|
|||||||
Reference in New Issue
Block a user