diff --git a/src/details/details.tsx b/src/details/details.tsx
index 47db67c..be66953 100644
--- a/src/details/details.tsx
+++ b/src/details/details.tsx
@@ -4,7 +4,7 @@ import {
GedcomData,
getData,
getFileName,
- isImageFile,
+ getImageFileEntry
} from '../util/gedcom_util';
import {Events} from './events';
import {GedcomEntry} from 'parse-gedcom';
@@ -56,12 +56,7 @@ function dataDetails(entry: GedcomEntry) {
}
function fileDetails(objectEntry: GedcomEntry) {
- const imageFileEntry = objectEntry.tree.find(
- (entry) =>
- entry.tag === 'FILE' &&
- entry.data.startsWith('http') &&
- isImageFile(entry.data),
- );
+ const imageFileEntry = getImageFileEntry(objectEntry);
return imageFileEntry ? (
diff --git a/src/details/event-extras.tsx b/src/details/event-extras.tsx
new file mode 100644
index 0000000..31c345c
--- /dev/null
+++ b/src/details/event-extras.tsx
@@ -0,0 +1,191 @@
+import {FormattedMessage, IntlShape, useIntl} from 'react-intl';
+import {
+ Icon,
+ Item,
+ List,
+ Menu,
+ MenuItemProps,
+ Popup,
+ Tab,
+} from 'semantic-ui-react';
+import {useState} from 'react';
+import {WrappedImage} from './wrapped-image';
+import * as React from 'react';
+import {MultilineText} from './multiline-text';
+import {DateOrRange} from 'topola';
+import {formatDateOrRange} from '../util/date_util';
+import Linkify from 'react-linkify';
+
+export interface Image {
+ url: string;
+ filename: string;
+ title?: string;
+}
+
+export interface Source {
+ title?: string;
+ author?: string;
+ page?: string;
+ date?: DateOrRange;
+ publicationInfo?: string;
+}
+
+interface Props {
+ images?: Image[];
+ notes?: string[][];
+ sources?: Source[];
+ indi: string;
+}
+
+function eventImages(images: Image[] | undefined) {
+ return (
+ !!images &&
+ images.map((image, index) => (
+
+
+
+
+
+ ))
+ );
+}
+
+function eventNotes(notes: string[][] | undefined) {
+ return (
+ !!notes?.length &&
+ notes.map((note, index) => (
+
+ (
+ {line}
+ ))}
+ />
+
+ ))
+ );
+}
+
+function eventSources(sources: Source[] | undefined, intl: IntlShape) {
+ return (
+ !!sources?.length && (
+
+ {sources.map((source, index) => (
+
+
+
+
+
+ {[source.author, source.title, source.publicationInfo]
+ .filter((sourceElement) => sourceElement)
+ .join(', ')}
+
+
+
+ {source.page}
+ {source.date
+ ? ' [' + formatDateOrRange(source.date, intl) + ']'
+ : null}
+
+
+
+ ))}
+
+ )
+ );
+}
+
+export function EventExtras(props: Props) {
+ const intl = useIntl();
+ const [activeIndex, setActiveIndex] = useState(-1);
+ const [indi, setIndi] = useState('');
+
+ if (!indi || indi !== props.indi) {
+ setActiveIndex(-1);
+ setIndi(props.indi);
+ }
+
+ function handleTabOnClick(
+ event: React.MouseEvent
,
+ menuItemProps: MenuItemProps,
+ ) {
+ menuItemProps.index !== undefined && activeIndex !== menuItemProps.index
+ ? setActiveIndex(menuItemProps.index)
+ : setActiveIndex(-1);
+ }
+
+ const imageTab = props.images?.length && {
+ menuItem: (
+
+
+ }
+ size="mini"
+ position="bottom center"
+ trigger={}
+ />
+
+ ),
+ render: () => {eventImages(props.images)},
+ };
+
+ const noteTab = props.notes?.length && {
+ menuItem: (
+
+
+ }
+ size="mini"
+ position="bottom center"
+ trigger={}
+ />
+
+ ),
+ render: () => {eventNotes(props.notes)},
+ };
+
+ const sourceTab = props.sources?.length && {
+ menuItem: (
+
+
+ }
+ size="mini"
+ position="bottom center"
+ trigger={}
+ />
+
+ ),
+ render: () => {eventSources(props.sources, intl)},
+ };
+
+ const panes = [imageTab, noteTab, sourceTab].flatMap((tab) =>
+ tab ? [tab] : [],
+ );
+
+ if (panes.length) {
+ return (
+
+
+
+ );
+ }
+ return null;
+}
diff --git a/src/details/events.tsx b/src/details/events.tsx
index 32b2fd3..c84689c 100644
--- a/src/details/events.tsx
+++ b/src/details/events.tsx
@@ -3,14 +3,21 @@ import flatMap from 'array.prototype.flatmap';
import {calcAge} from '../util/age_util';
import {compareDates, formatDateOrRange} from '../util/date_util';
import {DateOrRange, getDate} from 'topola';
-import {dereference, GedcomData, getData, getName} from '../util/gedcom_util';
+import {
+ dereference,
+ GedcomData,
+ getData,
+ getImageFileEntry,
+ getFileName,
+ getName,
+} from '../util/gedcom_util';
import {GedcomEntry} from 'parse-gedcom';
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';
import {TranslatedTag} from './translated-tag';
import {Header, Item} from 'semantic-ui-react';
+import {EventExtras, Image, Source} from './event-extras';
function PersonLink(props: {person: GedcomEntry}) {
const location = useLocation();
@@ -45,7 +52,10 @@ interface EventData {
age?: string;
personLink?: GedcomEntry;
place?: string[];
- notes: string[][];
+ images?: Image[];
+ notes?: string[][];
+ sources?: Source[];
+ indi: string;
}
const EVENT_TAGS = [
@@ -115,6 +125,71 @@ function eventPlace(entry: GedcomEntry) {
return place?.data ? getData(place) : undefined;
}
+function eventImages(entry: GedcomEntry, gedcom: GedcomData): Image[] {
+ return entry.tree
+ .filter((subEntry) => 'OBJE' === subEntry.tag)
+ .map((objectEntry) =>
+ dereference(objectEntry, gedcom, (gedcom) => gedcom.other),
+ )
+ .map((objectEntry) => getImageFileEntry(objectEntry))
+ .flatMap((imageFileEntry) =>
+ imageFileEntry
+ ? [
+ {
+ url: imageFileEntry?.data || '',
+ filename: getFileName(imageFileEntry) || '',
+ },
+ ]
+ : [],
+ );
+}
+
+function eventSources(entry: GedcomEntry, gedcom: GedcomData): Source[] {
+ return entry.tree
+ .filter((subEntry) => 'SOUR' === subEntry.tag)
+ .map((sourceEntryReference) => {
+ const sourceEntry = dereference(
+ sourceEntryReference,
+ gedcom,
+ (gedcom) => gedcom.other,
+ );
+
+ const title = sourceEntry.tree.find(
+ (subEntry) => 'TITL' === subEntry.tag,
+ );
+
+ const abbr = sourceEntry.tree.find(
+ (subEntry) => 'ABBR' === subEntry.tag,
+ );
+
+ const author = sourceEntry.tree.find(
+ (subEntry) => 'AUTH' === subEntry.tag,
+ );
+
+ const publicationInfo = sourceEntry.tree.find(
+ (subEntry) => 'PUBL' === subEntry.tag,
+ );
+
+ const page = sourceEntryReference.tree.find(
+ (subEntry) => 'PAGE' === subEntry.tag,
+ );
+
+ const sourceData = sourceEntryReference.tree.find(
+ (subEntry) => 'DATA' === subEntry.tag,
+ );
+
+ const date = sourceData ? resolveDate(sourceData) : undefined;
+
+ return {
+ title: title?.data || abbr?.data,
+ author: author?.data,
+ page: page?.data,
+ date: date ? getDate(date.data) : undefined,
+ publicationInfo: publicationInfo?.data,
+ };
+ });
+}
+
function eventNotes(entry: GedcomEntry, gedcom: GedcomData): string[][] {
return entry.tree
.filter((subentry) => ['NOTE', 'TYPE'].includes(subentry.tag))
@@ -147,7 +222,10 @@ function toIndiEvent(
type: entry.tag,
age: getAge(entry, indi, gedcom, intl),
place: eventPlace(entry),
+ images: eventImages(entry, gedcom),
notes: eventNotes(entry, gedcom),
+ sources: eventSources(entry, gedcom),
+ indi: indi,
},
];
}
@@ -171,7 +249,10 @@ function toFamilyEvents(
type: familyMarriageEvent.tag,
personLink: getSpouse(indi, family, gedcom),
place: eventPlace(familyMarriageEvent),
+ images: eventImages(familyMarriageEvent, gedcom),
notes: eventNotes(familyMarriageEvent, gedcom),
+ sources: eventSources(familyMarriageEvent, gedcom),
+ indi: indi,
};
});
}
@@ -188,19 +269,12 @@ function Event(props: {event: EventData}) {
{!!props.event.place && (
{props.event.place}
)}
- {!!props.event.notes.length && (
-
- {props.event.notes.map((note, index) => (
-
- (
- {line}
- ))}
- />
-
- ))}
-
- )}
+
);
diff --git a/src/index.css b/src/index.css
index 103391b..21805ee 100644
--- a/src/index.css
+++ b/src/index.css
@@ -206,3 +206,29 @@ div.zoom {
.image-placeholder {
height: 100%;
}
+
+.event-extras .ui.attached.menu {
+ border: none;
+ min-height: auto;
+}
+
+.event-extras .ui.attached.segment.tab {
+ border: none;
+}
+
+.event-extras .ui.tabular.menu .item {
+ border: none;
+}
+
+.event-extras .ui.menu .active.item .icon{
+ background-color: #1b1c1d;
+ color: #ffffff;
+}
+
+.event-extras .ui.attached.segment.active.tab{
+ word-break: normal;
+ overflow-wrap: anywhere;
+ max-width: 289px;
+ padding: 14px 0px 0px;
+}
+
diff --git a/src/translations/pl.json b/src/translations/pl.json
index e46783e..dd9fb30 100644
--- a/src/translations/pl.json
+++ b/src/translations/pl.json
@@ -91,5 +91,8 @@
"config.colors.NO_COLOR": "brak",
"config.colors.COLOR_BY_GENERATION": "według pokolenia",
"config.colors.COLOR_BY_SEX": "według płci",
- "name.unknown_name": "N.N."
+ "name.unknown_name": "N.N.",
+ "extras.images": "Zdjęcia",
+ "extras.notes": "Notatki",
+ "extras.sources": "Źródła"
}
diff --git a/src/util/date_util.ts b/src/util/date_util.ts
index b1df6cd..df66761 100644
--- a/src/util/date_util.ts
+++ b/src/util/date_util.ts
@@ -27,7 +27,9 @@ function formatDate(date: TopolaDate, intl: IntlShape) {
formatOptions,
).format(dateObject);
- return [translatedQualifier, translatedDate].join(' ');
+ return [translatedQualifier, translatedDate]
+ .filter((dateElement) => dateElement)
+ .join(' ');
}
function formatDateRage(dateRange: DateRange, intl: IntlShape) {
diff --git a/src/util/gedcom_util.ts b/src/util/gedcom_util.ts
index 731ff6d..bda9e3b 100644
--- a/src/util/gedcom_util.ts
+++ b/src/util/gedcom_util.ts
@@ -291,3 +291,14 @@ export function getFileName(fileEntry: GedcomEntry): string | undefined {
return fileTitle && fileExtension && fileTitle + '.' + fileExtension;
}
+
+export function getImageFileEntry(
+ objectEntry: GedcomEntry,
+): GedcomEntry | undefined {
+ return objectEntry.tree.find(
+ (entry) =>
+ entry.tag === 'FILE' &&
+ entry.data.startsWith('http') &&
+ isImageFile(entry.data),
+ );
+}