Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/chart/photo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ const indi = (m: MediaLink[], restrictions: string[] = []) =>
const on = {allowRemote: true, hideRestricted: true};

describe('photoOf', () => {
it('skips scanned documents: only a photo or an image of unknown kind', () => {
const scan = (medium: string) =>
media('doc.jpg', {
files: [{path: 'doc.jpg', medium, translations: []}],
});
const t = tree({'doc.jpg': 'blob:doc', 'me.jpg': 'blob:me'});
expect(photoOf(indi([scan('OTHER'), media('me.jpg')]), t, on)?.src).toBe(
'blob:me',
);
expect(photoOf(indi([scan('CARD')]), t, on)).toBeUndefined();
expect(photoOf(indi([scan('PHOTO')]), t, on)?.src).toBe('blob:doc');
});

it('takes the first image that can be shown', () => {
const t = tree({'photos/anna.jpg': 'blob:anna'});
const p = photoOf(
Expand Down
6 changes: 3 additions & 3 deletions src/chart/photo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// that can be shown, cropped as GEDCOM 7.0 CROP says.

import type {Crop, IndiView, LoadedTree, MediaLink} from '../gedcom/types';
import {isImageFile, mediaUrl} from '../load';
import {isPortraitFile, mediaUrl} from '../load';

export interface Photo {
src: string;
Expand All @@ -16,7 +16,7 @@ const isRestricted = (r: readonly string[]) =>

/**
* The person's portrait: the first OBJE (the most preferred one in GEDCOM)
* whose first file is an image we may show — bundled with the tree, or a
* whose first file is a photo (not a scanned document) we may show — bundled with the tree, or a
* linked http(s) image when `allowRemote` is on. Restricted media are
* skipped when `hideRestricted` is on.
*/
Expand Down Expand Up @@ -45,7 +45,7 @@ function usable(
): Photo | undefined {
if (hideRestricted && isRestricted(media.restrictions)) return undefined;
const file = media.files[0];
if (!file || !isImageFile(file)) return undefined;
if (!file || !isPortraitFile(file)) return undefined;
const src = mediaUrl(tree.media, file.path);
if (!src) return undefined;
if (/^https?:/i.test(src) && !allowRemote) return undefined;
Expand Down
1 change: 1 addition & 0 deletions src/load/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export {looksLikeZip, openGedzip} from './zip';
export {
decodeFilePath,
isImageFile,
isPortraitFile,
lookupMedia,
mediaUrl,
safeMediaUrl,
Expand Down
13 changes: 13 additions & 0 deletions src/model/mediaUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,16 @@ export function mediaUrl(media: MediaMap, path: string): string | undefined {
export function isImageFile(file: {path: string; mime?: string}): boolean {
return (file.mime ?? guessMime(file.path))?.startsWith('image/') ?? false;
}

/**
* True when the file may stand for the person as a portrait: an image that
* is a photo, or of unknown medium. A scanned document, card or tombstone
* (MEDI other than PHOTO) is an image but not a portrait.
*/
export function isPortraitFile(file: {
path: string;
mime?: string;
medium?: string;
}): boolean {
return isImageFile(file) && (!file.medium || file.medium === 'PHOTO');
}
24 changes: 24 additions & 0 deletions src/ui/media.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,27 @@ describe('titleYear', () => {
expect(titleYear('Портрет')).toBeUndefined();
});
});

describe('the portrait', () => {
const docTree = treeOf(
doc7(
'0 @I1@ INDI',
'1 OBJE @O1@',
'1 OBJE @O2@',
'0 @O1@ OBJE',
'1 FILE https://images.example.test/O1.jpg',
'2 FORM image/jpeg',
'3 MEDI OTHER',
'2 TITL Свидетельство о смерти',
...obje('O2', 'Портрет'),
),
);

it('is a photo, never a scanned document', () => {
const entries = mediaEntries(docTree, docTree.indis.get('I1')!, {
hide: true,
allowRemote: true,
});
expect(portraitOf(entries)?.entry.title).toBe('Портрет');
});
});
12 changes: 9 additions & 3 deletions src/ui/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
MediaLink,
} from '../gedcom/types';
import {yearOf} from '../model/bounds';
import {isImageFile, mediaUrl} from '../load';
import {isImageFile, isPortraitFile, mediaUrl} from '../load';
import {cropBox} from '../chart/photo';

export interface MediaEntry {
Expand Down Expand Up @@ -151,15 +151,21 @@ export function blockedHosts(entries: MediaEntry[]): string[] {
];
}

/** The portrait: the first image that can be shown, and its place. */
/** The portrait: the first photo that can be shown (a scanned document
* is not one), and its place. */
export function portraitOf(
entries: MediaEntry[],
): {entry: MediaEntry; index: number} | undefined {
// The first image in the file (GEDCOM's preferred one), wherever the
// years put it.
let index = -1;
entries.forEach((e, i) => {
if (e.image && e.src && (index === -1 || e.order < entries[index]!.order))
if (
e.image &&
isPortraitFile(e.file) &&
e.src &&
(index === -1 || e.order < entries[index]!.order)
)
index = i;
});
return index === -1 ? undefined : {entry: entries[index]!, index};
Expand Down
Loading