Skip to content

Commit

Permalink
Save track data
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Sep 5, 2024
1 parent 5f4b45f commit 8ca799b
Show file tree
Hide file tree
Showing 23 changed files with 1,030 additions and 192 deletions.
65 changes: 65 additions & 0 deletions packages/core/assemblyManager/loadRefNameMap.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
70 changes: 70 additions & 0 deletions packages/core/assemblyManager/util.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>

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<BasicRegion[]> {
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)
}
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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",
"is-object": "^1.0.1",
"jexl": "^2.3.0",
Expand Down
44 changes: 44 additions & 0 deletions packages/core/pluggableElementTypes/models/BaseTrackModel.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { lazy } from 'react'
import { transaction } from 'mobx'
import {
getRoot,
Expand All @@ -16,10 +17,19 @@ 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'

// locals
import { stringifyGFF3 } from './saveTrackFileTypes/gff3'
import { stringifyGBK } from './saveTrackFileTypes/genbank'
import { stringifyBED } from './saveTrackFileTypes/bed'

// lazies
const SaveTrackDataDlg = lazy(() => import('./components/SaveTrackData'))

export function getCompatibleDisplays(self: IAnyStateTreeNode) {
const { pluginManager } = getEnv(self)
const view = getContainingView(self)
Expand Down Expand Up @@ -183,6 +193,27 @@ export function createBaseTrackModel(
})
},
}))
.views(() => ({
saveTrackFileFormatOptions() {
return {
gff3: {
name: 'GFF3',
extension: 'gff3',
callback: stringifyGFF3,
},
genbank: {
name: 'GenBank',
extension: 'gbk',
callback: stringifyGBK,
},
bed: {
name: 'BED',
extension: 'bed',
callback: stringifyBED,
},
}
},
}))
.views(self => ({
/**
* #method
Expand All @@ -196,6 +227,19 @@ export function createBaseTrackModel(

return [
...menuItems,
{
label: 'Save track data',
icon: Save,
onClick: () => {
getSession(self).queueDialog(handleClose => [
SaveTrackDataDlg,
{
model: self,
handleClose,
},
])
},
},
...(compatDisp.length > 1
? [
{
Expand Down
Loading

0 comments on commit 8ca799b

Please sign in to comment.