Name types in details panel (#109)

This commit is contained in:
czifumasa 2022-08-30 15:58:39 +02:00 committed by GitHub
parent a92f06e43d
commit 0733690058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 168 additions and 35 deletions

12
package-lock.json generated
View File

@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
"name": "topola-viewer",
"version": "1.0.0",
"dependencies": {
"@artsy/fresnel": "^1.3.1",
@ -38,6 +37,7 @@
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.3",
"topola": "^3.5.0",
"turbocommons-ts": "^3.8.0",
"unified": "^10.1.0",
"wikitree-js": "^0.1.0"
},
@ -22907,6 +22907,11 @@
"node": "*"
}
},
"node_modules/turbocommons-ts": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/turbocommons-ts/-/turbocommons-ts-3.8.0.tgz",
"integrity": "sha512-EQRCm2r944M/TqzfRutaCwTN+eHVLPAedaWF8catAHd49biN1UjB9CGLTBT2kI3i9lCccSt84s8YFf54u1WcAg=="
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
@ -44181,6 +44186,11 @@
"safe-buffer": "^5.0.1"
}
},
"turbocommons-ts": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/turbocommons-ts/-/turbocommons-ts-3.8.0.tgz",
"integrity": "sha512-EQRCm2r944M/TqzfRutaCwTN+eHVLPAedaWF8catAHd49biN1UjB9CGLTBT2kI3i9lCccSt84s8YFf54u1WcAg=="
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",

View File

@ -33,6 +33,7 @@
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.3",
"topola": "^3.5.0",
"turbocommons-ts": "^3.8.0",
"unified": "^10.1.0",
"wikitree-js": "^0.1.0"
},

View File

