mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-07-29 15:11:52 +00:00
Display images in details panel fromm gedzip files
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
getImageFileEntry,
|
||||
getNonImageFileEntry,
|
||||
mapToSource,
|
||||
resolveFileUrl,
|
||||
} from '../../util/gedcom_util';
|
||||
import {Config, Ids} from '../config/config';
|
||||
import {AdditionalFiles, FileEntry} from './additional-files';
|
||||
@@ -91,7 +92,11 @@ function attributeDetails(entry: GedcomEntry) {
|
||||
}
|
||||
}
|
||||
|
||||
function imageDetails(objectEntryReference: GedcomEntry, gedcom: GedcomData) {
|
||||
function imageDetails(
|
||||
objectEntryReference: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
images?: Map<string, string>,
|
||||
) {
|
||||
const imageEntry = dereference(
|
||||
objectEntryReference,
|
||||
gedcom,
|
||||
@@ -107,7 +112,7 @@ function imageDetails(objectEntryReference: GedcomEntry, gedcom: GedcomData) {
|
||||
return (
|
||||
<div className="person-image">
|
||||
<WrappedImage
|
||||
url={imageFileEntry.data}
|
||||
url={resolveFileUrl(imageFileEntry.data, images)}
|
||||
filename={getFileName(imageFileEntry) || ''}
|
||||
/>
|
||||
</div>
|
||||
@@ -138,7 +143,11 @@ function sourceDetails(
|
||||
);
|
||||
}
|
||||
|
||||
function fileDetails(objectEntries: GedcomEntry[], gedcom: GedcomData) {
|
||||
function fileDetails(
|
||||
objectEntries: GedcomEntry[],
|
||||
gedcom: GedcomData,
|
||||
images?: Map<string, string>,
|
||||
) {
|
||||
const files: FileEntry[] = [];
|
||||
objectEntries
|
||||
.map((objectEntry) =>
|
||||
@@ -148,7 +157,7 @@ function fileDetails(objectEntries: GedcomEntry[], gedcom: GedcomData) {
|
||||
const fileEntry = getNonImageFileEntry(objectEntry);
|
||||
if (fileEntry) {
|
||||
files.push({
|
||||
url: fileEntry.data,
|
||||
url: resolveFileUrl(fileEntry.data, images),
|
||||
filename: getFileName(fileEntry),
|
||||
titl: objectEntry.tree.find((entry) => entry.tag === 'TITL')?.data,
|
||||
});
|
||||
@@ -249,12 +258,14 @@ function getSectionForEachMatchingEntry(
|
||||
detailsFunction: (
|
||||
entry: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
images?: Map<string, string>,
|
||||
) => React.ReactNode | null,
|
||||
images?: Map<string, string>,
|
||||
): React.ReactNode[] {
|
||||
return flatMap(tags, (tag) =>
|
||||
entries
|
||||
.filter((entry) => entry.tag === tag)
|
||||
.map((entry) => detailsFunction(entry, gedcom)),
|
||||
.map((entry) => detailsFunction(entry, gedcom, images)),
|
||||
)
|
||||
.filter((element) => element !== null)
|
||||
.map((element, index) => (
|
||||
@@ -271,14 +282,16 @@ function combineAllMatchingEntriesIntoSingleSection(
|
||||
detailsFunction: (
|
||||
entries: GedcomEntry[],
|
||||
gedcom: GedcomData,
|
||||
images?: Map<string, string>,
|
||||
) => React.ReactNode | null,
|
||||
images?: Map<string, string>,
|
||||
): React.ReactNode {
|
||||
const entriesWithMatchingTag = flatMap(tags, (tag) =>
|
||||
entries.filter((entry) => entry.tag === tag),
|
||||
).filter((element) => element !== null);
|
||||
|
||||
const sectionWithDetails = entriesWithMatchingTag.length
|
||||
? detailsFunction(entriesWithMatchingTag, gedcom)
|
||||
? detailsFunction(entriesWithMatchingTag, gedcom, images)
|
||||
: null;
|
||||
|
||||
if (!sectionWithDetails) {
|
||||
@@ -334,6 +347,7 @@ interface Props {
|
||||
gedcom: GedcomData;
|
||||
indi: string;
|
||||
config: Config;
|
||||
images?: Map<string, string>;
|
||||
}
|
||||
|
||||
export function Details(props: Props) {
|
||||
@@ -353,9 +367,15 @@ export function Details(props: Props) {
|
||||
props.gedcom,
|
||||
['OBJE'],
|
||||
imageDetails,
|
||||
props.images,
|
||||
)}
|
||||
<ImmediateFamily gedcom={props.gedcom} indi={props.indi} />
|
||||
<Events gedcom={props.gedcom} entries={entries} indi={props.indi} />
|
||||
<Events
|
||||
gedcom={props.gedcom}
|
||||
entries={entries}
|
||||
indi={props.indi}
|
||||
images={props.images}
|
||||
/>
|
||||
{props.config.id === Ids.SHOW ? getSectionForId(props.indi) : null}
|
||||
{getSectionForEachMatchingEntry(
|
||||
entries,
|
||||
@@ -375,6 +395,7 @@ export function Details(props: Props) {
|
||||
props.gedcom,
|
||||
['OBJE'],
|
||||
fileDetails,
|
||||
props.images,
|
||||
)}
|
||||
{combineAllMatchingEntriesIntoSingleSection(
|
||||
entries,
|
||||
|
||||
@@ -138,7 +138,7 @@ export function EventExtras(props: Props) {
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="extras.files"
|
||||
defaultMessage="Additonal files"
|
||||
defaultMessage="Additional files"
|
||||
/>
|
||||
}
|
||||
size="mini"
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getNonImageFileEntry,
|
||||
mapToSource,
|
||||
resolveDate,
|
||||
resolveFileUrl,
|
||||
resolveType,
|
||||
Source,
|
||||
} from '../../util/gedcom_util';
|
||||
@@ -26,6 +27,7 @@ interface Props {
|
||||
gedcom: GedcomData;
|
||||
indi: string;
|
||||
entries: GedcomEntry[];
|
||||
images?: Map<string, string>;
|
||||
}
|
||||
|
||||
interface EventData {
|
||||
@@ -164,42 +166,55 @@ function eventPlace(entry: GedcomEntry) {
|
||||
return place?.data ? getData(place) : undefined;
|
||||
}
|
||||
|
||||
function eventImages(entry: GedcomEntry, gedcom: GedcomData): Image[] {
|
||||
return entry.tree
|
||||
function eventImages(
|
||||
entry: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
images?: Map<string, string>,
|
||||
): Image[] {
|
||||
const result: Image[] = [];
|
||||
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) || '',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
.forEach((objectEntryReference) => {
|
||||
const objectEntry = dereference(
|
||||
objectEntryReference,
|
||||
gedcom,
|
||||
(gedcom) => gedcom.other,
|
||||
);
|
||||
const fileEntry = getImageFileEntry(objectEntry);
|
||||
if (fileEntry) {
|
||||
result.push({
|
||||
url: resolveFileUrl(fileEntry.data, images),
|
||||
filename: getFileName(fileEntry) || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function eventFiles(entry: GedcomEntry, gedcom: GedcomData): Image[] {
|
||||
return entry.tree
|
||||
function eventFiles(
|
||||
entry: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
images?: Map<string, string>,
|
||||
): FileEntry[] {
|
||||
const result: FileEntry[] = [];
|
||||
entry.tree
|
||||
.filter((subEntry) => 'OBJE' === subEntry.tag)
|
||||
.map((objectEntry) =>
|
||||
dereference(objectEntry, gedcom, (gedcom) => gedcom.other),
|
||||
)
|
||||
.map((objectEntry) => getNonImageFileEntry(objectEntry))
|
||||
.flatMap((fileEntry) =>
|
||||
fileEntry
|
||||
? [
|
||||
{
|
||||
url: fileEntry?.data || '',
|
||||
filename: getFileName(fileEntry) || '',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
.forEach((objectEntryReference) => {
|
||||
const objectEntry = dereference(
|
||||
objectEntryReference,
|
||||
gedcom,
|
||||
(gedcom) => gedcom.other,
|
||||
);
|
||||
const fileEntry = getNonImageFileEntry(objectEntry);
|
||||
if (fileEntry) {
|
||||
result.push({
|
||||
url: resolveFileUrl(fileEntry.data, images),
|
||||
filename: getFileName(fileEntry),
|
||||
titl: objectEntry.tree.find((entry) => entry.tag === 'TITL')?.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function eventSources(entry: GedcomEntry, gedcom: GedcomData): Source[] {
|
||||
@@ -235,11 +250,12 @@ function toEvent(
|
||||
gedcom: GedcomData,
|
||||
indi: string,
|
||||
intl: IntlShape,
|
||||
images?: Map<string, string>,
|
||||
): EventData[] {
|
||||
if (entry.tag === 'FAMS') {
|
||||
return toFamilyEvents(entry, gedcom, indi);
|
||||
return toFamilyEvents(entry, gedcom, indi, images);
|
||||
}
|
||||
return toIndiEvent(entry, gedcom, indi, intl);
|
||||
return toIndiEvent(entry, gedcom, indi, intl, images);
|
||||
}
|
||||
|
||||
function toIndiEvent(
|
||||
@@ -247,6 +263,7 @@ function toIndiEvent(
|
||||
gedcom: GedcomData,
|
||||
indi: string,
|
||||
intl: IntlShape,
|
||||
images?: Map<string, string>,
|
||||
): EventData[] {
|
||||
const date = resolveDate(entry) || null;
|
||||
return [
|
||||
@@ -256,8 +273,8 @@ function toIndiEvent(
|
||||
type: resolveType(entry),
|
||||
age: getAge(entry, indi, gedcom, intl),
|
||||
place: eventPlace(entry),
|
||||
images: eventImages(entry, gedcom),
|
||||
files: eventFiles(entry, gedcom),
|
||||
images: eventImages(entry, gedcom, images),
|
||||
files: eventFiles(entry, gedcom, images),
|
||||
notes: eventNotes(entry, gedcom),
|
||||
sources: eventSources(entry, gedcom),
|
||||
indi: indi,
|
||||
@@ -269,6 +286,7 @@ function toFamilyEvents(
|
||||
entry: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
indi: string,
|
||||
images?: Map<string, string>,
|
||||
): EventData[] {
|
||||
const family = dereference(entry, gedcom, (gedcom) => gedcom.fams);
|
||||
return flatMap(FAMILY_EVENT_TAGS, (tag) =>
|
||||
@@ -281,8 +299,8 @@ function toFamilyEvents(
|
||||
type: resolveType(familyEvent),
|
||||
personLink: getSpouse(indi, family, gedcom),
|
||||
place: eventPlace(familyEvent),
|
||||
images: eventImages(familyEvent, gedcom),
|
||||
files: eventFiles(familyEvent, gedcom),
|
||||
images: eventImages(familyEvent, gedcom, images),
|
||||
files: eventFiles(familyEvent, gedcom, images),
|
||||
notes: eventNotes(familyEvent, gedcom),
|
||||
sources: eventSources(familyEvent, gedcom),
|
||||
indi: indi,
|
||||
@@ -320,7 +338,9 @@ export function Events(props: Props) {
|
||||
const events = flatMap(SORTED_EVENT_TYPE_GROUPS, (eventTypeGroup) =>
|
||||
props.entries
|
||||
.filter((entry) => eventTypeGroup.includes(entry.tag))
|
||||
.map((eventEntry) => toEvent(eventEntry, props.gedcom, props.indi, intl))
|
||||
.map((eventEntry) =>
|
||||
toEvent(eventEntry, props.gedcom, props.indi, intl, props.images),
|
||||
)
|
||||
.flatMap((events) => events)
|
||||
.sort((event1, event2) => compareDates(event1.date, event2.date)),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {SyntheticEvent, useState} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {
|
||||
Card,
|
||||
Container,
|
||||
Icon,
|
||||
Image,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
Modal,
|
||||
Placeholder,
|
||||
} from 'semantic-ui-react';
|
||||
import {isBrowserLoadable} from '../../util/gedcom_util';
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
@@ -22,6 +24,33 @@ export function WrappedImage(props: Props) {
|
||||
const [imageFailed, setImageFailed] = useState(false);
|
||||
const [imageSrc, setImageSrc] = useState('');
|
||||
|
||||
if (!isBrowserLoadable(props.url)) {
|
||||
return (
|
||||
<Card
|
||||
centered
|
||||
style={{width: '100%', maxWidth: '290px', margin: '0 auto 14px'}}
|
||||
>
|
||||
<Card.Content textAlign="center" style={{backgroundColor: '#f9f9f9'}}>
|
||||
<Icon
|
||||
name="image"
|
||||
size="huge"
|
||||
color="grey"
|
||||
style={{marginBottom: '10px', opacity: 0.6}}
|
||||
/>
|
||||
<Card.Header style={{fontSize: '14px', wordBreak: 'break-all'}}>
|
||||
{props.title || props.filename}
|
||||
</Card.Header>
|
||||
<Card.Meta style={{marginTop: '5px', fontStyle: 'italic'}}>
|
||||
<FormattedMessage
|
||||
id="media.not_uploaded"
|
||||
defaultMessage="File not uploaded"
|
||||
/>
|
||||
</Card.Meta>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (imageLoaded && imageSrc !== props.url) {
|
||||
setImageLoaded(false);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,12 @@ export function SidePanel({
|
||||
defaultMessage: 'Info',
|
||||
}),
|
||||
render: () => (
|
||||
<Details gedcom={data.gedcom} indi={selectedIndiId} config={config} />
|
||||
<Details
|
||||
gedcom={data.gedcom}
|
||||
indi={selectedIndiId}
|
||||
config={config}
|
||||
images={data.images}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user