Proof of concept loading data from WikiTree

This commit is contained in:
Przemek Wiech
2020-01-13 22:42:26 +01:00
parent 07bcafc7dc
commit 7b09936a84
2 changed files with 178 additions and 6 deletions

View File

@@ -12,6 +12,7 @@ import {Intro} from './intro';
import {Loader, Message, Portal, Responsive} from 'semantic-ui-react';
import {Redirect, Route, RouteComponentProps, Switch} from 'react-router-dom';
import {TopBar} from './top_bar';
import {loadWikiTree} from './wikitree';
/** Shows an error message in the middle of the screen. */
function ErrorMessage(props: {message?: string}) {
@@ -117,11 +118,16 @@ export class App extends React.Component<RouteComponentProps, {}> {
hash: string | undefined,
url: string | undefined,
gedcom: string | undefined,
source: string | undefined,
): boolean {
return (
!!(hash && hash !== this.state.hash) ||
!!(url && this.state.url !== url) ||
(!!gedcom && !this.state.loading && !this.state.data)
(!!gedcom && !this.state.loading && !this.state.data) ||
(source === 'wikitree' &&
!this.state.loading &&
!this.state.data &&
!this.state.error)
);
}
@@ -226,6 +232,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
const handleCors = getParam('handleCors') !== 'false'; // True by default.
const standalone = getParam('standalone') !== 'false'; // True by default.
const view = getParam('view');
const source = getParam('source');
const chartTypes = new Map<string | undefined, ChartType>([
['relatives', ChartType.Relatives],
@@ -238,9 +245,9 @@ export class App extends React.Component<RouteComponentProps, {}> {
const images =
this.props.location.state && this.props.location.state.images;
if (!url && !hash) {
if (!url && !hash && !source) {
this.props.history.replace({pathname: '/'});
} else if (this.isNewData(hash, url, gedcom)) {
} else if (this.isNewData(hash, url, gedcom, source)) {
try {
// Set loading state.
this.setState(
@@ -255,9 +262,12 @@ export class App extends React.Component<RouteComponentProps, {}> {
chartType,
}),
);
const data = hash
? await loadGedcom(hash, gedcom, images)
: await loadFromUrl(url!, handleCors);
const data =
source === 'wikitree'
? await loadWikiTree(indi!, handleCors)
: hash
? await loadGedcom(hash, gedcom, images)
: await loadFromUrl(url!, handleCors);
const software = getSoftware(data.gedcom.head);
analyticsEvent(hash ? 'upload_file_loaded' : 'url_file_loaded', {

162
src/wikitree.ts Normal file
View File

@@ -0,0 +1,162 @@
// WikiTree support is currenlty implemented only as proof of concept.
// It works for a specific id (12082793) and few others.
import md5 from 'md5';
import {loadGedcom} from './load_data';
interface GetAncestorsRequest {
action: 'getAncestors';
key: string;
fields: string;
}
interface GetRelatives {
action: 'getRelatives';
keys: string;
getChildren?: true;
getSpouses?: true;
}
type WikiTreeRequest = GetAncestorsRequest | GetRelatives;
async function wikiTreeGet(request: WikiTreeRequest, handleCors: boolean) {
const requestData = new FormData();
requestData.append('format', 'json');
for (const key in request) {
requestData.append(key, request[key]);
}
const apiUrl = handleCors
? 'https://cors-anywhere.herokuapp.com/https://apps.wikitree.com/api.php'
: 'https://apps.wikitree.com/api.php';
const response = await window.fetch(apiUrl, {
method: 'POST',
body: requestData,
});
const responseBody = await response.text();
return JSON.parse(responseBody);
}
function getFamilyId(id1: number, id2: number) {
if (id2 > id1) {
return `${id1}_${id2}`;
}
return `${id2}_${id1}`;
}
export async function loadWikiTree(id: string, handleCors: boolean) {
const firstRelativesData = await wikiTreeGet(
{
action: 'getRelatives',
keys: id,
getChildren: true,
getSpouses: true,
},
handleCors,
);
const spouseIds = Object.values(
firstRelativesData[0].items[0].person.Spouses,
).map((s: any) => s.Id);
const everyone: any[] = [];
[id].concat(spouseIds).forEach(async (personId) => {
const ancestorsData = await wikiTreeGet(
{
action: 'getAncestors',
key: personId,
fields: '*',
},
handleCors,
);
const ancestors = ancestorsData[0].ancestors;
ancestors.forEach((a: any) => everyone.push(a));
});
let toFetch = [id];
while (toFetch.length > 0) {
const relativesData = await wikiTreeGet(
{
action: 'getRelatives',
keys: toFetch.join(','),
getChildren: true,
getSpouses: true,
},
handleCors,
);
const people = relativesData[0].items.map((x: any) => x.person);
toFetch = [];
people.forEach((person: any) => {
everyone.push(person);
const spouses = Object.values(person.Spouses);
spouses.forEach((s) => everyone.push(s));
const children = Object.values(person.Children);
const childrenKeys = (children as any[]).map((c) => c.Name);
childrenKeys.forEach((k) => toFetch.push(k));
});
}
// Map from person id to the set of families where they are a spouse.
const families = new Map<number, Set<string>>();
const children = new Map<string, Set<number>>();
const spouses = new Map<string, {wife?: number; husband?: number}>();
function getSet<K, V>(map: Map<K, Set<V>>, id: K): Set<V> {
const set = map.get(id);
if (set) {
return set;
}
const newSet = new Set<V>();
map.set(id, newSet);
return newSet;
}
everyone.forEach((person: any) => {
if (person.Mother || person.Father) {
const famId = getFamilyId(person.Mother, person.Father);
getSet(families, person.Mother).add(famId);
getSet(families, person.Father).add(famId);
getSet(children, famId).add(person.Id);
spouses.set(famId, {
wife: person.Mother || undefined,
husband: person.Father || undefined,
});
}
});
const gedcomLines: string[] = ['0 HEAD'];
const converted = new Set<number>();
everyone.forEach((person: any) => {
if (converted.has(person.Id)) {
return;
}
converted.add(person.Id);
gedcomLines.push(`0 @${person.Id}@ INDI`);
gedcomLines.push(`1 NAME ${person.FirstName} /${person.LastNameAtBirth}/`);
if (person.Mother || person.Father) {
gedcomLines.push(`1 FAMC @${getFamilyId(person.Mother, person.Father)}@`);
}
// TODO: add to spouses map for each spouse.
getSet(families, person.Id).forEach((famId) =>
gedcomLines.push(`1 FAMS @${famId}@`),
);
});
spouses.forEach((value, key) => {
gedcomLines.push(`0 @${key}@ FAM`);
if (value.wife) {
gedcomLines.push(`1 WIFE @${value.wife}@`);
}
if (value.husband) {
gedcomLines.push(`1 HUSB @${value.husband}@`);
}
getSet(children, key).forEach((child) => {
gedcomLines.push(`1 CHIL @${child}@`);
});
});
gedcomLines.push('0 TRLR');
const gedcom = gedcomLines.join('\n');
const hash = md5(gedcom);
return await loadGedcom(hash, gedcom);
}