|
| 1 | +'use client'; |
| 2 | + |
| 3 | +import React, { useMemo, useState, useEffect } from 'react'; |
| 4 | +import RootContainer from '@/components/RootContainer'; |
| 5 | +import CombinedTreeAlignment from '@/components/CombinedTreeAlignment'; |
| 6 | +import receptors from '../../../public/receptors.json'; |
| 7 | + |
| 8 | +interface Receptor { |
| 9 | + geneName: string; |
| 10 | + class: string; |
| 11 | + numOrthologs: number; |
| 12 | + lca: string; |
| 13 | + gpcrdbId: string; |
| 14 | + tree: string; |
| 15 | + alignment: string; |
| 16 | + conservationFile: string; |
| 17 | + snakePlot: string; |
| 18 | + svgTree: string; |
| 19 | + name: string; |
| 20 | +} |
| 21 | + |
| 22 | +export default function MsaTreeCombinedPage() { |
| 23 | + const [receptorName, setReceptorName] = useState<string>('5HT1A'); |
| 24 | + const [receptor, setReceptor] = useState<Receptor | null>(null); |
| 25 | + const [newick, setNewick] = useState<string>(''); |
| 26 | + const [alignmentFasta, setAlignmentFasta] = useState<string>(''); |
| 27 | + const [loading, setLoading] = useState<boolean>(false); |
| 28 | + |
| 29 | + // Fixed settings |
| 30 | + const showSupport = false; |
| 31 | + const mirror = false; |
| 32 | + const fontSize = 14; |
| 33 | + const rowSpacing = 13; |
| 34 | + |
| 35 | + // Load receptor data when receptor name changes |
| 36 | + useEffect(() => { |
| 37 | + const found = receptors.find((r: Receptor) => r.geneName === receptorName); |
| 38 | + setReceptor(found ?? null); |
| 39 | + }, [receptorName]); |
| 40 | + |
| 41 | + // Load alignment and tree files when receptor changes |
| 42 | + useEffect(() => { |
| 43 | + if (!receptor) return; |
| 44 | + |
| 45 | + setLoading(true); |
| 46 | + |
| 47 | + // Load both files in parallel |
| 48 | + Promise.all([ |
| 49 | + fetch(receptor.tree).then(res => res.text()), |
| 50 | + fetch(receptor.alignment).then(res => res.text()) |
| 51 | + ]) |
| 52 | + .then(([treeData, alignmentData]) => { |
| 53 | + setNewick(treeData.trim()); |
| 54 | + setAlignmentFasta(alignmentData); |
| 55 | + }) |
| 56 | + .catch(error => { |
| 57 | + console.error('Error loading files:', error); |
| 58 | + }) |
| 59 | + .finally(() => { |
| 60 | + setLoading(false); |
| 61 | + }); |
| 62 | + }, [receptor]); |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | + const trimmed = useMemo(() => newick.trim(), [newick]); |
| 67 | + |
| 68 | + return ( |
| 69 | + <RootContainer> |
| 70 | + <div className="container mx-auto max-w-[1200px] px-4 py-6"> |
| 71 | + <div className="space-y-6"> |
| 72 | + <div className="rounded-lg bg-card p-6 text-card-foreground shadow-md"> |
| 73 | + <h1 className="text-xl font-semibold mb-4">Phylogenetic Tree + Alignment Viewer</h1> |
| 74 | + <div className="space-y-4"> |
| 75 | + <div className="flex items-center gap-4"> |
| 76 | + <label className="text-sm font-medium">Receptor Gene:</label> |
| 77 | + <select |
| 78 | + className="rounded border border-input bg-background p-2 min-w-[200px]" |
| 79 | + value={receptorName} |
| 80 | + onChange={e => setReceptorName(e.target.value)} |
| 81 | + > |
| 82 | + {receptors.map((r: Receptor) => ( |
| 83 | + <option key={r.geneName} value={r.geneName}> |
| 84 | + {r.geneName} - {r.name} |
| 85 | + </option> |
| 86 | + ))} |
| 87 | + </select> |
| 88 | + {loading && ( |
| 89 | + <div className="h-5 w-5 animate-spin rounded-full border-b-2 border-foreground" /> |
| 90 | + )} |
| 91 | + </div> |
| 92 | + |
| 93 | + {receptor && ( |
| 94 | + <div className="text-sm text-muted-foreground"> |
| 95 | + <p>Class: {receptor.class} | Orthologs: {receptor.numOrthologs} | LCA: {receptor.lca}</p> |
| 96 | + </div> |
| 97 | + )} |
| 98 | + </div> |
| 99 | + </div> |
| 100 | + |
| 101 | + <div className="rounded-lg bg-card p-2 text-card-foreground shadow-md" style={{ height: 600 }}> |
| 102 | + {loading ? ( |
| 103 | + <div className="flex items-center justify-center h-full"> |
| 104 | + <div className="text-center"> |
| 105 | + <div className="h-8 w-8 animate-spin rounded-full border-b-2 border-foreground mx-auto mb-2" /> |
| 106 | + <p className="text-muted-foreground">Loading tree and alignment data...</p> |
| 107 | + </div> |
| 108 | + </div> |
| 109 | + ) : trimmed ? ( |
| 110 | + <CombinedTreeAlignment |
| 111 | + newick={trimmed} |
| 112 | + alignmentFasta={alignmentFasta} |
| 113 | + receptor={receptor} |
| 114 | + showSupportOnBranches={showSupport} |
| 115 | + mirrorRightToLeft={mirror} |
| 116 | + fontSize={fontSize} |
| 117 | + leafRowSpacing={rowSpacing} |
| 118 | + treeWidthPx={300} |
| 119 | + alignmentBoxWidthPx={900} |
| 120 | + /> |
| 121 | + ) : ( |
| 122 | + <div className="flex items-center justify-center h-full"> |
| 123 | + <p className="text-muted-foreground">Select a receptor to view its phylogenetic tree and alignment</p> |
| 124 | + </div> |
| 125 | + )} |
| 126 | + </div> |
| 127 | + </div> |
| 128 | + </div> |
| 129 | + </RootContainer> |
| 130 | + ); |
| 131 | +} |
| 132 | + |
| 133 | + |
0 commit comments