diff --git a/packages/core/assemblyManager/loadRefNameMap.ts b/packages/core/assemblyManager/loadRefNameMap.ts new file mode 100644 index 00000000000..67928d2b7c2 --- /dev/null +++ b/packages/core/assemblyManager/loadRefNameMap.ts @@ -0,0 +1,65 @@ +import { BaseOptions, checkRefName, RefNameAliases } from './util' +import RpcManager from '../rpc/RpcManager' +import { when } from '../util' + +export interface BasicRegion { + start: number + end: number + refName: string + assemblyName: string +} + +export async function loadRefNameMap( + assembly: { + name: string + regions: BasicRegion[] | undefined + refNameAliases: RefNameAliases | undefined + getCanonicalRefName: (arg: string) => string + rpcManager: RpcManager + }, + adapterConfig: unknown, + options: BaseOptions, + signal?: AbortSignal, +) { + const { sessionId } = options + await when(() => !!(assembly.regions && assembly.refNameAliases), { + signal, + name: 'when assembly ready', + }) + + const refNames = (await assembly.rpcManager.call( + sessionId, + 'CoreGetRefNames', + { + adapterConfig, + signal, + ...options, + }, + { timeout: 1000000 }, + )) as string[] + + const { refNameAliases } = assembly + if (!refNameAliases) { + throw new Error(`error loading assembly ${assembly.name}'s refNameAliases`) + } + + const refNameMap = Object.fromEntries( + refNames.map(name => { + checkRefName(name) + return [assembly.getCanonicalRefName(name), name] + }), + ) + + // make the reversed map too + const reversed = Object.fromEntries( + Object.entries(refNameMap).map(([canonicalName, adapterName]) => [ + adapterName, + canonicalName, + ]), + ) + + return { + forwardMap: refNameMap, + reverseMap: reversed, + } +} diff --git a/packages/core/assemblyManager/util.ts b/packages/core/assemblyManager/util.ts new file mode 100644 index 00000000000..e779fec009b --- /dev/null +++ b/packages/core/assemblyManager/util.ts @@ -0,0 +1,70 @@ +import { AnyConfigurationModel } from '../configuration' +import jsonStableStringify from 'json-stable-stringify' +import { BaseRefNameAliasAdapter } from '../data_adapters/BaseAdapter' +import PluginManager from '../PluginManager' +import { BasicRegion } from './loadRefNameMap' + +export type RefNameAliases = Record + +export interface BaseOptions { + signal?: AbortSignal + sessionId: string + statusCallback?: Function +} + +export async function getRefNameAliases( + config: AnyConfigurationModel, + pm: PluginManager, + signal?: AbortSignal, +) { + const type = pm.getAdapterType(config.type) + const CLASS = await type.getAdapterClass() + const adapter = new CLASS(config, undefined, pm) as BaseRefNameAliasAdapter + return adapter.getRefNameAliases({ signal }) +} + +export async function getCytobands( + config: AnyConfigurationModel, + pm: PluginManager, +) { + const type = pm.getAdapterType(config.type) + const CLASS = await type.getAdapterClass() + const adapter = new CLASS(config, undefined, pm) + + // @ts-expect-error + return adapter.getData() +} + +export async function getAssemblyRegions( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + assembly: any, + adapterConfig: AnyConfigurationModel, + signal?: AbortSignal, +): Promise { + const sessionId = 'loadRefNames' + return assembly.rpcManager.call( + sessionId, + 'CoreGetRegions', + { + adapterConfig, + sessionId, + signal, + }, + { timeout: 1000000 }, + ) +} + +const refNameRegex = new RegExp( + '[0-9A-Za-z!#$%&+./:;?@^_|~-][0-9A-Za-z!#$%&*+./:;=?@^_|~-]*', +) + +// Valid refName pattern from https://samtools.github.io/hts-specs/SAMv1.pdf +export function checkRefName(refName: string) { + if (!refNameRegex.test(refName)) { + throw new Error(`Encountered invalid refName: "${refName}"`) + } +} + +export function getAdapterId(adapterConf: unknown) { + return jsonStableStringify(adapterConf) +} diff --git a/packages/core/package.json b/packages/core/package.json index a4b346bf036..115f5ca344d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,6 +44,7 @@ "dompurify": "^3.0.0", "escape-html": "^1.0.3", "fast-deep-equal": "^3.1.3", + "file-saver": "^2.0.0", "generic-filehandle": "^3.0.0", "http-range-fetcher": "^2.0.0", "is-object": "^1.0.1", diff --git a/packages/core/pluggableElementTypes/models/BaseTrackModel.ts b/packages/core/pluggableElementTypes/models/BaseTrackModel.ts index 10619233a2b..4009929deea 100644 --- a/packages/core/pluggableElementTypes/models/BaseTrackModel.ts +++ b/packages/core/pluggableElementTypes/models/BaseTrackModel.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import { transaction } from 'mobx' import { getRoot, @@ -16,10 +17,14 @@ import { } from '../../configuration' import PluginManager from '../../PluginManager' import { MenuItem } from '../../ui' +import { Save } from '../../ui/Icons' import { getContainingView, getEnv, getSession } from '../../util' import { isSessionModelWithConfigEditing } from '../../util/types' import { ElementId } from '../../util/types/mst' +// lazies +const SaveTrackDataDlg = lazy(() => import('./components/SaveTrackData')) + export function getCompatibleDisplays(self: IAnyStateTreeNode) { const { pluginManager } = getEnv(self) const view = getContainingView(self) @@ -211,6 +216,16 @@ export function createBaseTrackModel( return [ ...menuItems, + { + label: 'Save track data', + icon: Save, + onClick: () => { + getSession(self).queueDialog(handleClose => [ + SaveTrackDataDlg, + { model: self, handleClose }, + ]) + }, + }, ...(compatDisp.length > 1 ? [ { diff --git a/packages/core/pluggableElementTypes/models/components/SaveTrackData.tsx b/packages/core/pluggableElementTypes/models/components/SaveTrackData.tsx new file mode 100644 index 00000000000..18cbf1f8b0d --- /dev/null +++ b/packages/core/pluggableElementTypes/models/components/SaveTrackData.tsx @@ -0,0 +1,171 @@ +import React, { useEffect, useState } from 'react' +import { + Button, + DialogActions, + DialogContent, + FormControl, + FormControlLabel, + FormLabel, + Radio, + RadioGroup, + TextField, + Typography, +} from '@mui/material' +import { IAnyStateTreeNode } from 'mobx-state-tree' +import { makeStyles } from 'tss-react/mui' +import { saveAs } from 'file-saver' +import { observer } from 'mobx-react' +import { Dialog, ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui' +import { + getSession, + getContainingView, + Feature, + Region, + AbstractTrackModel, +} from '@jbrowse/core/util' +import { getConf } from '@jbrowse/core/configuration' + +// icons +import GetAppIcon from '@mui/icons-material/GetApp' + +// locals +import { stringifyGFF3 } from './gff3' +import { stringifyGenbank } from './genbank' + +const useStyles = makeStyles()({ + root: { + width: '80em', + }, + textAreaFont: { + fontFamily: 'Courier New', + }, +}) + +async function fetchFeatures( + track: IAnyStateTreeNode, + regions: Region[], + signal?: AbortSignal, +) { + const { rpcManager } = getSession(track) + const adapterConfig = getConf(track, ['adapter']) + const sessionId = 'getFeatures' + return rpcManager.call(sessionId, 'CoreGetFeatures', { + adapterConfig, + regions, + sessionId, + signal, + }) as Promise +} + +export default observer(function SaveTrackDataDlg({ + model, + handleClose, +}: { + model: AbstractTrackModel + handleClose: () => void +}) { + const { classes } = useStyles() + const [error, setError] = useState() + const [features, setFeatures] = useState() + const [type, setType] = useState('gff3') + const [str, setStr] = useState('') + const options = { + gff3: { name: 'GFF3', extension: 'gff3' }, + genbank: { name: 'GenBank', extension: 'genbank' }, + } + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ;(async () => { + try { + const view = getContainingView(model) as { visibleRegions?: Region[] } + setError(undefined) + setFeatures(await fetchFeatures(model, view.visibleRegions || [])) + } catch (e) { + console.error(e) + setError(e) + } + })() + }, [model]) + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ;(async () => { + try { + const view = getContainingView(model) + const session = getSession(model) + if (!features) { + return + } + const str = await (type === 'gff3' + ? stringifyGFF3(features) + : stringifyGenbank({ + features, + session, + assemblyName: view.dynamicBlocks.contentBlocks[0].assemblyName, + })) + + setStr(str) + } catch (e) { + setError(e) + } + })() + }, [type, features, model]) + + return ( + + + {error ? : null} + {!features ? ( + + ) : !features.length ? ( + No features found + ) : null} + + + File type + setType(e.target.value)}> + {Object.entries(options).map(([key, val]) => ( + } + label={val.name} + /> + ))} + + + + + + + + + + + ) +}) diff --git a/packages/core/pluggableElementTypes/models/components/genbank.ts b/packages/core/pluggableElementTypes/models/components/genbank.ts new file mode 100644 index 00000000000..838e7f1a9cd --- /dev/null +++ b/packages/core/pluggableElementTypes/models/components/genbank.ts @@ -0,0 +1,184 @@ +import { + AbstractSessionModel, + Feature, + max, + min, + Region, +} from '@jbrowse/core/util' +import { getConf } from '@jbrowse/core/configuration' + +const coreFields = new Set([ + 'uniqueId', + 'refName', + 'source', + 'type', + 'start', + 'end', + 'strand', + 'parent', + 'parentId', + 'score', + 'subfeatures', + 'phase', +]) + +const blank = ' ' + +const retitle = { + name: 'Name', +} as { [key: string]: string | undefined } + +function fmt(obj: unknown): string { + if (Array.isArray(obj)) { + return obj.map(o => fmt(o)).join(',') + } else if (typeof obj === 'object') { + return JSON.stringify(obj) + } else { + return `${obj}` + } +} + +function formatTags(f: Feature, parentId?: string, parentType?: string) { + return [ + parentId && parentType ? `${blank}/${parentType}="${parentId}"` : '', + f.get('id') ? `${blank}/name=${f.get('id')}` : '', + ...f + .tags() + .filter(tag => !coreFields.has(tag)) + .map(tag => [tag, fmt(f.get(tag))]) + .filter(tag => !!tag[1] && tag[0] !== parentType) + .map(tag => `${blank}/${retitle[tag[0]] || tag[0]}="${tag[1]}"`), + ].filter(f => !!f) +} + +function rs(f: Feature, min: number) { + return f.get('start') - min + 1 +} +function re(f: Feature, min: number) { + return f.get('end') - min +} +function loc(f: Feature, min: number) { + return `${rs(f, min)}..${re(f, min)}` +} +function formatFeat( + f: Feature, + min: number, + parentType?: string, + parentId?: string, +) { + const type = `${f.get('type')}`.slice(0, 16) + const l = loc(f, min) + const locstrand = f.get('strand') === -1 ? `complement(${l})` : l + return [ + ` ${type.padEnd(16)}${locstrand}`, + ...formatTags(f, parentType, parentId), + ] +} + +function formatCDS( + feats: Feature[], + parentId: string, + parentType: string, + strand: number, + min: number, +) { + const cds = feats.map(f => loc(f, min)) + const pre = `join(${cds})` + const str = strand === -1 ? `complement(${pre})` : pre + return feats.length + ? [` ${'CDS'.padEnd(16)}${str}`, `${blank}/${parentType}="${parentId}"`] + : [] +} + +export function formatFeatWithSubfeatures( + feature: Feature, + min: number, + parentId?: string, + parentType?: string, +): string { + const primary = formatFeat(feature, min, parentId, parentType) + const subfeatures = feature.get('subfeatures') || [] + const cds = subfeatures.filter(f => f.get('type') === 'CDS') + const sansCDS = subfeatures.filter( + f => f.get('type') !== 'CDS' && f.get('type') !== 'exon', + ) + const newParentId = feature.get('id') + const newParentType = feature.get('type') + const newParentStrand = feature.get('strand') + return [ + ...primary, + ...formatCDS(cds, newParentId, newParentType, newParentStrand, min), + ...sansCDS.flatMap(sub => + formatFeatWithSubfeatures(sub, min, newParentId, newParentType), + ), + ].join('\n') +} + +export async function stringifyGenbank({ + features, + assemblyName, + session, +}: { + assemblyName: string + session: AbstractSessionModel + features: Feature[] +}) { + const today = new Date() + const month = today.toLocaleString('en-US', { month: 'short' }).toUpperCase() + const day = today.toLocaleString('en-US', { day: 'numeric' }) + const year = today.toLocaleString('en-US', { year: 'numeric' }) + const date = `${day}-${month}-${year}` + + const start = min(features.map(f => f.get('start'))) + const end = max(features.map(f => f.get('end'))) + const length = end - start + const refName = features[0].get('refName') + + const l1 = [ + `${'LOCUS'.padEnd(12)}`, + `${refName}:${start + 1}..${end}`.padEnd(20), + ` ${`${length} bp`}`.padEnd(15), + ` ${'DNA'.padEnd(10)}`, + `${'linear'.padEnd(10)}`, + `${'UNK ' + date}`, + ].join('') + const l2 = 'FEATURES Location/Qualifiers' + const seq = await fetchSequence({ + session, + assemblyName, + regions: [{ assemblyName, start, end, refName }], + }) + const contig = seq.map(f => f.get('seq') || '').join('') + const lines = features.map(feat => formatFeatWithSubfeatures(feat, start)) + const seqlines = ['ORIGIN', `\t1 ${contig}`, '//'] + return [l1, l2, ...lines, ...seqlines].join('\n') +} + +async function fetchSequence({ + session, + regions, + signal, + assemblyName, +}: { + assemblyName: string + session: AbstractSessionModel + regions: Region[] + signal?: AbortSignal +}) { + const { rpcManager, assemblyManager } = session + const assembly = assemblyManager.get(assemblyName) + if (!assembly) { + throw new Error(`assembly ${assemblyName} not found`) + } + + const sessionId = 'getSequence' + return rpcManager.call(sessionId, 'CoreGetFeatures', { + adapterConfig: getConf(assembly, ['sequence', 'adapter']), + regions: regions.map(r => ({ + ...r, + refName: assembly.getCanonicalRefName(r.refName), + })), + sessionId, + signal, + }) as Promise +} diff --git a/packages/core/pluggableElementTypes/models/components/gff3.ts b/packages/core/pluggableElementTypes/models/components/gff3.ts new file mode 100644 index 00000000000..b5e69d11377 --- /dev/null +++ b/packages/core/pluggableElementTypes/models/components/gff3.ts @@ -0,0 +1,80 @@ +import { Feature } from '@jbrowse/core/util' + +const coreFields = new Set([ + 'uniqueId', + 'refName', + 'source', + 'type', + 'start', + 'end', + 'strand', + 'parent', + 'parentId', + 'score', + 'subfeatures', + 'phase', +]) + +const retitle = { + id: 'ID', + name: 'Name', + alias: 'Alias', + parent: 'Parent', + target: 'Target', + gap: 'Gap', + derives_from: 'Derives_from', + note: 'Note', + description: 'Note', + dbxref: 'Dbxref', + ontology_term: 'Ontology_term', + is_circular: 'Is_circular', +} as { [key: string]: string } + +function fmt(obj: unknown): string { + if (Array.isArray(obj)) { + return obj.map(o => fmt(o)).join(',') + } else if (typeof obj === 'object') { + return JSON.stringify(obj) + } else { + return `${obj}` + } +} + +function formatFeat(f: Feature, parentId?: string, parentRef?: string) { + return [ + f.get('refName') || parentRef, + f.get('source') || '.', + f.get('type') || '.', + f.get('start') + 1, + f.get('end'), + f.get('score') || '.', + f.get('strand') === 1 ? '+' : f.get('strand') === -1 ? '-' : '.', + f.get('phase') || '.', + (parentId ? `Parent=${parentId};` : '') + + f + .tags() + .filter(tag => !coreFields.has(tag)) + .map(tag => [tag, fmt(f.get(tag))]) + .filter(tag => !!tag[1]) + .map(tag => `${retitle[tag[0]] || tag[0]}=${tag[1]}`) + .join(';'), + ].join('\t') +} +export function formatMultiLevelFeat( + f: Feature, + parentId?: string, + parentRef?: string, +): string { + const fRef = parentRef || f.get('refName') + const fId = f.get('id') + const primary = formatFeat(f, parentId, fRef) + const subs = + f.get('subfeatures')?.map(sub => formatMultiLevelFeat(sub, fId, fRef)) || [] + return [primary, ...subs].join('\n') +} + +export function stringifyGFF3(feats: Feature[]) { + return ['##gff-version 3', ...feats.map(f => formatMultiLevelFeat(f))].join( + '\n', + ) +} diff --git a/packages/core/rpc/coreRpcMethods.ts b/packages/core/rpc/coreRpcMethods.ts index e81beffae91..64f8296d3e3 100644 --- a/packages/core/rpc/coreRpcMethods.ts +++ b/packages/core/rpc/coreRpcMethods.ts @@ -6,4 +6,5 @@ export { default as CoreGetFeatures } from './methods/CoreGetFeatures' export { default as CoreRender } from './methods/CoreRender' export { default as CoreFreeResources } from './methods/CoreFreeResources' export { default as CoreGetFeatureDensityStats } from './methods/CoreGetFeatureDensityStats' +export { default as CoreGetRegions } from './methods/CoreGetRegions' export { type RenderArgs } from './methods/util' diff --git a/packages/core/rpc/methods/CoreGetRefNames.ts b/packages/core/rpc/methods/CoreGetRefNames.ts index 936a2187729..2a2bf329d48 100644 --- a/packages/core/rpc/methods/CoreGetRefNames.ts +++ b/packages/core/rpc/methods/CoreGetRefNames.ts @@ -19,10 +19,8 @@ export default class CoreGetRefNames extends RpcMethodType { const deserializedArgs = await this.deserializeArguments(args, rpcDriver) const { sessionId, adapterConfig } = deserializedArgs const { dataAdapter } = await getAdapter(pm, sessionId, adapterConfig) - - if (isFeatureAdapter(dataAdapter)) { - return dataAdapter.getRefNames(deserializedArgs) - } - return [] + return isFeatureAdapter(dataAdapter) + ? dataAdapter.getRefNames(deserializedArgs) + : [] } } diff --git a/packages/core/rpc/methods/CoreGetRegions.ts b/packages/core/rpc/methods/CoreGetRegions.ts new file mode 100644 index 00000000000..0761c2c62f0 --- /dev/null +++ b/packages/core/rpc/methods/CoreGetRegions.ts @@ -0,0 +1,26 @@ +import { getAdapter } from '../../data_adapters/dataAdapterCache' +import RpcMethodType from '../../pluggableElementTypes/RpcMethodType' + +import { RemoteAbortSignal } from '../remoteAbortSignals' +import { isRegionsAdapter } from '../../data_adapters/BaseAdapter' + +export default class CoreGetRegions extends RpcMethodType { + name = 'CoreGetRegions' + + async execute( + args: { + sessionId: string + signal: RemoteAbortSignal + adapterConfig: {} + }, + rpcDriver: string, + ) { + const pm = this.pluginManager + const deserializedArgs = await this.deserializeArguments(args, rpcDriver) + const { sessionId, adapterConfig } = deserializedArgs + const { dataAdapter } = await getAdapter(pm, sessionId, adapterConfig) + return isRegionsAdapter(dataAdapter) + ? dataAdapter.getRegions(deserializedArgs) + : [] + } +} diff --git a/packages/core/rpc/methods/CoreSaveFeatureData.ts b/packages/core/rpc/methods/CoreSaveFeatureData.ts new file mode 100644 index 00000000000..fddc5ea49f6 --- /dev/null +++ b/packages/core/rpc/methods/CoreSaveFeatureData.ts @@ -0,0 +1,67 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { toArray } from 'rxjs/operators' +import { firstValueFrom } from 'rxjs' + +// locals +import { getAdapter } from '../../data_adapters/dataAdapterCache' +import RpcMethodType from '../../pluggableElementTypes/RpcMethodType' +import { RenderArgs } from './util' +import { RemoteAbortSignal } from '../remoteAbortSignals' +import { isFeatureAdapter } from '../../data_adapters/BaseAdapter' +import { renameRegionsIfNeeded, Region } from '../../util' +import SimpleFeature, { + SimpleFeatureSerialized, +} from '../../util/simpleFeature' + +export default class CoreGetFeatures extends RpcMethodType { + name = 'CoreGetFeatures' + + async deserializeReturn( + feats: SimpleFeatureSerialized[], + args: unknown, + rpcDriver: string, + ) { + const superDeserialized = (await super.deserializeReturn( + feats, + args, + rpcDriver, + )) as SimpleFeatureSerialized[] + return superDeserialized.map(feat => new SimpleFeature(feat)) + } + + async serializeArguments(args: RenderArgs, rpcDriver: string) { + const { rootModel } = this.pluginManager + const assemblyManager = rootModel!.session!.assemblyManager + const renamedArgs = await renameRegionsIfNeeded(assemblyManager, args) + return super.serializeArguments( + renamedArgs, + rpcDriver, + ) as Promise + } + + async execute( + args: { + sessionId: string + regions: Region[] + adapterConfig: {} + signal?: RemoteAbortSignal + // eslint-disable-next-line @typescript-eslint/no-explicit-any + opts?: any + }, + rpcDriver: string, + ) { + const pm = this.pluginManager + const deserializedArgs = await this.deserializeArguments(args, rpcDriver) + const { signal, sessionId, adapterConfig, regions, opts } = deserializedArgs + const { dataAdapter } = await getAdapter(pm, sessionId, adapterConfig) + if (!isFeatureAdapter(dataAdapter)) { + throw new Error('Adapter does not support retrieving features') + } + const ret = dataAdapter.getFeaturesInMultipleRegions(regions, { + ...opts, + signal, + }) + const r = await firstValueFrom(ret.pipe(toArray())) + return r.map(f => f.toJSON()) + } +} diff --git a/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx b/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx index be54004939c..1da2d271682 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx @@ -40,30 +40,30 @@ function SetMaxHeightDlg(props: { onChange={event => setMax(event.target.value)} placeholder="Enter max height for layout" /> - - - - + + + + ) } diff --git a/plugins/dotplot-view/src/DotplotView/1dview.ts b/plugins/dotplot-view/src/1dview.ts similarity index 100% rename from plugins/dotplot-view/src/DotplotView/1dview.ts rename to plugins/dotplot-view/src/1dview.ts diff --git a/plugins/dotplot-view/src/DotplotRenderer/DotplotRenderer.ts b/plugins/dotplot-view/src/DotplotRenderer/DotplotRenderer.ts index ea238442dd4..48fe12078a4 100644 --- a/plugins/dotplot-view/src/DotplotRenderer/DotplotRenderer.ts +++ b/plugins/dotplot-view/src/DotplotRenderer/DotplotRenderer.ts @@ -17,7 +17,7 @@ import ComparativeRenderer, { import { MismatchParser } from '@jbrowse/plugin-alignments' // locals -import { Dotplot1DView, Dotplot1DViewModel } from '../DotplotView/model' +import { Dotplot1DView, Dotplot1DViewModel } from '../1dview' import { createJBrowseTheme } from '@jbrowse/core/ui' const { parseCigar } = MismatchParser diff --git a/plugins/dotplot-view/src/DotplotView/model.ts b/plugins/dotplot-view/src/DotplotView/model.ts index 044077c8bee..4c9e027d268 100644 --- a/plugins/dotplot-view/src/DotplotView/model.ts +++ b/plugins/dotplot-view/src/DotplotView/model.ts @@ -35,7 +35,7 @@ import FolderOpenIcon from '@mui/icons-material/FolderOpen' import PhotoCameraIcon from '@mui/icons-material/PhotoCamera' // locals -import { Dotplot1DView, DotplotHView, DotplotVView } from './1dview' +import { Dotplot1DView, DotplotHView, DotplotVView } from '../1dview' import { getBlockLabelKeysToHide, makeTicks } from './components/util' import { BaseBlock } from './blockTypes' @@ -721,5 +721,4 @@ export default function stateModelFactory(pm: PluginManager) { export type DotplotViewStateModel = ReturnType export type DotplotViewModel = Instance - -export { type Dotplot1DViewModel, Dotplot1DView } from './1dview' +export { type Dotplot1DViewModel, Dotplot1DView } from '../1dview' diff --git a/plugins/gff3/src/Gff3TabixAdapter/Gff3TabixAdapter.ts b/plugins/gff3/src/Gff3TabixAdapter/Gff3TabixAdapter.ts index 9d181a9d1b1..a38ddf83845 100644 --- a/plugins/gff3/src/Gff3TabixAdapter/Gff3TabixAdapter.ts +++ b/plugins/gff3/src/Gff3TabixAdapter/Gff3TabixAdapter.ts @@ -7,7 +7,7 @@ import { doesIntersect2 } from '@jbrowse/core/util/range' import { Region } from '@jbrowse/core/util/types' import { openLocation } from '@jbrowse/core/util/io' import { ObservableCreate } from '@jbrowse/core/util/rxjs' -import SimpleFeature, { Feature } from '@jbrowse/core/util/simpleFeature' +import { SimpleFeature, Feature } from '@jbrowse/core/util' import { TabixIndexedFile } from '@gmod/tabix' import gff, { GFF3Feature, GFF3FeatureLineWithRefs } from '@gmod/gff' import { Observer } from 'rxjs' @@ -36,22 +36,18 @@ export default class extends BaseFeatureDataAdapter { pluginManager?: PluginManager, ) { super(config, getSubAdapter, pluginManager) + const pm = this.pluginManager const gffGzLocation = readConfObject(config, 'gffGzLocation') const indexType = readConfObject(config, ['index', 'indexType']) const location = readConfObject(config, ['index', 'location']) const dontRedispatch = readConfObject(config, 'dontRedispatch') this.dontRedispatch = dontRedispatch || ['chromosome', 'contig', 'region'] + const loc = openLocation(location, pm) this.gff = new TabixIndexedFile({ - filehandle: openLocation(gffGzLocation, this.pluginManager), - csiFilehandle: - indexType === 'CSI' - ? openLocation(location, this.pluginManager) - : undefined, - tbiFilehandle: - indexType !== 'CSI' - ? openLocation(location, this.pluginManager) - : undefined, + filehandle: openLocation(gffGzLocation, pm), + csiFilehandle: indexType === 'CSI' ? loc : undefined, + tbiFilehandle: indexType !== 'CSI' ? loc : undefined, chunkCacheSize: 50 * 2 ** 20, renameRefSeqs: (n: string) => n, }) @@ -152,7 +148,8 @@ export default class extends BaseFeatureDataAdapter { f.get('end'), originalQuery.start, originalQuery.end, - ) + ) && + f.get('type') !== 'region' ) { observer.next(f) } diff --git a/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx b/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx index 52dc8bf0d64..8984c3daa64 100644 --- a/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx +++ b/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx @@ -42,30 +42,30 @@ function SetMaxHeightDlg({ onChange={event => setMax(event.target.value)} placeholder="Enter max score" /> - - - - + + + + ) } diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx index c34a9152b80..4012a8036ea 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx @@ -44,9 +44,6 @@ const useStyles = makeStyles()({ type LGV = LinearGenomeViewModel -/** - * Fetches and returns a list features for a given list of regions - */ async function fetchSequence( model: LGV, regions: Region[], @@ -69,7 +66,6 @@ async function fetchSequence( throw new Error(`assembly ${assemblyName} not found`) } const adapterConfig = getConf(assembly, ['sequence', 'adapter']) - const sessionId = 'getSequence' return rpcManager.call(sessionId, 'CoreGetFeatures', { adapterConfig, @@ -186,7 +182,6 @@ function SequenceDialog({ ) : null} setCopied(false), 500) }} disabled={loading || !!error || sequenceTooLarge} - color="primary" startIcon={} > {copied ? 'Copied' : 'Copy to clipboard'} @@ -253,7 +247,6 @@ function SequenceDialog({ ) }} disabled={loading || !!error} - color="primary" startIcon={} > Download FASTA diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/TrackLabel.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/TrackLabel.tsx index a0cb756b30d..46d00b96d5b 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/TrackLabel.tsx +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/TrackLabel.tsx @@ -29,9 +29,6 @@ const useStyles = makeStyles()(theme => ({ }), }, trackName: { - margin: '0 auto', - width: '90%', - fontSize: '0.8rem', pointerEvents: 'none', }, dragHandle: { diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap b/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap index d9a4072ba77..c73db7a54c1 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap @@ -476,7 +476,7 @@ exports[`renders one track, one region 1`] = ` /> Foo Track @@ -1295,7 +1295,7 @@ exports[`renders two tracks, two regions 1`] = ` /> Foo Track @@ -1427,7 +1427,7 @@ exports[`renders two tracks, two regions 1`] = ` /> Bar Track diff --git a/plugins/linear-genome-view/src/LinearGenomeView/model.ts b/plugins/linear-genome-view/src/LinearGenomeView/model.ts index 3c6e5290f3d..e1fd1cf2cc0 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/model.ts +++ b/plugins/linear-genome-view/src/LinearGenomeView/model.ts @@ -1515,6 +1515,10 @@ export function stateModelFactory(pluginManager: PluginManager) { ? this.pxToBp(self.width / 2) : undefined }, + + get visibleRegions() { + return self.dynamicBlocks.contentBlocks + }, })) } diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap index e9fd58b4cbb..e18f81089d7 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap @@ -965,7 +965,7 @@ exports[` renders successfully 1`] = ` /> Reference sequence (volvox)