/** * Sequence display panel for a selected chain * Shows sequence length and sequence with copy/download features */ import React, { useState } from 'react'; import { ChainMetadata } from '../../../types/protein'; import { formatText, formatNumber } from '../../../utils/format'; interface SequencePanelProps { metadata: ChainMetadata | null; isLoading?: boolean; } const SequencePanel: React.FC = ({ metadata, isLoading }) => { const [copied, setCopied] = useState(false); const [showAll, setShowAll] = useState(false); const sequence = metadata?.polymer_entity?.seq_can; const sequenceLength = metadata?.polymer_entity?.sequence_length; // Format sequence with line breaks every 60 chars const formatSequence = (seq: string): string[] => { if (!seq) return []; const lines = []; for (let i = 0; i < seq.length; i += 60) { lines.push(seq.substring(i, i + 60)); } return lines; }; const handleCopy = async () => { if (sequence) { try { await navigator.clipboard.writeText(sequence); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error('Failed to copy:', err); } } }; const handleDownload = () => { if (!sequence || !metadata) return; const pdbId = formatText(metadata.pdb_id || metadata.entry?.rcsb_id, 'unknown'); const chainId = formatText(metadata.chain_id || metadata.polymer_entity_instance?.auth_asym_id, 'X'); const title = formatText(metadata.entry?.title, 'Unknown protein'); const fasta = `>${pdbId}:${chainId} ${title}\n${sequence}`; const blob = new Blob([fasta], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${pdbId}_${chainId}.fasta`; a.click(); URL.revokeObjectURL(url); }; if (isLoading) { return (

Amino Acid Sequence

Loading sequence...
); } if (!sequence) { return (

Amino Acid Sequence

No sequence data available
); } const lines = formatSequence(sequence); const displayLines = showAll ? lines : lines.slice(0, 10); const hasMore = lines.length > 10; const formattedLength = formatNumber(sequenceLength, { decimals: 0 }); return (
{/* Header + Actions */}

Amino Acid Sequence

{formattedLength !== '—' && (
{formattedLength} residues
)}
{/* Sequence Display */}
{displayLines.map((line, idx) => (
{idx * 60 + 1} {line}
))}
{hasMore && (
)}
); }; export default SequencePanel;