diff --git a/packages/core/pluggableElementTypes/models/BaseTrackModel.ts b/packages/core/pluggableElementTypes/models/BaseTrackModel.ts index 67b7913fb1..62651a1e9f 100644 --- a/packages/core/pluggableElementTypes/models/BaseTrackModel.ts +++ b/packages/core/pluggableElementTypes/models/BaseTrackModel.ts @@ -84,6 +84,13 @@ export function createBaseTrackModel( return getConf(self, 'textSearchAdapter') }, + /** + * #getter + */ + get adapterConfig() { + return getConf(self, 'adapter') + }, + /** * #getter */ diff --git a/plugins/alignments/src/BamAdapter/BamAdapter.ts b/plugins/alignments/src/BamAdapter/BamAdapter.ts index d348770630..9b972102ff 100644 --- a/plugins/alignments/src/BamAdapter/BamAdapter.ts +++ b/plugins/alignments/src/BamAdapter/BamAdapter.ts @@ -80,38 +80,31 @@ export default class BamAdapter extends BaseFeatureDataAdapter { return bam.getHeaderText() } - private async setupPre(opts?: BaseOptions) { - const { statusCallback = () => {} } = opts || {} + private async setupPre(_opts?: BaseOptions) { const { bam } = await this.configure() - this.samHeader = await updateStatus( - 'Downloading index', - statusCallback, - async () => { - const samHeader = await bam.getHeader() - - // use the @SQ lines in the header to figure out the - // mapping between ref ref ID numbers and names - const idToName: string[] = [] - const nameToId: Record = {} - samHeader - ?.filter(l => l.tag === 'SQ') - .forEach((sqLine, refId) => { - const SN = sqLine.data.find(item => item.tag === 'SN') - if (SN) { - // this is the ref name - const refName = SN.value - nameToId[refName] = refId - idToName[refId] = refName - } - }) - - return { idToName, nameToId } - }, - ) + const samHeader = await bam.getHeader() + + // use the @SQ lines in the header to figure out the + // mapping between ref ref ID numbers and names + const idToName: string[] = [] + const nameToId: Record = {} + samHeader + ?.filter(l => l.tag === 'SQ') + .forEach((sqLine, refId) => { + const SN = sqLine.data.find(item => item.tag === 'SN') + if (SN) { + // this is the ref name + const refName = SN.value + nameToId[refName] = refId + idToName[refId] = refName + } + }) + + this.samHeader = { idToName, nameToId } return this.samHeader } - async setup(opts?: BaseOptions) { + async setupPre2(opts?: BaseOptions) { if (!this.setupP) { this.setupP = this.setupPre(opts).catch((e: unknown) => { this.setupP = undefined @@ -121,6 +114,13 @@ export default class BamAdapter extends BaseFeatureDataAdapter { return this.setupP } + async setup(opts?: BaseOptions) { + const { statusCallback = () => {} } = opts || {} + return updateStatus('Downloading index', statusCallback, () => + this.setupPre2(opts), + ) + } + async getRefNames(opts?: BaseOptions) { const { idToName } = await this.setup(opts) return idToName diff --git a/plugins/alignments/src/CramAdapter/CramAdapter.ts b/plugins/alignments/src/CramAdapter/CramAdapter.ts index 1a348f954a..339376605c 100644 --- a/plugins/alignments/src/CramAdapter/CramAdapter.ts +++ b/plugins/alignments/src/CramAdapter/CramAdapter.ts @@ -148,42 +148,39 @@ export default class CramAdapter extends BaseFeatureDataAdapter { return sequence } - private async setupPre(opts?: BaseOptions) { - const { statusCallback = () => {} } = opts || {} - return updateStatus('Downloading index', statusCallback, async () => { - const conf = await this.configure() - const { cram } = conf - const samHeader = await cram.cram.getSamHeader() - - // use the @SQ lines in the header to figure out the mapping between ref - // ID numbers and names - const idToName: string[] = [] - const nameToId: Record = {} - samHeader - .filter(l => l.tag === 'SQ') - .forEach((sqLine, refId) => { - const SN = sqLine.data.find(item => item.tag === 'SN') - if (SN) { - const refName = SN.value - nameToId[refName] = refId - idToName[refId] = refName - } - }) + private async setupPre(_opts?: BaseOptions) { + const conf = await this.configure() + const { cram } = conf + const samHeader = await cram.cram.getSamHeader() + + // use the @SQ lines in the header to figure out the mapping between ref + // ID numbers and names + const idToName: string[] = [] + const nameToId: Record = {} + samHeader + .filter(l => l.tag === 'SQ') + .forEach((sqLine, refId) => { + const SN = sqLine.data.find(item => item.tag === 'SN') + if (SN) { + const refName = SN.value + nameToId[refName] = refId + idToName[refId] = refName + } + }) - const readGroups = samHeader - .filter(l => l.tag === 'RG') - .map(rgLine => rgLine.data.find(item => item.tag === 'ID')?.value) + const readGroups = samHeader + .filter(l => l.tag === 'RG') + .map(rgLine => rgLine.data.find(item => item.tag === 'ID')?.value) - const data = { idToName, nameToId, readGroups } - this.samHeader = data - return { - samHeader: data, - ...conf, - } - }) + const data = { idToName, nameToId, readGroups } + this.samHeader = data + return { + samHeader: data, + ...conf, + } } - private async setup(opts?: BaseOptions) { + private async setupPre2(opts?: BaseOptions) { if (!this.setupP) { this.setupP = this.setupPre(opts).catch((e: unknown) => { this.setupP = undefined @@ -193,6 +190,13 @@ export default class CramAdapter extends BaseFeatureDataAdapter { return this.setupP } + async setup(opts?: BaseOptions) { + const { statusCallback = () => {} } = opts || {} + return updateStatus('Downloading index', statusCallback, () => + this.setupPre2(opts), + ) + } + async getRefNames(opts?: BaseOptions) { const { samHeader } = await this.setup(opts) if (!samHeader.idToName) { diff --git a/plugins/alignments/src/LinearPileupDisplay/doAfterAttach.ts b/plugins/alignments/src/LinearPileupDisplay/doAfterAttach.ts new file mode 100644 index 0000000000..575c1d8401 --- /dev/null +++ b/plugins/alignments/src/LinearPileupDisplay/doAfterAttach.ts @@ -0,0 +1,107 @@ +import { getContainingView, getSession } from '@jbrowse/core/util' +import { getRpcSessionId } from '@jbrowse/core/util/tracks' +import { isAlive } from 'mobx-state-tree' + +import { getUniqueModifications } from '../shared/getUniqueModifications' +import { createAutorun } from '../util' + +import type { ModificationType, SortedBy } from '../shared/types' +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' + +type LGV = LinearGenomeViewModel + +export function doAfterAttach(model: { + autorunReady: boolean + sortedBy?: SortedBy + adapterConfig: AnyConfigurationModel + rendererType: { name: string } + sortReady: boolean + currSortBpPerPx: number + parentTrack: any + renderPropsPre: () => Record + setCurrSortBpPerPx: (arg: number) => void + setError: (arg: unknown) => void + updateVisibleModifications: (arg: ModificationType[]) => void + setModificationsReady: (arg: boolean) => void + setSortReady: (arg: boolean) => void + setMessage: (arg: string) => void +}) { + createAutorun( + model, + async () => { + const view = getContainingView(model) as LGV + if (!model.autorunReady) { + return + } + + model.setCurrSortBpPerPx(view.bpPerPx) + }, + { delay: 1000 }, + ) + createAutorun( + model, + async () => { + const { rpcManager } = getSession(model) + const view = getContainingView(model) as LGV + if (!model.autorunReady) { + return + } + + const { sortedBy, adapterConfig, rendererType, sortReady } = model + const { bpPerPx } = view + + if (sortedBy && (!sortReady || model.currSortBpPerPx === view.bpPerPx)) { + const { pos, refName, assemblyName } = sortedBy + // render just the sorted region first + // @ts-expect-error + await model.rendererType.renderInClient(rpcManager, { + assemblyName, + regions: [ + { + start: pos, + end: pos + 1, + refName, + assemblyName, + }, + ], + adapterConfig, + rendererType: rendererType.name, + sessionId: getRpcSessionId(model), + layoutId: view.id, + timeout: 1_000_000, + statusCallback: (arg: string) => { + model.setMessage(arg) + }, + ...model.renderPropsPre(), + }) + } + if (isAlive(model)) { + model.setCurrSortBpPerPx(bpPerPx) + model.setSortReady(true) + } + }, + { delay: 1000 }, + ) + + createAutorun( + model, + async () => { + if (!model.autorunReady) { + return + } + const { adapterConfig } = model + const { staticBlocks } = getContainingView(model) as LGV + const vals = await getUniqueModifications({ + model, + adapterConfig, + blocks: staticBlocks, + }) + if (isAlive(model)) { + model.updateVisibleModifications(vals) + model.setModificationsReady(true) + } + }, + { delay: 1000 }, + ) +} diff --git a/plugins/alignments/src/LinearPileupDisplay/model.ts b/plugins/alignments/src/LinearPileupDisplay/model.ts index 07a47e580d..d43e77b0aa 100644 --- a/plugins/alignments/src/LinearPileupDisplay/model.ts +++ b/plugins/alignments/src/LinearPileupDisplay/model.ts @@ -6,21 +6,15 @@ import { readConfObject, } from '@jbrowse/core/configuration' import { getContainingView, getEnv, getSession } from '@jbrowse/core/util' -import { getRpcSessionId } from '@jbrowse/core/util/tracks' import ColorLensIcon from '@mui/icons-material/ColorLens' import SwapVertIcon from '@mui/icons-material/SwapVert' import VisibilityIcon from '@mui/icons-material/Visibility' import WorkspacesIcon from '@mui/icons-material/Workspaces' import { observable } from 'mobx' -import { isAlive, types } from 'mobx-state-tree' +import { types } from 'mobx-state-tree' import { SharedLinearPileupDisplayMixin } from './SharedLinearPileupDisplayMixin' -import { getUniqueModifications } from '../shared/getUniqueModifications' -import { - createAutorun, - getColorForModification, - modificationData, -} from '../util' +import { getColorForModification, modificationData } from '../util' import type { ModificationType, @@ -453,78 +447,16 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { }) .actions(self => ({ afterAttach() { - createAutorun( - self, - async () => { - const view = getContainingView(self) as LGV - if (!self.autorunReady) { - return - } - - self.setCurrSortBpPerPx(view.bpPerPx) - }, - { delay: 1000 }, - ) - createAutorun( - self, - async () => { - const { rpcManager } = getSession(self) - const view = getContainingView(self) as LGV - if (!self.autorunReady) { - return - } - - const { sortedBy, adapterConfig, rendererType, sortReady } = self - const { bpPerPx } = view - - if ( - sortedBy && - (!sortReady || self.currSortBpPerPx === view.bpPerPx) - ) { - const { pos, refName, assemblyName } = sortedBy - // render just the sorted region first - // @ts-expect-error - await self.rendererType.renderInClient(rpcManager, { - assemblyName, - regions: [ - { - start: pos, - end: pos + 1, - refName, - assemblyName, - }, - ], - adapterConfig, - rendererType: rendererType.name, - sessionId: getRpcSessionId(self), - layoutId: view.id, - timeout: 1_000_000, - ...self.renderPropsPre(), - }) - } - if (isAlive(self)) { - self.setCurrSortBpPerPx(bpPerPx) - self.setSortReady(true) - } - }, - { delay: 1000 }, - ) - - createAutorun(self, async () => { - if (!self.autorunReady) { - return - } - const { staticBlocks } = getContainingView(self) as LGV - const vals = await getUniqueModifications({ - self, - adapterConfig: getConf(self.parentTrack, 'adapter'), - blocks: staticBlocks, - }) - if (isAlive(self)) { - self.updateVisibleModifications(vals) - self.setModificationsReady(true) + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ;(async () => { + try { + const { doAfterAttach } = await import('./doAfterAttach') + doAfterAttach(self) + } catch (e) { + getSession(self).notifyError(`${e}`, e) + console.error(e) } - }) + })() }, })) } diff --git a/plugins/alignments/src/LinearSNPCoverageDisplay/model.ts b/plugins/alignments/src/LinearSNPCoverageDisplay/model.ts index b0b5ab40c6..8dd18269c4 100644 --- a/plugins/alignments/src/LinearSNPCoverageDisplay/model.ts +++ b/plugins/alignments/src/LinearSNPCoverageDisplay/model.ts @@ -263,11 +263,11 @@ function stateModelFactory( } const view = getContainingView(self) as LGV const { staticBlocks } = view - const { colorBy } = self + const { colorBy, adapterConfig } = self if (colorBy?.type === 'modifications') { const vals = await getUniqueModifications({ - self, - adapterConfig: getConf(self.parentTrack, 'adapter'), + model: self, + adapterConfig, blocks: staticBlocks, }) if (isAlive(self)) { diff --git a/plugins/alignments/src/PileupRPC/methods/GetGlobalValueForTag.ts b/plugins/alignments/src/PileupRPC/methods/GetGlobalValueForTag.ts index 93fc99b6f6..f34bf62e81 100644 --- a/plugins/alignments/src/PileupRPC/methods/GetGlobalValueForTag.ts +++ b/plugins/alignments/src/PileupRPC/methods/GetGlobalValueForTag.ts @@ -21,14 +21,17 @@ export default class PileupGetGlobalValueForTag extends PileupBaseRPC { }, rpcDriver: string, ) { - const { adapterConfig, sessionId, regions, tag } = - await this.deserializeArguments(args, rpcDriver) + const deserializedArgs = await this.deserializeArguments(args, rpcDriver) + const { adapterConfig, sessionId, regions, tag } = deserializedArgs const dataAdapter = ( await getAdapter(this.pluginManager, sessionId, adapterConfig) ).dataAdapter as BaseFeatureDataAdapter - const features = dataAdapter.getFeaturesInMultipleRegions(regions) + const features = dataAdapter.getFeaturesInMultipleRegions( + regions, + deserializedArgs, + ) const featuresArray = await firstValueFrom(features.pipe(toArray())) return [ ...new Set( diff --git a/plugins/alignments/src/PileupRPC/methods/GetReducedFeatures.ts b/plugins/alignments/src/PileupRPC/methods/GetReducedFeatures.ts index 9eed223f9d..22e69f811b 100644 --- a/plugins/alignments/src/PileupRPC/methods/GetReducedFeatures.ts +++ b/plugins/alignments/src/PileupRPC/methods/GetReducedFeatures.ts @@ -25,14 +25,16 @@ export default class PileupGetReducedFeatures extends PileupBaseRPC { }, rpcDriver: string, ) { - const des = await this.deserializeArguments(args, rpcDriver) - const { adapterConfig, sessionId, regions } = des + const deserializedArgs = await this.deserializeArguments(args, rpcDriver) + const { adapterConfig, sessionId, regions } = deserializedArgs const dataAdapter = ( await getAdapter(this.pluginManager, sessionId, adapterConfig) ).dataAdapter as BaseFeatureDataAdapter const featuresArray = await firstValueFrom( - dataAdapter.getFeaturesInMultipleRegions(regions, des).pipe(toArray()), + dataAdapter + .getFeaturesInMultipleRegions(regions, deserializedArgs) + .pipe(toArray()), ) const reduced = dedupe( diff --git a/plugins/alignments/src/PileupRPC/methods/GetVisibleModifications.ts b/plugins/alignments/src/PileupRPC/methods/GetVisibleModifications.ts index e2472665cc..cb78c950e4 100644 --- a/plugins/alignments/src/PileupRPC/methods/GetVisibleModifications.ts +++ b/plugins/alignments/src/PileupRPC/methods/GetVisibleModifications.ts @@ -24,24 +24,29 @@ export default class PileupGetVisibleModifications extends PileupBaseRPC { }, rpcDriver: string, ) { - const { adapterConfig, sessionId, regions } = - await this.deserializeArguments(args, rpcDriver) + const deserializeArguments = await this.deserializeArguments( + args, + rpcDriver, + ) + const { adapterConfig, sessionId, regions } = deserializeArguments const dataAdapter = ( await getAdapter(this.pluginManager, sessionId, adapterConfig) ).dataAdapter as BaseFeatureDataAdapter const featuresArray = await firstValueFrom( - dataAdapter.getFeaturesInMultipleRegions(regions).pipe(toArray()), + dataAdapter + .getFeaturesInMultipleRegions(regions, deserializeArguments) + .pipe(toArray()), ) const uniqueModifications = new Map() - featuresArray.forEach(f => { - for (const mod of getModTypes(getTagAlt(f, 'MM', 'Mm') || '')) { + for (const feat of featuresArray) { + for (const mod of getModTypes(getTagAlt(feat, 'MM', 'Mm') || '')) { if (!uniqueModifications.has(mod.type)) { uniqueModifications.set(mod.type, mod) } } - }) + } return [...uniqueModifications.values()] } } diff --git a/plugins/alignments/src/PileupRenderer/PileupRenderer.ts b/plugins/alignments/src/PileupRenderer/PileupRenderer.ts index 302728f495..24a841c1ad 100644 --- a/plugins/alignments/src/PileupRenderer/PileupRenderer.ts +++ b/plugins/alignments/src/PileupRenderer/PileupRenderer.ts @@ -1,7 +1,7 @@ import { readConfObject } from '@jbrowse/core/configuration' import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' import BoxRendererType from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType' -import { renderToAbstractCanvas } from '@jbrowse/core/util' +import { renderToAbstractCanvas, updateStatus } from '@jbrowse/core/util' import { PileupLayoutSession } from './PileupLayoutSession' import { fetchSequence } from '../util' @@ -53,39 +53,39 @@ export default class PileupRenderer extends BoxRendererType { async render(renderProps: RenderArgsDeserialized) { const features = await this.getFeatures(renderProps) const layout = this.createLayoutInWorker(renderProps) - const { colorBy, regions, bpPerPx } = renderProps + const { statusCallback = () => {}, colorBy, regions, bpPerPx } = renderProps const region = regions[0]! - - const regionSequence = - colorBy?.type === 'methylation' && features.size - ? await this.fetchSequence(renderProps, region) - : undefined - + const width = (region.end - region.start) / bpPerPx const { layoutRecords, height } = layoutFeats({ ...renderProps, features, layout, }) - const width = (region.end - region.start) / bpPerPx - const { makeImageData } = await import('./makeImageData') - const res = await renderToAbstractCanvas( - width, - height, - renderProps, - ctx => { - makeImageData({ - ctx, - layoutRecords, - canvasWidth: width, - renderArgs: { - ...renderProps, - layout, - features, - regionSequence, - }, + const res = await updateStatus( + 'Rendering alignments', + statusCallback, + async () => { + const regionSequence = + colorBy?.type === 'methylation' && features.size + ? await this.fetchSequence(renderProps, region) + : undefined + const { makeImageData } = await import('./makeImageData') + + return renderToAbstractCanvas(width, height, renderProps, ctx => { + makeImageData({ + ctx, + layoutRecords, + canvasWidth: width, + renderArgs: { + ...renderProps, + layout, + features, + regionSequence, + }, + }) + return undefined }) - return undefined }, ) diff --git a/plugins/alignments/src/PileupRenderer/types.ts b/plugins/alignments/src/PileupRenderer/types.ts index ecb178b232..256036ee4b 100644 --- a/plugins/alignments/src/PileupRenderer/types.ts +++ b/plugins/alignments/src/PileupRenderer/types.ts @@ -20,6 +20,7 @@ export interface RenderArgsDeserialized extends BoxRenderArgsDeserialized { sortedBy?: SortedBy showSoftClip: boolean highResolutionScaling: number + statusCallback?: (arg: string) => void } export interface ProcessedRenderArgs extends RenderArgsDeserialized { diff --git a/plugins/alignments/src/SNPCoverageRenderer/SNPCoverageRenderer.ts b/plugins/alignments/src/SNPCoverageRenderer/SNPCoverageRenderer.ts index bc88878af4..4172f833f9 100644 --- a/plugins/alignments/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +++ b/plugins/alignments/src/SNPCoverageRenderer/SNPCoverageRenderer.ts @@ -1,3 +1,4 @@ +import { updateStatus } from '@jbrowse/core/util' import { WiggleBaseRenderer } from '@jbrowse/plugin-wiggle' import type { RenderArgsDeserializedWithFeatures } from './types' @@ -10,8 +11,11 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer { ctx: CanvasRenderingContext2D, props: RenderArgsDeserializedWithFeatures, ) { + const { statusCallback = () => {} } = props const { makeImage } = await import('./makeImage') - await makeImage(ctx, props) + await updateStatus('Rendering coverage', statusCallback, () => + makeImage(ctx, props), + ) return undefined } } diff --git a/plugins/alignments/src/SNPCoverageRenderer/types.ts b/plugins/alignments/src/SNPCoverageRenderer/types.ts index 1f4bf61fe8..cf77dba145 100644 --- a/plugins/alignments/src/SNPCoverageRenderer/types.ts +++ b/plugins/alignments/src/SNPCoverageRenderer/types.ts @@ -16,5 +16,6 @@ export interface RenderArgsDeserializedWithFeatures ticks: { values: number[] } displayCrossHatches: boolean visibleModifications?: Record + statusCallback?: (arg: string) => void colorBy: ColorBy } diff --git a/plugins/alignments/src/shared/getUniqueModifications.ts b/plugins/alignments/src/shared/getUniqueModifications.ts index 7997449a1d..9bf0c2c2cf 100644 --- a/plugins/alignments/src/shared/getUniqueModifications.ts +++ b/plugins/alignments/src/shared/getUniqueModifications.ts @@ -6,23 +6,25 @@ import type { AnyConfigurationModel } from '@jbrowse/core/configuration' import type { BlockSet } from '@jbrowse/core/util/blockTypes' import type { IAnyStateTreeNode } from 'mobx-state-tree' +export interface ModificationOpts { + headers?: Record + stopToken?: string + filters: string[] +} + export async function getUniqueModifications({ - self, + model, adapterConfig, blocks, opts, }: { - self: IAnyStateTreeNode + model: IAnyStateTreeNode adapterConfig: AnyConfigurationModel blocks: BlockSet - opts?: { - headers?: Record - stopToken?: string - filters: string[] - } + opts?: ModificationOpts }) { - const { rpcManager } = getSession(self) - const sessionId = getRpcSessionId(self) + const { rpcManager } = getSession(model) + const sessionId = getRpcSessionId(model) const values = await rpcManager.call( sessionId, 'PileupGetVisibleModifications', @@ -30,6 +32,9 @@ export async function getUniqueModifications({ adapterConfig, sessionId, regions: blocks.contentBlocks, + statusCallback: (arg: string) => { + model.setMessage(arg) + }, ...opts, }, )