UNPKG

@jbrowse/plugin-linear-genome-view

Version:

JBrowse 2 linear genome view

129 lines (128 loc) 6.82 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useState } from 'react'; import { getConf } from '@jbrowse/core/configuration'; import { Dialog } from '@jbrowse/core/ui'; import { ContentCopy as ContentCopyIcon } from '@jbrowse/core/ui/Icons'; import { complement, getSession, reverse } from '@jbrowse/core/util'; import { formatSeqFasta } from '@jbrowse/core/util/formatFastaStrings'; import GetAppIcon from '@mui/icons-material/GetApp'; import { Button, Checkbox, CircularProgress, Container, DialogActions, DialogContent, FormControlLabel, FormGroup, TextField, Typography, } from '@mui/material'; import copy from 'copy-to-clipboard'; import { saveAs } from 'file-saver'; import { observer } from 'mobx-react'; import { makeStyles } from 'tss-react/mui'; const useStyles = makeStyles()({ dialogContent: { width: '80em', }, textAreaFont: { fontFamily: 'Courier New', }, ml: { marginLeft: 10, }, }); async function fetchSequence(model, regions) { const session = getSession(model); const { leftOffset, rightOffset } = model; if (!leftOffset || !rightOffset) { throw new Error('no offsets on model to use for range'); } if (leftOffset.assemblyName !== rightOffset.assemblyName) { throw new Error('not able to fetch sequences from multiple assemblies'); } const { rpcManager, assemblyManager } = session; const assemblyName = leftOffset.assemblyName || rightOffset.assemblyName || ''; const assembly = assemblyManager.get(assemblyName); if (!assembly) { throw new Error(`assembly ${assemblyName} not found`); } const adapterConfig = getConf(assembly, ['sequence', 'adapter']); const sessionId = 'getSequence'; return rpcManager.call(sessionId, 'CoreGetFeatures', { adapterConfig, regions, sessionId, }); } const GetSequenceDialog = observer(function ({ model, handleClose, }) { const { classes } = useStyles(); const [error, setError] = useState(); const [sequenceChunks, setSequenceChunks] = useState(); const [rev, setReverse] = useState(false); const [copied, setCopied] = useState(false); const [comp, setComplement] = useState(false); const { leftOffset, rightOffset } = model; const loading = Boolean(sequenceChunks === undefined); useEffect(() => { const controller = new AbortController(); (async () => { try { const selection = model.getSelectedRegions(leftOffset, rightOffset); if (selection.length === 0) { throw new Error('Selected region is out of bounds'); } const chunks = await fetchSequence(model, selection); setSequenceChunks(chunks); } catch (e) { console.error(e); setError(e); } })(); return () => { controller.abort(); }; }, [model, leftOffset, rightOffset]); const sequence = sequenceChunks ? formatSeqFasta(sequenceChunks.map(chunk => { let chunkSeq = chunk.get('seq'); const chunkRefName = chunk.get('refName'); const chunkStart = chunk.get('start') + 1; const chunkEnd = chunk.get('end'); const loc = `${chunkRefName}:${chunkStart}-${chunkEnd}`; if ((chunkSeq === null || chunkSeq === void 0 ? void 0 : chunkSeq.length) !== chunkEnd - chunkStart + 1) { throw new Error(`${loc} returned ${chunkSeq.length.toLocaleString()} bases, but should have returned ${(chunkEnd - chunkStart).toLocaleString()}`); } if (rev) { chunkSeq = reverse(chunkSeq); } if (comp) { chunkSeq = complement(chunkSeq); } return { header: loc + (rev ? '-rev' : '') + (comp ? '-comp' : ''), seq: chunkSeq, }; })) : ''; const sequenceTooLarge = sequence ? sequence.length > 1000000 : false; return (_jsxs(Dialog, { maxWidth: "xl", open: true, onClose: () => { handleClose(); model.setOffsets(); }, title: "Reference sequence", children: [_jsxs(DialogContent, { children: [error ? (_jsx(Typography, { color: "error", children: `${error}` })) : loading ? (_jsxs(Container, { children: ["Retrieving reference sequence...", _jsx(CircularProgress, { className: classes.ml, size: 20, disableShrink: true })] })) : null, _jsx(TextField, { "data-testid": "rubberband-sequence", variant: "outlined", multiline: true, minRows: 5, maxRows: 10, disabled: sequenceTooLarge, className: classes.dialogContent, fullWidth: true, value: sequenceTooLarge ? 'Reference sequence too large to display, use the download FASTA button' : sequence, slotProps: { input: { readOnly: true, classes: { input: classes.textAreaFont, }, }, } }), _jsxs(FormGroup, { children: [_jsx(FormControlLabel, { control: _jsx(Checkbox, { value: rev, onChange: event => { setReverse(event.target.checked); } }), label: "Reverse sequence" }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { value: comp, onChange: event => { setComplement(event.target.checked); } }), label: "Complement sequence" })] }), _jsx(Typography, { style: { margin: 10 }, children: "Note: Check both boxes for the \"reverse complement\"" })] }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: () => { copy(sequence); setCopied(true); setTimeout(() => { setCopied(false); }, 500); }, disabled: loading || !!error || sequenceTooLarge, color: "primary", startIcon: _jsx(ContentCopyIcon, {}), children: copied ? 'Copied' : 'Copy to clipboard' }), _jsx(Button, { onClick: () => { saveAs(new Blob([sequence || ''], { type: 'text/x-fasta;charset=utf-8', }), 'jbrowse_ref_seq.fa'); }, disabled: loading || !!error, color: "primary", startIcon: _jsx(GetAppIcon, {}), children: "Download FASTA" }), _jsx(Button, { onClick: handleClose, variant: "contained", children: "Close" })] })] })); }); export default GetSequenceDialog;