/** * Main interaction logic */ import React, { useState, useEffect, useCallback } from "react"; import Detail from "./Detail.js"; import Legend from "./Legend.js"; import Quote from "./Quote.js"; import Credits from "./Credits.js"; import Title from "./Title.js"; import buildGraph from "../graph.js"; import { footprint } from "../utils/shoelace.js"; export default function Graph({ db }) { const [node, setNode] = useState(null); const [graph, setGraph] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null); const [detailVisible, setDetailVisible] = useState(false); const [creditsVisible, setCreditsVisible] = useState(false); const [introDone, setIntroDone] = useState(false); const [introCurtainDone, setIntroCurtainDone] = useState(false); const [introTextDone, setIntroTextDone] = useState(false); /** Build the graph */ useEffect(() => { const graph = buildGraph({ db, handlers: { click: handleClick, }, }); setGraph(graph); setTimeout(() => { setIntroCurtainDone(true); graph.onLoad(); setTimeout(() => { setIntroDone(true); }, 4000); setTimeout(() => { setIntroTextDone(true); }, 8000); }, 10); }, []); /** Click to open a node */ const handleClick = useCallback((node) => { setNode(node); setDetailVisible(true); footprint("details", { id: node.id + 1 }); }); /** Click to close the media modal */ const handleClose = useCallback((node) => { setDetailVisible(false); }); /** Select or clear the category */ const handleSelect = useCallback((category) => { if (category === selectedCategory) { setSelectedCategory(null); graph.onSelect(null); } else { setSelectedCategory(category); graph.onSelect(category); } }); const handleCredits = useCallback((newState) => { setCreditsVisible(newState); if (newState) { footprint("credits"); } }, []); return (