// ===================================================================== // 京剧剧本可视分析 — Main App // Coordinates 6 panels through shared state ("linked brushing") // ===================================================================== const DATA = window.JJ_DATA; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": "vermillion", "density": "comfortable", "showAnnotations": true, "networkLayout": "force", "dimNonSelected": true }/*EDITMODE-END*/; function App() { const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS); // ---- shared state ------------------------------------------------- const [activeGenres, setActiveGenres] = useState(new Set(["history","family","court"])); const [eraRange, setEraRange] = useState([1800, 1950]); const [selectedPlay, setSelectedPlay] = useState(null); const [selectedRole, setSelectedRole] = useState(null); // "sheng" or "sheng:老生" const [selectedChar, setSelectedChar] = useState(null); // char id within play, or play:char key cross-play const [selectedCharData, setSelectedCharData] = useState(null); const [hoverChar, setHoverChar] = useState(null); const [themeFilter, setThemeFilter] = useState(null); const [hoverGenre, setHoverGenre] = useState(null); // ---- derived: visible plays -------------------------------------- const visiblePlays = useMemo(() => { return DATA.plays.filter(p => activeGenres.has(p.genre) && p.era >= eraRange[0] && p.era <= eraRange[1] ); }, [activeGenres, eraRange]); // ensure selected play is still visible useEffect(() => { if (selectedPlay && !visiblePlays.find(p => p.id === selectedPlay)) { setSelectedPlay(null); setSelectedChar(null); setSelectedCharData(null); } }, [visiblePlays, selectedPlay]); const focusPlayData = selectedPlay ? DATA.plays.find(p => p.id === selectedPlay) : null; function toggleGenre(g) { const n = new Set(activeGenres); if (n.has(g)) n.delete(g); else n.add(g); if (n.size === 0) { n.add(g); return; } // never empty setActiveGenres(n); } function handleSelectPlay(id) { if (selectedPlay === id) { setSelectedPlay(null); setSelectedChar(null); setSelectedCharData(null); } else { setSelectedPlay(id); setSelectedChar(null); setSelectedCharData(null); } } function handleSelectChar(charKey, nodeData) { if (!charKey) { setSelectedChar(null); setSelectedCharData(null); return; } setSelectedChar(charKey); setSelectedCharData(nodeData); } // when hover from outside, set hoverChar // ------------------------------------------------------------------ return (