mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-07-25 13:11:48 +00:00
226 lines
12 KiB
Markdown
226 lines
12 KiB
Markdown
# Immediate Family Section Design
|
|
|
|
## Problem
|
|
|
|
Topola Viewer renders an interactive graphical family tree centered on a
|
|
selected individual, displaying connected nodes for ancestors, descendants, and
|
|
spouses. However, as family structures expand or users zoom in for detail,
|
|
directly related family members frequently flow off the visible screen
|
|
boundaries, requiring tedious canvas manipulation to locate and select them. To
|
|
improve navigation efficiency and provide clear relational context at a glance,
|
|
the application needs a dedicated layout block in the side panel that
|
|
consolidates and displays the focused person's parents, spouses, and children.
|
|
Providing these primary relationships as direct clickable links ensures users
|
|
can rapidly transition focus between immediate family members regardless of
|
|
their current viewport position on the graphical canvas.
|
|
|
|
## Technical Plan
|
|
|
|
To introduce the immediate family view without disrupting existing features, the
|
|
implementation introduces a focused visual component that acts as an
|
|
intermediary between the raw genealogy data and the user interface.
|
|
|
|
The system relies on three primary components working together:
|
|
|
|
1. **Genealogy Data Store**: The core repository containing all parsed
|
|
individuals and family records. It provides lookup utilities to resolve
|
|
cross-references between children and their parents or spouses.
|
|
2. **Immediate Family Component**: A dedicated new interface element embedded
|
|
directly inside the side panel. It queries the data store for the currently
|
|
selected person, extracts their primary set of parents, and groups all
|
|
associated children under their respective spouses or partners.
|
|
3. **Navigation Module**: When a user clicks on any parent, spouse, or child
|
|
link within the new block, this module updates the application's active web
|
|
address with the chosen relative's identifier. This action instantly
|
|
re-centers both the side panel and the primary visual tree onto the newly
|
|
selected family member.
|
|
|
|
## Alternatives
|
|
|
|
During the initial design discussions, several alternative implementation
|
|
strategies were evaluated and ultimately ruled out to ensure optimal user
|
|
experience and maintainability:
|
|
|
|
### 1. Integrating Relationships into the Event Timeline
|
|
|
|
- **Concept**: Interweave parents and children directly into the chronologically
|
|
sorted lifecycle event list (e.g., rendering children as "Child Born" timeline
|
|
events and placing parents near the target person's birth event).
|
|
- **Reason for Rejection**: Structural relationships are fundamentally distinct
|
|
from point-in-time events. Forcing them into an event-driven layout scatters
|
|
family members across the full vertical height of the panel, creating severe
|
|
timeline clutter and undermining the core goal of rapid navigation.
|
|
|
|
### 2. Removing the Timeline Spouse Link
|
|
|
|
- **Concept**: Strip out the standalone spouse navigation link from the
|
|
chronological marriage event entries to prevent duplicating the link in both
|
|
the timeline and the new Immediate Family block.
|
|
- **Reason for Rejection**: Detaching the spouse link from the timeline breaks
|
|
historical context, separating the marriage ceremony record from the actual
|
|
partner involved. Maintaining both preserves event completeness while
|
|
establishing a centralized navigation block.
|
|
|
|
### 3. Rendering a Flat List of Children
|
|
|
|
- **Concept**: Consolidate all children into a single flat list ordered strictly
|
|
by birth date, omitting spousal boundaries.
|
|
- **Reason for Rejection**: Fails to represent blended families clearly.
|
|
Explicitly grouping children under their respective spouse or partner headers
|
|
makes full-sibling versus half-sibling structures immediately obvious to the
|
|
user.
|
|
|
|
### 4. Uniformly Showing or Hiding Unknown Spouses
|
|
|
|
- **Concept**: Apply a blanket rule to either always display an "Unknown Spouse"
|
|
header for single-parent records or entirely hide unknown headers across all
|
|
scenarios.
|
|
- **Reason for Rejection**: Always showing the header adds unnecessary visual
|
|
noise to standard single-family layouts. Always hiding it obscures complex
|
|
relationship boundaries when an individual has children across multiple
|
|
partners where some partner names are unrecorded. The conditional approach
|
|
dynamically cleans up single-family displays while preserving vital structural
|
|
boundaries for multi-partner families.
|
|
|
|
### 5. Supporting Multiple Sets of Parents Immediately
|
|
|
|
- **Concept**: Query all associated `FAMC` records to display biological,
|
|
adoptive, and foster parent sets side-by-side from day one.
|
|
- **Reason for Rejection**: Introduces substantial UI complexity for labeling
|
|
pedigree types (`PEDI` sub-tags) and requires wider structural refactoring of
|
|
active focus state handling across the core canvas layout. Deferring to a
|
|
single primary parent set keeps the initial feature scope robust and
|
|
achievable.
|
|
|
|
## Detailed Implementation
|
|
|
|
Executing this feature requires clean extensions across the side panel
|
|
architecture. To maintain high code quality and ensure strict boundaries, the
|
|
implementation avoids modifying shared core state or event logic directly,
|
|
instead isolating rendering responsibilities into modular layers.
|
|
|
|
Below is the exhaustive file-by-file breakdown enumerating every file that will
|
|
be created or modified, accompanied by detailed implementation step guidelines
|
|
and specific technical rationale.
|
|
|
|
### 1. Component Creation
|
|
|
|
#### [NEW] [immediate-family.tsx](../src/sidepanel/details/immediate-family.tsx)
|
|
|
|
- **Rationale**: Encapsulating the new feature inside a dedicated component file
|
|
separates concerns, avoids bloating the orchestration component
|
|
([details.tsx](../src/sidepanel/details/details.tsx)), and centralizes
|
|
specialized rendering logic for single parent sets, childless spouses,
|
|
conditional unknown headers, and chronologically sorted child groupings.
|
|
- **Implementation Steps**:
|
|
1. **Imports**: Import React elements, Semantic UI layout wrappers (`Item`,
|
|
`Header`), navigation routing dependencies (`Link`, `useLocation` from
|
|
`react-router`, `query-string`), localization wrappers (`FormattedMessage`,
|
|
`useIntl` from `react-intl`), and GEDCOM utility functions from
|
|
`../../util/gedcom_util`
|
|
([dereference](../src/util/gedcom_util.ts#L171-L183),
|
|
[getName](../src/util/gedcom_util.ts#L292-L302),
|
|
[pointerToId](../src/util/gedcom_util.ts#L42-L44),
|
|
[resolveDate](../src/util/gedcom_util.ts#L339-L342)).
|
|
2. **Relative Link Helper**: Define an internal component (e.g.,
|
|
`RelativeLink`) that renders an individual relative profile as a `<Link>`.
|
|
It extracts the target relative's plain ID directly from the raw sub-entry
|
|
string via `pointerToId(subEntry.data)` before dereferencing to prevent
|
|
runtime crashes on broken references. It parses `location.search` using
|
|
`useLocation()`, sets the `indi` query parameter to the target ID,
|
|
stringifies the search parameters, routes to `/view`, and outputs the
|
|
resolved display name via `getName` (falling back to localized unknown
|
|
string if undefined).
|
|
3. **Parents Block Renderer**: Implement a rendering method that extracts the
|
|
active individual's tree array. Find the first sub-entry where
|
|
`tag === 'FAMC'`. Guard against `undefined` if parents are unrecorded.
|
|
Safely dereference this pointer against the repository's family mapping
|
|
array to locate the parental family record. Scan the resolved family entry
|
|
for all `HUSB` and `WIFE` sub-entries. Extract plain IDs via
|
|
`pointerToId(subEntry.data)` and dereference each against the repository's
|
|
individual mapping array to resolve profile records. Conditionally output
|
|
localized sub-headers (e.g., "Father", "Mother") accompanied by clickable
|
|
`RelativeLink` instances only for present parent records.
|
|
4. **Spouses and Children Block Renderer**: Implement a rendering method that
|
|
iterates over all `FAMS` tags within the active individual's tree.
|
|
Dereference each entry to fetch its corresponding family record. For each
|
|
family record, extract the partner/spouse pointers (`HUSB` or `WIFE` tags
|
|
ensuring `!subEntry.data.includes(props.indi)` to prevent extracting the
|
|
focused person as their own spouse) and extract all child records (`CHIL`).
|
|
5. **Child Record Dereferencing and Sorting**: Explicitly dereference each
|
|
extracted `CHIL` sub-entry against `gedcom.indis` to access child profile
|
|
records. Extract their birth dates using `resolveDate` and sort the
|
|
children array chronologically by birth date before outputting to ensure
|
|
correct relational flow.
|
|
6. **Conditional Unknown Spouse Logic**: Prior to outputting headers, evaluate
|
|
the total length of the mapped `FAMS` array. Guard against incomplete/empty
|
|
family entries by suppressing groups containing neither a valid spouse
|
|
pointer nor children. If a spouse profile pointer is absent and total
|
|
`FAMS` equals `1`, suppress the spouse header output. If a spouse pointer
|
|
is absent and total valid `FAMS` groups exceed `1`, output a visible
|
|
"Spouse: Unknown" block header to clearly define half-sibling boundaries.
|
|
7. **Sequential Group Output**: Output each valid spousal family group as an
|
|
encapsulated sub-block. Render childless spouses as complete standalone
|
|
items. Output the resolved, sorted children collection as relative links
|
|
below their respective spouse/partner header.
|
|
8. **Container Wrapper**: Export the primary `ImmediateFamily` component
|
|
wrapper. If active parent, spouse, or child nodes exist, wrap the
|
|
consolidated layout blocks inside a single Semantic UI `<Item>` container
|
|
structured with `<Item.Content>`. If no immediate family members exist,
|
|
return `null` to prevent empty dividers in the side panel DOM.
|
|
|
|
### 2. Orchestration Layer Modification
|
|
|
|
#### [MODIFY] [details.tsx](../src/sidepanel/details/details.tsx)
|
|
|
|
- **Rationale**: Acts as the root layout orchestrator for the side panel info
|
|
tab. Needs to import and render the new view block near the top of the
|
|
component flow to guarantee primary relational links are visible above the
|
|
fold without requiring users to scroll past massive event timelines.
|
|
- **Implementation Steps**:
|
|
1. Import the `ImmediateFamily` component from `./immediate-family`.
|
|
2. Locate the main [Details](../src/sidepanel/details/details.tsx#L338-L387)
|
|
component export function.
|
|
3. Identify the execution block rendering
|
|
`{getSectionForEachMatchingEntry(entries, props.gedcom, ['OBJE'], imageDetails)}`
|
|
(line 350-355).
|
|
4. Directly below this block, and strictly _before_ the `<Events>` timeline
|
|
rendering execution line, embed
|
|
`<ImmediateFamily gedcom={props.gedcom} indi={props.indi} />` directly
|
|
within the `<Item.Group divided>` flow. Rely on the component's internal
|
|
conditional logic to render the outer item bounds.
|
|
|
|
### 3. Registry and Documentation Updates
|
|
|
|
#### [MODIFY] [README.md](../src/sidepanel/details/README.md)
|
|
|
|
- **Rationale**: Functions as the official index cataloging all available
|
|
component views within the side panel details sub-package. Documenting the new
|
|
file preserves architectural readability.
|
|
- **Implementation Steps**:
|
|
1. Locate the alphabetical files registry list.
|
|
2. Insert a descriptive entry for `immediate-family.tsx`, explaining its role
|
|
as a dedicated side panel module for grouping and displaying parents,
|
|
spouses, and children as rapid-navigation links.
|
|
|
|
### 4. Localization Contracts
|
|
|
|
#### [MODIFY] Translation JSON Files (`src/translations/*.json`)
|
|
|
|
- **Rationale**: Guarantees that every new UI header label introduced by the
|
|
feature supports multi-language localization cleanly across all supported
|
|
international environments.
|
|
- **Implementation Steps**:
|
|
1. Establish explicit string descriptor keys to be mapped across all JSON
|
|
locale files (e.g., `src/translations/en.json`, `de.json`, `pl.json`,
|
|
`fr.json`, `it.json`, etc.) to avoid production fallback leakage:
|
|
- `"family.immediate_family": "Immediate Family"`
|
|
- `"family.parents": "Parents"`
|
|
- `"family.father": "Father"`
|
|
- `"family.mother": "Mother"`
|
|
- `"family.spouse": "Spouse"`
|
|
- `"family.unknown_spouse": "Unknown Spouse"`
|
|
- `"family.children": "Children"`
|
|
2. Ensure corresponding `<FormattedMessage>` invocations inside
|
|
`immediate-family.tsx` reference these specific string descriptor keys.
|