import flatMap from 'array.prototype.flatmap';
import {GedcomEntry} from 'parse-gedcom';
import {FormattedMessage} from 'react-intl';
import {Header, Item} from 'semantic-ui-react';
import {
dereference,
GedcomData,
getData,
getFileName,
getImageFileEntry,
getNonImageFileEntry,
mapToSource,
resolveFileUrl,
} from '../../util/gedcom_util';
import {Config, Ids} from '../config/config';
import {AdditionalFiles, FileEntry} from './additional-files';
import {ALL_SUPPORTED_EVENT_TYPES, Events} from './events';
import {ImmediateFamily} from './immediate-family';
import {MultilineText} from './multiline-text';
import {Sources} from './sources';
import {TranslatedTag} from './translated-tag';
import {WrappedImage} from './wrapped-image';
const EXCLUDED_TAGS = [
...ALL_SUPPORTED_EVENT_TYPES,
'NAME',
'SEX',
'FAMC',
'FAMS',
'NOTE',
'SOUR',
'FACT',
];
function dataDetails(entry: GedcomEntry) {
const lines = [];
if (entry.data) {
lines.push(...getData(entry));
}
entry.tree
.filter((subentry) => subentry.tag === 'NOTE')
.forEach((note) =>
getData(note).forEach((line) => lines.push({line})),
);
if (!lines.length) {
return null;
}
return (
<>
>
);
}
function attributeDetails(entry: GedcomEntry) {
if (!entry.data) {
return null;
}
const attributeName = entry.tree
.filter((subentry) => subentry.tag === 'TYPE')
.flatMap((type) => getData(type))
.join()
.trim();
const attributeValue = getData(entry).join(' ').trim();
if (attributeName) {
return (
<>
{attributeName}: {attributeValue}
>
);
} else {
return (
<>
{attributeValue}
>
);
}
}
function imageDetails(
objectEntryReference: GedcomEntry,
gedcom: GedcomData,
images?: Map,
) {
const imageEntry = dereference(
objectEntryReference,
gedcom,
(gedcom) => gedcom.other,
);
const imageFileEntry = getImageFileEntry(imageEntry);
if (!imageFileEntry || !hasData(imageEntry)) {
return null;
}
return (
);
}
function sourceDetails(
sourceReferenceEntries: GedcomEntry[],
gedcom: GedcomData,
) {
const sources = sourceReferenceEntries.map((sourceEntryReference) =>
mapToSource(sourceEntryReference, gedcom),
);
if (!sources.length) {
return null;
}
return (
<>
>
);
}
function fileDetails(
objectEntries: GedcomEntry[],
gedcom: GedcomData,
images?: Map,
) {
const files: FileEntry[] = [];
objectEntries
.map((objectEntry) =>
dereference(objectEntry, gedcom, (gedcom) => gedcom.other),
)
.forEach((objectEntry) => {
const fileEntry = getNonImageFileEntry(objectEntry);
if (fileEntry) {
files.push({
url: resolveFileUrl(fileEntry.data, images),
filename: getFileName(fileEntry),
titl: objectEntry.tree.find((entry) => entry.tag === 'TITL')?.data,
});
}
});
if (!files.length) {
return null;
}
return (
<>
>
);
}
function noteDetails(noteEntryReference: GedcomEntry, gedcom: GedcomData) {
const noteEntry = dereference(
noteEntryReference,
gedcom,
(gedcom) => gedcom.other,
);
if (!noteEntry || !hasData(noteEntry)) {
return null;
}
return (
(
{line}
))}
/>
);
}
function nameDetails(entry: GedcomEntry) {
const prefix = entry.tree.find((entry) => entry.tag === 'NPFX')?.data;
let given = entry.tree.find((entry) => entry.tag === 'GIVN')?.data;
let rufname = entry.tree.find((entry) => entry.tag === '_RUFNAME')?.data;
const nickname = entry.tree.find((entry) => entry.tag === 'NICK')?.data;
const surnamePrefix = entry.tree.find((entry) => entry.tag === 'SPFX')?.data;
const surname = entry.tree.find((entry) => entry.tag === 'SURN')?.data;
const suffix = entry.tree.find((entry) => entry.tag === 'NSFX')?.data;
// If _RUFNAME is included in GIVN, then replace this part in GIVN with this part in quotation marks,
// so that this name is not shown twice.
if (given && rufname && given.includes(rufname)) {
given = given.replace(rufname, `"${rufname}"`);
rufname = undefined;
}
const fullNameParts = [
prefix,
given,
rufname && `"${rufname}"`,
nickname && `(${nickname})`,
surnamePrefix,
surname,
suffix,
].filter(Boolean);
const fullName =
fullNameParts.join(' ').trim() || entry.data.replaceAll('/', '') || '';
const nameType = entry.tree.find(
(entry) => entry.tag === 'TYPE' && entry.data !== 'Unknown',
)?.data;
return (
<>
{fullName ? (
fullName
) : (
)}
{fullName && nameType && (
)}
>
);
}
function getSectionForEachMatchingEntry(
entries: GedcomEntry[],
gedcom: GedcomData,
tags: string[],
detailsFunction: (
entry: GedcomEntry,
gedcom: GedcomData,
images?: Map,
) => React.ReactNode | null,
images?: Map,
): React.ReactNode[] {
return flatMap(tags, (tag) =>
entries
.filter((entry) => entry.tag === tag)
.map((entry) => detailsFunction(entry, gedcom, images)),
)
.filter((element) => element !== null)
.map((element, index) => (
-
{element}
));
}
function combineAllMatchingEntriesIntoSingleSection(
entries: GedcomEntry[],
gedcom: GedcomData,
tags: string[],
detailsFunction: (
entries: GedcomEntry[],
gedcom: GedcomData,
images?: Map,
) => React.ReactNode | null,
images?: Map,
): React.ReactNode {
const entriesWithMatchingTag = flatMap(tags, (tag) =>
entries.filter((entry) => entry.tag === tag),
).filter((element) => element !== null);
const sectionWithDetails = entriesWithMatchingTag.length
? detailsFunction(entriesWithMatchingTag, gedcom, images)
: null;
if (!sectionWithDetails) {
return null;
}
return (
-
{sectionWithDetails}
);
}
/**
* Returns true if there is displayable information in this entry.
* Returns false if there is no data in this entry or this is only a reference
* to another entry.
*/
function hasData(entry: GedcomEntry) {
return entry.tree.length > 0 || (entry.data && !entry.data.startsWith('@'));
}
function getOtherSections(entries: GedcomEntry[], gedcom: GedcomData) {
return entries
.filter((entry) => !EXCLUDED_TAGS.includes(entry.tag))
.map((entry) => dereference(entry, gedcom, (gedcom) => gedcom.other))
.filter(hasData)
.map((entry) => dataDetails(entry))
.filter((element) => element !== null)
.map((element, index) => (
-
{element}
));
}
function getSectionForId(indi: string): React.ReactNode {
return (
-
{indi}
);
}
interface Props {
gedcom: GedcomData;
indi: string;
config: Config;
images?: Map;
}
export function Details(props: Props) {
const entries = props.gedcom.indis[props.indi].tree;
return (
{getSectionForEachMatchingEntry(
entries,
props.gedcom,
['NAME'],
nameDetails,
)}
{getSectionForEachMatchingEntry(
entries,
props.gedcom,
['OBJE'],
imageDetails,
props.images,
)}
{props.config.id === Ids.SHOW ? getSectionForId(props.indi) : null}
{getSectionForEachMatchingEntry(
entries,
props.gedcom,
['FACT'],
attributeDetails,
)}
{getOtherSections(entries, props.gedcom)}
{getSectionForEachMatchingEntry(
entries,
props.gedcom,
['NOTE'],
noteDetails,
)}
{combineAllMatchingEntriesIntoSingleSection(
entries,
props.gedcom,
['OBJE'],
fileDetails,
props.images,
)}
{combineAllMatchingEntriesIntoSingleSection(
entries,
props.gedcom,
['SOUR'],
sourceDetails,
)}
);
}