@ -14,12 +14,13 @@ import {GedcomEntry} from 'parse-gedcom';
import {IntlShape} from 'react-intl';
import {TopolaError} from '../util/error';
import {isValidDateOrRange} from '../util/date_util';
import {StringUtils} from 'turbocommons-ts';
import {
getAncestors as getAncestorsApi,
getRelatives as getRelativesApi,
clientLogin,
getLoggedInUserName,
Person
Person,
} from 'wikitree-js';
/** Prefix for IDs of private individuals. */
@ -66,7 +67,7 @@ async function getAncestors(
if (cachedData) {
return JSON.parse(cachedData);
}
const result = getAncestorsApi(key, {}, getApiOptions(handleCors));
const result = await getAncestorsApi(key, {}, getApiOptions(handleCors));
setSessionStorageItem(cacheKey, JSON.stringify(result));
return result;
}
@ -225,6 +226,12 @@ export async function loadWikiTree(
generation++;
}
//Map from human-readable person id to person names
const personNames = new Map<
string,
{birth?: string; married?: string; aka?: string}
>();
// Map from person id to the set of families where they are a spouse.
const families = new Map<number, Set<string>>();
// Map from family id to the set of children.
@ -268,6 +275,9 @@ export async function loadWikiTree(
`https://www.wikitree.com${person.PhotoData.path}`,
);
}
personNames.set(person.Name, convertPersonNames(person));
if (person.Spouses) {
Object.values(person.Spouses).forEach((spouse) => {
const famId = getFamilyId(person.Id, spouse.Id);
@ -314,7 +324,7 @@ export async function loadWikiTree(
});
const chartData = normalizeGedcom({indis, fams});
const gedcom = buildGedcom(chartData, fullSizePhotoUrls);
const gedcom = buildGedcom(chartData, fullSizePhotoUrls, personNames);
return {chartData, gedcom};
}
@ -387,6 +397,50 @@ function convertPerson(person: Person, intl: IntlShape): JsonIndi {
}
return indi;
}
/**
Resolve birth name, married name and aka name with following logic:
- birth name is always prioritized and is set if exists and is not unknown
- married name is based on LastNameCurrent and is set if it's different than birth name
and one of the spouses has it as their birth name
- aka name is based on LastNameOther and is set if it's different than others
*/
function convertPersonNames(person: Person) {
return {
birth:
person.LastNameAtBirth !== 'Unknown' ? person.LastNameAtBirth : undefined,
married:
person.Spouses &&
person.LastNameCurrent !== 'Unknown' &&
person.LastNameCurrent !== person.LastNameAtBirth &&
Object.entries(person.Spouses)
.flatMap(([, spousePerson]) =>
spousePerson.LastNameAtBirth.split(/[- ,]/),
)
.filter(
(spousePersonNamePart) =>
/* In some languages the same names can differ a bit between genders,
so regular equals comparison cannot be used.
To verify if spouse has the same name, person name is split to include people with double names,
then there is a check if any name part is at least 75% similar to spouse name.
*/
person.LastNameCurrent.split(/[- ,]/).filter(
(personNamePart) =>
StringUtils.compareSimilarityPercent(
spousePersonNamePart,
personNamePart,
) >= 75,
).length,
).length
? person.LastNameCurrent
: undefined,
aka:
person.LastNameOther !== 'Unknown' &&
person.LastNameAtBirth !== person.LastNameOther &&
person.LastNameCurrent !== person.LastNameOther
? person.LastNameOther
: undefined,
};
}
/**
* Parses a date in the format returned by WikiTree and converts in to
@ -468,6 +522,24 @@ function dateOrRangeToGedcom(dateOrRange: DateOrRange): string {
return '';
}
function nameToGedcom(type: string, firstName?: string, lastName?: string) {
return {
level: 1,
pointer: '',
tag: 'NAME',
data: `${firstName || ''} /${lastName || ''}/`,
tree: [
{
level: 2,
pointer: '',
tag: 'TYPE',
data: type,
tree: [],
},
],
};
}
function eventToGedcom(event: JsonEvent): GedcomEntry[] {
const result = [];
if (isValidDateOrRange(event)) {
@ -524,6 +596,7 @@ function imageToGedcom(
function indiToGedcom(
indi: JsonIndi,
fullSizePhotoUrl: Map<string, string>,
personNames: {birth?: string; married?: string; aka?: string},
): GedcomEntry {
// WikiTree URLs replace spaces with underscores.
const escapedId = indi.id.replace(/ /g, '_');
@ -532,16 +605,21 @@ function indiToGedcom(
pointer: `@${indi.id}@`,
tag: 'INDI',
data: '',
tree: [
{
level: 1,
pointer: '',
tag: 'NAME',
data: `${indi.firstName || ''} /${indi.lastName || ''}/`,
tree: [],
},
],
tree: [],
};
if (personNames.birth) {
record.tree.push(nameToGedcom('birth', indi.firstName, personNames.birth));
}
if (personNames.married) {
record.tree.push(
nameToGedcom('married', indi.firstName, personNames.married),
);
}
if (personNames.aka) {
record.tree.push(nameToGedcom('aka', indi.firstName, personNames.aka));
}
if (indi.birth) {
record.tree.push({
level: 1,
@ -653,11 +731,16 @@ function famToGedcom(fam: JsonFam): GedcomEntry {
function buildGedcom(
data: JsonGedcomData,
fullSizePhotoUrls: Map<string, string>,
personNames: Map<string, {birth?: string; married?: string; aka?: string}>,
): GedcomData {
const gedcomIndis: {[key: string]: GedcomEntry} = {};
const gedcomFams: {[key: string]: GedcomEntry} = {};
data.indis.forEach((indi) => {
gedcomIndis[indi.id] = indiToGedcom(indi, fullSizePhotoUrls);
gedcomIndis[indi.id] = indiToGedcom(
indi,
fullSizePhotoUrls,
personNames.get(indi.id) || {},
);
});
data.fams.forEach((fam) => {
gedcomFams[fam.id] = famToGedcom(fam);

View File

@ -11,6 +11,7 @@ import {GedcomEntry} from 'parse-gedcom';
import {MultilineText} from './multiline-text';
import {TranslatedTag} from './translated-tag';
import {Header, Item} from 'semantic-ui-react';
import {FormattedMessage} from 'react-intl';
import {WrappedImage} from './wrapped-image';
const EXCLUDED_TAGS = [
@ -83,18 +84,27 @@ function noteDetails(entry: GedcomEntry) {
}
function nameDetails(entry: GedcomEntry) {
const fullName = entry.data.replaceAll('/', '');
const nameType = entry.tree.find(
(entry) => entry.tag === 'TYPE' && entry.data !== 'Unknown',
)?.data;
return (
<Header size="large">
{entry.data
.split('/')
.filter((name) => !!name)
.map((name, index) => (
<div key={index}>
{name}
<br />
</div>
))}
</Header>
<>
<Header as="span" size="large">
{fullName ? (
fullName
) : (
<FormattedMessage id="name.unknown_name" defaultMessage="N.N." />
)}
</Header>
{fullName && nameType && (
<Item.Meta>
<TranslatedTag tag={nameType} />
</Item.Meta>
)}
</>
);
}

View File

@ -5,7 +5,7 @@ import {compareDates, formatDateOrRange} from '../util/date_util';
import {DateOrRange, getDate} from 'topola';
import {dereference, GedcomData, getData, getName} from '../util/gedcom_util';
import {GedcomEntry} from 'parse-gedcom';
import {IntlShape, useIntl} from 'react-intl';
import {FormattedMessage, IntlShape, useIntl} from 'react-intl';
import {Link, useLocation} from 'react-router-dom';
import {MultilineText} from './multiline-text';
import {pointerToId} from '../util/gedcom_util';
@ -16,9 +16,6 @@ function PersonLink(props: {person: GedcomEntry}) {
const location = useLocation();
const name = getName(props.person);
if (!name) {
return <></>;
}
const search = queryString.parse(location.search);
search['indi'] = pointerToId(props.person.pointer);
@ -26,7 +23,11 @@ function PersonLink(props: {person: GedcomEntry}) {
return (
<Item.Meta>
<Link to={{pathname: '/view', search: queryString.stringify(search)}}>
{name}
{name ? (
name
) : (
<FormattedMessage id="name.unknown_name" defaultMessage="N.N." />
)}
</Link>
</Item.Meta>
);

View File

@ -22,6 +22,11 @@ const TAG_DESCRIPTIONS = new Map([
['OCCU', 'Occupation'],
['TITL', 'Title'],
['WWW', 'WWW'],
['birth', 'Birth name'],
['married', 'Married name'],
['maiden', 'Maiden name'],
['immigrant', 'Immigrant name'],
['aka', 'Also known as'],
]);
interface Props {

View File

@ -6,10 +6,7 @@ import {FormattedMessage, useIntl} from 'react-intl';
import {MenuItem, MenuType} from './menu_item';
import {useEffect, useRef, useState} from 'react';
import {useHistory, useLocation} from 'react-router';
import {
getLoggedInUserName,
navigateToLoginPage,
} from 'wikitree-js';
import {getLoggedInUserName, navigateToLoginPage} from 'wikitree-js';
interface Props {
menuType: MenuType;

View File

@ -55,6 +55,10 @@
"gedcom.WWW": "Stránka WWW",
"gedcom.RELI": "Vyznání",
"gedcom._UPD": "Poslední aktualizace",
"gedcom.birth": "Rodinné jméno",
"gedcom.married": "Manželské jméno",
"gedcom.maiden": "Jméno za svobodna",
"gedcom.aka": "Také znám(a) jako",
"date.abt": "kolem",
"date.cal": "spočteno",
"date.est": "asi",

View File

@ -51,6 +51,10 @@
"gedcom.TITL": "Titel",
"gedcom.WWW": "Website",
"gedcom._UPD": "Zuletzt aktualisiert",
"gedcom.birth": "Geburtsname",
"gedcom.married": "Ehenamen",
"gedcom.maiden": "Mädchenname",
"gedcom.aka": "Auch bekannt als",
"date.abt": "about",
"date.cal": "berechnet",
"date.est": "geschätzt",

View File

@ -50,6 +50,10 @@
"gedcom.RIN": "ID",
"gedcom.TITL": "Titre",
"gedcom.WWW": "Site Web",
"gedcom.birth": "Nom de naissance",
"gedcom.married": "Nom marital",
"gedcom.maiden": "Nom de jeune fille",
"gedcom.aka": "Alias",
"gedcom._UPD": "Dernière mise à jour",
"date.abt": "environ",
"date.cal": "calculé",

View File

@ -53,6 +53,10 @@
"gedcom.TITL": "Titolo",
"gedcom.WWW": "Sito web",
"gedcom._UPD": "Ultimo aggiornamento",
"gedcom.birth": "Nome alla nascita",
"gedcom.married": "Nome da coniugato/a",
"gedcom.maiden": "Nome da nubile",
"gedcom.aka": "Conosciuto anche come",
"date.abt": "circa",
"date.cal": "calcolato",
"date.est": "stimato",

View File

@ -58,6 +58,11 @@
"gedcom._UPD": "Ostatnia aktualizacja",
"gedcom.MARR": "Małżeństwo",
"gedcom.DIV": "Rozwód",
"gedcom.birth": "Nazwisko rodowe",
"gedcom.married": "Nazwisko po małżeństwie",
"gedcom.maiden": "Nazwisko panieńskie",
"gedcom.immigrant": "Nazwisko po imigracji",
"gedcom.aka": "Alias",
"date.abt": "około",
"date.cal": "wyliczone",
"date.est": "oszacowane",
@ -85,5 +90,6 @@
"config.colors": "Kolory",
"config.colors.NO_COLOR": "brak",
"config.colors.COLOR_BY_GENERATION": "według pokolenia",
"config.colors.COLOR_BY_SEX": "według płci"
"config.colors.COLOR_BY_SEX": "według płci",
"name.unknown_name": "N.N."
}

View File

@ -59,6 +59,10 @@
"gedcom.TITL": "Титул",
"gedcom.WWW": "Веб-сайт WWW",
"gedcom._UPD": "Последнее обновление",
"gedcom.birth": "Имя при рождении",
"gedcom.married": "Имя в браке",
"gedcom.maiden": "Девичья фамилия",
"gedcom.aka": "Он(а) же",
"date.abt": "около",
"date.cal": "рассчитано",
"date.est": "приблизительно",