// Inline study panel shown under a verse in the Reader: commentary from several // public-domain / open sources, plus the people, places and events tied to the verse. import { useState } from 'react'; import { useApp } from '../context/AppContext.js'; import { COMMENTARIES, loadCommentary, loadTheographic, loadPerson, pickCommentaryEntry, useAsync, } from '../hooks/useVerseStudy.js'; export function CommentaryTab({ book, chapter, verse }) { // The chosen commentary is a synced preference, shared with the Study stage const { readerCommentary: sourceId, setReaderCommentary: changeSource } = useApp(); const source = COMMENTARIES.find((c) => c.id === sourceId) ?? COMMENTARIES[0]; const { status, data } = useAsync(() => loadCommentary(source.id, book, chapter), [source.id, book, chapter]); const entry = pickCommentaryEntry(data, verse); return (
{status === 'loading' &&

Loading…

} {status === 'empty' &&

{source.name} has no notes for this chapter.

} {status === 'ready' && !entry &&

No notes at or before this verse in {source.name}.

} {status === 'ready' && entry && ( <> {entry.number !== verse && (

Notes on the passage starting at verse {entry.number}.

)}
{entry.text}
)}

{source.credit}

); } function PersonDetails({ id }) { const { status, data } = useAsync(() => loadPerson(id), [id]); if (status === 'loading') return

Loading…

; if (status === 'empty') return

No details available.

; const era = (y) => `${Math.abs(y)}${y < 0 ? ' BC' : ' AD'}`; return (
{(data.description ?? []).map((d, i) =>

{d}

)} {!data.description?.length &&

No description available.

} {data.minYear != null && (

Lived approx. {era(data.minYear)}{data.maxYear != null && data.maxYear !== data.minYear ? ` – ${era(data.maxYear)}` : ''}

)}
); } export function PersonCard({ person }) { const [open, setOpen] = useState(false); return (
  • {open && }
  • ); } export function PeoplePlacesTab({ book, chapter, verse }) { const { status, data } = useAsync(() => loadTheographic(book, chapter), [book, chapter]); if (status === 'loading') return

    Loading…

    ; if (status === 'empty') return

    No people or places data for this chapter.

    ; const here = (list) => list.filter((x) => x.verses?.includes(verse)); // Theographic lists some places twice (variant spellings) — collapse by name. const places = [...new Map(here(data.places).map((p) => [p.name, p])).values()]; const people = here(data.people); const events = here(data.events); if (!people.length && !places.length && !events.length) { return

    Nothing tagged on verse {verse}.

    ; } return (
    {people.length > 0 && (

    People

    )} {places.length > 0 && (

    Places

    )} {events.length > 0 && (

    Events

    )}

    People, places and events: Theographic Bible Metadata, licensed CC BY-SA 4.0.

    ); } export default function StudyPanel({ book, chapter, verse, onClose }) { const [tab, setTab] = useState('commentary'); const tabBtn = (id, label) => ( ); return (
    {tabBtn('commentary', 'Commentary')} {tabBtn('people', 'People & Places')}
    {tab === 'commentary' ? : }
    ); }