From 6a4bb69c39de9c64f974b24758795df1f99b9443 Mon Sep 17 00:00:00 2001 From: l0drex Date: Fri, 16 Jun 2023 15:26:18 +0200 Subject: [PATCH 001/110] Add confidence color mode --- src/backend/ViewGraph.ts | 3 ++- src/components/TreeView.tsx | 9 ++++++++- src/components/View.tsx | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/backend/ViewGraph.ts b/src/backend/ViewGraph.ts index 21f24d50..9fcc3494 100644 --- a/src/backend/ViewGraph.ts +++ b/src/backend/ViewGraph.ts @@ -17,7 +17,8 @@ export enum ViewMode { export enum ColorMode { GENDER = "gender", AGE = "age", - NAME = "name" + NAME = "name", + CONFIDENCE = "confidence" } type eventTypes = "remove" | "add" | "progress"; diff --git a/src/components/TreeView.tsx b/src/components/TreeView.tsx index 336b95b0..b3811a73 100644 --- a/src/components/TreeView.tsx +++ b/src/components/TreeView.tsx @@ -10,6 +10,7 @@ import {GraphFamily, GraphPerson} from "../backend/graph"; import {Loading} from "./Loading"; import {strings} from "../main"; import {FocusPersonContext} from "./View"; +import {Confidence} from "../backend/gedcomx-enums"; const d3cola = cola.d3adaptor(d3); @@ -160,7 +161,6 @@ async function animateTree(graph: ViewGraph, colorMode: ColorMode, isLandscape: } else { d3cola.flowLayout("y", config.gridSize * 3) } - // todo this is a problem d3cola.start(iterations, 0, iterations); let nodesLayer = d3.select("#nodes"); @@ -222,6 +222,13 @@ async function animateTree(graph: ViewGraph, colorMode: ColorMode, isLandscape: .style("box-shadow", d => `0 0 1rem ${genderColor(d.getGender())}`); break; } + case ColorMode.CONFIDENCE: { + const confidenceColor = d3.scaleOrdinal([Confidence.Low, Confidence.Medium, Confidence.High], d3.schemeRdYlGn[3]); + personNode + .select(".bg") + .style("background-color", d => confidenceColor(d.data.getConfidence() as Confidence)) + .style("color", d => d.data.getConfidence() as Confidence === Confidence.Medium ? "black" : "") + } } personNode diff --git a/src/components/View.tsx b/src/components/View.tsx index e8608bd1..027601b7 100644 --- a/src/components/View.tsx +++ b/src/components/View.tsx @@ -37,6 +37,7 @@ function ViewOptions(props) { + From 5dda4a076ee74408540101b730e3ffbe747f03c2 Mon Sep 17 00:00:00 2001 From: l0drex Date: Fri, 16 Jun 2023 15:41:52 +0200 Subject: [PATCH 002/110] Hide relationships behind drop down --- src/App.css | 10 +++- src/components/InfoPanel.tsx | 99 +++++++++++++++++++----------------- src/locales/de.json | 1 + src/locales/en.json | 1 + 4 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/App.css b/src/App.css index b00a1ae9..fd88d467 100644 --- a/src/App.css +++ b/src/App.css @@ -102,13 +102,21 @@ aside .title h2 { } } -aside > article { +aside article { padding: 1rem; box-sizing: border-box; box-shadow: inset 0 .25rem .25rem rgba(0, 0, 0, .33); flex-basis: 200px; } +aside details { + padding-right: 0; +} + +aside details article { + margin-top: 1rem; +} + footer { color: var(--foreground-secondary); text-align: left; diff --git a/src/components/InfoPanel.tsx b/src/components/InfoPanel.tsx index 7d4919e3..c2c9b59c 100644 --- a/src/components/InfoPanel.tsx +++ b/src/components/InfoPanel.tsx @@ -167,54 +167,57 @@ function InfoPanel() { - {parents && parents.length > 0 &&
-

πŸ‘ͺ {strings.infoPanel.parents}

-
    - {parents?.map(p =>
  • {p.fullName}
  • )} -
-
} - - {children && children.length > 0 &&
-

🍼 {strings.infoPanel.children}

-
    - {children.map(p =>
  • {p.fullName}
  • )} -
-
} - - {partner && partner.length > 0 &&
-

❀️ {strings.infoPanel.partner}

-
    - {partner.map(p =>
  • {p.fullName}
  • )} -
-
} - - {godparents && godparents.length > 0 &&
-

β›… {strings.infoPanel.godparents}

-
    - {godparents.map(p =>
  • {p.fullName}
  • )} -
-
} - - {godchildren && godchildren.length > 0 &&
-

β›… {strings.infoPanel.godchildren}

-
    - {godchildren.map(p =>
  • {p.fullName}
  • )} -
-
} - - {enslavedBy && enslavedBy.length > 0 &&
-

⛓️ {strings.infoPanel.enslavedBy}

-
    - {enslavedBy.map(p =>
  • {p.fullName}
  • )} -
-
} - - {slaves && slaves.length > 0 &&
-

⛓️ {strings.infoPanel.slaves}

-
    - {slaves.map(p =>
  • {p.fullName}
  • )} -
-
} +
+ {strings.infoPanel.relationships} + {parents && parents.length > 0 &&
+

πŸ‘ͺ {strings.infoPanel.parents}

+
    + {parents?.map(p =>
  • {p.fullName}
  • )} +
+
} + + {children && children.length > 0 &&
+

🍼 {strings.infoPanel.children}

+
    + {children.map(p =>
  • {p.fullName}
  • )} +
+
} + + {partner && partner.length > 0 &&
+

❀️ {strings.infoPanel.partner}

+
    + {partner.map(p =>
  • {p.fullName}
  • )} +
+
} + + {godparents && godparents.length > 0 &&
+

β›… {strings.infoPanel.godparents}

+
    + {godparents.map(p =>
  • {p.fullName}
  • )} +
+
} + + {godchildren && godchildren.length > 0 &&
+

β›… {strings.infoPanel.godchildren}

+
    + {godchildren.map(p =>
  • {p.fullName}
  • )} +
+
} + + {enslavedBy && enslavedBy.length > 0 &&
+

⛓️ {strings.infoPanel.enslavedBy}

+
    + {enslavedBy.map(p =>
  • {p.fullName}
  • )} +
+
} + + {slaves && slaves.length > 0 &&
+

⛓️ {strings.infoPanel.slaves}

+
    + {slaves.map(p =>
  • {p.fullName}
  • )} +
+
} +
{person.getNotes().filter(filterLang).map((note, i) => { return diff --git a/src/locales/de.json b/src/locales/de.json index 744d16fc..65efa6df 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -47,6 +47,7 @@ "confidenceExplanation": "Wie sehr kann den Daten vertraut werden", "confidenceLabel": "Zuversicht: ", "attribution": "Erstellt am {0} von {1}. Bearbeitet am {2} von {3}:", + "relationships": "Verbundene Personen", "children": "Kinder", "parents": "Eltern", "partner": "Partner", diff --git a/src/locales/en.json b/src/locales/en.json index 5a0d345d..ebda3423 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -47,6 +47,7 @@ "confidenceExplanation": "How much can the data be trusted", "confidenceLabel": "Confidence: ", "attribution": "Created on {0} by {1}. Modified on {2} by {3}:", + "relationships": "Relationships", "children": "Children", "parents": "Parents", "partner": "Partner", From 32717e0f58a48d4f29bebe6ded9b195c2b0de0e6 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 00:17:13 +0200 Subject: [PATCH 003/110] Add source overview --- src/App.css | 44 ++++++++ src/App.tsx | 6 +- src/backend/db.ts | 4 +- src/backend/gedcomx-extensions.ts | 24 ++++- src/components/Article.css | 2 +- src/components/Form.tsx | 2 +- src/components/GedcomXComponents.tsx | 123 +++++++++++++++++++++++ src/components/Header.tsx | 3 +- src/components/InfoPanel.tsx | 46 +-------- src/components/{View.tsx => Persons.tsx} | 4 +- src/components/SourceDescriptions.tsx | 37 +++++++ src/components/TreeView.tsx | 2 +- src/locales/de.json | 3 + src/locales/en.json | 3 + types/gedcomx-js/gedcomx-js.d.ts | 2 +- 15 files changed, 252 insertions(+), 53 deletions(-) create mode 100644 src/components/GedcomXComponents.tsx rename src/components/{View.tsx => Persons.tsx} (99%) create mode 100644 src/components/SourceDescriptions.tsx diff --git a/src/App.css b/src/App.css index fd88d467..53a7a12b 100644 --- a/src/App.css +++ b/src/App.css @@ -140,6 +140,50 @@ li:not(:last-child) { margin-bottom: .5rem; } +ul.clickable, ol.clickable { + padding: 0; +} + +ul.clickable li { + list-style: none; + padding: .5rem; + border-radius: .5rem; +} + +ul.clickable li:hover { + background: var(--background-lower); +} + +.center { + display: block; + margin: 0 auto; + border-radius: 1rem; +} + +p.misc { + color: var(--foreground-secondary); +} + +video, audio { + width: inherit; +} + +figure { + margin: 1rem auto; + overflow: auto; + white-space: pre-line; + max-height: 30rem; +} + +object { + max-width: 100%; +} + +object[type="application/pdf"], object[type^="video"] { + width: 100%; + height: 30rem; +} + @media (orientation: portrait) { @media (max-width: 749px) { #title { diff --git a/src/App.tsx b/src/App.tsx index a417adac..47da39bc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,18 @@ import * as React from "react"; import './App.css'; import {strings} from "./main"; -import View from "./components/View"; +import Persons from "./components/Persons"; import {BrowserRouter, Route, Routes} from "react-router-dom"; import {Home, Imprint} from "./components/Home"; import Statistics from "./components/Statistics"; +import {SourceDescriptions} from "./components/SourceDescriptions"; function App() { return }/> - }/> + }/> + }/> }/> }/> diff --git a/src/backend/db.ts b/src/backend/db.ts index 8896811f..6db10d21 100644 --- a/src/backend/db.ts +++ b/src/backend/db.ts @@ -4,7 +4,7 @@ import { IGroup } from "./gedcomx-types"; import * as GedcomX from "gedcomx-js"; -import {Person, Relationship, setReferenceAge} from "./gedcomx-extensions"; +import {Person, Relationship, setReferenceAge, SourceDescription} from "./gedcomx-extensions"; import {PersonFactTypes, RelationshipTypes} from "./gedcomx-enums"; import {ResourceReference} from "gedcomx-js"; @@ -101,7 +101,7 @@ export class FamilyDB extends Dexie { } return this.sourceDescriptions.where("id").equals(id).first() - .then(sd => new GedcomX.SourceDescription(sd)); + .then(sd => new SourceDescription(sd)); } async agentWithId(id: string | ResourceReference) { diff --git a/src/backend/gedcomx-extensions.ts b/src/backend/gedcomx-extensions.ts index a4f6a4a8..c74e29cf 100644 --- a/src/backend/gedcomx-extensions.ts +++ b/src/backend/gedcomx-extensions.ts @@ -3,7 +3,7 @@ import "./gedcomx-js-rs"; import {Equals, filterLang, strings} from "../main"; import config from "../config"; import { - baseUri, + baseUri, KnownResourceTypes, NameTypes, PersonFactQualifiers, PersonFactTypes @@ -322,6 +322,28 @@ export class FamilyView extends GedcomX.FamilyView implements Equals { } } +export class SourceDescription extends GedcomX.SourceDescription { + get title() { + if (this.getTitles().length > 0) return this.getTitles()[0]; + return this.getCitations()[0].getValue(); + } + + get emoji() { + switch (this.getResourceType()) { + case KnownResourceTypes.Collection: + return "πŸ“š"; + case KnownResourceTypes.PhysicalArtifact: + return "πŸ“–"; + case KnownResourceTypes.DigitalArtifact: + return "πŸ’Ώ"; + case KnownResourceTypes.Record: + return "πŸ“œ"; + } + + return "πŸ“–"; + } +} + let referenceAge: { age: number, generation: number } = { age: undefined, generation: undefined diff --git a/src/components/Article.css b/src/components/Article.css index bb890703..d8df257c 100644 --- a/src/components/Article.css +++ b/src/components/Article.css @@ -11,7 +11,7 @@ article h1 { padding-bottom: .75rem; } -article > div { +article > div, article > section { padding: .5rem 0; } diff --git a/src/components/Form.tsx b/src/components/Form.tsx index a11c31ff..fd6db97f 100644 --- a/src/components/Form.tsx +++ b/src/components/Form.tsx @@ -100,7 +100,7 @@ export function saveDataAndRedirect(fileContent) { db.load(data) .then(() => { let url = new URL(window.location.href); - url.pathname = "/family-tree/view"; + url.pathname = "/family-tree/persons"; window.location.href = url.href; }); } diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx new file mode 100644 index 00000000..4899e1be --- /dev/null +++ b/src/components/GedcomXComponents.tsx @@ -0,0 +1,123 @@ +import * as gedcomX from "gedcomx-js"; +import {filterLang, strings} from "../main"; +import {useLiveQuery} from "dexie-react-hooks"; +import {db} from "../backend/db"; +import {GDate, SourceDescription as SourceDescriptionClass} from "../backend/gedcomx-extensions"; +import {useEffect, useState} from "react"; + +export function Note(props: { note: gedcomX.Note }) { + return
+

πŸ“ {props.note.getSubject() || strings.infoPanel.note}

+

{props.note.getText()}

+ {props.note.getAttribution() && } +
+} + +/** + * @todo this is untested as I don't have data to do so. Please file a bug if you find something weird. + */ +export function Attribution(props: { attribution: gedcomX.Attribution }) { + let created = props.attribution.getCreated().toString(); + let creatorRef = props.attribution.getCreator(); + const creator = useLiveQuery(async () => db.agentWithId(creatorRef), [creatorRef]); + let creatorName = creator.getNames().filter(filterLang)[0].getValue(); + let modified = props.attribution.getModified().toString(); + let contributorRef = props.attribution.getContributor(); + const contributor = useLiveQuery(async () => db.agentWithId(contributorRef), [contributorRef]); + let contributorName = contributor.getNames().filter(filterLang)[0].getValue(); + let message = props.attribution.getChangeMessage(); + + return {strings.formatString(strings.infoPanel.attribution, created, creatorName, modified, contributorName)} {message} +} + +export function SourceDescription(props: { description: SourceDescriptionClass }) { + const [text, setText] = useState(""); + const hasMedia = props.description.mediaType && props.description.about; + useEffect(() => { + if (!hasMedia) return; + let mediaType = props.description.mediaType.split('/'); + if (mediaType[0] !== "text") return; + + fetch(props.description.getAbout()) + .then(r => r.text()) + .then(t => setText(t)); + }, [hasMedia, props.description]) + + const title = (props.description.getTitles().length > 0) ? props.description.getTitles()[0].value : strings.infoPanel.source; + const componentOf = props.description.getComponentOf(); + const analysis = useLiveQuery(() => { + let id = props.description.getAnalysis()?.substring(1); + //return db.documentWithId(id); + return id; + }, [props.description]) + let media; + if (hasMedia) { + if (props.description.mediaType.startsWith("text")) + media =

{text}

+ else + media = + {props.description.getDescriptions().filter(filterLang)[0]?.getValue()} + ; + } + + const hasMisc = componentOf || props.description.rights || props.description.repository || analysis; + + return <> +
+

{props.description?.emoji} {title}

+ {hasMisc &&
+ {componentOf && } + {props.description.rights &&
©️{props.description.rights}
} + {props.description.repository &&
repository: {props.description.repository}
} + {analysis && Analysis} +
} + {hasMedia &&
{media}
} + {props.description.getDescriptions().filter(filterLang).map((d, i) => +

{d.getValue()}

+ )} + {props.description.getMediator() &&

{`Mediator: ${props.description.getMediator()}`}

} + {props.description.citations &&
+ Citations: +
    + {props.description.getCitations() + .filter(filterLang) + .map((c, i) =>
  • {c.getValue()}
  • )} +
+
} +
+ {props.description.getCoverage().map((c, i) => )} + {props.description.getNotes().filter(filterLang).map((n, i) => )} + {props.description.getSources().map((s, i) => )} + +} + +export function SourceReference(props: { reference: gedcomX.SourceReference }) { + return +} + +export function Coverage(props: { coverage: gedcomX.Coverage }) { + let date = new GDate(props.coverage.temporal.toJSON()).toString(); + return
+

πŸ—ΊοΈ {strings.sourceDescriptions.coverage}

+

+ {props.coverage.temporal && <>{date}} + {props.coverage.spatial && } +

+
+} + +export function PlaceReference(props: { reference: gedcomX.PlaceReference }) { + let original = props.reference.original ?? props.reference.description ?? "?"; + if (props.reference.description) { + return {original} + } + return {original} +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index ab006891..ee4e8518 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -14,8 +14,9 @@ function Header(props) { {props.children} ); diff --git a/src/components/InfoPanel.tsx b/src/components/InfoPanel.tsx index c2c9b59c..5bafe856 100644 --- a/src/components/InfoPanel.tsx +++ b/src/components/InfoPanel.tsx @@ -3,12 +3,12 @@ import {Confidence, PersonFactTypes} from "../backend/gedcomx-enums"; import {filterLang, strings} from "../main"; import {Gallery} from "./Gallery"; import Sidebar from "./Sidebar"; -import * as gedcomX from "gedcomx-js"; import {db} from "../backend/db"; import {useLiveQuery} from "dexie-react-hooks"; import {GDate, Person} from "../backend/gedcomx-extensions"; import {useContext} from "react"; -import {FocusPersonContext} from "./View"; +import {FocusPersonContext} from "./Persons"; +import {Note, SourceReference} from "./GedcomXComponents"; function InfoPanel() { const person = useContext(FocusPersonContext); @@ -84,13 +84,6 @@ function InfoPanel() { Promise.all(children.map(r => db.personWithId(r)))); }, [person]) - const sourceDescriptions = useLiveQuery(async () => { - if (!person) return; - - return Promise.all(person.getSources() - .map(s => db.sourceDescriptionWithId(s.getDescription()))) - }, [person]) - // todo: // person.getEvidence() // person.getIdentifiers() @@ -121,8 +114,7 @@ function InfoPanel() { let credit = image.getCitations()[0].getValue(); return
{strings.formatString(strings.infoPanel.personImageAlt, + alt={image.getDescriptions().filter(filterLang)[0]?.getValue()}/> Β© {credit} @@ -223,11 +215,8 @@ function InfoPanel() { return })} - {sourceDescriptions && sourceDescriptions.map(description => { - return
-

πŸ“š {strings.infoPanel.source}

-

{description.getCitations()[0].getValue()}

-
+ {person.getSources().length > 0 && person.getSources().map((reference, i) => { + return })} {person.getConfidence() &&
@@ -238,31 +227,6 @@ function InfoPanel() { ); } -function Note(props: { note: gedcomX.Note }) { - return
-

πŸ“ {props.note.getSubject() || strings.infoPanel.note}

-

{props.note.getText()}

- {props.note.getAttribution() && } -
-} - -/** - * @todo this is untested as I don't have data to do so. Please file a bug if you find something weird. - */ -function Attribution(props: { attribution: gedcomX.Attribution }) { - let created = props.attribution.getCreated().toString(); - let creatorRef = props.attribution.getCreator(); - const creator = useLiveQuery(async () => db.agentWithId(creatorRef), [creatorRef]); - let creatorName = creator.getNames().filter(filterLang)[0].getValue(); - let modified = props.attribution.getModified().toString(); - let contributorRef = props.attribution.getContributor(); - const contributor = useLiveQuery(async () => db.agentWithId(contributorRef), [contributorRef]); - let contributorName = contributor.getNames().filter(filterLang)[0].getValue(); - let message = props.attribution.getChangeMessage(); - - return {strings.formatString(strings.infoPanel.attribution, created, creatorName, modified, contributorName)} {message} -} - async function getImages(person: Person) { let mediaRefs = person.getMedia().map(media => media.getDescription()); const sourceDescriptions = await Promise.all(mediaRefs.map(id => db.sourceDescriptionWithId(id))); diff --git a/src/components/View.tsx b/src/components/Persons.tsx similarity index 99% rename from src/components/View.tsx rename to src/components/Persons.tsx index 027601b7..b7022c09 100644 --- a/src/components/View.tsx +++ b/src/components/Persons.tsx @@ -54,7 +54,7 @@ function ViewOptions(props) { ); } -function View() { +function Persons() { const [viewMode, setViewMode] = useState(getUrlOption("view", ViewMode.DEFAULT)); const [colorMode, setColorMode] = useState(getUrlOption("colorMode", ColorMode.GENDER)); const [focusPerson, setFocus] = useState(null); @@ -141,4 +141,4 @@ function View() { ); } -export default View; +export default Persons; diff --git a/src/components/SourceDescriptions.tsx b/src/components/SourceDescriptions.tsx new file mode 100644 index 00000000..2a8dff2c --- /dev/null +++ b/src/components/SourceDescriptions.tsx @@ -0,0 +1,37 @@ +import Header from "./Header"; +import {SourceDescription as SourceDescriptionClass} from "../backend/gedcomx-extensions"; +import {useLiveQuery} from "dexie-react-hooks"; +import {db} from "../backend/db"; +import {strings} from "../main"; +import {SourceDescription} from "./GedcomXComponents"; + +export function SourceDescriptions() { + const description = useLiveQuery(() => { + let url = new URL(window.location.href); + let id = url.hash.substring(1); + if (id.length === 0) return null; + return db.sourceDescriptionWithId(id); + }, [window.location.href]); + + return <> +
+
+ {description ? : } +
+ +} + +function DescriptionOverview() { + const descriptions = useLiveQuery(() => { + return db.sourceDescriptions.toArray().then(sds => sds.map(s => new SourceDescriptionClass(s))); + }, []) + + return +} diff --git a/src/components/TreeView.tsx b/src/components/TreeView.tsx index b3811a73..c8b29473 100644 --- a/src/components/TreeView.tsx +++ b/src/components/TreeView.tsx @@ -9,7 +9,7 @@ import {ColorMode, ViewGraph, ViewMode} from "../backend/ViewGraph"; import {GraphFamily, GraphPerson} from "../backend/graph"; import {Loading} from "./Loading"; import {strings} from "../main"; -import {FocusPersonContext} from "./View"; +import {FocusPersonContext} from "./Persons"; import {Confidence} from "../backend/gedcomx-enums"; const d3cola = cola.d3adaptor(d3); diff --git a/src/locales/de.json b/src/locales/de.json index 65efa6df..4afdf88a 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -330,6 +330,9 @@ "lifeExpectancy": "Lebenserwartung", "marriageAge": "Heiratsalter" }, + "sourceDescriptions": { + "coverage": "Abdeckung" + }, "loading": { "familyTree": "Lade Stammbaum", "statistic": "Berechne Statistik" diff --git a/src/locales/en.json b/src/locales/en.json index ebda3423..121b44dd 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -330,6 +330,9 @@ "lifeExpectancy": "Life Expectancy", "marriageAge": "Marriage age" }, + "sourceDescriptions": { + "coverage": "Coverage" + }, "loading": { "familyTree": "Loading family tree", "statistic": "Calculating statistic" diff --git a/types/gedcomx-js/gedcomx-js.d.ts b/types/gedcomx-js/gedcomx-js.d.ts index 12c34824..2cc173fe 100644 --- a/types/gedcomx-js/gedcomx-js.d.ts +++ b/types/gedcomx-js/gedcomx-js.d.ts @@ -285,7 +285,7 @@ declare module "gedcomx-js" { addRight(right: ResourceReference | object): SourceDescription - getCoverage(): Coverage + getCoverage(): Coverage[] setCoverage(coverage: Coverage[] | object[]): SourceDescription From 84ddc07745ef2ee6d1260c9825802fd928cc322c Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 11:03:06 +0200 Subject: [PATCH 004/110] Fix double hash --- src/components/GedcomXComponents.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx index 4899e1be..2ef5bce0 100644 --- a/src/components/GedcomXComponents.tsx +++ b/src/components/GedcomXComponents.tsx @@ -97,7 +97,7 @@ export function SourceReference(props: { reference: gedcomX.SourceReference }) { return From 8cd45adb42b03e3a563416c73e8b53913dbd6266 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 12:10:00 +0200 Subject: [PATCH 005/110] Add document overview --- src/App.tsx | 2 ++ src/backend/db.ts | 9 ++++- src/backend/gedcomx-enums.ts | 5 +++ src/backend/gedcomx-extensions.ts | 31 ++++++++++++++++-- src/components/Documents.tsx | 40 +++++++++++++++++++++++ src/components/GedcomXComponents.tsx | 49 +++++++++++++++++++++++----- src/components/Header.tsx | 1 + src/components/InfoPanel.css | 2 +- src/components/InfoPanel.tsx | 27 ++------------- src/locales/de.json | 5 +++ src/locales/en.json | 5 +++ 11 files changed, 140 insertions(+), 36 deletions(-) create mode 100644 src/components/Documents.tsx diff --git a/src/App.tsx b/src/App.tsx index 47da39bc..a93bf17e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import {BrowserRouter, Route, Routes} from "react-router-dom"; import {Home, Imprint} from "./components/Home"; import Statistics from "./components/Statistics"; import {SourceDescriptions} from "./components/SourceDescriptions"; +import {Documents} from "./components/Documents"; function App() { return @@ -13,6 +14,7 @@ function App() { }/> }/> }/> + }/> }/> }/> diff --git a/src/backend/db.ts b/src/backend/db.ts index 6db10d21..b8e755ce 100644 --- a/src/backend/db.ts +++ b/src/backend/db.ts @@ -4,7 +4,7 @@ import { IGroup } from "./gedcomx-types"; import * as GedcomX from "gedcomx-js"; -import {Person, Relationship, setReferenceAge, SourceDescription} from "./gedcomx-extensions"; +import {Document, Person, Relationship, setReferenceAge, SourceDescription} from "./gedcomx-extensions"; import {PersonFactTypes, RelationshipTypes} from "./gedcomx-enums"; import {ResourceReference} from "gedcomx-js"; @@ -110,6 +110,13 @@ export class FamilyDB extends Dexie { return this.agents.where({"id": id}).first(); } + async documentWithId(id: string | ResourceReference) { + id = toResource(id).resource.substring(1); + + return this.documents.where({"id": id}).first() + .then(d => new Document(d)); + } + async getCoupleRelationsOf(person: ResourceReference | string): Promise { let id = (person instanceof ResourceReference) ? person.resource.substring(1) : person; diff --git a/src/backend/gedcomx-enums.ts b/src/backend/gedcomx-enums.ts index 07c4f685..3a9f6fbe 100644 --- a/src/backend/gedcomx-enums.ts +++ b/src/backend/gedcomx-enums.ts @@ -165,6 +165,11 @@ export enum DocumentTypes { Analysis = "http://gedcomx.org/Analysis" } +export enum TextTypes { + Plain = "plain", + XHtml = "xhtml" +} + export enum EventTypes { Adoption = "http://gedcomx.org/Adoption", Birth = "http://gedcomx.org/Birth", diff --git a/src/backend/gedcomx-extensions.ts b/src/backend/gedcomx-extensions.ts index c74e29cf..15e3f063 100644 --- a/src/backend/gedcomx-extensions.ts +++ b/src/backend/gedcomx-extensions.ts @@ -3,10 +3,10 @@ import "./gedcomx-js-rs"; import {Equals, filterLang, strings} from "../main"; import config from "../config"; import { - baseUri, KnownResourceTypes, + baseUri, DocumentTypes, KnownResourceTypes, NameTypes, PersonFactQualifiers, - PersonFactTypes + PersonFactTypes, TextTypes } from "./gedcomx-enums"; import * as factEmojis from './factEmojies.json'; import { @@ -344,6 +344,33 @@ export class SourceDescription extends GedcomX.SourceDescription { } } +export class Document extends GedcomX.Document { + get isPlainText() { + // return true if text type is plain or undefined + return !this.getTextType() || this.getTextType() === TextTypes.Plain; + } + + get isExtracted(): boolean { + return Boolean(this.getExtracted()); + } + // todo get attribution from containing data set + + get emoji(): string { + switch (this.getType()) { + case DocumentTypes.Abstract: + return "πŸ“„"; + case DocumentTypes.Transcription: + return "πŸ“"; + case DocumentTypes.Translation: + return "🌍"; + case DocumentTypes.Analysis: + return "πŸ”"; + default: + return "πŸ“„"; + } + } +} + let referenceAge: { age: number, generation: number } = { age: undefined, generation: undefined diff --git a/src/components/Documents.tsx b/src/components/Documents.tsx new file mode 100644 index 00000000..e4642475 --- /dev/null +++ b/src/components/Documents.tsx @@ -0,0 +1,40 @@ +import {useLiveQuery} from "dexie-react-hooks"; +import {db} from "../backend/db"; +import {strings} from "../main"; +import {Document as DocumentClass} from "../backend/gedcomx-extensions"; +import Header from "./Header"; +import {Document} from "./GedcomXComponents"; + +export function Documents() { + const document = useLiveQuery(() => { + let url = new URL(window.location.href); + let id = url.hash.substring(1); + if (id.length === 0) return null; + return db.documentWithId(id); + }); + + return <> +
+
+ {document ? : } +
+ +} + +function DocumentOverview() { + const documents = useLiveQuery(() => { + return db.documents.toArray().then(docs => docs.map(d => new DocumentClass(d))); + }, []); + + const hasDocuments = documents && documents.length > 0; + + return +} diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx index 2ef5bce0..00a696a1 100644 --- a/src/components/GedcomXComponents.tsx +++ b/src/components/GedcomXComponents.tsx @@ -2,8 +2,9 @@ import * as gedcomX from "gedcomx-js"; import {filterLang, strings} from "../main"; import {useLiveQuery} from "dexie-react-hooks"; import {db} from "../backend/db"; -import {GDate, SourceDescription as SourceDescriptionClass} from "../backend/gedcomx-extensions"; +import {GDate, SourceDescription as SourceDescriptionClass, Document as DocumentClass} from "../backend/gedcomx-extensions"; import {useEffect, useState} from "react"; +import {Confidence as ConfidenceEnum} from "../backend/gedcomx-enums"; export function Note(props: { note: gedcomX.Note }) { return
@@ -45,11 +46,6 @@ export function SourceDescription(props: { description: SourceDescriptionClass } const title = (props.description.getTitles().length > 0) ? props.description.getTitles()[0].value : strings.infoPanel.source; const componentOf = props.description.getComponentOf(); - const analysis = useLiveQuery(() => { - let id = props.description.getAnalysis()?.substring(1); - //return db.documentWithId(id); - return id; - }, [props.description]) let media; if (hasMedia) { if (props.description.mediaType.startsWith("text")) @@ -60,7 +56,7 @@ export function SourceDescription(props: { description: SourceDescriptionClass } ; } - const hasMisc = componentOf || props.description.rights || props.description.repository || analysis; + const hasMisc = componentOf || props.description.rights || props.description.repository || props.description.analysis; return <>
@@ -71,7 +67,7 @@ export function SourceDescription(props: { description: SourceDescriptionClass }
} {props.description.rights &&
©️{props.description.rights}
} {props.description.repository &&
repository: {props.description.repository}
} - {analysis && Analysis} + {props.description.getAnalysis() && Analysis} } {hasMedia &&
{media}
} {props.description.getDescriptions().filter(filterLang).map((d, i) => @@ -121,3 +117,40 @@ export function PlaceReference(props: { reference: gedcomX.PlaceReference }) { } return {original} } + +export function Document(props: { document: DocumentClass }) { + // todo sanitize and render xhtml + return <> +
+

πŸ“„ Document

+ {props.document.isExtracted &&

{strings.gedcomX.document.extracted}

} + {props.document.isPlainText && props.document.getText()} + {props.document.getAttribution() && } +
+ {props.document.getNotes().filter(filterLang).map((n, i) => )} + {props.document.getSources().map((s, i) => )} + +} + +export function Confidence(props: { confidence: ConfidenceEnum | string }) { + let confidenceLevel; + switch (props.confidence) { + case ConfidenceEnum.Low: + confidenceLevel = 1; + break; + case ConfidenceEnum.Medium: + confidenceLevel = 2; + break; + case ConfidenceEnum.High: + confidenceLevel = 3; + break; + default: + confidenceLevel = undefined; + break; + } + + return
+ {strings.infoPanel.confidenceLabel} + {props.confidence} +
+} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index ee4e8518..968fdd9c 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -17,6 +17,7 @@ function Header(props) { 🌳 πŸ“Š πŸ“š + πŸ“„ ); diff --git a/src/components/InfoPanel.css b/src/components/InfoPanel.css index 37c9adee..5c8cd966 100644 --- a/src/components/InfoPanel.css +++ b/src/components/InfoPanel.css @@ -47,7 +47,7 @@ article.gallery div { font-size: .75rem; } -#confidence { +.confidence { flex-grow: 2; text-align: center; } diff --git a/src/components/InfoPanel.tsx b/src/components/InfoPanel.tsx index 5bafe856..63f0b02a 100644 --- a/src/components/InfoPanel.tsx +++ b/src/components/InfoPanel.tsx @@ -1,5 +1,5 @@ import './InfoPanel.css'; -import {Confidence, PersonFactTypes} from "../backend/gedcomx-enums"; +import {PersonFactTypes} from "../backend/gedcomx-enums"; import {filterLang, strings} from "../main"; import {Gallery} from "./Gallery"; import Sidebar from "./Sidebar"; @@ -8,7 +8,7 @@ import {useLiveQuery} from "dexie-react-hooks"; import {GDate, Person} from "../backend/gedcomx-extensions"; import {useContext} from "react"; import {FocusPersonContext} from "./Persons"; -import {Note, SourceReference} from "./GedcomXComponents"; +import {Confidence, Note, SourceReference} from "./GedcomXComponents"; function InfoPanel() { const person = useContext(FocusPersonContext); @@ -17,24 +17,6 @@ function InfoPanel() { if (person) return getImages(person); }, [person]) - let confidence; - if (person) { - switch (person.confidence) { - case Confidence.Low: - confidence = 1; - break; - case Confidence.Medium: - confidence = 2; - break; - case Confidence.High: - confidence = 3; - break; - default: - confidence = undefined; - break; - } - } - const parents = useLiveQuery(async () => { if (!person) return; @@ -219,10 +201,7 @@ function InfoPanel() { return })} - {person.getConfidence() &&
- {strings.infoPanel.confidenceLabel} - {person.getConfidence()} -
} + {person && person.getConfidence() && } ); } diff --git a/src/locales/de.json b/src/locales/de.json index 4afdf88a..3f1f8cd9 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -295,6 +295,11 @@ "Single": "ledig", "Widowed": "verwitwet" }, + "document": { + "extracted": "Die folgenden Informationen wurden aus einem Dokument extrahiert.", + "documents": "Dokumente", + "noDocuments": "Es sind keine Dokumente vorhanden." + }, "gender": "Geschlecht", "confidence": "Zuversicht", "ageQualifier": "mit {0} Jahren", diff --git a/src/locales/en.json b/src/locales/en.json index 121b44dd..9839720c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -295,6 +295,11 @@ "Single": "single", "Widowed": "widowed" }, + "document": { + "extracted": "This information was extracted from a document.", + "documents": "Documents", + "noDocuments": "There are no documents." + }, "gender": "Sex", "confidence": "Confidence", "ageQualifier": "with {0} years old", From 1c3c007d2c425ec1e63ae32840477d316bf9dcfd Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 12:24:24 +0200 Subject: [PATCH 006/110] Streamline locales --- src/backend/gedcomx-extensions.ts | 2 +- src/components/GedcomXComponents.tsx | 8 ++++---- src/components/SourceDescriptions.tsx | 11 +++++++---- src/locales/de.json | 12 ++++++++---- src/locales/en.json | 10 ++++++++-- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/backend/gedcomx-extensions.ts b/src/backend/gedcomx-extensions.ts index 15e3f063..3a8d4c83 100644 --- a/src/backend/gedcomx-extensions.ts +++ b/src/backend/gedcomx-extensions.ts @@ -324,7 +324,7 @@ export class FamilyView extends GedcomX.FamilyView implements Equals { export class SourceDescription extends GedcomX.SourceDescription { get title() { - if (this.getTitles().length > 0) return this.getTitles()[0]; + if (this.getTitles().length > 0) return this.getTitles()[0].value; return this.getCitations()[0].getValue(); } diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx index 00a696a1..731f663d 100644 --- a/src/components/GedcomXComponents.tsx +++ b/src/components/GedcomXComponents.tsx @@ -8,7 +8,7 @@ import {Confidence as ConfidenceEnum} from "../backend/gedcomx-enums"; export function Note(props: { note: gedcomX.Note }) { return
-

πŸ“ {props.note.getSubject() || strings.infoPanel.note}

+

πŸ“ {props.note.getSubject() || strings.gedcomX.note}

{props.note.getText()}

{props.note.getAttribution() && }
@@ -28,7 +28,7 @@ export function Attribution(props: { attribution: gedcomX.Attribution }) { let contributorName = contributor.getNames().filter(filterLang)[0].getValue(); let message = props.attribution.getChangeMessage(); - return {strings.formatString(strings.infoPanel.attribution, created, creatorName, modified, contributorName)} {message} + return {strings.formatString(strings.gedcomX.attribution, created, creatorName, modified, contributorName)} {message} } export function SourceDescription(props: { description: SourceDescriptionClass }) { @@ -44,7 +44,7 @@ export function SourceDescription(props: { description: SourceDescriptionClass } .then(t => setText(t)); }, [hasMedia, props.description]) - const title = (props.description.getTitles().length > 0) ? props.description.getTitles()[0].value : strings.infoPanel.source; + const title = props.description.title; const componentOf = props.description.getComponentOf(); let media; if (hasMedia) { @@ -91,7 +91,7 @@ export function SourceDescription(props: { description: SourceDescriptionClass } export function SourceReference(props: { reference: gedcomX.SourceReference }) { return
-

{"πŸ“–"} {strings.infoPanel.source}

+

{"πŸ“–"} {strings.gedcomX.sourceDescription.sourceDescription}

{props.reference.description} {props.reference.attribution && } diff --git a/src/components/SourceDescriptions.tsx b/src/components/SourceDescriptions.tsx index 2a8dff2c..d9c7f7e7 100644 --- a/src/components/SourceDescriptions.tsx +++ b/src/components/SourceDescriptions.tsx @@ -24,14 +24,17 @@ export function SourceDescriptions() { function DescriptionOverview() { const descriptions = useLiveQuery(() => { return db.sourceDescriptions.toArray().then(sds => sds.map(s => new SourceDescriptionClass(s))); - }, []) + }, []); + + const hasSources = descriptions && descriptions.length > 0; return

-

πŸ“š {strings.infoPanel.source}

-
    +

    πŸ“š {strings.gedcomX.sourceDescription.sourceDescriptions}

    + {hasSources && +
} + {!hasSources &&

{strings.gedcomX.sourceDescription.noSourceDescriptions}

}
} diff --git a/src/locales/de.json b/src/locales/de.json index 3f1f8cd9..b8098460 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -41,12 +41,9 @@ "aka": "alias {0}", "nickname": "Spitzname: {0}", "personImageAlt": "Bild von {0}.", - "note": "Anmerkung", - "source": "Quelle", "noSourceDescriptionError": "Source description {0} konnte nicht gefunden werden", "confidenceExplanation": "Wie sehr kann den Daten vertraut werden", "confidenceLabel": "Zuversicht: ", - "attribution": "Erstellt am {0} von {1}. Bearbeitet am {2} von {3}:", "relationships": "Verbundene Personen", "children": "Kinder", "parents": "Eltern", @@ -295,6 +292,11 @@ "Single": "ledig", "Widowed": "verwitwet" }, + "sourceDescription": { + "sourceDescription": "Quelle", + "sourceDescriptions": "Quellen", + "noSourceDescriptions": "Es sind keine Quellen vorhanden." + }, "document": { "extracted": "Die folgenden Informationen wurden aus einem Dokument extrahiert.", "documents": "Dokumente", @@ -308,7 +310,9 @@ "year": "{0}", "time": "um {0}", "place": "in {0}", - "firstName": "Vorname" + "firstName": "Vorname", + "note": "Anmerkung", + "attribution": "Erstellt am {0} von {1}. Bearbeitet am {2} von {3}:" }, "searchField": { "noPersonFound": "Es konnte keine Person mit diesem Namen gefunden werden!", diff --git a/src/locales/en.json b/src/locales/en.json index 9839720c..17a381d0 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -46,7 +46,6 @@ "noSourceDescriptionError": "Source description {0} could not be found", "confidenceExplanation": "How much can the data be trusted", "confidenceLabel": "Confidence: ", - "attribution": "Created on {0} by {1}. Modified on {2} by {3}:", "relationships": "Relationships", "children": "Children", "parents": "Parents", @@ -295,6 +294,11 @@ "Single": "single", "Widowed": "widowed" }, + "sourceDescription": { + "sourceDescription": "Source", + "sourceDescriptions": "Sources", + "noSourceDescriptions": "There are no sources." + }, "document": { "extracted": "This information was extracted from a document.", "documents": "Documents", @@ -308,7 +312,9 @@ "year": "in {0}", "time": "at {0}", "place": "in {0}", - "firstName": "first name" + "firstName": "first name", + "note": "Note", + "attribution": "Created on {0} by {1}. Modified on {2} by {3}:" }, "searchField": { "noPersonFound": "No person with that name found!", From 483ffd58950bdcf6c7e4466f45f4126bbd7cd29e Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 12:33:06 +0200 Subject: [PATCH 007/110] Add confidence to document --- src/components/GedcomXComponents.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx index 731f663d..ff376e11 100644 --- a/src/components/GedcomXComponents.tsx +++ b/src/components/GedcomXComponents.tsx @@ -124,7 +124,8 @@ export function Document(props: { document: DocumentClass }) {

πŸ“„ Document

{props.document.isExtracted &&

{strings.gedcomX.document.extracted}

} - {props.document.isPlainText && props.document.getText()} + {props.document.getConfidence() && } + {props.document.isPlainText &&

{props.document.getText()}

} {props.document.getAttribution() && }
{props.document.getNotes().filter(filterLang).map((n, i) => )} From 4ea12fe399680d94e2609234fa94db7b8c52e92d Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 12:46:05 +0200 Subject: [PATCH 008/110] Fix return type --- src/backend/Person.test.ts | 4 ++-- types/gedcomx-js/gedcomx-js.d.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/backend/Person.test.ts b/src/backend/Person.test.ts index b37f8cca..5fc022b1 100644 --- a/src/backend/Person.test.ts +++ b/src/backend/Person.test.ts @@ -47,8 +47,8 @@ test("get full name returns the correct name", () => { strings.setLanguage("en"); person.addName(new GedcomX.Name() - .setLang(strings.getLanguage()) - .addNameForm(new GedcomX.NameForm().setFullText("John Doe"))); + .addNameForm(new GedcomX.NameForm().setFullText("John Doe"))) + .setLang(strings.getLanguage()); expect(person.fullName).toBe("John Doe"); let preferredName = new GedcomX.Name() diff --git a/types/gedcomx-js/gedcomx-js.d.ts b/types/gedcomx-js/gedcomx-js.d.ts index 2cc173fe..280859e9 100644 --- a/types/gedcomx-js/gedcomx-js.d.ts +++ b/types/gedcomx-js/gedcomx-js.d.ts @@ -154,31 +154,31 @@ declare module "gedcomx-js" { getAttribution(): Attribution; - setAttribution(attribution: Attribution); + setAttribution(attribution: Attribution): Conclusion; getAnalysis(): ResourceReference; - setAnalysis(analysis: ResourceReference); + setAnalysis(analysis: ResourceReference): Conclusion; getConfidence(): string; - setConfidence(confidence: string); + setConfidence(confidence: string): Conclusion; getLang(): string; - setLang(lang: string); + setLang(lang: string): Conclusion; getNotes(): Note[]; - setNotes(notes: Note[]); + setNotes(notes: Note[]): Conclusion; - addNote(note: Note); + addNote(note: Note): Conclusion; getSources(): SourceReference[]; - setSources(sources: SourceReference[]); + setSources(sources: SourceReference[]): Conclusion; - addSource(source: SourceReference); + addSource(source: SourceReference): Conclusion; } export class EvidenceReference extends ResourceReference { From d5fc3f3032067133eeda4c3d0c13053db5fa3b16 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 13:02:37 +0200 Subject: [PATCH 009/110] Fix article shown when no facts are there --- src/components/InfoPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/InfoPanel.tsx b/src/components/InfoPanel.tsx index 63f0b02a..bd662492 100644 --- a/src/components/InfoPanel.tsx +++ b/src/components/InfoPanel.tsx @@ -104,7 +104,7 @@ function InfoPanel() { })} } -
+ {person.facts &&
    {person.getFacts() .filter(filterLang) @@ -139,7 +139,7 @@ function InfoPanel() { {f.toString()} )}
-
+
}
{strings.infoPanel.relationships} From 40fcc125853111e18d8cedef5d51a68b2e05f195 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 13:04:02 +0200 Subject: [PATCH 010/110] Remove note and source from en --- src/locales/en.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 17a381d0..eecf3294 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -41,8 +41,6 @@ "aka": "aka {0}", "nickname": "Nickname: {0}", "personImageAlt": "Image of {0}.", - "note": "Note", - "source": "Source", "noSourceDescriptionError": "Source description {0} could not be found", "confidenceExplanation": "How much can the data be trusted", "confidenceLabel": "Confidence: ", From bc78823a22ff57354eb34b18571661383fdcb27f Mon Sep 17 00:00:00 2001 From: l0drex Date: Sat, 17 Jun 2023 13:04:13 +0200 Subject: [PATCH 011/110] Add button for test data --- package.json | 1 + src/backend/TestData.ts | 41 +++++++++++++++++++++++++++++++ src/backend/gedcomx-extensions.ts | 2 ++ src/components/Form.tsx | 15 +++++++---- src/components/Persons.tsx | 2 +- src/locales/de.json | 3 ++- src/locales/en.json | 3 ++- yarn.lock | 5 ++++ 8 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 src/backend/TestData.ts diff --git a/package.json b/package.json index 57c80409..34b88511 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@faker-js/faker": "^8.0.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", diff --git a/src/backend/TestData.ts b/src/backend/TestData.ts new file mode 100644 index 00000000..0564616c --- /dev/null +++ b/src/backend/TestData.ts @@ -0,0 +1,41 @@ +import {Name, NameForm, Note, Person, Relationship, ResourceReference, Root} from "gedcomx-js"; +import {faker} from "@faker-js/faker"; +import {RelationshipTypes} from "./gedcomx-enums"; + +let testData: Root; + +export default function getTestData(): object { + if (!testData) testData = minimalData(); + + return testData.toJSON(); +} + +function extensiveData() { + +} + +function minimalData() { + return new Root() + .addPerson(new Person() + .addName(new Name().addNameForm(new NameForm().setFullText(faker.person.fullName()))) + //.addNote(new Note().setText(faker.lorem.paragraphs(1))) + .setId("p1")) + .addPerson(new Person() + .addName(new Name().addNameForm(new NameForm().setFullText(faker.person.fullName()))) + .setId("p2")) + .addRelationship(new Relationship() + .setPerson1(new ResourceReference().setResource("#p1")) + .setPerson2(new ResourceReference().setResource("#p2")) + .setType(RelationshipTypes.Couple)) + .addPerson(new Person() + .addName(new Name().addNameForm(new NameForm().setFullText(faker.person.fullName()))) + .setId("p3")) + .addRelationship(new Relationship() + .setType(RelationshipTypes.ParentChild) + .setPerson1(new ResourceReference().setResource("#p1")) + .setPerson2(new ResourceReference().setResource("#p3"))) + .addRelationship(new Relationship() + .setType(RelationshipTypes.ParentChild) + .setPerson1(new ResourceReference().setResource("#p2")) + .setPerson2(new ResourceReference().setResource("#p3"))); +} diff --git a/src/backend/gedcomx-extensions.ts b/src/backend/gedcomx-extensions.ts index 3a8d4c83..ccf9c05f 100644 --- a/src/backend/gedcomx-extensions.ts +++ b/src/backend/gedcomx-extensions.ts @@ -113,6 +113,8 @@ export class Person extends GedcomX.Person { } setFacts(facts: Fact[] | object[]): Person { + if (!facts) return this; + facts = facts.map(f => f instanceof Fact ? f : new Fact(f)); super.setFacts(facts); return this; diff --git a/src/components/Form.tsx b/src/components/Form.tsx index fd6db97f..27c2363d 100644 --- a/src/components/Form.tsx +++ b/src/components/Form.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import {hasData, strings} from "../main"; import {useEffect, useState} from "react"; import {db} from "../backend/db"; +import getTestData from "../backend/TestData"; function Form(props) { const [focused, setFocused] = useState(false); @@ -44,10 +45,15 @@ function Form(props) { setFocused(false); } + function loadTestData(e) { + e.preventDefault(); + saveDataAndRedirect(getTestData()); + } + return (
{ event.preventDefault(); - parseFile(input.current.files[0]).then(saveDataAndRedirect); + parseFile(input.current.files[0]).then(t => JSON.parse(t)).then(saveDataAndRedirect); }}>
- {dataExists && + + {dataExists && {strings.form.continueSession} } @@ -92,9 +99,7 @@ export async function parseFile(gedcomFile) { }); } -export function saveDataAndRedirect(fileContent) { - let data = JSON.parse(fileContent); - +export function saveDataAndRedirect(data: object) { if (typeof data !== "object") throw new Error("Data type is invalid!") db.load(data) diff --git a/src/components/Persons.tsx b/src/components/Persons.tsx index b7022c09..0c2edf0c 100644 --- a/src/components/Persons.tsx +++ b/src/components/Persons.tsx @@ -43,7 +43,7 @@ function ViewOptions(props) {
parseFile(fileInput.current.files[0]).then(saveDataAndRedirect)}/> + onChange={() => parseFile(fileInput.current.files[0]).then(t => JSON.parse(t)).then(saveDataAndRedirect)}/>
} export function Coverage(props: { coverage: gedcomX.Coverage }) { - let date = new GDate(props.coverage.temporal.toJSON()).toString(); + let date; + if (props.coverage.temporal) date = new GDate(props.coverage.temporal.toJSON()).toString(); + return

πŸ—ΊοΈ {strings.sourceDescriptions.coverage}

- {props.coverage.temporal && <>{date}} - {props.coverage.spatial && } + {props.coverage.temporal && <>{`${strings.gedcomX.coverage.temporal}: ${date}`}} + {props.coverage.spatial && <>{strings.gedcomX.coverage.spatial + ": "} }

} @@ -113,7 +150,7 @@ export function Coverage(props: { coverage: gedcomX.Coverage }) { export function PlaceReference(props: { reference: gedcomX.PlaceReference }) { let original = props.reference.original ?? props.reference.description ?? "?"; if (props.reference.description) { - return {original} + return {original} } return {original} } @@ -126,7 +163,7 @@ export function Document(props: { document: DocumentClass }) { {props.document.isExtracted &&

{strings.gedcomX.document.extracted}

} {props.document.getConfidence() && } {props.document.isPlainText &&

{props.document.getText()}

} - {props.document.getAttribution() && } + {props.document.getAttribution() &&

} {props.document.getNotes().filter(filterLang).map((n, i) => )} {props.document.getSources().map((s, i) => )} diff --git a/src/locales/de.json b/src/locales/de.json index 685e33c1..0fae2b40 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -313,8 +313,16 @@ "place": "in {0}", "firstName": "Vorname", "note": "Anmerkung", - "attribution": "Erstellt am {0} von {1}. Bearbeitet am {2} von {3}:" + "attribution": { + "created": "Erstellt", + "modified": "GeΓ€ndert" + }, + "coverage": { + "spatial": "rΓ€umlich", + "temporal": "zeitlich" + } }, + "byPerson": "von {0}", "searchField": { "noPersonFound": "Es konnte keine Person mit diesem Namen gefunden werden!", "searchLabel": "Name:", diff --git a/src/locales/en.json b/src/locales/en.json index 2669c7e8..f2290e9f 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -313,8 +313,16 @@ "place": "in {0}", "firstName": "first name", "note": "Note", - "attribution": "Created on {0} by {1}. Modified on {2} by {3}:" + "attribution": { + "created": "Created", + "modified": "Modified" + }, + "coverage": { + "spatial": "spatial", + "temporal": "temporal" + } }, + "byPerson": "by {0}", "searchField": { "noPersonFound": "No person with that name found!", "searchLabel": "Name:", diff --git a/types/gedcomx-js/gedcomx-js.d.ts b/types/gedcomx-js/gedcomx-js.d.ts index fa0f8a92..83955804 100644 --- a/types/gedcomx-js/gedcomx-js.d.ts +++ b/types/gedcomx-js/gedcomx-js.d.ts @@ -121,9 +121,9 @@ declare module "gedcomx-js" { export class Attribution extends ExtensibleData { changeMessage: string contributor: ResourceReference - created: Date + created creator: ResourceReference - modified: Date + modified getChangeMessage(): string @@ -133,17 +133,17 @@ declare module "gedcomx-js" { setContributor(contributor: object | ResourceReference): Attribution - getCreated(): Date + getCreated() - setCreated(date: Date | Number): Attribution + setCreated(date: number): Attribution getCreator(): ResourceReference - setCreator(creator: ResourceReference) + setCreator(creator: ResourceReference): Attribution - getModified(): Date + getModified() - setModified(date: Date | Number): Attribution + setModified(date: number): Attribution } export class Conclusion extends ExtensibleData { @@ -733,7 +733,7 @@ declare module "gedcomx-js" { setStreet6(street6: string): Address } - class Event extends Subject { + export class Event extends Subject { type: string date: Date place: PlaceReference @@ -758,7 +758,7 @@ declare module "gedcomx-js" { addRole(role: EventRole | object): Event } - class Document extends Conclusion { + export class Document extends Conclusion { type: string extracted: string textType: string @@ -781,7 +781,7 @@ declare module "gedcomx-js" { setText(text: string): Document } - class PlaceDescription extends Subject { + export class PlaceDescription extends Subject { type: string names: TextValue[] place: ResourceReference @@ -826,7 +826,7 @@ declare module "gedcomx-js" { setSpatialDescription(spatial: ResourceReference): PlaceDescription } - class EventRole extends Conclusion { + export class EventRole extends Conclusion { person: ResourceReference type: string details: string @@ -844,7 +844,7 @@ declare module "gedcomx-js" { setDetails(details: string): EventRole } - class Coverage extends ExtensibleData { + export class Coverage extends ExtensibleData { spatial: PlaceReference temporal: Date From 16777db700d05baebed05782f6c4bc5bcda075a9 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sun, 18 Jun 2023 11:01:36 +0200 Subject: [PATCH 017/110] Add agent overview --- src/App.tsx | 2 ++ src/backend/TestData.ts | 14 +++++++++- src/backend/db.ts | 5 ++-- src/backend/gedcomx-extensions.ts | 7 +++++ src/components/Agents.tsx | 38 ++++++++++++++++++++++++++++ src/components/GedcomXComponents.tsx | 36 ++++++++++++++++++++++---- src/components/Header.tsx | 1 + src/locales/de.json | 11 ++++++++ src/locales/en.json | 11 ++++++++ 9 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 src/components/Agents.tsx diff --git a/src/App.tsx b/src/App.tsx index 65ef9a7f..037cde40 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import {Home, Imprint} from "./components/Home"; import Statistics from "./components/Statistics"; import {SourceDescriptions} from "./components/SourceDescriptions"; import {Documents} from "./components/Documents"; +import {Agents} from "./components/Agents"; function App() { // todo: places, agents @@ -16,6 +17,7 @@ function App() { }/> }/> }/> + }/> }/> }/> diff --git a/src/backend/TestData.ts b/src/backend/TestData.ts index 844e6ab1..1322681a 100644 --- a/src/backend/TestData.ts +++ b/src/backend/TestData.ts @@ -1,12 +1,13 @@ import * as GedcomX from "gedcomx-js"; import { + Address, Agent, Attribution, Coverage, Fact, Gender, Name, NameForm, - Note, + Note, OnlineAccount, Person, PlaceReference, Relationship, @@ -145,9 +146,20 @@ function extensiveData() { .setId("s1")) .addAgent(new Agent() .addName(new TextValue().setValue(faker.person.fullName())) + .addEmail(new ResourceReference().setResource(faker.internet.email())) + .setHomepage(new ResourceReference().setResource(faker.internet.url())) + .addAccount(new OnlineAccount() + .setServiceHomepage(new ResourceReference().setResource(faker.internet.url())) + .setAccountName(faker.internet.userName())) + .addPhone(new ResourceReference().setResource(faker.phone.number())) + .addAddress(new Address().setValue(faker.address.streetAddress(true))) .setId("a1")) + .addAgent(new Agent() + .setPerson(new ResourceReference().setResource("#p1")) + .setId("a2")); } +// eslint-disable-next-line @typescript-eslint/no-unused-vars function minimalData() { return new Root() .addPerson(new Person() diff --git a/src/backend/db.ts b/src/backend/db.ts index 63142e84..bf854bbf 100644 --- a/src/backend/db.ts +++ b/src/backend/db.ts @@ -4,7 +4,7 @@ import { IGroup } from "./gedcomx-types"; import * as GedcomX from "gedcomx-js"; -import {Document, Person, Relationship, setReferenceAge, SourceDescription} from "./gedcomx-extensions"; +import {Agent, Document, Person, Relationship, setReferenceAge, SourceDescription} from "./gedcomx-extensions"; import {PersonFactTypes, RelationshipTypes} from "./gedcomx-enums"; import {ResourceReference} from "gedcomx-js"; @@ -107,7 +107,8 @@ export class FamilyDB extends Dexie { async agentWithId(id: string | ResourceReference) { id = toResource(id).resource.substring(1); - return this.agents.where({"id": id}).first(); + return this.agents.where({"id": id}).first() + .then(a => new Agent(a)); } async documentWithId(id: string | ResourceReference) { diff --git a/src/backend/gedcomx-extensions.ts b/src/backend/gedcomx-extensions.ts index fd16f064..016d3dbf 100644 --- a/src/backend/gedcomx-extensions.ts +++ b/src/backend/gedcomx-extensions.ts @@ -377,6 +377,13 @@ export class Document extends GedcomX.Document { } } +export class Agent extends GedcomX.Agent { + get name(): string { + if (!this.names || this.names.length === 0) return undefined; + return this.names[0].value; + } +} + let referenceAge: { age: number, generation: number } = { age: undefined, generation: undefined diff --git a/src/components/Agents.tsx b/src/components/Agents.tsx new file mode 100644 index 00000000..62320e9c --- /dev/null +++ b/src/components/Agents.tsx @@ -0,0 +1,38 @@ +import {useLiveQuery} from "dexie-react-hooks"; +import {db} from "../backend/db"; +import Header from "./Header"; +import {Agent} from "./GedcomXComponents"; +import {Agent as AgentClass} from "../backend/gedcomx-extensions"; +import {strings} from "../main"; + +export function Agents() { + const agent = useLiveQuery(() => { + let url = new URL(window.location.href); + let id = url.hash.substring(1); + if (id.length === 0) return null; + return db.agentWithId(id); + }, [window.location.href]); + + return <> +
+
+ {agent ? : } +
+ +} + +function AgentOverview() { + const agents = useLiveQuery(() => { + return db.agents.toArray().then(agents => agents.map(a => new AgentClass(a))); + }); + + const hasAgents = agents && agents.length > 0; + + return +} diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx index 49634945..db12fff6 100644 --- a/src/components/GedcomXComponents.tsx +++ b/src/components/GedcomXComponents.tsx @@ -5,7 +5,8 @@ import {db} from "../backend/db"; import { GDate, SourceDescription as SourceDescriptionClass, - Document as DocumentClass, formatJDate + Document as DocumentClass, formatJDate, + Agent as AgentClass } from "../backend/gedcomx-extensions"; import {useEffect, useState} from "react"; import {Confidence as ConfidenceEnum} from "../backend/gedcomx-enums"; @@ -40,7 +41,7 @@ export function Attribution(props: { attribution: gedcomX.Attribution }) { if (!contributorRef) return undefined; return db.agentWithId(contributorRef); }, [contributorRef]); - let contributorName = contributor?.names.filter(filterLang)[0].value; + let contributorName = contributor?.names?.filter(filterLang)[0].value; let message = props.attribution.getChangeMessage(); let modifiedString = ""; @@ -50,16 +51,15 @@ export function Attribution(props: { attribution: gedcomX.Attribution }) { if (modified && contributorName) modifiedString += " "; } - return {createdString} {creator && strings.formatString(strings.byPerson, - {creator.names.filter(filterLang)[0].value} + {creator.names?.filter(filterLang)[0].value} )}
{modifiedString} {contributor && strings.formatString(strings.byPerson, - {contributor.names.filter(filterLang)[0].value} + {contributor.names?.filter(filterLang)[0].value} )} {message && ` ("${message}")`}
@@ -192,3 +192,29 @@ export function Confidence(props: { confidence: ConfidenceEnum | string }) { {props.confidence}
} + +export function Agent(props: {agent: AgentClass}) { + return <> +
+

πŸ‘€ {`${strings.gedcomX.agent.agent} ${props.agent.name ?? ""}`}

+ {props.agent.names?.length > 1 &&

{strings.infoPanel.aka + props.agent.names.map(n => n.value).join(', ')}

} + {props.agent.homepage &&

{strings.gedcomX.agent.homepage}: {props.agent.homepage.resource}

} + {props.agent.openid &&

OpenID: {props.agent.openid.resource}

} + {props.agent.accounts && <>{strings.gedcomX.agent.accounts}:
    + {props.agent.accounts.map((a, i) =>
  • + {strings.formatString(strings.gedcomX.agent.onlineAccount, <>{a.accountName}, {a.serviceHomepage.resource})} +
  • )} +
} + {props.agent.emails && <>{strings.gedcomX.agent.emails}: } + {props.agent.phones && <>{strings.gedcomX.agent.phones}: + } + {props.agent.addresses && <>{strings.gedcomX.agent.addresses}:
    + {props.agent.addresses.map(a =>
  • {a.value}
  • )} +
} + {props.agent.person &&

{strings.gedcomX.agent.person}: {props.agent.person.resource}

} +
+ +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 968fdd9c..ac1e0210 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -18,6 +18,7 @@ function Header(props) { πŸ“Š πŸ“š πŸ“„ + πŸ‘€ ); diff --git a/src/locales/de.json b/src/locales/de.json index 0fae2b40..0e9424a2 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -303,6 +303,17 @@ "documents": "Dokumente", "noDocuments": "Es sind keine Dokumente vorhanden." }, + "agent": { + "agents": "Agenten", + "agent": "Agent", + "homepage": "Homepage", + "accounts": "Accounts", + "onlineAccount": "{0} auf {1}", + "person": "Person", + "emails": "Emails", + "phones": "Telefonnummern", + "addresses": "Adressen" + }, "gender": "Geschlecht", "confidence": "Zuversicht", "ageQualifier": "mit {0} Jahren", diff --git a/src/locales/en.json b/src/locales/en.json index f2290e9f..e6faf315 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -303,6 +303,17 @@ "documents": "Documents", "noDocuments": "There are no documents." }, + "agent": { + "agents": "Agents", + "agent": "Agent", + "homepage": "Homepage", + "accounts": "Accounts", + "onlineAccount": "{0} on {1}", + "person": "Person", + "emails": "Emails", + "phones": "Phones", + "addresses": "Addresses" + }, "gender": "Sex", "confidence": "Confidence", "ageQualifier": "with {0} years old", From ffca99997ad21d3b0e6b7c93852ab8be2af81264 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sun, 18 Jun 2023 11:27:59 +0200 Subject: [PATCH 018/110] Move header to app --- src/App.tsx | 11 +++++- src/components/Agents.tsx | 12 ++---- src/components/Documents.tsx | 10 ++--- src/components/Home.tsx | 53 ++++++++++++--------------- src/components/Persons.tsx | 12 +++--- src/components/SourceDescriptions.tsx | 10 ++--- 6 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 037cde40..3dcec41a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,13 +8,20 @@ import Statistics from "./components/Statistics"; import {SourceDescriptions} from "./components/SourceDescriptions"; import {Documents} from "./components/Documents"; import {Agents} from "./components/Agents"; +import Header from "./components/Header"; +import {useState} from "react"; function App() { - // todo: places, agents + const [headerChildren, setChildren] = useState([]); + + // todo: places return +
+ {headerChildren} +
}/> - }/> + }/> }/> }/> }/> diff --git a/src/components/Agents.tsx b/src/components/Agents.tsx index 62320e9c..3b1a3f97 100644 --- a/src/components/Agents.tsx +++ b/src/components/Agents.tsx @@ -1,6 +1,5 @@ import {useLiveQuery} from "dexie-react-hooks"; import {db} from "../backend/db"; -import Header from "./Header"; import {Agent} from "./GedcomXComponents"; import {Agent as AgentClass} from "../backend/gedcomx-extensions"; import {strings} from "../main"; @@ -13,12 +12,9 @@ export function Agents() { return db.agentWithId(id); }, [window.location.href]); - return <> -
-
- {agent ? : } -
- + return
+ {agent ? : } +
} function AgentOverview() { @@ -32,7 +28,7 @@ function AgentOverview() {

πŸ‘€ {strings.gedcomX.agent.agents}

{hasAgents && } } diff --git a/src/components/Documents.tsx b/src/components/Documents.tsx index e4642475..597864f0 100644 --- a/src/components/Documents.tsx +++ b/src/components/Documents.tsx @@ -2,7 +2,6 @@ import {useLiveQuery} from "dexie-react-hooks"; import {db} from "../backend/db"; import {strings} from "../main"; import {Document as DocumentClass} from "../backend/gedcomx-extensions"; -import Header from "./Header"; import {Document} from "./GedcomXComponents"; export function Documents() { @@ -13,12 +12,9 @@ export function Documents() { return db.documentWithId(id); }); - return <> -
-
- {document ? : } -
- + return
+ {document ? : } +
} function DocumentOverview() { diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 10bb4640..59237e55 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,12 +1,10 @@ import * as React from "react"; -import Header from "./Header"; import {strings} from "../main"; import Form from "./Form"; import "./Article.css"; export function Home() { return <> -
@@ -48,35 +46,32 @@ function Article(props) { function NavigationTutorial() { return
-

- {strings.formatString(strings.home.navigationArticle.content, - {strings.ctrl})} -

+

+ {strings.formatString(strings.home.navigationArticle.content, + {strings.ctrl})} +

} export function Imprint() { - return <> -
-
- - -
- + return
+ + +
} diff --git a/src/components/Persons.tsx b/src/components/Persons.tsx index 0c2edf0c..4f7c8da4 100644 --- a/src/components/Persons.tsx +++ b/src/components/Persons.tsx @@ -5,7 +5,6 @@ import "./View.css"; import {ColorMode, ViewMode} from "../backend/ViewGraph"; import TreeView from "./TreeView"; import InfoPanel from "./InfoPanel"; -import Header from "./Header"; import SearchField from "./SearchField"; import {Person} from "../backend/gedcomx-extensions"; import {parseFile, saveDataAndRedirect} from "./Form"; @@ -54,7 +53,7 @@ function ViewOptions(props) { ); } -function Persons() { +function Persons(props: { setHeaderChildren: (children) => void }) { const [viewMode, setViewMode] = useState(getUrlOption("view", ViewMode.DEFAULT)); const [colorMode, setColorMode] = useState(getUrlOption("colorMode", ColorMode.GENDER)); const [focusPerson, setFocus] = useState(null); @@ -119,13 +118,14 @@ function Persons() { setFocus(newFocus); } + props.setHeaderChildren([ + + ]) + return ( <> -
- -
- {!focusHidden && } + {!focusHidden && }
diff --git a/src/components/SourceDescriptions.tsx b/src/components/SourceDescriptions.tsx index d9c7f7e7..f442f0f5 100644 --- a/src/components/SourceDescriptions.tsx +++ b/src/components/SourceDescriptions.tsx @@ -1,4 +1,3 @@ -import Header from "./Header"; import {SourceDescription as SourceDescriptionClass} from "../backend/gedcomx-extensions"; import {useLiveQuery} from "dexie-react-hooks"; import {db} from "../backend/db"; @@ -13,12 +12,9 @@ export function SourceDescriptions() { return db.sourceDescriptionWithId(id); }, [window.location.href]); - return <> -
-
- {description ? : } -
- + return
+ {description ? : } +
} function DescriptionOverview() { From 106946a8954288aa2e4bbcf95cae73ffcf37e068 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sun, 18 Jun 2023 12:11:36 +0200 Subject: [PATCH 019/110] Generalize doc view approach --- src/backend/db.ts | 59 ++++++++++++++-------- src/components/Agents.tsx | 12 +---- src/components/Documents.tsx | 12 +---- src/components/ElementView.tsx | 15 ++++++ src/components/GedcomXComponents.tsx | 72 +++++++++++++-------------- src/components/SourceDescriptions.tsx | 15 ++---- 6 files changed, 97 insertions(+), 88 deletions(-) create mode 100644 src/components/ElementView.tsx diff --git a/src/backend/db.ts b/src/backend/db.ts index bf854bbf..c5e8e7c2 100644 --- a/src/backend/db.ts +++ b/src/backend/db.ts @@ -8,6 +8,8 @@ import {Agent, Document, Person, Relationship, setReferenceAge, SourceDescriptio import {PersonFactTypes, RelationshipTypes} from "./gedcomx-enums"; import {ResourceReference} from "gedcomx-js"; +export type RootType = "person" | "relationship" | "sourceDescription" | "agent" | "event" | "document" | "place" | "group"; + export class FamilyDB extends Dexie { persons!: Table relationships!: Table @@ -77,14 +79,44 @@ export class FamilyDB extends Dexie { return this.relationships.where("type").equals(RelationshipTypes.ParentChild); } - async personWithId(id: string | ResourceReference) { + async elementWithId(id: string | ResourceReference, type: RootType) { try { id = toResource(id).resource.substring(1); } catch (e) { - return Promise.reject(id); + return Promise.reject(e); + } + + switch (type) { + case "person": + return this.persons.where("id").equals(id).first() + .then(p => new Person(p)); + case "relationship": + return this.relationships.where("id").equals(id).first() + .then(r => new Relationship(r)); + case "sourceDescription": + return this.sourceDescriptions.where("id").equals(id).first() + .then(sd => new SourceDescription(sd)); + case "agent": + return this.agents.where("id").equals(id).first() + .then(a => new Agent(a)); + case "event": + return this.events.where("id").equals(id).first() + .then(e => new GedcomX.Event(e)); + case "document": + return this.documents.where("id").equals(id).first() + .then(d => new Document(d)); + case "place": + return this.places.where("id").equals(id).first() + .then(p => new GedcomX.PlaceDescription(p)); + case "group": + return this.groups.where("id").equals(id).first(); + default: + return Promise.reject(`Unknown type ${type}`); } + } - return this.persons.where("id").equals(id).first().then(p => new Person(p)); + async personWithId(id: string | ResourceReference): Promise { + return this.elementWithId(id, "person").then(p => new Person(p)); } async personWithName(name: string) { @@ -94,28 +126,11 @@ export class FamilyDB extends Dexie { } async sourceDescriptionWithId(id: string | ResourceReference) { - try { - id = toResource(id).resource.substring(1); - } catch (e) { - return Promise.reject(id); - } - - return this.sourceDescriptions.where("id").equals(id).first() - .then(sd => new SourceDescription(sd)); + return this.elementWithId(id, "sourceDescription").then(sd => new SourceDescription(sd)); } async agentWithId(id: string | ResourceReference) { - id = toResource(id).resource.substring(1); - - return this.agents.where({"id": id}).first() - .then(a => new Agent(a)); - } - - async documentWithId(id: string | ResourceReference) { - id = toResource(id).resource.substring(1); - - return this.documents.where({"id": id}).first() - .then(d => new Document(d)); + return this.elementWithId(id, "agent").then(a => new Agent(a)); } async getCoupleRelationsOf(person: ResourceReference | string): Promise { diff --git a/src/components/Agents.tsx b/src/components/Agents.tsx index 3b1a3f97..02d36d45 100644 --- a/src/components/Agents.tsx +++ b/src/components/Agents.tsx @@ -3,18 +3,10 @@ import {db} from "../backend/db"; import {Agent} from "./GedcomXComponents"; import {Agent as AgentClass} from "../backend/gedcomx-extensions"; import {strings} from "../main"; +import {ElementView} from "./ElementView"; export function Agents() { - const agent = useLiveQuery(() => { - let url = new URL(window.location.href); - let id = url.hash.substring(1); - if (id.length === 0) return null; - return db.agentWithId(id); - }, [window.location.href]); - - return
- {agent ? : } -
+ return } function AgentOverview() { diff --git a/src/components/Documents.tsx b/src/components/Documents.tsx index 597864f0..de2a282f 100644 --- a/src/components/Documents.tsx +++ b/src/components/Documents.tsx @@ -3,18 +3,10 @@ import {db} from "../backend/db"; import {strings} from "../main"; import {Document as DocumentClass} from "../backend/gedcomx-extensions"; import {Document} from "./GedcomXComponents"; +import {ElementView} from "./ElementView"; export function Documents() { - const document = useLiveQuery(() => { - let url = new URL(window.location.href); - let id = url.hash.substring(1); - if (id.length === 0) return null; - return db.documentWithId(id); - }); - - return
- {document ? : } -
+ return } function DocumentOverview() { diff --git a/src/components/ElementView.tsx b/src/components/ElementView.tsx new file mode 100644 index 00000000..1426d4b0 --- /dev/null +++ b/src/components/ElementView.tsx @@ -0,0 +1,15 @@ +import {useLiveQuery} from "dexie-react-hooks"; +import {db, RootType} from "../backend/db"; + +export function ElementView(props: { type: RootType, ElementOverview, ElementView }) { + const element = useLiveQuery(() => { + let url = new URL(window.location.href); + let id = url.hash.substring(1); + if (id.length === 0) return null; + return db.elementWithId(id, props.type); + }); + + return
+ {element ? : } +
+} diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx index db12fff6..6a095c90 100644 --- a/src/components/GedcomXComponents.tsx +++ b/src/components/GedcomXComponents.tsx @@ -65,61 +65,61 @@ export function Attribution(props: { attribution: gedcomX.Attribution }) { } -export function SourceDescription(props: { description: SourceDescriptionClass }) { +export function SourceDescription(props: { data: SourceDescriptionClass }) { const [text, setText] = useState(""); - const hasMedia = props.description.mediaType && props.description.about; + const hasMedia = props.data.mediaType && props.data.about; useEffect(() => { if (!hasMedia) return; - let mediaType = props.description.mediaType.split('/'); + let mediaType = props.data.mediaType.split('/'); if (mediaType[0] !== "text") return; - fetch(props.description.getAbout()) + fetch(props.data.getAbout()) .then(r => r.text()) .then(t => setText(t)); - }, [hasMedia, props.description]) + }, [hasMedia, props.data]) - const title = props.description.title; - const componentOf = props.description.getComponentOf(); + const title = props.data.title; + const componentOf = props.data.getComponentOf(); let media; if (hasMedia) { - if (props.description.mediaType.startsWith("text")) + if (props.data.mediaType.startsWith("text")) media =

{text}

else - media = - {props.description.getDescriptions().filter(filterLang)[0]?.getValue()} + media = + {props.data.getDescriptions().filter(filterLang)[0]?.getValue()} ; } - const hasMisc = componentOf || props.description.rights || props.description.repository || props.description.analysis; + const hasMisc = componentOf || props.data.rights || props.data.repository || props.data.analysis; return <>
-

{props.description?.emoji} {title}

+

{props.data?.emoji} {title}

{hasMisc &&
{componentOf && } - {props.description.rights &&
©️{props.description.rights}
} - {props.description.repository &&
repository: {props.description.repository}
} - {props.description.getAnalysis() && Analysis} + {props.data.rights &&
©️{props.data.rights}
} + {props.data.repository &&
repository: {props.data.repository}
} + {props.data.getAnalysis() && Analysis}
} {hasMedia &&
{media}
} - {props.description.getDescriptions().filter(filterLang).map((d, i) => + {props.data.getDescriptions().filter(filterLang).map((d, i) =>

{d.getValue()}

)} - {props.description.getMediator() &&

{`Mediator: ${props.description.getMediator()}`}

} - {props.description.citations &&
+ {props.data.getMediator() &&

{`Mediator: ${props.data.getMediator()}`}

} + {props.data.citations &&
Citations:
    - {props.description.getCitations() + {props.data.getCitations() .filter(filterLang) .map((c, i) =>
  • {c.getValue()}
  • )}
}
- {props.description.getCoverage().map((c, i) => )} - {props.description.getNotes().filter(filterLang).map((n, i) => )} - {props.description.getSources().map((s, i) => )} + {props.data.getCoverage().map((c, i) => )} + {props.data.getNotes().filter(filterLang).map((n, i) => )} + {props.data.getSources().map((s, i) => )} } @@ -193,28 +193,28 @@ export function Confidence(props: { confidence: ConfidenceEnum | string }) { } -export function Agent(props: {agent: AgentClass}) { +export function Agent(props: {data: AgentClass}) { return <>
-

πŸ‘€ {`${strings.gedcomX.agent.agent} ${props.agent.name ?? ""}`}

- {props.agent.names?.length > 1 &&

{strings.infoPanel.aka + props.agent.names.map(n => n.value).join(', ')}

} - {props.agent.homepage &&

{strings.gedcomX.agent.homepage}: {props.agent.homepage.resource}

} - {props.agent.openid &&

OpenID: {props.agent.openid.resource}

} - {props.agent.accounts && <>{strings.gedcomX.agent.accounts}:
    - {props.agent.accounts.map((a, i) =>
  • +

    πŸ‘€ {`${strings.gedcomX.agent.agent} ${props.data.name ?? ""}`}

    + {props.data.names?.length > 1 &&

    {strings.infoPanel.aka + props.data.names.map(n => n.value).join(', ')}

    } + {props.data.homepage &&

    {strings.gedcomX.agent.homepage}: {props.data.homepage.resource}

    } + {props.data.openid &&

    OpenID: {props.data.openid.resource}

    } + {props.data.accounts && <>{strings.gedcomX.agent.accounts}:
      + {props.data.accounts.map((a, i) =>
    • {strings.formatString(strings.gedcomX.agent.onlineAccount, <>{a.accountName}, {a.serviceHomepage.resource})}
    • )}
    } - {props.agent.emails && <>{strings.gedcomX.agent.emails}:
      - {props.agent.emails.map(e =>
    • {e.resource}
    • )} + {props.data.emails && <>{strings.gedcomX.agent.emails}: } - {props.agent.phones && <>{strings.gedcomX.agent.phones}: -
        {props.agent.phones.map(p =>
      • {p.resource}
      • )} + {props.data.phones && <>{strings.gedcomX.agent.phones}: + } - {props.agent.addresses && <>{strings.gedcomX.agent.addresses}:
          - {props.agent.addresses.map(a =>
        • {a.value}
        • )} + {props.data.addresses && <>{strings.gedcomX.agent.addresses}:
            + {props.data.addresses.map(a =>
          • {a.value}
          • )}
          } - {props.agent.person &&

          {strings.gedcomX.agent.person}: {props.agent.person.resource}

          } + {props.data.person &&

          {strings.gedcomX.agent.person}: {props.data.person.resource}

          }
} diff --git a/src/components/SourceDescriptions.tsx b/src/components/SourceDescriptions.tsx index f442f0f5..427285a1 100644 --- a/src/components/SourceDescriptions.tsx +++ b/src/components/SourceDescriptions.tsx @@ -3,18 +3,13 @@ import {useLiveQuery} from "dexie-react-hooks"; import {db} from "../backend/db"; import {strings} from "../main"; import {SourceDescription} from "./GedcomXComponents"; +import {ElementView} from "./ElementView"; export function SourceDescriptions() { - const description = useLiveQuery(() => { - let url = new URL(window.location.href); - let id = url.hash.substring(1); - if (id.length === 0) return null; - return db.sourceDescriptionWithId(id); - }, [window.location.href]); - - return
- {description ? : } -
+ return } function DescriptionOverview() { From 783a8ad746625ca33591e0e0e6ba14344665bbee Mon Sep 17 00:00:00 2001 From: l0drex Date: Sun, 18 Jun 2023 12:18:03 +0200 Subject: [PATCH 020/110] Fix infinite rerenders --- src/components/Persons.tsx | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/Persons.tsx b/src/components/Persons.tsx index 4f7c8da4..ed36ec8e 100644 --- a/src/components/Persons.tsx +++ b/src/components/Persons.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import {createContext, useEffect, useState} from "react"; +import {createContext, useEffect, useMemo, useState} from "react"; import {getUrlOption, strings} from "../main"; import "./View.css"; import {ColorMode, ViewMode} from "../backend/ViewGraph"; @@ -104,23 +104,31 @@ function Persons(props: { setHeaderChildren: (children) => void }) { setColorMode(colorMode as ColorMode); } - function onRefocus(newFocus: Person) { - if (newFocus.getId() === focusPerson.getId()) { - hideFocus(!focusHidden) - return; + const onRefocus = useMemo(() => { + function onRefocus(newFocus: Person) { + if (newFocus.getId() === focusPerson.getId()) { + hideFocus(!focusHidden) + return; + } + + let url = new URL(window.location.href); + url.hash = newFocus.getId(); + window.history.pushState({}, "", url.toString()); + + hideFocus(false); + setFocus(newFocus); } - let url = new URL(window.location.href); - url.hash = newFocus.getId(); - window.history.pushState({}, "", url.toString()); + return onRefocus; + }, [focusHidden, focusPerson]); - hideFocus(false); - setFocus(newFocus); - } + const setHeaderChildren = props.setHeaderChildren; - props.setHeaderChildren([ - - ]) + useEffect(() => { + setHeaderChildren([ + + ]) + }, [setHeaderChildren, onRefocus]); return ( <> From 21a9f1f0b661a80f71f8f2c22c1791fad638880b Mon Sep 17 00:00:00 2001 From: l0drex Date: Sun, 18 Jun 2023 12:40:17 +0200 Subject: [PATCH 021/110] Improve document view --- src/backend/TestData.ts | 14 +++++++++++--- src/components/Documents.tsx | 2 +- src/components/GedcomXComponents.tsx | 18 ++++++++++-------- src/locales/de.json | 1 + src/locales/en.json | 1 + types/gedcomx-js/gedcomx-js.d.ts | 4 ++-- 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/backend/TestData.ts b/src/backend/TestData.ts index 1322681a..a12cc35b 100644 --- a/src/backend/TestData.ts +++ b/src/backend/TestData.ts @@ -12,11 +12,12 @@ import { PlaceReference, Relationship, ResourceReference, - Root, SourceCitation, SourceDescription, SourceReference, TextValue + Root, SourceCitation, SourceDescription, SourceReference, TextValue, + Document } from "gedcomx-js"; import {faker} from "@faker-js/faker"; import { - Confidence, + Confidence, DocumentTypes, GenderTypes, NameTypes, PersonFactTypes, @@ -156,7 +157,14 @@ function extensiveData() { .setId("a1")) .addAgent(new Agent() .setPerson(new ResourceReference().setResource("#p1")) - .setId("a2")); + .setId("a2")) + .addDocument(new Document() + .setType(DocumentTypes.Analysis) + .setExtracted(true) + .setText(faker.lorem.paragraphs(3)) + .setConfidence(Confidence.High) + .addNote(new Note().setText(faker.lorem.paragraphs(1))) + .setId("d1")) } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/components/Documents.tsx b/src/components/Documents.tsx index de2a282f..8e9ed204 100644 --- a/src/components/Documents.tsx +++ b/src/components/Documents.tsx @@ -20,7 +20,7 @@ function DocumentOverview() {

πŸ“„ {strings.gedcomX.document.documents}

{hasDocuments && } {!hasDocuments &&

{strings.gedcomX.document.noDocuments}

} diff --git a/src/components/GedcomXComponents.tsx b/src/components/GedcomXComponents.tsx index 6a095c90..f2fa3f8c 100644 --- a/src/components/GedcomXComponents.tsx +++ b/src/components/GedcomXComponents.tsx @@ -155,18 +155,20 @@ export function PlaceReference(props: { reference: gedcomX.PlaceReference }) { return {original} } -export function Document(props: { document: DocumentClass }) { +export function Document(props: { data: DocumentClass }) { // todo sanitize and render xhtml return <>
-

πŸ“„ Document

- {props.document.isExtracted &&

{strings.gedcomX.document.extracted}

} - {props.document.getConfidence() && } - {props.document.isPlainText &&

{props.document.getText()}

} - {props.document.getAttribution() &&

} +

πŸ“„ {strings.gedcomX.document.document}

+
+ {props.data.isExtracted &&

{strings.gedcomX.document.extracted}

} + {props.data.getConfidence() && } +
+ {props.data.isPlainText &&

{props.data.getText()}

} + {props.data.getAttribution() &&

}
- {props.document.getNotes().filter(filterLang).map((n, i) => )} - {props.document.getSources().map((s, i) => )} + {props.data.getNotes().filter(filterLang).map((n, i) => )} + {props.data.getSources().map((s, i) => )} } diff --git a/src/locales/de.json b/src/locales/de.json index 0e9424a2..179eb9c3 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -301,6 +301,7 @@ "document": { "extracted": "Die folgenden Informationen wurden aus einem Dokument extrahiert.", "documents": "Dokumente", + "document": "Dokument", "noDocuments": "Es sind keine Dokumente vorhanden." }, "agent": { diff --git a/src/locales/en.json b/src/locales/en.json index e6faf315..824996a3 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -301,6 +301,7 @@ "document": { "extracted": "This information was extracted from a document.", "documents": "Documents", + "document": "Document", "noDocuments": "There are no documents." }, "agent": { diff --git a/types/gedcomx-js/gedcomx-js.d.ts b/types/gedcomx-js/gedcomx-js.d.ts index 83955804..54f4486c 100644 --- a/types/gedcomx-js/gedcomx-js.d.ts +++ b/types/gedcomx-js/gedcomx-js.d.ts @@ -91,9 +91,9 @@ declare module "gedcomx-js" { getDocuments(): Document[] - setDocuments(documents: Document[]): Root + setDocuments(documents: Document[] | object[]): Root - addDocument(document: Document): Root + addDocument(document: Document | object): Root getPlaces(): PlaceDescription[] From 48be916eeee25cc51a13ffa4a1a838c5ad6ce094 Mon Sep 17 00:00:00 2001 From: l0drex Date: Sun, 18 Jun 2023 13:06:30 +0200 Subject: [PATCH 022/110] Use react router to route to entries --- src/App.tsx | 8 ++++---- src/components/Agents.tsx | 2 +- src/components/Documents.tsx | 2 +- src/components/ElementView.tsx | 6 +++--- src/components/Form.tsx | 2 +- src/components/GedcomXComponents.tsx | 14 +++++++------- src/components/SourceDescriptions.tsx | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 3dcec41a..bf5d19e7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,16 +22,16 @@ function App() { }/> }/> - }/> - }/> - }/> + }/> + }/> + }/> }/> }/>