diff --git a/packages/core/ReExports/DataGrid.tsx b/packages/core/ReExports/DataGrid.tsx index 308c2568b7..39f996b10d 100644 --- a/packages/core/ReExports/DataGrid.tsx +++ b/packages/core/ReExports/DataGrid.tsx @@ -1,3 +1,2 @@ // convert named export to default for lazy react - export { DataGrid as default } from '@mui/x-data-grid' diff --git a/packages/core/configuration/util.ts b/packages/core/configuration/util.ts index ebf6b6c605..78a74d08fc 100644 --- a/packages/core/configuration/util.ts +++ b/packages/core/configuration/util.ts @@ -42,9 +42,8 @@ export function readConfObject( args: Record = {}, ): any { if (!slotPath) { - return JSON.parse(JSON.stringify(getSnapshot(confObject))) - } - if (typeof slotPath === 'string') { + return structuredClone(getSnapshot(confObject)) + } else if (typeof slotPath === 'string') { let slot = confObject[slotPath] // check for the subconf being a map if we don't find it immediately if ( @@ -66,15 +65,13 @@ export function readConfObject( // schemaType.name // })`, // ) + } else { + const val = slot.expr ? slot.expr.evalSync(args) : slot + return isStateTreeNode(val) + ? JSON.parse(JSON.stringify(getSnapshot(val))) + : val } - - const val = slot.expr ? slot.expr.evalSync(args) : slot - return isStateTreeNode(val) - ? JSON.parse(JSON.stringify(getSnapshot(val))) - : val - } - - if (Array.isArray(slotPath)) { + } else if (Array.isArray(slotPath)) { const slotName = slotPath[0]! if (slotPath.length > 1) { const newPath = slotPath.slice(1) @@ -172,39 +169,35 @@ export function isBareConfigurationSchemaType( export function isConfigurationSchemaType( thing: unknown, ): thing is AnyConfigurationSchemaType { - if (!isType(thing)) { - return false - } - - // written as a series of if-statements instead of a big logical OR - // because this construction gives much better debugging backtraces. + // written as a series of if-statements instead of a big logical because this + // construction gives much better debugging backtraces. - // also, note that the order of these statements matters, because - // for example some union types are also optional types + // also, note that the order of these statements matters, because for example + // some union types are also optional types - if (isBareConfigurationSchemaType(thing)) { + if (!isType(thing)) { + return false + } else if (isBareConfigurationSchemaType(thing)) { return true - } - - if (isUnionType(thing)) { + } else if (isUnionType(thing)) { return getUnionSubTypes(thing).every( t => isConfigurationSchemaType(t) || t.name === 'undefined', ) - } - - if (isOptionalType(thing) && isConfigurationSchemaType(getSubType(thing))) { + } else if ( + isOptionalType(thing) && + isConfigurationSchemaType(getSubType(thing)) + ) { return true - } - - if (isArrayType(thing) && isConfigurationSchemaType(getSubType(thing))) { + } else if ( + isArrayType(thing) && + isConfigurationSchemaType(getSubType(thing)) + ) { return true - } - - if (isMapType(thing) && isConfigurationSchemaType(getSubType(thing))) { + } else if (isMapType(thing) && isConfigurationSchemaType(getSubType(thing))) { return true + } else { + return false } - - return false } export function isConfigurationModel( diff --git a/packages/core/package.json b/packages/core/package.json index 8970f4603d..9971d7b587 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "@babel/runtime": "^7.17.9", + "@floating-ui/react": "^0.27.0", "@gmod/abortable-promise-cache": "^2.0.0", "@gmod/bgzf-filehandle": "^1.4.3", "@gmod/http-range-fetcher": "^3.0.4", diff --git a/packages/core/pluggableElementTypes/PluggableElementBase.ts b/packages/core/pluggableElementTypes/PluggableElementBase.ts index 9d6b6b420d..adbfc6d8c9 100644 --- a/packages/core/pluggableElementTypes/PluggableElementBase.ts +++ b/packages/core/pluggableElementTypes/PluggableElementBase.ts @@ -2,9 +2,9 @@ export default abstract class PluggableElementBase { name: string maybeDisplayName?: string - constructor(args: { name?: string; displayName?: string }) { - this.name = args.name || 'UNKNOWN' - this.maybeDisplayName = args.displayName + constructor(args?: { name?: string; displayName?: string }) { + this.name = args?.name || 'UNKNOWN' + this.maybeDisplayName = args?.displayName } get displayName() { diff --git a/packages/core/pluggableElementTypes/RpcMethodType.ts b/packages/core/pluggableElementTypes/RpcMethodType.ts index 48eb0a7d23..6ebc95b49f 100644 --- a/packages/core/pluggableElementTypes/RpcMethodType.ts +++ b/packages/core/pluggableElementTypes/RpcMethodType.ts @@ -15,7 +15,7 @@ export type RpcMethodConstructor = new (pm: PluginManager) => RpcMethodType export default abstract class RpcMethodType extends PluggableElementBase { constructor(public pluginManager: PluginManager) { - super({}) + super() } async serializeArguments( diff --git a/packages/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions.ts b/packages/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions.ts new file mode 100644 index 0000000000..be88d89dd5 --- /dev/null +++ b/packages/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions.ts @@ -0,0 +1,40 @@ +import RpcMethodType from './RpcMethodType' +import { renameRegionsIfNeeded } from '../util' +import SerializableFilterChain from './renderers/util/serializableFilterChain' + +import type { RenderArgs } from '@jbrowse/core/rpc/coreRpcMethods' + +export default abstract class RpcMethodTypeWithFiltersAndRenameRegions extends RpcMethodType { + async deserializeArguments(args: any, rpcDriverClassName: string) { + const l = await super.deserializeArguments(args, rpcDriverClassName) + return { + ...l, + filters: args.filters + ? new SerializableFilterChain({ + filters: args.filters, + }) + : undefined, + } + } + + async serializeArguments( + args: RenderArgs & { + stopToken?: string + statusCallback?: (arg: string) => void + }, + rpcDriverClassName: string, + ) { + const pm = this.pluginManager + const assemblyManager = pm.rootModel?.session?.assemblyManager + if (!assemblyManager) { + throw new Error('no assembly manager') + } + + const renamedArgs = await renameRegionsIfNeeded(assemblyManager, { + ...args, + filters: args.filters?.toJSON().filters, + }) + + return super.serializeArguments(renamedArgs, rpcDriverClassName) + } +} diff --git a/packages/core/rpc/BaseRpcDriver.ts b/packages/core/rpc/BaseRpcDriver.ts index bcb272df49..b9479d6472 100644 --- a/packages/core/rpc/BaseRpcDriver.ts +++ b/packages/core/rpc/BaseRpcDriver.ts @@ -31,32 +31,10 @@ function isCloneable(thing: unknown) { return !(typeof thing === 'function') && !(thing instanceof Error) } -// watches the given worker object, returns a promise that will be rejected if -// the worker times out -export async function watchWorker( - worker: WorkerHandle, - pingTime: number, - rpcDriverClassName: string, -) { - // after first ping succeeds, apply wait for timeout - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - while (true) { - await worker.call('ping', [], { - timeout: pingTime * 2, - rpcDriverClassName, - }) - await new Promise(resolve => setTimeout(resolve, pingTime)) - } -} - function detectHardwareConcurrency() { const mainThread = typeof window !== 'undefined' const canDetect = mainThread && 'hardwareConcurrency' in window.navigator - if (mainThread && canDetect) { - return window.navigator.hardwareConcurrency - } - return 1 + return mainThread && canDetect ? window.navigator.hardwareConcurrency : 1 } class LazyWorker { workerP?: Promise | undefined @@ -65,27 +43,10 @@ class LazyWorker { async getWorker() { if (!this.workerP) { - this.workerP = this.driver - .makeWorker() - .then(worker => { - watchWorker(worker, this.driver.maxPingTime, this.driver.name).catch( - (error: unknown) => { - console.error( - 'worker did not respond, killing and generating new one', - ) - console.error(error) - worker.destroy() - worker.status = 'killed' - worker.error = error - this.workerP = undefined - }, - ) - return worker - }) - .catch((e: unknown) => { - this.workerP = undefined - throw e - }) + this.workerP = this.driver.makeWorker().catch((e: unknown) => { + this.workerP = undefined + throw e + }) } return this.workerP } @@ -197,7 +158,6 @@ export default abstract class BaseRpcDriver { if (!sessionId) { throw new TypeError('sessionId is required') } - let done = false const unextendedWorker = await this.getWorker(sessionId) const worker = pluginManager.evaluateExtensionPoint( 'Core-extendWorker', @@ -211,41 +171,13 @@ export default abstract class BaseRpcDriver { const filteredAndSerializedArgs = this.filterArgs(serializedArgs, sessionId) // now actually call the worker - const callP = worker - .call(functionName, filteredAndSerializedArgs, { - timeout: 5 * 60 * 1000, // 5 minutes - statusCallback: args.statusCallback, - rpcDriverClassName: this.name, - ...options, - }) - .finally(() => { - done = true - }) - - // check every 5 seconds to see if the worker has been killed, and - // reject the killedP promise if it has - let killedCheckInterval: ReturnType - const killedP = new Promise((resolve, reject) => { - killedCheckInterval = setInterval(() => { - // must've been killed - if (worker.status === 'killed') { - reject( - new Error( - `operation timed out, worker process stopped responding, ${worker.error}`, - ), - ) - } else if (done) { - resolve(true) - } - }, this.workerCheckFrequency) - }).finally(() => { - clearInterval(killedCheckInterval) + const call = await worker.call(functionName, filteredAndSerializedArgs, { + timeout: 5 * 60 * 1000, // 5 minutes + statusCallback: args.statusCallback, + rpcDriverClassName: this.name, + ...options, }) - // the result is a race between the actual result promise, and the "killed" - // promise. the killed promise will only actually win if the worker was - // killed before the call could return - const resultP = Promise.race([callP, killedP]) - return rpcMethod.deserializeReturn(resultP, args, this.name) + return rpcMethod.deserializeReturn(call, args, this.name) } } diff --git a/packages/core/ui/LoadingEllipses.tsx b/packages/core/ui/LoadingEllipses.tsx index 59e150b450..ec00771c65 100644 --- a/packages/core/ui/LoadingEllipses.tsx +++ b/packages/core/ui/LoadingEllipses.tsx @@ -40,9 +40,13 @@ export default function LoadingEllipses({ variant = 'body2', ...rest }: Props) { - const { classes } = useStyles() + const { cx, classes } = useStyles() return ( - + {message || 'Loading'} ) diff --git a/packages/product-core/src/rpcWorker.ts b/packages/product-core/src/rpcWorker.ts index 5587d2f8d1..fab47dd281 100644 --- a/packages/product-core/src/rpcWorker.ts +++ b/packages/product-core/src/rpcWorker.ts @@ -87,13 +87,7 @@ export async function initializeWorker( ) // @ts-expect-error - self.rpcServer = new RpcServer.Server({ - ...rpcConfig, - ping: async () => { - // the ping method is required by the worker driver for checking the - // health of the worker - }, - }) + self.rpcServer = new RpcServer.Server(rpcConfig) postMessage({ message: 'ready' }) } catch (e) { postMessage({ message: 'error', error: serializeError(e) }) diff --git a/packages/sv-core/package.json b/packages/sv-core/package.json index 07545492da..ed5a6a9632 100644 --- a/packages/sv-core/package.json +++ b/packages/sv-core/package.json @@ -41,11 +41,11 @@ "useSrc": "node ../../scripts/useSrc.js" }, "dependencies": { - "@jbrowse/plugin-linear-genome-view": "^2.17.0", + "@gmod/vcf": "^6.0.0", "@jbrowse/core": "^2.17.0", + "@jbrowse/plugin-linear-genome-view": "^2.17.0", "@mui/icons-material": "^6.0.0", - "@mui/material": "^6.0.0", - "@gmod/vcf": "^5.0.9" + "@mui/material": "^6.0.0" }, "peerDependencies": { "mobx": "^6.0.0", diff --git a/plugins/alignments/src/LinearPileupDisplay/components/LinearPileupDisplayBlurb.tsx b/plugins/alignments/src/LinearPileupDisplay/components/LinearPileupDisplayBlurb.tsx index 7ea4319d09..1f75a37392 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/LinearPileupDisplayBlurb.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/LinearPileupDisplayBlurb.tsx @@ -3,16 +3,13 @@ import React from 'react' import { Typography } from '@mui/material' import { observer } from 'mobx-react' +import type { SortedBy } from '../../shared/types' + const LinearPileupDisplayBlurb = observer(function ({ model, }: { model: { - sortedBy?: { - pos: number - refName: number - type: string - tag?: string - } + sortedBy?: SortedBy } }) { const { sortedBy } = model diff --git a/plugins/authentication/src/ExternalTokenModel/ExternalTokenEntryForm.tsx b/plugins/authentication/src/ExternalTokenModel/ExternalTokenEntryForm.tsx index d7030f63b8..8d3d11f478 100644 --- a/plugins/authentication/src/ExternalTokenModel/ExternalTokenEntryForm.tsx +++ b/plugins/authentication/src/ExternalTokenModel/ExternalTokenEntryForm.tsx @@ -17,6 +17,9 @@ export const ExternalTokenEntryForm = ({ open maxWidth="xl" data-testid="externalToken-form" + onClose={() => { + handleClose() + }} title={`Enter token for ${internetAccountId}`} > diff --git a/plugins/authentication/src/HTTPBasicModel/HTTPBasicLoginForm.tsx b/plugins/authentication/src/HTTPBasicModel/HTTPBasicLoginForm.tsx index 53511962f5..6bffd4582e 100644 --- a/plugins/authentication/src/HTTPBasicModel/HTTPBasicLoginForm.tsx +++ b/plugins/authentication/src/HTTPBasicModel/HTTPBasicLoginForm.tsx @@ -19,6 +19,9 @@ export function HTTPBasicLoginForm({ maxWidth="xl" data-testid="login-httpbasic" title={`Log in for ${internetAccountId}`} + onClose={() => { + handleClose() + }} >
{ diff --git a/plugins/breakpoint-split-view/package.json b/plugins/breakpoint-split-view/package.json index 31fa6b6a8c..2c6b525aba 100644 --- a/plugins/breakpoint-split-view/package.json +++ b/plugins/breakpoint-split-view/package.json @@ -36,7 +36,7 @@ "clean": "rimraf dist esm *.tsbuildinfo" }, "dependencies": { - "@gmod/vcf": "^5.0.9", + "@gmod/vcf": "^6.0.0", "@mui/icons-material": "^6.0.0", "@types/file-saver": "^2.0.1", "file-saver": "^2.0.0" diff --git a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx index e502e5c21c..09b9310ddd 100644 --- a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx +++ b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx @@ -7,14 +7,22 @@ import { import { Paper } from '@mui/material' import { observer } from 'mobx-react' +import type { SimpleFeatureSerialized } from '@jbrowse/core/util' + const BreakpointAlignmentsFeatureDetail = observer(function ({ model, }: { - model: { featureData: Record } + model: { + featureData: { + feature1: SimpleFeatureSerialized + feature2: SimpleFeatureSerialized + } + } }) { - const { feature1, feature2 } = JSON.parse(JSON.stringify(model.featureData)) + const { featureData } = model + const { feature1, feature2 } = structuredClone(featureData) return ( - + diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.tsx b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.tsx index 062f1b0a81..56aec71fe8 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.tsx +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.tsx @@ -24,7 +24,12 @@ const CloseConnectionDialog = observer(function CloseConnectionDialog({ }) { const { name, dereferenceTypeCount, safelyBreakConnection } = modalInfo return ( - + {dereferenceTypeCount ? ( <> diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.tsx b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.tsx index 948f6d1cb1..039583cf1c 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.tsx +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.tsx @@ -26,7 +26,7 @@ const DeleteConnectionDialog = observer(function DeleteConnectionDialog({ }) { const { connectionConf, name } = deleteDialogDetails return ( - + Are you sure you want to delete this connection? diff --git a/plugins/dotplot-view/package.json b/plugins/dotplot-view/package.json index cdafeb67bb..2c889e46c8 100644 --- a/plugins/dotplot-view/package.json +++ b/plugins/dotplot-view/package.json @@ -36,7 +36,6 @@ "clean": "rimraf dist esm *.tsbuildinfo" }, "dependencies": { - "@floating-ui/react": "^0.26.3", "@mui/icons-material": "^6.0.0", "@mui/x-data-grid": "^7.0.0", "@types/file-saver": "^2.0.1", diff --git a/plugins/dotplot-view/src/ComparativeRenderer/index.ts b/plugins/dotplot-view/src/ComparativeRenderer/index.ts index 9334271c33..025aaafb60 100644 --- a/plugins/dotplot-view/src/ComparativeRenderer/index.ts +++ b/plugins/dotplot-view/src/ComparativeRenderer/index.ts @@ -19,11 +19,6 @@ interface RenderArgsSerialized extends ComparativeRenderArgsSerialized { rendererType: string } -/** - * call a synteny renderer with the given args - * param views: a set of views that each contain a set of regions - * used instead of passing regions directly as in render() - */ export default class ComparativeRender extends RpcMethodType { name = 'ComparativeRender' diff --git a/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/ExportBookmarksDialog.tsx b/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/ExportBookmarksDialog.tsx index 7ccde292d4..8f8700d943 100644 --- a/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/ExportBookmarksDialog.tsx +++ b/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/ExportBookmarksDialog.tsx @@ -33,19 +33,25 @@ const useStyles = makeStyles()({ }, }) -const ExportBookmarksDialog = observer(function ExportBookmarksDialog({ +const ExportBookmarksDialog = observer(function ({ model, onClose, }: { model: GridBookmarkModel - onClose: (arg: boolean) => void + onClose: () => void }) { const { classes } = useStyles() const [fileType, setFileType] = useState('BED') const { selectedBookmarks } = model const exportAll = selectedBookmarks.length === 0 return ( - + { + onClose() + }} + title="Export bookmarks" + > {exportAll ? ( @@ -81,7 +87,7 @@ const ExportBookmarksDialog = observer(function ExportBookmarksDialog({ startIcon={} onClick={() => { downloadBookmarkFile(fileType, model) - onClose(false) + onClose() }} > Download diff --git a/plugins/linear-comparative-view/package.json b/plugins/linear-comparative-view/package.json index 197baa3b10..c4db3390c7 100644 --- a/plugins/linear-comparative-view/package.json +++ b/plugins/linear-comparative-view/package.json @@ -36,7 +36,6 @@ "clean": "rimraf dist esm *.tsbuildinfo" }, "dependencies": { - "@floating-ui/react": "^0.26.3", "@mui/icons-material": "^6.0.0", "copy-to-clipboard": "^3.3.1", "file-saver": "^2.0.0" diff --git a/plugins/linear-comparative-view/src/LGVSyntenyDisplay/configSchemaF.ts b/plugins/linear-comparative-view/src/LGVSyntenyDisplay/configSchemaF.ts index c9765946eb..7ea0daf8ed 100644 --- a/plugins/linear-comparative-view/src/LGVSyntenyDisplay/configSchemaF.ts +++ b/plugins/linear-comparative-view/src/LGVSyntenyDisplay/configSchemaF.ts @@ -2,6 +2,7 @@ import { ConfigurationSchema } from '@jbrowse/core/configuration' import { linearPileupDisplayConfigSchemaFactory } from '@jbrowse/plugin-alignments' import type PluginManager from '@jbrowse/core/PluginManager' +import type { Feature } from '@jbrowse/core/util' /** * #config LGVSyntenyDisplay @@ -9,13 +10,18 @@ import type PluginManager from '@jbrowse/core/PluginManager' * - [LinearPileupDisplay](../linearpileupdisplay) */ function configSchemaF(pluginManager: PluginManager) { + pluginManager.jexl.addFunction('lgvSyntenyTooltip', (f: Feature) => { + const m = f.get('mate') + return [f.get('name') || f.get('id'), m?.name || m?.id] + .filter(f => !!f) + .join('
') + }) return ConfigurationSchema( 'LGVSyntenyDisplay', { mouseover: { type: 'string', - defaultValue: - 'jexl:(get(feature,"name")||"")+ "
" + (get(feature,"mate").name||"")', + defaultValue: 'jexl:lgvSyntenyTooltip(feature)', }, }, { diff --git a/plugins/linear-comparative-view/src/SyntenyFeatureDetail/SyntenyFeatureDetail.tsx b/plugins/linear-comparative-view/src/SyntenyFeatureDetail/SyntenyFeatureDetail.tsx index 83b74a0ba7..f05e47a462 100644 --- a/plugins/linear-comparative-view/src/SyntenyFeatureDetail/SyntenyFeatureDetail.tsx +++ b/plugins/linear-comparative-view/src/SyntenyFeatureDetail/SyntenyFeatureDetail.tsx @@ -114,7 +114,7 @@ const SyntenyFeatureDetail = observer(function ({ model: SyntenyFeatureDetailModel }) { return ( - + diff --git a/plugins/linear-genome-view/package.json b/plugins/linear-genome-view/package.json index 847af55e15..125c79bbfa 100644 --- a/plugins/linear-genome-view/package.json +++ b/plugins/linear-genome-view/package.json @@ -38,7 +38,6 @@ "useSrc": "node ../../scripts/useSrc.js" }, "dependencies": { - "@floating-ui/react": "^0.26.3", "@mui/icons-material": "^6.0.0", "@types/file-saver": "^2.0.1", "copy-to-clipboard": "^3.3.1", diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/components/BlockError.tsx b/plugins/linear-genome-view/src/BaseLinearDisplay/components/BlockError.tsx new file mode 100644 index 0000000000..96a27d4051 --- /dev/null +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/components/BlockError.tsx @@ -0,0 +1,47 @@ +import React from 'react' + +import ErrorMessageStackTraceDialog from '@jbrowse/core/ui/ErrorMessageStackTraceDialog' +import { getSession } from '@jbrowse/core/util' +import RefreshIcon from '@mui/icons-material/Refresh' +import ReportIcon from '@mui/icons-material/Report' +import { IconButton, Tooltip } from '@mui/material' +import { observer } from 'mobx-react' + +import BlockMsg from './BlockMsg' + +const BlockError = observer(function ({ model }: { model: any }) { + return ( + + + { + model.reload() + }} + > + + + + + { + getSession(model).queueDialog(onClose => [ + ErrorMessageStackTraceDialog, + { onClose, error: model.error as Error }, + ]) + }} + > + + + + + } + /> + ) +}) + +export default BlockError diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/components/BlockMsg.tsx b/plugins/linear-genome-view/src/BaseLinearDisplay/components/BlockMsg.tsx index 6717373a33..0ce79ccee5 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/components/BlockMsg.tsx +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/components/BlockMsg.tsx @@ -24,6 +24,9 @@ export default function BlockMsg({ const { classes } = useStyles() return ( { + event.stopPropagation() + }} severity={severity} action={action} classes={{ message: classes.ellipses }} diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx b/plugins/linear-genome-view/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx index c718d8c833..84c39d465a 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx @@ -1,19 +1,14 @@ -import React, { lazy } from 'react' +import React, { Suspense, lazy } from 'react' import { LoadingEllipses } from '@jbrowse/core/ui' -import { getSession } from '@jbrowse/core/util' -import RefreshIcon from '@mui/icons-material/Refresh' -import ReportIcon from '@mui/icons-material/Report' -import { IconButton, Tooltip } from '@mui/material' import { observer } from 'mobx-react' import { getParent } from 'mobx-state-tree' import { makeStyles } from 'tss-react/mui' import BlockMsg from './BlockMsg' -const ErrorMessageStackTraceDialog = lazy( - () => import('@jbrowse/core/ui/ErrorMessageStackTraceDialog'), -) +// lazies +const BlockError = lazy(() => import('./BlockError')) const useStyles = makeStyles()(theme => { const bg = theme.palette.action.disabledBackground @@ -53,39 +48,13 @@ const ServerSideRenderedBlockContent = observer(function ({ }) { if (model.error) { return ( - - - { - model.reload() - }} - > - - - - - { - getSession(model).queueDialog(onClose => [ - ErrorMessageStackTraceDialog, - { onClose, error: model.error as Error }, - ]) - }} - > - - - - - } - /> + + + ) } else if (model.message) { - // the message can be a fully rendered react component, e.g. the region too large message + // the message can be a fully rendered react component, e.g. the region too + // large message return React.isValidElement(model.message) ? ( model.message ) : ( diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx b/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx index 5a78a8e616..88678b0c83 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx @@ -85,7 +85,13 @@ function stateModelFactory() { }), ) .volatile(() => ({ + /** + * #volatile + */ featureIdUnderMouse: undefined as undefined | string, + /** + * #volatile + */ contextMenuFeature: undefined as undefined | Feature, })) .views(self => ({ @@ -393,9 +399,9 @@ function stateModelFactory() { return renderBaseLinearDisplaySvg(self as BaseLinearDisplayModel, opts) }, afterAttach() { - // watch the parent's blocks to update our block state when they change, - // then we recreate the blocks on our own model (creating and deleting to - // match the parent blocks) + // watch the parent's blocks to update our block state when they + // change, then we recreate the blocks on our own model (creating and + // deleting to match the parent blocks) addDisposer( self, autorun(() => { diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/models/autorunFeatureDensityStats.ts b/plugins/linear-genome-view/src/BaseLinearDisplay/models/autorunFeatureDensityStats.ts index b62c98e5f8..8440eda8b0 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/models/autorunFeatureDensityStats.ts +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/models/autorunFeatureDensityStats.ts @@ -1,4 +1,4 @@ -import { getContainingView } from '@jbrowse/core/util' +import { getContainingView, isAbortException } from '@jbrowse/core/util' import { isAlive } from 'mobx-state-tree' import type { BaseLinearDisplayModel } from './BaseLinearDisplayModel' @@ -40,7 +40,7 @@ export default async function autorunFeatureDensityStats( } } catch (e) { console.error(e) - if (isAlive(self)) { + if (isAlive(self) && !isAbortException(e)) { self.setError(e) } } diff --git a/plugins/linear-genome-view/src/LinearBareDisplay/model.ts b/plugins/linear-genome-view/src/LinearBareDisplay/model.ts index 82971a0222..6e577ab881 100644 --- a/plugins/linear-genome-view/src/LinearBareDisplay/model.ts +++ b/plugins/linear-genome-view/src/LinearBareDisplay/model.ts @@ -1,4 +1,5 @@ -import { ConfigurationReference } from '@jbrowse/core/configuration' +import { ConfigurationReference, getConf } from '@jbrowse/core/configuration' +import { getEnv } from '@jbrowse/core/util' import { getParentRenderProps } from '@jbrowse/core/util/tracks' import { types } from 'mobx-state-tree' @@ -28,7 +29,23 @@ export function stateModelFactory(configSchema: AnyConfigurationSchemaType) { configuration: ConfigurationReference(configSchema), }), ) + .views(self => ({ + /** + * #getter + */ + get rendererConfig() { + const configBlob = getConf(self, ['renderer']) || {} + const config = configBlob as Omit + return self.rendererType.configSchema.create(config, getEnv(self)) + }, + /** + * #getter + */ + get rendererTypeName() { + return getConf(self, ['renderer', 'type']) + }, + })) .views(self => { const { renderProps: superRenderProps } = self return { @@ -40,16 +57,9 @@ export function stateModelFactory(configSchema: AnyConfigurationSchemaType) { ...superRenderProps(), ...getParentRenderProps(self), rpcDriverName: self.rpcDriverName, - config: self.configuration.renderer, + config: self.rendererConfig, } }, - - /** - * #getter - */ - get rendererTypeName() { - return self.configuration.renderer.type - }, } }) } diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx index 4daaacb30d..dc45afdf02 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/GetSequenceDialog.tsx @@ -33,6 +33,9 @@ const useStyles = makeStyles()({ textAreaFont: { fontFamily: 'Courier New', }, + ml: { + marginLeft: 10, + }, }) type LGV = LinearGenomeViewModel @@ -158,11 +161,7 @@ const GetSequenceDialog = observer(function ({ ) : loading ? ( Retrieving reference sequence... - + ) : null} (null) + const [mouseY, setMouseY] = useState() + const [mouseX, setMouseX] = useState() + const { width } = getContainingView(model) as LinearGenomeViewModel + + return ( +
{ + const rect = ref.current?.getBoundingClientRect() + const top = rect?.top || 0 + const left = rect?.left || 0 + setMouseY(event.clientY - top) + setMouseX(event.clientX - left) + }} + onMouseLeave={() => { + setMouseY(undefined) + setMouseX(undefined) + }} + > + + + {mouseY && sources ? ( +
+ + + + + + key !== 'color') + .map(([key, value]) => `${key}:${value}`) + .join('\n')} + /> + +
+ ) : null} +
+ ) +}) + +export default MultiLinearVariantDisplayComponent diff --git a/plugins/variants/src/MultiLinearVariantDisplay/configSchema.ts b/plugins/variants/src/MultiLinearVariantDisplay/configSchema.ts new file mode 100644 index 0000000000..7e66014ead --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantDisplay/configSchema.ts @@ -0,0 +1,56 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' +import { types } from 'mobx-state-tree' + +// locals +import sharedVariantConfigFactory from '../shared/SharedVariantConfigSchema' + +import type PluginManager from '@jbrowse/core/PluginManager' + +/** + * #config MultiLinearVariantDisplay + * extends + * - [SharedVariantDisplay](../sharedvariantdisplay) + */ +function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars + +export default function VariantConfigFactory(pluginManager: PluginManager) { + const MultiVariantRendererConfigSchema = pluginManager.getRendererType( + 'MultiVariantRenderer', + )!.configSchema + + return ConfigurationSchema( + 'MultiLinearVariantDisplay', + { + /** + * #slot + */ + defaultRendering: { + type: 'stringEnum', + model: types.enumeration('Rendering', ['multivariant']), + defaultValue: 'multivariant', + }, + + /** + * #slot + */ + renderers: ConfigurationSchema('RenderersConfiguration', { + MultiVariantRenderer: MultiVariantRendererConfigSchema, + }), + + /** + * #slot + */ + height: { + type: 'number', + defaultValue: 200, + }, + }, + { + /** + * #baseConfiguration + */ + baseConfiguration: sharedVariantConfigFactory(), + explicitlyTyped: true, + }, + ) +} diff --git a/plugins/variants/src/MultiLinearVariantDisplay/index.ts b/plugins/variants/src/MultiLinearVariantDisplay/index.ts new file mode 100644 index 0000000000..bd15ebb01e --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantDisplay/index.ts @@ -0,0 +1,28 @@ +import { lazy } from 'react' + +import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType' + +// locals +import configSchemaFactory from './configSchema' +import modelFactory from './model' + +import type PluginManager from '@jbrowse/core/PluginManager' + +export default function MultiLinearVariantDisplayF( + pluginManager: PluginManager, +) { + pluginManager.addDisplayType(() => { + const configSchema = configSchemaFactory(pluginManager) + return new DisplayType({ + name: 'MultiLinearVariantDisplay', + displayName: 'Multi-variant display (regular)', + configSchema, + stateModel: modelFactory(pluginManager, configSchema), + trackType: 'VariantTrack', + viewType: 'LinearGenomeView', + ReactComponent: lazy( + () => import('./components/VariantDisplayComponent'), + ), + }) + }) +} diff --git a/plugins/variants/src/MultiLinearVariantDisplay/model.ts b/plugins/variants/src/MultiLinearVariantDisplay/model.ts new file mode 100644 index 0000000000..1c19fa6d31 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantDisplay/model.ts @@ -0,0 +1,332 @@ +import { lazy } from 'react' + +// jbrowse imports +import { set1 } from '@jbrowse/core/ui/colors' +import { getSession } from '@jbrowse/core/util' +import { stopStopToken } from '@jbrowse/core/util/stopToken' +import { + type ExportSvgDisplayOptions, + linearBareDisplayStateModelFactory, +} from '@jbrowse/plugin-linear-genome-view' +import deepEqual from 'fast-deep-equal' +import { isAlive, types } from 'mobx-state-tree' + +// locals +import { randomColor } from '../util' + +import type { Source } from '../util' +import type PluginManager from '@jbrowse/core/PluginManager' +import type { AnyConfigurationSchemaType } from '@jbrowse/core/configuration' +import type { AnyReactComponentType, Feature } from '@jbrowse/core/util' +import type { Instance } from 'mobx-state-tree' + +// lazies +const Tooltip = lazy(() => import('../shared/Tooltip')) +const SetColorDialog = lazy(() => import('../shared/SetColorDialog')) +const ClusterDialog = lazy(() => import('../shared/ClusterDialog')) + +/** + * #stateModel MultiLinearVariantDisplay + * extends + * - [SharedVariantMixin](../sharedvariantmixin) + */ +export function stateModelFactory( + pluginManager: PluginManager, + configSchema: AnyConfigurationSchemaType, +) { + return types + .compose( + 'MultiLinearVariantDisplay', + linearBareDisplayStateModelFactory(configSchema), + types.model({ + /** + * #property + */ + type: types.literal('MultiLinearVariantDisplay'), + + /** + * #property + */ + layout: types.optional(types.frozen(), []), + /** + * #property + * used only if autoHeight is false + */ + rowHeightSetting: types.optional(types.number, 11), + + /** + * #property + * adjust to height of track/display + */ + autoHeight: false, + + /** + * #property + */ + showSidebarLabelsSetting: true, + }), + ) + .volatile(() => ({ + /** + * #volatile + */ + sourcesLoadingStopToken: undefined as string | undefined, + /** + * #volatile + */ + featureUnderMouseVolatile: undefined as Feature | undefined, + /** + * #volatile + */ + sourcesVolatile: undefined as Source[] | undefined, + })) + .actions(self => ({ + /** + * #action + */ + setSourcesLoading(str: string) { + if (self.sourcesLoadingStopToken) { + stopStopToken(self.sourcesLoadingStopToken) + } + self.sourcesLoadingStopToken = str + }, + /** + * #action + */ + setLayout(layout: Source[]) { + self.layout = layout + }, + /** + * #action + */ + clearLayout() { + self.layout = [] + }, + + /** + * #action + */ + setSources(sources: Source[]) { + if (!deepEqual(sources, self.sourcesVolatile)) { + self.sourcesVolatile = sources + } + }, + + /** + * #action + */ + setFeatureUnderMouse(f?: Feature) { + self.featureUnderMouseVolatile = f + }, + /** + * #action + */ + setRowHeight(arg: number) { + self.rowHeightSetting = arg + }, + /** + * #action + */ + setAutoHeight(arg: boolean) { + self.autoHeight = arg + }, + + /** + * #action + */ + setShowSidebarLabels(arg: boolean) { + self.showSidebarLabelsSetting = arg + }, + })) + .views(self => ({ + /** + * #getter + */ + get featureUnderMouse() { + return self.featureUnderMouseVolatile + }, + /** + * #getter + */ + get TooltipComponent() { + return Tooltip as AnyReactComponentType + }, + })) + .views(self => ({ + get rendererTypeName() { + return 'MultiVariantRenderer' + }, + /** + * #getter + */ + get sources() { + const sources = Object.fromEntries( + self.sourcesVolatile?.map(s => [s.name, s]) || [], + ) + const iter = self.layout.length ? self.layout : self.sourcesVolatile + return iter + ?.map(s => ({ + ...sources[s.name], + ...s, + })) + .map((s, i) => ({ + ...s, + color: s.color || set1[i] || randomColor(s.name), + })) + }, + })) + + .views(self => ({ + /** + * #getter + */ + get rowHeight() { + const { autoHeight, sources, rowHeightSetting, height } = self + return autoHeight ? height / (sources?.length || 1) : rowHeightSetting + }, + })) + .views(self => { + const { renderProps: superRenderProps } = self + return { + /** + * #getter + */ + get canDisplayLabels() { + return self.rowHeight > 8 && self.showSidebarLabelsSetting + }, + /** + * #getter + */ + get totalHeight() { + return self.rowHeight * (self.sources?.length || 1) + }, + /** + * #method + */ + adapterProps() { + const superProps = superRenderProps() + return { + ...superProps, + displayModel: self, + config: self.rendererConfig, + rpcDriverName: self.rpcDriverName, + sources: self.sources, + } + }, + } + }) + .views(self => ({ + /** + * #method + */ + renderProps() { + const superProps = self.adapterProps() + return { + ...superProps, + notReady: superProps.notReady || !self.sources, + displayModel: self, + rpcDriverName: self.rpcDriverName, + height: self.height, + totalHeight: self.totalHeight, + rowHeight: self.rowHeight, + scrollTop: self.scrollTop, + onMouseMove: (_: unknown, f: Feature) => { + self.setFeatureUnderMouse(f) + }, + onMouseLeave: () => { + self.setFeatureUnderMouse(undefined) + }, + } + }, + })) + .views(self => { + const { trackMenuItems: superTrackMenuItems } = self + return { + /** + * #method + */ + trackMenuItems() { + return [ + ...superTrackMenuItems(), + { + label: 'Adjust to height of display?', + type: 'checkbox', + checked: self.autoHeight, + onClick: () => { + self.setAutoHeight(!self.autoHeight) + }, + }, + { + label: 'Show sidebar labels', + type: 'checkbox', + checked: self.showSidebarLabelsSetting, + onClick: () => { + self.setShowSidebarLabels(!self.showSidebarLabelsSetting) + }, + }, + { + label: 'Cluster by genotype', + onClick: () => { + getSession(self).queueDialog(handleClose => [ + ClusterDialog, + { + model: self, + handleClose, + }, + ]) + }, + }, + { + label: 'Edit colors/arrangement...', + onClick: () => { + getSession(self).queueDialog(handleClose => [ + SetColorDialog, + { + model: self, + handleClose, + }, + ]) + }, + }, + ] + }, + } + }) + .actions(self => { + const { renderSvg: superRenderSvg } = self + return { + afterAttach() { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ;(async () => { + try { + const { getMultiVariantSourcesAutorun } = await import( + '../getMultiVariantSourcesAutorun' + ) + getMultiVariantSourcesAutorun(self) + } catch (e) { + if (isAlive(self)) { + console.error(e) + getSession(self).notifyError(`${e}`, e) + } + } + })() + }, + + /** + * #action + */ + async renderSvg(opts: ExportSvgDisplayOptions) { + const { renderSvg } = await import('./renderSvg') + return renderSvg(self, opts, superRenderSvg) + }, + } + }) +} + +export type MultiLinearVariantDisplayStateModel = ReturnType< + typeof stateModelFactory +> +export type MultiLinearVariantDisplayModel = + Instance + +export default stateModelFactory diff --git a/plugins/variants/src/MultiLinearVariantDisplay/renderSvg.tsx b/plugins/variants/src/MultiLinearVariantDisplay/renderSvg.tsx new file mode 100644 index 0000000000..4c49e6e0a3 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantDisplay/renderSvg.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +import { getContainingView } from '@jbrowse/core/util' +import { when } from 'mobx' + +// locals +import LegendBar from '../shared/LegendBar' + +import type { MultiLinearVariantDisplayModel } from './model' +import type { + ExportSvgDisplayOptions, + LinearGenomeViewModel, +} from '@jbrowse/plugin-linear-genome-view' + +export async function renderSvg( + self: MultiLinearVariantDisplayModel, + opts: ExportSvgDisplayOptions, + superRenderSvg: (opts: ExportSvgDisplayOptions) => Promise, +) { + await when(() => !!self.regionCannotBeRenderedText) + const { offsetPx } = getContainingView(self) as LinearGenomeViewModel + return ( + <> + {await superRenderSvg(opts)} + + + + + ) +} diff --git a/plugins/variants/src/MultiLinearVariantMatrixDisplay/components/LinesConnectingMatrixToGenomicPosition.tsx b/plugins/variants/src/MultiLinearVariantMatrixDisplay/components/LinesConnectingMatrixToGenomicPosition.tsx new file mode 100644 index 0000000000..3df32e59dc --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixDisplay/components/LinesConnectingMatrixToGenomicPosition.tsx @@ -0,0 +1,86 @@ +import React from 'react' + +import { getContainingView, getSession } from '@jbrowse/core/util' +import { observer } from 'mobx-react' + +import type { MultiLinearVariantMatrixDisplayModel } from '../model' +import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' + +const Wrapper = observer(function ({ + children, + model, + exportSVG, +}: { + model: MultiLinearVariantMatrixDisplayModel + children: React.ReactNode + exportSVG?: boolean +}) { + const { height } = model + return exportSVG ? ( + + {children} + + ) : ( + + {children} + + ) +}) + +const LinesConnectingMatrixToGenomicPosition = observer(function ({ + model, + exportSVG, +}: { + model: MultiLinearVariantMatrixDisplayModel + exportSVG?: boolean +}) { + const { assemblyManager } = getSession(model) + const view = getContainingView(model) as LinearGenomeViewModel + const { featuresVolatile } = model + const { offsetPx, assemblyNames } = view + const assembly = assemblyManager.get(assemblyNames[0]!) + const b0 = view.dynamicBlocks.contentBlocks[0]?.widthPx || 0 + const w = b0 / (featuresVolatile?.length || 1) + return assembly && featuresVolatile ? ( + + {featuresVolatile.map((f, i) => { + const c = + (view.bpToPx({ + refName: + assembly.getCanonicalRefName(f.get('refName')) || + f.get('refName'), + coord: f.get('start'), + })?.offsetPx || 0) - Math.max(offsetPx, 0) + return ( + + ) + })} + + ) : null +}) + +export default LinesConnectingMatrixToGenomicPosition diff --git a/plugins/variants/src/MultiLinearVariantMatrixDisplay/components/VariantDisplayComponent.tsx b/plugins/variants/src/MultiLinearVariantMatrixDisplay/components/VariantDisplayComponent.tsx new file mode 100644 index 0000000000..94ab8501a1 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixDisplay/components/VariantDisplayComponent.tsx @@ -0,0 +1,82 @@ +import React, { useRef, useState } from 'react' + +import { SanitizedHTML } from '@jbrowse/core/ui' +import BaseTooltip from '@jbrowse/core/ui/BaseTooltip' +import { getContainingView } from '@jbrowse/core/util' +import { BaseLinearDisplayComponent } from '@jbrowse/plugin-linear-genome-view' +import { observer } from 'mobx-react' +import { makeStyles } from 'tss-react/mui' + +import LinesConnectingMatrixToGenomicPosition from './LinesConnectingMatrixToGenomicPosition' +import LegendBar from '../../shared/LegendBar' + +import type { MultiLinearVariantMatrixDisplayModel } from '../model' +import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' + +const useStyles = makeStyles()({ + cursor: { + pointerEvents: 'none', + zIndex: 1000, + position: 'relative', + }, +}) + +const MultiLinearVariantMatrixDisplayComponent = observer(function (props: { + model: MultiLinearVariantMatrixDisplayModel +}) { + const { classes } = useStyles() + const { model } = props + const { height, sources, rowHeight } = model + const ref = useRef(null) + const [mouseY, setMouseY] = useState() + const [mouseX, setMouseX] = useState() + const { width } = getContainingView(model) as LinearGenomeViewModel + + return ( +
{ + const rect = ref.current?.getBoundingClientRect() + const top = rect?.top || 0 + const left = rect?.left || 0 + setMouseY(event.clientY - top) + setMouseX(event.clientX - left) + }} + onMouseLeave={() => { + setMouseY(undefined) + setMouseX(undefined) + }} + > +
+ +
+ + +
+
+ + {mouseY && + mouseY > 20 && + sources?.[Math.floor((mouseY - 20) / rowHeight)] ? ( + <> + + + + + + key !== 'color') + .map(([key, value]) => `${key}:${value}`) + .join('\n')} + /> + + + ) : null} +
+ ) +}) + +export default MultiLinearVariantMatrixDisplayComponent diff --git a/plugins/variants/src/MultiLinearVariantMatrixDisplay/configSchema.ts b/plugins/variants/src/MultiLinearVariantMatrixDisplay/configSchema.ts new file mode 100644 index 0000000000..36e9e1ce68 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixDisplay/configSchema.ts @@ -0,0 +1,40 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' +import { linearBasicDisplayConfigSchemaFactory } from '@jbrowse/plugin-linear-genome-view' + +// locals +import configSchema from '../MultiLinearVariantMatrixRenderer/configSchema' + +import type PluginManager from '@jbrowse/core/PluginManager' + +/** + * #config LinearVariantMatrixDisplay + */ +function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars + +export default function configSchemaF(pluginManager: PluginManager) { + return ConfigurationSchema( + 'LinearVariantMatrixDisplay', + { + /** + * #slot + * MultiLinearVariantMatrixRenderer + */ + renderer: configSchema, + + /** + * #slot + */ + height: { + type: 'number', + defaultValue: 250, + }, + }, + { + /** + * #baseConfiguration + */ + baseConfiguration: linearBasicDisplayConfigSchemaFactory(pluginManager), + explicitlyTyped: true, + }, + ) +} diff --git a/plugins/variants/src/MultiLinearVariantMatrixDisplay/index.ts b/plugins/variants/src/MultiLinearVariantMatrixDisplay/index.ts new file mode 100644 index 0000000000..2b7df7e58c --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixDisplay/index.ts @@ -0,0 +1,24 @@ +import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType' + +import MultiLinearVariantMatrixDisplayComponent from './components/VariantDisplayComponent' +import configSchemaF from './configSchema' +import stateModelFactory from './model' + +import type PluginManager from '@jbrowse/core/PluginManager' + +export default function LinearVariantMatrixDisplayF( + pluginManager: PluginManager, +) { + pluginManager.addDisplayType(() => { + const configSchema = configSchemaF(pluginManager) + return new DisplayType({ + name: 'LinearVariantMatrixDisplay', + displayName: 'Multi-variant display (matrix)', + configSchema, + stateModel: stateModelFactory(configSchema), + trackType: 'VariantTrack', + viewType: 'LinearGenomeView', + ReactComponent: MultiLinearVariantMatrixDisplayComponent, + }) + }) +} diff --git a/plugins/variants/src/MultiLinearVariantMatrixDisplay/model.ts b/plugins/variants/src/MultiLinearVariantMatrixDisplay/model.ts new file mode 100644 index 0000000000..f848a27ff0 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixDisplay/model.ts @@ -0,0 +1,312 @@ +import { lazy } from 'react' + +import { ConfigurationReference } from '@jbrowse/core/configuration' +import { set1 } from '@jbrowse/core/ui/colors' +import { getSession } from '@jbrowse/core/util' +import { stopStopToken } from '@jbrowse/core/util/stopToken' +import { linearBareDisplayStateModelFactory } from '@jbrowse/plugin-linear-genome-view' +import deepEqual from 'fast-deep-equal' +import { isAlive, types } from 'mobx-state-tree' + +import { randomColor } from '../util' + +import type { Source } from '../util' +import type { AnyConfigurationSchemaType } from '@jbrowse/core/configuration' +import type { Feature } from '@jbrowse/core/util' +import type { ExportSvgDisplayOptions } from '@jbrowse/plugin-linear-genome-view' +import type { Instance } from 'mobx-state-tree' + +// lazies +const SetColorDialog = lazy(() => import('../shared/SetColorDialog')) +const MAFFilterDialog = lazy(() => import('../shared/MAFFilterDialog')) +const ClusterDialog = lazy(() => import('../shared/ClusterDialog')) + +/** + * #stateModel LinearVariantMatrixDisplay + * extends + * - [LinearBasicDisplay](../linearbasicdisplay) + */ +export default function stateModelFactory( + configSchema: AnyConfigurationSchemaType, +) { + return types + .compose( + 'LinearVariantMatrixDisplay', + linearBareDisplayStateModelFactory(configSchema), + types.model({ + /** + * #property + */ + type: types.literal('LinearVariantMatrixDisplay'), + + /** + * #property + */ + layout: types.optional(types.frozen(), []), + /** + * #property + */ + configuration: ConfigurationReference(configSchema), + + /** + * #property + */ + mafFilter: types.optional(types.number, 0.1), + + /** + * #property + */ + showSidebarLabelsSetting: true, + }), + ) + .volatile(() => ({ + /** + * #volatile + */ + sourcesLoadingStopToken: undefined as string | undefined, + /** + * #volatile + */ + featureUnderMouseVolatile: undefined as Feature | undefined, + /** + * #volatile + */ + sourcesVolatile: undefined as Source[] | undefined, + /** + * #volatile + */ + featuresVolatile: undefined as Feature[] | undefined, + /** + * #volatile + */ + lineZoneHeight: 20, + })) + .actions(self => ({ + /** + * #action + */ + setFeatures(f: Feature[]) { + self.featuresVolatile = f + }, + /** + * #action + */ + setLayout(layout: Source[]) { + self.layout = layout + }, + /** + * #action + */ + clearLayout() { + self.layout = [] + }, + /** + * #action + */ + setSourcesLoading(str: string) { + if (self.sourcesLoadingStopToken) { + stopStopToken(self.sourcesLoadingStopToken) + } + self.sourcesLoadingStopToken = str + }, + + /** + * #action + */ + setSources(sources: Source[]) { + if (!deepEqual(sources, self.sourcesVolatile)) { + self.sourcesVolatile = sources + } + }, + /** + * #action + */ + setMafFilter(arg: number) { + self.mafFilter = arg + }, + /** + * #action + */ + setShowSidebarLabels(arg: boolean) { + self.showSidebarLabelsSetting = arg + }, + })) + .views(self => ({ + get preSources() { + return self.layout.length ? self.layout : self.sourcesVolatile + }, + /** + * #getter + */ + get sources() { + const sources = Object.fromEntries( + self.sourcesVolatile?.map(s => [s.name, s]) || [], + ) + return this.preSources + ?.map(s => ({ + ...sources[s.name], + ...s, + })) + .map((s, i) => ({ + ...s, + color: s.color || set1[i] || randomColor(s.name), + })) + }, + })) + .views(self => { + const { + trackMenuItems: superTrackMenuItems, + renderProps: superRenderProps, + } = self + + return { + /** + * #method + */ + adapterProps() { + const superProps = superRenderProps() + return { + ...superProps, + rpcDriverName: self.rpcDriverName, + displayModel: self, + config: self.rendererConfig, + } + }, + /** + * #method + */ + trackMenuItems() { + return [ + ...superTrackMenuItems(), + { + label: 'Show sidebar labels', + type: 'checkbox', + checked: self.showSidebarLabelsSetting, + onClick: () => { + self.setShowSidebarLabels(!self.showSidebarLabelsSetting) + }, + }, + + { + label: 'Minimum allele frequency', + onClick: () => { + getSession(self).queueDialog(handleClose => [ + MAFFilterDialog, + { + model: self, + handleClose, + }, + ]) + }, + }, + { + label: 'Cluster by genotype', + onClick: () => { + getSession(self).queueDialog(handleClose => [ + ClusterDialog, + { + model: self, + handleClose, + }, + ]) + }, + }, + { + label: 'Edit colors/arrangement...', + onClick: () => { + getSession(self).queueDialog(handleClose => [ + SetColorDialog, + { + model: self, + handleClose, + }, + ]) + }, + }, + ] + }, + } + }) + .views(self => ({ + /** + * #getter + */ + get blockType() { + return 'dynamicBlocks' + }, + /** + * #getter + */ + get totalHeight() { + return self.height - self.lineZoneHeight + }, + + /** + * #getter + */ + get rowHeight() { + return this.totalHeight / (self.sources?.length || 1) + }, + /** + * #method + */ + renderProps() { + const superProps = self.adapterProps() + return { + ...superProps, + notReady: + superProps.notReady || !self.sources || !self.featuresVolatile, + mafFilter: self.mafFilter, + height: self.height, + sources: self.sources, + } + }, + })) + .views(self => ({ + /** + * #getter + */ + get canDisplayLabels() { + return self.rowHeight > 8 && self.showSidebarLabelsSetting + }, + })) + .actions(self => { + const { renderSvg: superRenderSvg } = self + return { + afterAttach() { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ;(async () => { + try { + const { getMultiVariantSourcesAutorun } = await import( + '../getMultiVariantSourcesAutorun' + ) + const { getMultiVariantFeaturesAutorun } = await import( + '../getMultiVariantFeaturesAutorun' + ) + getMultiVariantSourcesAutorun(self) + getMultiVariantFeaturesAutorun(self) + } catch (e) { + if (isAlive(self)) { + console.error(e) + getSession(self).notifyError(`${e}`, e) + } + } + })() + }, + + /** + * #action + */ + async renderSvg(opts: ExportSvgDisplayOptions) { + const { renderSvg } = await import('./renderSvg') + return renderSvg(self, opts, superRenderSvg) + }, + } + }) +} + +export type MultiLinearVariantMatrixDisplayStateModel = ReturnType< + typeof stateModelFactory +> +export type MultiLinearVariantMatrixDisplayModel = + Instance diff --git a/plugins/variants/src/MultiLinearVariantMatrixDisplay/renderSvg.tsx b/plugins/variants/src/MultiLinearVariantMatrixDisplay/renderSvg.tsx new file mode 100644 index 0000000000..33e872333d --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixDisplay/renderSvg.tsx @@ -0,0 +1,34 @@ +import React from 'react' + +import { getContainingView } from '@jbrowse/core/util' +import { when } from 'mobx' + +// locals +import LegendBar from '../shared/LegendBar' +import LinesConnectingMatrixToGenomicPosition from './components/LinesConnectingMatrixToGenomicPosition' + +import type { MultiLinearVariantMatrixDisplayModel } from './model' +import type { + ExportSvgDisplayOptions, + LinearGenomeViewModel, +} from '@jbrowse/plugin-linear-genome-view' + +export async function renderSvg( + model: MultiLinearVariantMatrixDisplayModel, + opts: ExportSvgDisplayOptions, + superRenderSvg: (opts: ExportSvgDisplayOptions) => Promise, +) { + await when(() => !!model.regionCannotBeRenderedText) + const { offsetPx } = getContainingView(model) as LinearGenomeViewModel + return ( + <> + + + + {await superRenderSvg(opts)} + + + + + ) +} diff --git a/plugins/variants/src/MultiLinearVariantMatrixRenderer/LinearVariantMatrixRenderer.ts b/plugins/variants/src/MultiLinearVariantMatrixRenderer/LinearVariantMatrixRenderer.ts new file mode 100644 index 0000000000..a1f0993bfb --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixRenderer/LinearVariantMatrixRenderer.ts @@ -0,0 +1,161 @@ +import BoxRendererType from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType' +import { SimpleFeature, renderToAbstractCanvas } from '@jbrowse/core/util' + +import { getCol } from '../util' + +import type { Source } from '../util' +import type { RenderArgsDeserialized as BoxRenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType' +import type { Feature } from '@jbrowse/core/util' + +export interface SortParams { + type: string + pos: number + refName: string + assemblyName: string + tag?: string +} + +export interface RenderArgsDeserialized extends BoxRenderArgsDeserialized { + sources: { name: string }[] + mafFilter: number + highResolutionScaling: number + height: number +} + +export interface RenderArgsDeserializedWithFeaturesAndLayout + extends RenderArgsDeserialized { + sources: Source[] + features: Map +} + +const fudgeFactor = 0.6 +const f2 = fudgeFactor / 2 + +export default class LinearVariantMatrixRenderer extends BoxRendererType { + supportsSVG = true + + makeImageData({ + ctx, + canvasWidth, + canvasHeight, + renderArgs, + }: { + ctx: CanvasRenderingContext2D + canvasWidth: number + canvasHeight: number + renderArgs: RenderArgsDeserializedWithFeaturesAndLayout + }) { + const { mafFilter, sources, features } = renderArgs + const feats = [...features.values()] + const h = canvasHeight / sources.length + const mafs = [] as Feature[] + for (const feat of feats) { + let c = 0 + let c2 = 0 + const samp = feat.get('genotypes') + + // only draw smallish indels + if (feat.get('end') - feat.get('start') <= 10) { + for (const { name } of sources) { + const s = samp[name]! + if (s === '0|0' || s === './.') { + c2++ + } else if (s === '1|0' || s === '0|1') { + c++ + } else if (s === '1|1') { + c++ + c2++ + } else { + c++ + } + } + if ( + c / sources.length > mafFilter && + c2 / sources.length < 1 - mafFilter + ) { + mafs.push(feat) + } + } + } + + const w = canvasWidth / mafs.length + for (let i = 0; i < mafs.length; i++) { + const f = mafs[i]! + + // only draw smallish indels + if (f.get('end') - f.get('start') <= 10) { + const samp = f.get('genotypes') + const x = (i / mafs.length) * canvasWidth + for (let j = 0; j < sources.length; j++) { + const y = (j / sources.length) * canvasHeight + const { name } = sources[j]! + ctx.fillStyle = getCol(samp[name]) + ctx.fillRect(x - f2, y - f2, w + f2, h + f2) + } + } + } + return { mafs } + } + + async render(renderProps: RenderArgsDeserialized) { + const features = await this.getFeatures(renderProps) + const { height, sources, regions, bpPerPx } = renderProps + const region = regions[0]! + const { end, start } = region + + const width = (end - start) / bpPerPx + // @ts-expect-error + const { mafs, ...res } = await renderToAbstractCanvas( + width, + height, + renderProps, + ctx => { + return this.makeImageData({ + ctx, + canvasWidth: width, + canvasHeight: height, + renderArgs: { + ...renderProps, + features, + sources, + }, + }) + }, + ) + + const results = await super.render({ + ...renderProps, + ...res, + features, + height, + width, + }) + + return { + ...results, + ...res, + features: new Map(), + simplifiedFeatures: mafs.map( + (s: Feature) => + new SimpleFeature({ + id: s.id(), + data: { + start: s.get('start'), + end: s.get('end'), + refName: s.get('refName'), + }, + }), + ), + height, + width, + } + } +} + +export type { + RenderArgs, + RenderArgsSerialized, + RenderResults, + ResultsDeserialized, + ResultsSerialized, +} from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType' diff --git a/plugins/variants/src/MultiLinearVariantMatrixRenderer/components/LinearVariantMatrixRendering.tsx b/plugins/variants/src/MultiLinearVariantMatrixRenderer/components/LinearVariantMatrixRendering.tsx new file mode 100644 index 0000000000..52e2f551bc --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixRenderer/components/LinearVariantMatrixRendering.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +import { PrerenderedCanvas } from '@jbrowse/core/ui' +import { observer } from 'mobx-react' + +const LinearVariantMatrixRendering = observer(function (props: { + width: number + height: number +}) { + return +}) + +export default LinearVariantMatrixRendering diff --git a/plugins/variants/src/MultiLinearVariantMatrixRenderer/configSchema.ts b/plugins/variants/src/MultiLinearVariantMatrixRenderer/configSchema.ts new file mode 100644 index 0000000000..71e751baf1 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixRenderer/configSchema.ts @@ -0,0 +1,13 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' + +/** + * #config LinearVariantMatrixRenderer + */ +function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars + +const LinearVariantMatrixRenderer = ConfigurationSchema( + 'LinearVariantMatrixRenderer', + {}, + { explicitlyTyped: true }, +) +export default LinearVariantMatrixRenderer diff --git a/plugins/variants/src/MultiLinearVariantMatrixRenderer/index.ts b/plugins/variants/src/MultiLinearVariantMatrixRenderer/index.ts new file mode 100644 index 0000000000..e93c91210a --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantMatrixRenderer/index.ts @@ -0,0 +1,20 @@ +import { PrerenderedCanvas } from '@jbrowse/core/ui' + +import LinearVariantMatrixRenderer from './LinearVariantMatrixRenderer' +import configSchema from './configSchema' + +import type PluginManager from '@jbrowse/core/PluginManager' + +export default function LinearVariantMatrixRendererF( + pluginManager: PluginManager, +) { + pluginManager.addRendererType(() => { + return new LinearVariantMatrixRenderer({ + name: 'LinearVariantMatrixRenderer', + displayName: 'Linear variant matrix renderer', + ReactComponent: PrerenderedCanvas, + configSchema, + pluginManager, + }) + }) +} diff --git a/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRenderer.ts b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRenderer.ts new file mode 100644 index 0000000000..d271f994f9 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRenderer.ts @@ -0,0 +1,98 @@ +import FeatureRendererType from '@jbrowse/core/pluggableElementTypes/renderers/FeatureRendererType' +import { featureSpanPx, renderToAbstractCanvas } from '@jbrowse/core/util' + +// locals +import { getCol } from '../util' + +import type { Source } from '../util' +import type { RenderArgsDeserialized as FeatureRenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/FeatureRendererType' +import type { Feature } from '@jbrowse/core/util' +import type { ThemeOptions } from '@mui/material' + +export interface RenderArgsDeserialized extends FeatureRenderArgsDeserialized { + bpPerPx: number + height: number + highResolutionScaling: number + themeOptions: ThemeOptions +} + +export interface RenderArgsDeserializedWithFeatures + extends RenderArgsDeserialized { + features: Map +} + +export interface MultiRenderArgsDeserialized + extends RenderArgsDeserializedWithFeatures { + sources: Source[] + rowHeight: number + scrollTop: number +} + +export default class MultiVariantBaseRenderer extends FeatureRendererType { + supportsSVG = true + + async render(renderProps: MultiRenderArgsDeserialized) { + const features = await this.getFeatures(renderProps) + const { height, regions, bpPerPx } = renderProps + const region = regions[0]! + const width = (region.end - region.start) / bpPerPx + + const rest = await renderToAbstractCanvas( + width, + height, + renderProps, + async ctx => { + await this.draw(ctx, { + ...renderProps, + features, + }) + return undefined + }, + ) + + const results = await super.render({ + ...renderProps, + ...rest, + features, + height, + width, + }) + + return { + ...results, + ...rest, + features: new Map(), + height, + width, + containsNoTransferables: true, + } + } + async draw( + ctx: CanvasRenderingContext2D, + props: MultiRenderArgsDeserialized, + ) { + const { scrollTop, sources, rowHeight, features, regions, bpPerPx } = props + const region = regions[0]! + + for (const feature of features.values()) { + if (feature.get('end') - feature.get('start') <= 10) { + const [leftPx, rightPx] = featureSpanPx(feature, region, bpPerPx) + const w = Math.max(Math.round(rightPx - leftPx), 2) + const genotypes = feature.get('genotypes') as Record + let t = -scrollTop + for (const { name } of sources) { + ctx.fillStyle = getCol(genotypes[name]!) + ctx.fillRect(Math.floor(leftPx), t, w, Math.max(t + rowHeight, 1)) + t += rowHeight + } + } + } + } +} + +export type { + RenderArgsSerialized, + RenderResults, + ResultsDeserialized, + ResultsSerialized, +} from '@jbrowse/core/pluggableElementTypes/renderers/FeatureRendererType' diff --git a/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRendering.tsx b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRendering.tsx new file mode 100644 index 0000000000..b494712c84 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRendering.tsx @@ -0,0 +1,49 @@ +import React, { useRef } from 'react' + +import { PrerenderedCanvas } from '@jbrowse/core/ui' +import { observer } from 'mobx-react' + +import type { Source } from '../util' +import type { Feature } from '@jbrowse/core/util' +import type { Region } from '@jbrowse/core/util/types' + +// locals + +const MultiVariantRendering = observer(function (props: { + regions: Region[] + features: Map + bpPerPx: number + width: number + height: number + sources: Source[] + scrollTop: number + totalHeight: number + onMouseLeave?: (event: React.MouseEvent) => void + onMouseMove?: (event: React.MouseEvent, arg?: Feature) => void + onFeatureClick?: (event: React.MouseEvent, arg?: Feature) => void +}) { + const { totalHeight, scrollTop } = props + const ref = useRef(null) + + return ( +
+ +
+ ) +}) + +export default MultiVariantRendering diff --git a/plugins/variants/src/MultiLinearVariantRenderer/configSchema.ts b/plugins/variants/src/MultiLinearVariantRenderer/configSchema.ts new file mode 100644 index 0000000000..3623df7721 --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantRenderer/configSchema.ts @@ -0,0 +1,22 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' + +import baseWiggleRendererConfigSchema from '../configSchema' + +/** + * #config MultiVariantRenderer + */ +function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars + +const configSchema = ConfigurationSchema( + 'MultiVariantRenderer', + {}, + { + /** + * #baseConfiguration + */ + baseConfiguration: baseWiggleRendererConfigSchema, + explicitlyTyped: true, + }, +) + +export default configSchema diff --git a/plugins/variants/src/MultiLinearVariantRenderer/index.ts b/plugins/variants/src/MultiLinearVariantRenderer/index.ts new file mode 100644 index 0000000000..af2280fc4a --- /dev/null +++ b/plugins/variants/src/MultiLinearVariantRenderer/index.ts @@ -0,0 +1,18 @@ +import MultiVariantRenderer from './MultiVariantRenderer' +import ReactComponent from './MultiVariantRendering' +import configSchema from './configSchema' + +import type PluginManager from '@jbrowse/core/PluginManager' + +// locals + +export default function MultiVariantRendererF(pluginManager: PluginManager) { + pluginManager.addRendererType(() => { + return new MultiVariantRenderer({ + name: 'MultiVariantRenderer', + ReactComponent, + configSchema, + pluginManager, + }) + }) +} diff --git a/plugins/variants/src/MultiVariantBaseRenderer.ts b/plugins/variants/src/MultiVariantBaseRenderer.ts new file mode 100644 index 0000000000..7f721f26d7 --- /dev/null +++ b/plugins/variants/src/MultiVariantBaseRenderer.ts @@ -0,0 +1,78 @@ +import FeatureRendererType from '@jbrowse/core/pluggableElementTypes/renderers/FeatureRendererType' +import { renderToAbstractCanvas } from '@jbrowse/core/util' + +import type { Source } from './util' +import type { RenderArgsDeserialized as FeatureRenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/FeatureRendererType' +import type { Feature } from '@jbrowse/core/util' +import type { ThemeOptions } from '@mui/material' + +// locals + +export interface RenderArgsDeserialized extends FeatureRenderArgsDeserialized { + bpPerPx: number + height: number + highResolutionScaling: number + themeOptions: ThemeOptions +} + +export interface RenderArgsDeserializedWithFeatures + extends RenderArgsDeserialized { + features: Map +} + +export interface MultiRenderArgsDeserialized + extends RenderArgsDeserializedWithFeatures { + sources: Source[] +} + +export default abstract class MultiVariantBaseRenderer extends FeatureRendererType { + supportsSVG = true + + async render(renderProps: RenderArgsDeserialized) { + const features = await this.getFeatures(renderProps) + const { height, regions, bpPerPx } = renderProps + const region = regions[0]! + const width = (region.end - region.start) / bpPerPx + + const rest = await renderToAbstractCanvas(width, height, renderProps, ctx => + this.draw(ctx, { + ...renderProps, + features, + }), + ) + + const results = await super.render({ + ...renderProps, + ...rest, + features, + height, + width, + }) + + return { + ...results, + ...rest, + features: new Map(), + height, + width, + containsNoTransferables: true, + } + } + + /** + * draw features to context given props, to be used by derived renderer + * classes + */ + abstract draw( + ctx: CanvasRenderingContext2D, + + props: T, + ): Promise | undefined> +} + +export type { + RenderArgsSerialized, + RenderResults, + ResultsDeserialized, + ResultsSerialized, +} from '@jbrowse/core/pluggableElementTypes/renderers/FeatureRendererType' diff --git a/plugins/variants/src/Tooltip.tsx b/plugins/variants/src/Tooltip.tsx new file mode 100644 index 0000000000..c11d56450d --- /dev/null +++ b/plugins/variants/src/Tooltip.tsx @@ -0,0 +1,70 @@ +import React, { Suspense } from 'react' + +import BaseTooltip from '@jbrowse/core/ui/BaseTooltip' +import { observer } from 'mobx-react' +import { makeStyles } from 'tss-react/mui' + +import type { Feature } from '@jbrowse/core/util' + +const useStyles = makeStyles()({ + hoverVertical: { + background: '#333', + border: 'none', + width: 1, + height: '100%', + cursor: 'default', + position: 'absolute', + pointerEvents: 'none', + }, +}) + +type Coord = [number, number] + +// React.forwardRef component for the tooltip, the ref is used for measuring +// the size of the tooltip +export type TooltipContentsComponent = React.ForwardRefExoticComponent< + { feature: Feature; model: any } & React.RefAttributes +> + +const Tooltip = observer(function Tooltip({ + model, + height, + clientMouseCoord, + offsetMouseCoord, + clientRect, + TooltipContents, + useClientY, +}: { + model: { featureUnderMouse?: Feature } + useClientY?: boolean + height: number + clientMouseCoord: Coord + offsetMouseCoord: Coord + clientRect?: DOMRect + TooltipContents: TooltipContentsComponent +}) { + const { featureUnderMouse } = model + const { classes } = useStyles() + + const x = clientMouseCoord[0] + 5 + const y = useClientY ? clientMouseCoord[1] : clientRect?.top || 0 + return featureUnderMouse ? ( + <> + + + + + + +
+ + ) : null +}) + +export default Tooltip diff --git a/plugins/variants/src/VariantRPC/MultiVariantGetGenotypeMatrix.ts b/plugins/variants/src/VariantRPC/MultiVariantGetGenotypeMatrix.ts new file mode 100644 index 0000000000..feb75dca11 --- /dev/null +++ b/plugins/variants/src/VariantRPC/MultiVariantGetGenotypeMatrix.ts @@ -0,0 +1,87 @@ +import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' +import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions' +import { firstValueFrom, toArray } from 'rxjs' + +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter' +import type { Feature, Region } from '@jbrowse/core/util' + +export class MultiVariantGetGenotypeMatrix extends RpcMethodTypeWithFiltersAndRenameRegions { + name = 'MultiVariantGetGenotypeMatrix' + + async execute( + args: { + adapterConfig: AnyConfigurationModel + stopToken?: string + sessionId: string + headers?: Record + regions: Region[] + bpPerPx: number + }, + rpcDriverClassName: string, + ) { + const pm = this.pluginManager + const deserializedArgs = await this.deserializeArguments( + args, + rpcDriverClassName, + ) + const { sources, mafFilter, regions, adapterConfig, sessionId } = + deserializedArgs + const adapter = await getAdapter(pm, sessionId, adapterConfig) + const dataAdapter = adapter.dataAdapter as BaseFeatureDataAdapter + const region = regions[0] + + const feats = await firstValueFrom( + dataAdapter.getFeatures(region, deserializedArgs).pipe(toArray()), + ) + + // a 'factor' in the R sense of the term (ordinal) + const genotypeFactor = new Set() + const mafs = [] as Feature[] + for (const feat of feats) { + let c = 0 + let c2 = 0 + const samp = feat.get('genotypes') + + // only draw smallish indels + if (feat.get('end') - feat.get('start') <= 10) { + for (const { name } of sources) { + const s = samp[name]! + genotypeFactor.add(s) + if (s === '0|0' || s === './.') { + c2++ + } else if (s === '1|0' || s === '0|1') { + c++ + } else if (s === '1|1') { + c++ + c2++ + } else { + c++ + } + } + if ( + c / sources.length > mafFilter && + c2 / sources.length < 1 - mafFilter + ) { + mafs.push(feat) + } + } + } + + const genotypeFactorMap = Object.fromEntries( + [...genotypeFactor].map((type, idx) => [type, idx]), + ) + const rows = {} as Record + for (const feat of mafs) { + const samp = feat.get('genotypes') as Record + for (const { name } of sources) { + if (!rows[name]) { + rows[name] = { name, genotypes: [] } + } + rows[name].genotypes.push(genotypeFactorMap[samp[name]!]!) + } + } + + return rows + } +} diff --git a/plugins/variants/src/VariantRPC/MultiVariantGetSimplifiedFeatures.ts b/plugins/variants/src/VariantRPC/MultiVariantGetSimplifiedFeatures.ts new file mode 100644 index 0000000000..de2139ec11 --- /dev/null +++ b/plugins/variants/src/VariantRPC/MultiVariantGetSimplifiedFeatures.ts @@ -0,0 +1,78 @@ +import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' +import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions' +import { firstValueFrom, toArray } from 'rxjs' + +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter' +import type { Feature, Region } from '@jbrowse/core/util' + +export class MultiVariantGetSimplifiedFeatures extends RpcMethodTypeWithFiltersAndRenameRegions { + name = 'MultiVariantGetSimplifiedFeatures' + + async execute( + args: { + adapterConfig: AnyConfigurationModel + stopToken?: string + sessionId: string + headers?: Record + regions: Region[] + bpPerPx: number + }, + rpcDriverClassName: string, + ) { + const pm = this.pluginManager + const deserializedArgs = await this.deserializeArguments( + args, + rpcDriverClassName, + ) + const { mafFilter, sources, regions, adapterConfig, sessionId } = + deserializedArgs + const { dataAdapter } = await getAdapter(pm, sessionId, adapterConfig) + const feats = await firstValueFrom( + (dataAdapter as BaseFeatureDataAdapter) + .getFeaturesInMultipleRegions(regions, deserializedArgs) + .pipe(toArray()), + ) + + // a 'factor' in the R sense of the term (ordinal) + const genotypeFactor = new Set() + const mafs = [] as Feature[] + for (const feat of feats) { + let c = 0 + let c2 = 0 + const samp = feat.get('genotypes') + + // only draw smallish indels + if (feat.get('end') - feat.get('start') <= 10) { + for (const { name } of sources) { + const s = samp[name]! + genotypeFactor.add(s) + if (s === '0|0' || s === './.') { + c2++ + } else if (s === '1|0' || s === '0|1') { + c++ + } else if (s === '1|1') { + c++ + c2++ + } else { + c++ + } + } + if ( + c / sources.length > mafFilter && + c2 / sources.length < 1 - mafFilter + ) { + mafs.push(feat) + } + } + } + return mafs.map(f => ({ + id: f.id(), + data: { + start: f.get('start'), + end: f.get('end'), + refName: f.get('refName'), + }, + })) + } +} diff --git a/plugins/variants/src/VariantRPC/MultiVariantGetSources.ts b/plugins/variants/src/VariantRPC/MultiVariantGetSources.ts new file mode 100644 index 0000000000..f0614d7e30 --- /dev/null +++ b/plugins/variants/src/VariantRPC/MultiVariantGetSources.ts @@ -0,0 +1,32 @@ +import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' +import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions' + +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { Region } from '@jbrowse/core/util' + +export class MultiVariantGetSources extends RpcMethodTypeWithFiltersAndRenameRegions { + name = 'MultiVariantGetSources' + + async execute( + args: { + adapterConfig: AnyConfigurationModel + stopToken?: string + sessionId: string + headers?: Record + regions: Region[] + bpPerPx: number + }, + rpcDriverClassName: string, + ) { + const pm = this.pluginManager + const deserializedArgs = await this.deserializeArguments( + args, + rpcDriverClassName, + ) + const { regions, adapterConfig, sessionId } = deserializedArgs + const { dataAdapter } = await getAdapter(pm, sessionId, adapterConfig) + + // @ts-expect-error + return dataAdapter.getSources(regions, deserializedArgs) + } +} diff --git a/plugins/variants/src/VariantTrack/configSchema.ts b/plugins/variants/src/VariantTrack/configSchema.ts index f96dcbacc2..9968b41f53 100644 --- a/plugins/variants/src/VariantTrack/configSchema.ts +++ b/plugins/variants/src/VariantTrack/configSchema.ts @@ -8,10 +8,8 @@ import type PluginManager from '@jbrowse/core/PluginManager' * Mostly similar to feature track, but has `ChordDisplayType` registered to it, * and custom feature details in `LinearVariantDisplay` */ -function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars - -const configSchema = (pluginManager: PluginManager) => - ConfigurationSchema( +export default function VariantTrackF(pluginManager: PluginManager) { + return ConfigurationSchema( 'VariantTrack', {}, { @@ -21,5 +19,4 @@ const configSchema = (pluginManager: PluginManager) => baseConfiguration: createBaseTrackConfig(pluginManager), }, ) - -export default configSchema +} diff --git a/plugins/variants/src/VcfAdapter/__snapshots__/VcfAdapter.test.ts.snap b/plugins/variants/src/VcfAdapter/__snapshots__/VcfAdapter.test.ts.snap index a4bd8d8b25..ea1cb29bf4 100644 --- a/plugins/variants/src/VcfAdapter/__snapshots__/VcfAdapter.test.ts.snap +++ b/plugins/variants/src/VcfAdapter/__snapshots__/VcfAdapter.test.ts.snap @@ -13,8 +13,8 @@ exports[`adapter can fetch variants from volvox.vcf 2`] = ` "C", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, @@ -73,8 +73,8 @@ exports[`adapter can fetch variants from volvox.vcf 2`] = ` "C", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, @@ -139,8 +139,8 @@ exports[`adapter can fetch variants from volvox.vcf 2`] = ` "G", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 1, @@ -205,8 +205,8 @@ exports[`adapter can fetch variants from volvox.vcf 2`] = ` "A", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, @@ -265,8 +265,8 @@ exports[`adapter can fetch variants from volvox.vcf 2`] = ` "ct", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, diff --git a/plugins/variants/src/VcfFeature/__snapshots__/index.test.ts.snap b/plugins/variants/src/VcfFeature/__snapshots__/index.test.ts.snap index 6923e05b04..e4579436ff 100644 --- a/plugins/variants/src/VcfFeature/__snapshots__/index.test.ts.snap +++ b/plugins/variants/src/VcfFeature/__snapshots__/index.test.ts.snap @@ -2,7 +2,7 @@ exports[`null ALT 1`] = ` { - "ALT": null, + "ALT": undefined, "CHROM": "chr1", "FILTER": "PASS", "ID": [ diff --git a/plugins/variants/src/VcfFeature/index.ts b/plugins/variants/src/VcfFeature/index.ts index 2f9644d5b4..64cc2023ea 100644 --- a/plugins/variants/src/VcfFeature/index.ts +++ b/plugins/variants/src/VcfFeature/index.ts @@ -1,49 +1,64 @@ import { getSOTermAndDescription } from './util' -import type VCF from '@gmod/vcf' +import type VCFParser from '@gmod/vcf' +import type { Variant } from '@gmod/vcf' import type { Feature } from '@jbrowse/core/util' -type Samples = Record< - string, - Record -> - -interface FeatureData { - [key: string]: unknown - refName: string - start: number - end: number - description?: string - type?: string - name?: string - aliases?: string[] - samples?: Samples +type FeatureData = ReturnType + +function dataFromVariant(variant: Variant, parser: VCFParser) { + const { REF = '', ALT, POS, CHROM, ID } = variant + const start = POS - 1 + const [type, description] = getSOTermAndDescription(REF, ALT, parser) + + return { + refName: CHROM, + start, + end: getEnd(variant), + description, + type, + name: ID?.join(','), + aliases: ID && ID.length > 1 ? ID.slice(1) : undefined, + } +} +function getEnd(variant: Variant) { + const { POS, REF = '', ALT } = variant + const isTRA = ALT?.includes('') + const start = POS - 1 + const isSymbolic = ALT?.some(f => f.includes('<')) + if (isSymbolic) { + const info = variant.INFO + if (info.END && !isTRA) { + return +(info.END as string[])[0]! + } + } + return start + REF.length } export default class VCFFeature implements Feature { - private variant: any + private variant: Variant - private parser: VCF + private parser: VCFParser private data: FeatureData private _id: string - constructor(args: { variant: any; parser: VCF; id: string }) { + constructor(args: { variant: Variant; parser: VCFParser; id: string }) { this.variant = args.variant this.parser = args.parser - this.data = this.dataFromVariant(this.variant) + this.data = dataFromVariant(this.variant, this.parser) this._id = args.id } get(field: string): any { return field === 'samples' - ? this.variant.SAMPLES - : (this.data[field] ?? this.variant[field]) + ? this.variant.SAMPLES() + : field === 'genotypes' + ? this.variant.GENOTYPES() + : (this.data[field as keyof typeof this.data] ?? + this.variant[field as keyof typeof this.variant]) } - - set() {} - parent() { return undefined } @@ -52,45 +67,17 @@ export default class VCFFeature implements Feature { return undefined } - tags() { - return [...Object.keys(this.data), ...Object.keys(this.variant), 'samples'] - } - id() { return this._id } - dataFromVariant(variant: { - REF: string - POS: number - ALT?: string[] - CHROM: string - INFO: any - ID?: string[] - }): FeatureData { - const { REF, ALT, POS, CHROM, INFO, ID } = variant - const start = POS - 1 - const [type, description] = getSOTermAndDescription(REF, ALT, this.parser) - const isTRA = ALT?.includes('') - const isSymbolic = ALT?.some(f => f.includes('<')) - - return { - refName: CHROM, - start, - end: isSymbolic && INFO.END && !isTRA ? +INFO.END[0] : start + REF.length, - description, - type, - name: ID?.join(','), - aliases: ID && ID.length > 1 ? ID.slice(1) : undefined, - } - } - toJSON(): any { + const { SAMPLES, GENOTYPES, ...rest } = this.variant return { uniqueId: this._id, - ...this.variant, + ...rest, ...this.data, - samples: this.variant.SAMPLES, + samples: this.variant.SAMPLES(), } } } diff --git a/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.test.ts b/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.test.ts index 3d80b54ba9..45d867d1c9 100644 --- a/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.test.ts +++ b/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.test.ts @@ -57,7 +57,9 @@ test('adapter can fetch variants from volvox.vcf.gz', async () => { const featArray = await firstValueFrom(feat.pipe(toArray())) const csiFeaturesArray = await firstValueFrom(csiFeatures.pipe(toArray())) expect(featArray.slice(0, 5)).toMatchSnapshot() - expect(csiFeaturesArray.slice(0, 5)).toEqual(featArray.slice(0, 5)) + expect(JSON.stringify(csiFeaturesArray.slice(0, 5))).toEqual( + JSON.stringify(featArray.slice(0, 5)), + ) const featNonExist = adapter.getFeatures({ refName: 'ctgC', diff --git a/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.ts b/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.ts index 6b16a4f01f..17e57e4d77 100644 --- a/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.ts +++ b/plugins/variants/src/VcfTabixAdapter/VcfTabixAdapter.ts @@ -84,5 +84,12 @@ export default class VcfTabixAdapter extends BaseFeatureDataAdapter { }, opts.stopToken) } + async getSources() { + const { parser } = await this.configure() + return parser.samples.map(name => ({ + name, + })) + } + public freeResources(/* { region } */): void {} } diff --git a/plugins/variants/src/VcfTabixAdapter/__snapshots__/VcfTabixAdapter.test.ts.snap b/plugins/variants/src/VcfTabixAdapter/__snapshots__/VcfTabixAdapter.test.ts.snap index 148ae8fe40..f98719ed26 100644 --- a/plugins/variants/src/VcfTabixAdapter/__snapshots__/VcfTabixAdapter.test.ts.snap +++ b/plugins/variants/src/VcfTabixAdapter/__snapshots__/VcfTabixAdapter.test.ts.snap @@ -13,8 +13,8 @@ exports[`adapter can fetch variants from volvox.vcf.gz 2`] = ` "C", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, @@ -73,8 +73,8 @@ exports[`adapter can fetch variants from volvox.vcf.gz 2`] = ` "C", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, @@ -139,8 +139,8 @@ exports[`adapter can fetch variants from volvox.vcf.gz 2`] = ` "G", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 1, @@ -205,8 +205,8 @@ exports[`adapter can fetch variants from volvox.vcf.gz 2`] = ` "A", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, @@ -265,8 +265,8 @@ exports[`adapter can fetch variants from volvox.vcf.gz 2`] = ` "ct", ], "CHROM": "ctgA", - "FILTER": null, - "ID": null, + "FILTER": undefined, + "ID": undefined, "INFO": { "AC1": [ 2, diff --git a/plugins/variants/src/__snapshots__/index.test.ts.snap b/plugins/variants/src/__snapshots__/index.test.ts.snap deleted file mode 100644 index 34004264d3..0000000000 --- a/plugins/variants/src/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`plugin in a stock JBrowse 1`] = `{}`; - -exports[`plugin in a stock JBrowse 2`] = ` -{ - "displays": [ - { - "displayId": "trackId0-LinearVariantDisplay", - "type": "LinearVariantDisplay", - }, - { - "displayId": "trackId0-ChordVariantDisplay", - "type": "ChordVariantDisplay", - }, - ], - "trackId": "trackId0", - "type": "VariantTrack", -} -`; diff --git a/plugins/variants/src/configSchema.ts b/plugins/variants/src/configSchema.ts new file mode 100644 index 0000000000..d804342908 --- /dev/null +++ b/plugins/variants/src/configSchema.ts @@ -0,0 +1,70 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' +import { types } from 'mobx-state-tree' + +/** + * #config WiggleRenderer + * this is the "base wiggle renderer config schema" + */ +function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars + +const WiggleRenderer = ConfigurationSchema( + 'WiggleRenderer', + { + /** + * #slot + */ + color: { + type: 'color', + description: 'the color of track, overrides posColor and negColor', + defaultValue: '#f0f', + }, + /** + * #slot + */ + posColor: { + type: 'color', + description: 'the color to use when the score is positive', + defaultValue: 'blue', + }, + /** + * #slot + */ + negColor: { + type: 'color', + description: 'the color to use when the score is negative', + defaultValue: 'red', + }, + /** + * #slot + */ + clipColor: { + type: 'color', + description: 'the color of the clipping marker', + defaultValue: 'red', + }, + /** + * #slot + */ + bicolorPivot: { + type: 'stringEnum', + model: types.enumeration('Scale type', [ + 'numeric', + 'mean', + 'z_score', + 'none', + ]), + description: 'type of bicolor pivot', + defaultValue: 'numeric', + }, + /** + * #slot + */ + bicolorPivotValue: { + type: 'number', + defaultValue: 0, + description: 'value to use for bicolor pivot', + }, + }, + { explicitlyTyped: true }, +) +export default WiggleRenderer diff --git a/plugins/variants/src/getMultiVariantFeaturesAutorun.ts b/plugins/variants/src/getMultiVariantFeaturesAutorun.ts new file mode 100644 index 0000000000..54644152f9 --- /dev/null +++ b/plugins/variants/src/getMultiVariantFeaturesAutorun.ts @@ -0,0 +1,72 @@ +// jbrowse +import { + SimpleFeature, + getContainingView, + getSession, +} from '@jbrowse/core/util' +import { isAbortException } from '@jbrowse/core/util/aborting' +import { getRpcSessionId } from '@jbrowse/core/util/tracks' +import { autorun } from 'mobx' +import { addDisposer, isAlive } from 'mobx-state-tree' + +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { Feature, SimpleFeatureSerialized } from '@jbrowse/core/util' +import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' + +export interface Source { + name: string + color?: string + group?: string + [key: string]: string | undefined +} + +export function getMultiVariantFeaturesAutorun(self: { + configuration: AnyConfigurationModel + adapterConfig: AnyConfigurationModel + sources?: Source[] + mafFilter: number + adapterProps: () => Record + setError: (error: unknown) => void + setFeatures: (f: Feature[]) => void + setMessage: (str: string) => void +}) { + addDisposer( + self, + autorun( + async () => { + try { + const view = getContainingView(self) as LinearGenomeViewModel + if (!view.initialized) { + return + } + const { rpcManager } = getSession(self) + const { sources, mafFilter, adapterConfig } = self + if (!sources) { + return + } + const sessionId = getRpcSessionId(self) + const features = (await rpcManager.call( + sessionId, + 'MultiVariantGetSimplifiedFeatures', + { + regions: view.dynamicBlocks.contentBlocks, + sources, + mafFilter, + sessionId, + adapterConfig, + }, + )) as SimpleFeatureSerialized[] + if (isAlive(self)) { + self.setFeatures(features.map(f => new SimpleFeature(f))) + } + } catch (e) { + if (!isAbortException(e) && isAlive(self)) { + console.error(e) + getSession(self).notifyError(`${e}`, e) + } + } + }, + { delay: 1000 }, + ), + ) +} diff --git a/plugins/variants/src/getMultiVariantSourcesAutorun.ts b/plugins/variants/src/getMultiVariantSourcesAutorun.ts new file mode 100644 index 0000000000..2383c1d142 --- /dev/null +++ b/plugins/variants/src/getMultiVariantSourcesAutorun.ts @@ -0,0 +1,62 @@ +import { getContainingView, getSession } from '@jbrowse/core/util' +import { isAbortException } from '@jbrowse/core/util/aborting' +import { createStopToken } from '@jbrowse/core/util/stopToken' +import { getRpcSessionId } from '@jbrowse/core/util/tracks' +import { autorun } from 'mobx' +import { addDisposer, isAlive } from 'mobx-state-tree' + +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' + +export interface Source { + name: string + color?: string + group?: string + [key: string]: string | undefined +} + +export function getMultiVariantSourcesAutorun(self: { + configuration: AnyConfigurationModel + adapterConfig: AnyConfigurationModel + adapterProps: () => Record + setSourcesLoading: (aborter: string) => void + setError: (error: unknown) => void + setMessage: (str: string) => void + setSources: (sources: Source[]) => void +}) { + addDisposer( + self, + autorun( + async () => { + try { + const view = getContainingView(self) as LinearGenomeViewModel + if (!view.initialized) { + return + } + const { rpcManager } = getSession(self) + const { adapterConfig } = self + const token = createStopToken() + self.setSourcesLoading(token) + const sessionId = getRpcSessionId(self) + const sources = (await rpcManager.call( + sessionId, + 'MultiVariantGetSources', + { + sessionId, + adapterConfig, + }, + )) as Source[] + if (isAlive(self)) { + self.setSources(sources) + } + } catch (e) { + if (!isAbortException(e) && isAlive(self)) { + console.error(e) + getSession(self).notifyError(`${e}`, e) + } + } + }, + { delay: 1000 }, + ), + ) +} diff --git a/plugins/variants/src/index.test.ts b/plugins/variants/src/index.test.ts deleted file mode 100644 index fb2687cb90..0000000000 --- a/plugins/variants/src/index.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import PluginManager from '@jbrowse/core/PluginManager' -import SVG from '@jbrowse/plugin-svg' -import { getSnapshot } from 'mobx-state-tree' - -import ThisPlugin from '.' - -test('plugin in a stock JBrowse', () => { - const pluginManager = new PluginManager([new ThisPlugin(), new SVG()]) - pluginManager.createPluggableElements() - pluginManager.configure() - expect(() => pluginManager.addPlugin(new ThisPlugin())).toThrow( - /JBrowse already configured, cannot add plugins/, - ) - - const VcfTabixAdapter = pluginManager.getAdapterType('VcfTabixAdapter')! - const config = VcfTabixAdapter.configSchema.create({ - type: 'VcfTabixAdapter', - }) - expect(getSnapshot(config)).toMatchSnapshot() - - const VariantTrack = pluginManager.getTrackType('VariantTrack')! - const config2 = VariantTrack.configSchema.create({ - type: 'VariantTrack', - trackId: 'trackId0', - adapter: { type: 'VcfTabixAdapter' }, - }) - expect(getSnapshot(config2)).toMatchSnapshot() -}) diff --git a/plugins/variants/src/index.ts b/plugins/variants/src/index.ts index bfaa510f60..f3fbf3c1e3 100644 --- a/plugins/variants/src/index.ts +++ b/plugins/variants/src/index.ts @@ -2,8 +2,15 @@ import Plugin from '@jbrowse/core/Plugin' import ChordVariantDisplayF from './ChordVariantDisplay' import LinearVariantDisplayF from './LinearVariantDisplay' +import MultiLinearVariantDisplayF from './MultiLinearVariantDisplay' +import LinearVariantMatrixDisplayF from './MultiLinearVariantMatrixDisplay' +import LinearVariantMatrixRendererF from './MultiLinearVariantMatrixRenderer' +import MultiVariantRendererF from './MultiLinearVariantRenderer' import StructuralVariantChordRendererF from './StructuralVariantChordRenderer' import VariantFeatureWidgetF from './VariantFeatureWidget' +import { MultiVariantGetGenotypeMatrix } from './VariantRPC/MultiVariantGetGenotypeMatrix' +import { MultiVariantGetSimplifiedFeatures } from './VariantRPC/MultiVariantGetSimplifiedFeatures' +import { MultiVariantGetSources } from './VariantRPC/MultiVariantGetSources' import VariantTrackF from './VariantTrack' import VcfAdapterF from './VcfAdapter' import VcfTabixAdapterF from './VcfTabixAdapter' @@ -21,8 +28,20 @@ export default class VariantsPlugin extends Plugin { VariantTrackF(pluginManager) ExtensionPointsF(pluginManager) LinearVariantDisplayF(pluginManager) + LinearVariantMatrixDisplayF(pluginManager) + MultiLinearVariantDisplayF(pluginManager) + MultiVariantRendererF(pluginManager) + LinearVariantMatrixRendererF(pluginManager) StructuralVariantChordRendererF(pluginManager) ChordVariantDisplayF(pluginManager) + + pluginManager.addRpcMethod(() => new MultiVariantGetSources(pluginManager)) + pluginManager.addRpcMethod( + () => new MultiVariantGetGenotypeMatrix(pluginManager), + ) + pluginManager.addRpcMethod( + () => new MultiVariantGetSimplifiedFeatures(pluginManager), + ) } } diff --git a/plugins/variants/src/shared/BulkEditPanel.tsx b/plugins/variants/src/shared/BulkEditPanel.tsx new file mode 100644 index 0000000000..0f0e3095f7 --- /dev/null +++ b/plugins/variants/src/shared/BulkEditPanel.tsx @@ -0,0 +1,129 @@ +import React, { useState } from 'react' + +import { ErrorMessage } from '@jbrowse/core/ui' +import { Button, TextField, Typography } from '@mui/material' +import { makeStyles } from 'tss-react/mui' + +import { type Source } from '../util' + +const useStyles = makeStyles()({ + textAreaFont: { + fontFamily: 'Courier New', + }, +}) + +export default function BulkEditPanel({ + setCurrLayout, + currLayout, +}: { + currLayout: Source[] + setCurrLayout: (arg: Source[]) => void +}) { + const { classes } = useStyles() + const [val, setVal] = useState('') + const [error, setError] = useState() + return ( +
+ + Paste CSV or TSV. If a header column is present. First line is a header. + If a column called "name" is present, it uses that to connect to IDs in + the table, otherwise it uses the first column no. + + { + setVal(event.target.value) + }} + slotProps={{ + input: { + classes: { + input: classes.textAreaFont, + }, + }, + }} + /> + + + {error ? : null} +
+ ) +} diff --git a/plugins/variants/src/shared/ClusterDialog.tsx b/plugins/variants/src/shared/ClusterDialog.tsx new file mode 100644 index 0000000000..5ff01b77a5 --- /dev/null +++ b/plugins/variants/src/shared/ClusterDialog.tsx @@ -0,0 +1,206 @@ +import React, { useEffect, useState } from 'react' + +import { Dialog, ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui' +import { + getContainingView, + getSession, + isAbortException, +} from '@jbrowse/core/util' +import { getRpcSessionId } from '@jbrowse/core/util/tracks' +import { + Button, + DialogActions, + DialogContent, + TextField, + Typography, +} from '@mui/material' +import copy from 'copy-to-clipboard' +import { saveAs } from 'file-saver' +import { isAlive } from 'mobx-state-tree' +import { makeStyles } from 'tss-react/mui' + +import type { Source } from '../util' +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' + +const useStyles = makeStyles()(theme => ({ + textAreaFont: { + fontFamily: 'Courier New', + }, + mgap: { + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(4), + }, +})) + +export default function HierarchicalCluster({ + model, + handleClose, +}: { + model: { + sources?: Source[] + mafFilter?: number + adapterConfig: AnyConfigurationModel + setLayout: (arg: Source[]) => void + } + handleClose: () => void +}) { + const { classes } = useStyles() + const [results, setResults] = useState() + const [error, setError] = useState() + const [paste, setPaste] = useState('') + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ;(async () => { + try { + setError(undefined) + const view = getContainingView(model) as LinearGenomeViewModel + if (!view.initialized) { + return + } + const { rpcManager } = getSession(model) + const { sources, mafFilter, adapterConfig } = model + const sessionId = getRpcSessionId(model) + const ret = (await rpcManager.call( + sessionId, + 'MultiVariantGetGenotypeMatrix', + { + regions: view.dynamicBlocks.contentBlocks, + sources, + mafFilter, + sessionId, + adapterConfig, + }, + )) as Record + + const entries = Object.values(ret) + const keys = Object.keys(ret) + const text = `try(library(fastcluster), silent=TRUE) +inputMatrix<-matrix(c(${entries.map(val => val.genotypes.join(',')).join(',\n')} +),nrow=${entries.length},byrow=TRUE) +rownames(inputMatrix)<-c(${keys.map(key => `'${key}'`).join(',')}) +resultClusters<-hclust(dist(inputMatrix), method='single') +cat(resultClusters$order,sep='\\n')` + setResults(text) + } catch (e) { + if (!isAbortException(e) && isAlive(model)) { + console.error(e) + setError(e) + } + } + })() + }, [model]) + + return ( + + +
+ + This page will produce an R script that will perform hierarchical + clustering on the visible genotype data using `hclust`. + + + You can then paste the results in this form to specify the row + ordering. + + {results ? ( +
+
+ Step 1:{' '} + {' '} + or{' '} + +
+ { + setPaste(event.target.value) + }} + slotProps={{ + input: { + classes: { + input: classes.textAreaFont, + }, + }, + }} + /> +
+
+
+ ) : ( + + )} + {error ? : null} +
+
+ + + + +
+ ) +} diff --git a/plugins/variants/src/shared/ColorLegend.tsx b/plugins/variants/src/shared/ColorLegend.tsx new file mode 100644 index 0000000000..84b81f6520 --- /dev/null +++ b/plugins/variants/src/shared/ColorLegend.tsx @@ -0,0 +1,65 @@ +import React from 'react' + +import { clamp } from '@jbrowse/core/util' +import { observer } from 'mobx-react' + +import RectBg from './RectBg' + +import type { Source } from '../util' + +const ColorLegend = observer(function ({ + model, + labelWidth, +}: { + model: { + canDisplayLabels: boolean + rowHeight: number + sources?: Source[] + } + labelWidth: number +}) { + const { canDisplayLabels, rowHeight, sources } = model + const svgFontSize = clamp(rowHeight, 8, 12) + const colorBoxWidth = 15 + const legendWidth = labelWidth + colorBoxWidth + 5 + + return sources ? ( + <> + {canDisplayLabels ? ( + + ) : null} + {sources.map((source, idx) => { + const { color, name } = source + return ( + + {color ? ( + + ) : null} + {canDisplayLabels ? ( + + {name} + + ) : null} + + ) + })} + + ) : null +}) + +export default ColorLegend diff --git a/plugins/variants/src/shared/DraggableDialog.tsx b/plugins/variants/src/shared/DraggableDialog.tsx new file mode 100644 index 0000000000..2958fee09e --- /dev/null +++ b/plugins/variants/src/shared/DraggableDialog.tsx @@ -0,0 +1,73 @@ +import React, { useRef } from 'react' + +import CloseIcon from '@mui/icons-material/Close' +import { + Dialog, + DialogTitle, + Divider, + IconButton, + Paper, + ScopedCssBaseline, +} from '@mui/material' +import { observer } from 'mobx-react' +import Draggable from 'react-draggable' +import { makeStyles } from 'tss-react/mui' + +import type { DialogProps, PaperProps } from '@mui/material' + +// icons + +const useStyles = makeStyles()(theme => ({ + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, +})) + +function PaperComponent(props: PaperProps) { + const ref = useRef(null) + return ( + arg.target?.className?.includes('MuiDialogTitle')} + > + + + ) +} + +const DraggableDialog = observer(function DraggableDialog( + props: DialogProps & { title: string }, +) { + const { classes } = useStyles() + const { title, children, onClose } = props + + return ( + + + + {title} + {onClose ? ( + { + // @ts-expect-error + onClose() + }} + > + + + ) : null} + + + {children} + + + ) +}) + +export default DraggableDialog diff --git a/plugins/variants/src/shared/LegendBar.tsx b/plugins/variants/src/shared/LegendBar.tsx new file mode 100644 index 0000000000..19618ad7ee --- /dev/null +++ b/plugins/variants/src/shared/LegendBar.tsx @@ -0,0 +1,82 @@ +import React from 'react' + +import { clamp, getContainingView, measureText } from '@jbrowse/core/util' +import { observer } from 'mobx-react' + +import ColorLegend from './ColorLegend' + +import type { Source } from '../util' + +interface ReducedModel { + scrollTop: number + totalHeight: number + rowHeight: number + lineZoneHeight?: number + sources?: Source[] + canDisplayLabels: boolean + height: number + id: string +} + +const Wrapper = observer(function ({ + children, + model, + exportSVG, +}: { + model: ReducedModel + children: React.ReactNode + exportSVG?: boolean +}) { + const { id, scrollTop, height } = model + const clipid = `legend-${id}` + return exportSVG ? ( + <> + + + + + + + {children} + + + ) : ( + + {children} + + ) +}) + +export const LegendBar = observer(function (props: { + model: ReducedModel + orientation?: string + exportSVG?: boolean +}) { + const { model } = props + const { canDisplayLabels, rowHeight, sources } = model + const svgFontSize = clamp(rowHeight, 8, 12) + return sources ? ( + + measureText(s.name, svgFontSize) + 10) + .map(width => (canDisplayLabels ? width : 20)), + )} + /> + + ) : null +}) + +export default LegendBar diff --git a/plugins/variants/src/shared/MAFFilterDialog.tsx b/plugins/variants/src/shared/MAFFilterDialog.tsx new file mode 100644 index 0000000000..70dae683e0 --- /dev/null +++ b/plugins/variants/src/shared/MAFFilterDialog.tsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react' + +import { + Button, + Dialog, + DialogActions, + DialogContent, + TextField, + Typography, +} from '@mui/material' +import { observer } from 'mobx-react' +import { makeStyles } from 'tss-react/mui' + +const useStyles = makeStyles()({ + root: { + width: 500, + }, +}) + +const MAFFilterDialog = observer(function ({ + model, + handleClose, +}: { + model: { + mafFilter?: number + setMafFilter: (arg: number) => void + } + handleClose: () => void +}) { + const { mafFilter = '' } = model + const { classes } = useStyles() + const [maf, setMaf] = useState(`${mafFilter}`) + + return ( + + + + Set minor allele frequency cutoff track. This will filter out rare + variants that might not contribute to meaningful large scale patterns + + { + setMaf(event.target.value) + }} + /> + + + + + + + ) +}) + +export default MAFFilterDialog diff --git a/plugins/variants/src/shared/RectBg.tsx b/plugins/variants/src/shared/RectBg.tsx new file mode 100644 index 0000000000..911aeed4ee --- /dev/null +++ b/plugins/variants/src/shared/RectBg.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +import { getFillProps } from '@jbrowse/core/util' + +const RectBg = (props: { + x: number + y: number + width: number + height: number + color?: string +}) => { + const { color = 'rgb(255,255,255)' } = props + return +} + +export default RectBg diff --git a/plugins/variants/src/shared/RowPalettizer.tsx b/plugins/variants/src/shared/RowPalettizer.tsx new file mode 100644 index 0000000000..ca831d5d1d --- /dev/null +++ b/plugins/variants/src/shared/RowPalettizer.tsx @@ -0,0 +1,53 @@ +import React from 'react' + +import { set1 } from '@jbrowse/core/ui/colors' +import { Button } from '@mui/material' + +import { type Source, randomColor } from '../util' + +export default function RowPalettizer({ + setCurrLayout, + currLayout, +}: { + currLayout: Source[] + setCurrLayout: (arg: Source[]) => void +}) { + return ( +
+ {Object.keys(currLayout[0] ?? []) + .filter(f => f !== 'name' && f !== 'color') + .map(r => { + return ( + + ) + })} +
+ ) +} diff --git a/plugins/variants/src/shared/SetColorDialog.tsx b/plugins/variants/src/shared/SetColorDialog.tsx new file mode 100644 index 0000000000..19e325242c --- /dev/null +++ b/plugins/variants/src/shared/SetColorDialog.tsx @@ -0,0 +1,146 @@ +import React, { useState } from 'react' + +import { useLocalStorage } from '@jbrowse/core/util' +import { Button, DialogActions, DialogContent } from '@mui/material' +import { makeStyles } from 'tss-react/mui' + +import DraggableDialog from './DraggableDialog' +import SourcesGrid from './SourcesGrid' +import { type Source } from '../util' +import BulkEditPanel from './BulkEditPanel' +import RowPalettizer from './RowPalettizer' + +const useStyles = makeStyles()({ + content: { + minWidth: 800, + }, + fr: { + float: 'right', + }, + textAreaFont: { + fontFamily: 'Courier New', + }, +}) + +interface ReducedModel { + sources?: Source[] + setLayout: (s: Source[]) => void + clearLayout: () => void +} + +export default function SetColorDialog({ + model, + handleClose, +}: { + model: ReducedModel + handleClose: () => void +}) { + const { classes } = useStyles() + const { sources } = model + const [showBulkEditor, setShowBulkEditor] = useState(false) + const [currLayout, setCurrLayout] = useState(sources || []) + const [showTips, setShowTips] = useLocalStorage( + 'multivariant-showTips', + false, + ) + return ( + + +
+ + +
+
+ {showTips ? : null} + + {showBulkEditor ? ( + + ) : null} + + + +
+ + + + + +
+ ) +} + +function HelpfulTips() { + return ( + <> + Helpful tips +
    +
  • You can select rows in the table with the checkboxes
  • +
  • Multi-select is enabled with shift-click and control-click
  • +
  • The "Move selected items up/down" can re-arrange subtracks
  • +
  • Sorting the data grid itself can also re-arrange subtracks
  • +
  • Changes are applied when you hit Submit
  • +
  • You can click and drag the dialog box to move it on the screen
  • +
  • + Columns in the table can be hidden using a vertical '...' menu on the + right side of each column +
  • +
+ + ) +} diff --git a/plugins/variants/src/shared/SetMinMaxDialog.tsx b/plugins/variants/src/shared/SetMinMaxDialog.tsx new file mode 100644 index 0000000000..be2afad6be --- /dev/null +++ b/plugins/variants/src/shared/SetMinMaxDialog.tsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react' + +import { Dialog } from '@jbrowse/core/ui' +import { + Button, + DialogActions, + DialogContent, + TextField, + Typography, +} from '@mui/material' + +export default function SetMinMaxDialog(props: { + model: { + minScore: number + maxScore: number + scaleType: string + setMinScore: (arg?: number) => void + setMaxScore: (arg?: number) => void + } + handleClose: () => void +}) { + const { model, handleClose } = props + const { minScore, maxScore, scaleType } = model + + const [min, setMin] = useState( + `${minScore !== Number.MIN_VALUE ? minScore : ''}`, + ) + const [max, setMax] = useState( + `${maxScore !== Number.MAX_VALUE ? maxScore : ''}`, + ) + + const ok = + min !== '' && max !== '' && !Number.isNaN(+min) && !Number.isNaN(+max) + ? +max > +min + : true + + const logOk = + scaleType === 'log' && min !== '' && !Number.isNaN(+min) ? +min > 0 : true + + return ( + + + Enter min/max score: + {!ok ? ( + + Max is greater than or equal to min + + ) : null} + + {!logOk ? ( + + Min score should be greater than 0 for log scale + + ) : null} + + { + setMin(event.target.value) + }} + placeholder="Enter min score" + /> + { + setMax(event.target.value) + }} + placeholder="Enter max score" + /> + + + + + + ) +} diff --git a/plugins/variants/src/shared/SharedVariantConfigSchema.ts b/plugins/variants/src/shared/SharedVariantConfigSchema.ts new file mode 100644 index 0000000000..9b2c366b4a --- /dev/null +++ b/plugins/variants/src/shared/SharedVariantConfigSchema.ts @@ -0,0 +1,92 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' +import { baseLinearDisplayConfigSchema } from '@jbrowse/plugin-linear-genome-view' +import { types } from 'mobx-state-tree' + +/** + * #config SharedVariantDisplay + * extends + * - [BaseLinearDisplay](../baselineardisplay) + */ +export default function sharedVariantConfigFactory() { + return ConfigurationSchema( + 'SharedVariantDisplay', + { + /** + * #slot + */ + autoscale: { + type: 'stringEnum', + defaultValue: 'local', + model: types.enumeration('Autoscale type', [ + 'global', + 'local', + 'globalsd', + 'localsd', + 'zscore', + ]), + description: + 'global/local using their min/max values or w/ standard deviations (globalsd/localsd)', + }, + + /** + * #slot + */ + minimalTicks: { + type: 'boolean', + defaultValue: false, + description: 'use the minimal amount of ticks', + }, + + /** + * #slot + */ + minScore: { + type: 'number', + defaultValue: Number.MIN_VALUE, + description: 'minimum value for the y-scale', + }, + /** + * #slot + */ + maxScore: { + type: 'number', + description: 'maximum value for the y-scale', + defaultValue: Number.MAX_VALUE, + }, + /** + * #slot + */ + numStdDev: { + type: 'number', + description: + 'number of standard deviations to use for autoscale types globalsd or localsd', + defaultValue: 3, + }, + /** + * #slot + */ + scaleType: { + type: 'stringEnum', + model: types.enumeration('Scale type', ['linear', 'log']), // todo zscale + description: 'The type of scale to use', + defaultValue: 'linear', + }, + + /** + * #slot + */ + inverted: { + type: 'boolean', + description: 'draw upside down', + defaultValue: false, + }, + }, + { + /** + * #baseConfiguration + */ + baseConfiguration: baseLinearDisplayConfigSchema, + explicitlyTyped: true, + }, + ) +} diff --git a/plugins/variants/src/shared/SharedVariantMixin.ts b/plugins/variants/src/shared/SharedVariantMixin.ts new file mode 100644 index 0000000000..1eec9ca179 --- /dev/null +++ b/plugins/variants/src/shared/SharedVariantMixin.ts @@ -0,0 +1,110 @@ +import { ConfigurationReference, getConf } from '@jbrowse/core/configuration' +import { getEnv, getSession, isSelectionContainer } from '@jbrowse/core/util' +import { BaseLinearDisplay } from '@jbrowse/plugin-linear-genome-view' +import { types } from 'mobx-state-tree' + +import type { AnyConfigurationSchemaType } from '@jbrowse/core/configuration' +import type { Feature } from '@jbrowse/core/util' + +/** + * #stateModel SharedVariantMixin + */ +export default function SharedVariantMixin( + configSchema: AnyConfigurationSchemaType, +) { + return types + .compose( + BaseLinearDisplay, + types.model({ + /** + * #property + */ + selectedRendering: types.optional(types.string, ''), + /** + * #property + */ + summaryScoreMode: types.maybe(types.string), + /** + * #property + */ + rendererTypeNameState: types.maybe(types.string), + /** + * #property + */ + configuration: ConfigurationReference(configSchema), + }), + ) + .volatile(() => ({ + /** + * #volatile + */ + message: undefined as undefined | string, + })) + .actions(self => ({ + /** + * #action + * this overrides the BaseLinearDisplayModel to avoid popping up a + * feature detail display, but still sets the feature selection on the + * model so listeners can detect a click + */ + selectFeature(feature: Feature) { + const session = getSession(self) + if (isSelectionContainer(session)) { + session.setSelection(feature) + } + }, + + /** + * #action + */ + setRendererType(val: string) { + self.rendererTypeNameState = val + }, + })) + + .views(self => ({ + /** + * #getter + */ + get adapterTypeName() { + return self.adapterConfig.type + }, + + /** + * #getter + */ + get rendererTypeNameSimple() { + return self.rendererTypeNameState ?? getConf(self, 'defaultRendering') + }, + + /** + * #getter + * subclasses can define these, as snpcoverage track does + */ + get filters() { + return undefined + }, + })) + .views(self => ({ + /** + * #getter + */ + get adapterCapabilities() { + const type = self.adapterTypeName + const { pluginManager } = getEnv(self) + return pluginManager.getAdapterType(type)!.adapterCapabilities + }, + })) + .actions(self => { + const { reload: superReload } = self + return { + /** + * #action + */ + async reload() { + self.setError() + superReload() + }, + } + }) +} diff --git a/plugins/variants/src/shared/SourcesGrid.tsx b/plugins/variants/src/shared/SourcesGrid.tsx new file mode 100644 index 0000000000..f232173a2e --- /dev/null +++ b/plugins/variants/src/shared/SourcesGrid.tsx @@ -0,0 +1,225 @@ +import React, { useState } from 'react' + +import { SanitizedHTML } from '@jbrowse/core/ui' +import ColorPicker, { ColorPopover } from '@jbrowse/core/ui/ColorPicker' +import { getStr, measureGridWidth } from '@jbrowse/core/util' +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp' +import KeyboardDoubleArrowDownIcon from '@mui/icons-material/KeyboardDoubleArrowDown' +import KeyboardDoubleArrowUpIcon from '@mui/icons-material/KeyboardDoubleArrowUp' +import { Button } from '@mui/material' +import { DataGrid } from '@mui/x-data-grid' +import { makeStyles } from 'tss-react/mui' + +// locals +import { moveDown, moveUp } from './util' + +import type { Source } from '../util' +import type { GridColDef } from '@mui/x-data-grid' + +// icons + +const useStyles = makeStyles()({ + cell: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, +}) + +interface SortField { + idx: number + field: string | null +} + +function SourcesGrid({ + rows, + onChange, + showTips, +}: { + rows: Source[] + onChange: (arg: Source[]) => void + showTips: boolean +}) { + const { classes } = useStyles() + const [selected, setSelected] = useState([] as string[]) + const { name: _name, color: _color, baseUri: _baseUri, ...rest } = rows[0]! + const [currSort, setCurrSort] = useState({ + idx: 0, + field: null, + }) + + return ( +
+ +
+ row.name} + checkboxSelection + disableRowSelectionOnClick + onRowSelectionModelChange={arg => { + setSelected(arg as string[]) + }} + rows={rows} + rowHeight={25} + columnHeaderHeight={33} + columns={[ + { + field: 'color', + headerName: 'Color', + renderCell: params => { + const { value, id } = params + return ( + { + const elt = rows.find(f => f.name === id) + if (elt) { + elt.color = c + } + onChange([...rows]) + }} + /> + ) + }, + }, + { + field: 'name', + headerName: 'Name', + width: measureGridWidth(rows.map(r => r.name)), + }, + ...Object.keys(rest).map( + val => + ({ + field: val, + renderCell: ({ value }) => ( +
+ +
+ ), + width: measureGridWidth( + rows.map(r => `${r[val as keyof Source]}`), + ), + }) satisfies GridColDef<(typeof rows)[0]>, + ), + ]} + sortModel={ + [ + /* we control the sort as a controlled component using + * onSortModelChange */ + ] + } + onSortModelChange={args => { + const sort = args[0] + // this idx%2 flip flops the sorting order, we could inspect args + // for sort direction asc or desc but this is just a simplified + // thing since we are controlling sort instead of the default data + // grid sort anyways + const idx = (currSort.idx + 1) % 2 + const field = sort!.field || currSort.field + setCurrSort({ idx, field }) + onChange( + field + ? [...rows].sort((a, b) => { + const aa = getStr(a[field as keyof Source]) + const bb = getStr(b[field as keyof Source]) + return idx === 1 + ? aa.localeCompare(bb) + : bb.localeCompare(aa) + }) + : rows, + ) + }} + /> +
+
+ ) +} + +function GridHeader({ + selected, + onChange, + rows, + showTips, +}: { + onChange: (arg: Source[]) => void + rows: Source[] + selected: string[] + showTips: boolean +}) { + const [anchorEl, setAnchorEl] = useState(null) + const [widgetColor, setWidgetColor] = useState('blue') + return ( + <> + + + + + + { + setWidgetColor(c) + selected.forEach(id => { + const elt = rows.find(f => f.name === id) + if (elt) { + elt.color = c + } + }) + + onChange([...rows]) + }} + onClose={() => { + setAnchorEl(null) + }} + /> + + ) +} + +export default SourcesGrid diff --git a/plugins/variants/src/shared/Tooltip.tsx b/plugins/variants/src/shared/Tooltip.tsx new file mode 100644 index 0000000000..e1ff89aa49 --- /dev/null +++ b/plugins/variants/src/shared/Tooltip.tsx @@ -0,0 +1,75 @@ +import React from 'react' + +import { observer } from 'mobx-react' + +// locals +import Tooltip from '../Tooltip' + +import type { TooltipContentsComponent } from '../Tooltip' +import type { Source } from '../util' +import type { Feature } from '@jbrowse/core/util' + +const en = (n: number) => n.toLocaleString('en-US') + +interface Props { + model: { sources: Source[] } + feature: Feature +} +const TooltipContents = React.forwardRef( + function TooltipContents2({ model, feature }, ref) { + const start = feature.get('start') + const end = feature.get('end') + const refName = feature.get('refName') + const coord = start === end ? en(start) : `${en(start)}..${en(end)}` + const sources = feature.get('sources') as + | Record + | undefined + const source = feature.get('source') + const obj = Object.fromEntries(model.sources.map(ent => [ent.name, ent])) + + return ( +
+ {[refName, coord].filter(f => !!f).join(':')} +
+ {sources ? ( + + + + + + + + + + {Object.entries(sources).map(([source, data]) => ( + + + + + + ))} + +
colorsourcescore
{source}{data.score}
+ ) : ( + {source} + )} +
+ ) + }, +) + +type Coord = [number, number] + +const VariantTooltip = observer( + (props: { + model: { featureUnderMouse: Feature; sources: Source[]; rowHeight: number } + height: number + offsetMouseCoord: Coord + clientMouseCoord: Coord + clientRect?: DOMRect + TooltipContents?: TooltipContentsComponent + }) => { + return + }, +) +export default VariantTooltip diff --git a/plugins/variants/src/shared/util.test.ts b/plugins/variants/src/shared/util.test.ts new file mode 100644 index 0000000000..131102842f --- /dev/null +++ b/plugins/variants/src/shared/util.test.ts @@ -0,0 +1,69 @@ +import { moveDown, moveUp } from './util' + +test('moves elements up once', () => { + let elts = [{ name: 'k1' }, { name: 'k2' }, { name: 'k3' }, { name: 'k4' }] + elts = moveUp(elts, ['k2', 'k3']) + expect(elts).toEqual([ + { name: 'k2' }, + { name: 'k3' }, + { name: 'k1' }, + { name: 'k4' }, + ]) +}) + +test('moves elements up twice', () => { + let elts = [{ name: 'k1' }, { name: 'k2' }, { name: 'k3' }, { name: 'k4' }] + elts = moveUp(elts, ['k2', 'k3']) + elts = moveUp(elts, ['k2', 'k3']) + expect(elts).toEqual([ + { name: 'k2' }, + { name: 'k3' }, + { name: 'k1' }, + { name: 'k4' }, + ]) +}) + +test('moves elements down once', () => { + let elts = [{ name: 'k1' }, { name: 'k2' }, { name: 'k3' }, { name: 'k4' }] + elts = moveDown(elts, ['k2', 'k3']) + expect(elts).toEqual([ + { name: 'k1' }, + { name: 'k4' }, + { name: 'k2' }, + { name: 'k3' }, + ]) +}) + +test('moves elements down twice', () => { + let elts = [{ name: 'k1' }, { name: 'k2' }, { name: 'k3' }, { name: 'k4' }] + elts = moveDown(elts, ['k2', 'k3']) + elts = moveDown(elts, ['k2', 'k3']) + expect(elts).toEqual([ + { name: 'k1' }, + { name: 'k4' }, + { name: 'k2' }, + { name: 'k3' }, + ]) +}) + +test('moves elements to bottom', () => { + let elts = [{ name: 'k1' }, { name: 'k2' }, { name: 'k3' }, { name: 'k4' }] + elts = moveDown(elts, ['k1', 'k2'], 4) + expect(elts).toEqual([ + { name: 'k3' }, + { name: 'k4' }, + { name: 'k1' }, + { name: 'k2' }, + ]) +}) + +test('moves elements to top', () => { + let elts = [{ name: 'k1' }, { name: 'k2' }, { name: 'k3' }, { name: 'k4' }] + elts = moveUp(elts, ['k3', 'k4'], 4) + expect(elts).toEqual([ + { name: 'k3' }, + { name: 'k4' }, + { name: 'k1' }, + { name: 'k2' }, + ]) +}) diff --git a/plugins/variants/src/shared/util.ts b/plugins/variants/src/shared/util.ts new file mode 100644 index 0000000000..877bde6b73 --- /dev/null +++ b/plugins/variants/src/shared/util.ts @@ -0,0 +1,31 @@ +export function moveUp(arr: { name: string }[], sel: string[], by = 1) { + const idxs = sel + .map(l => arr.findIndex(v => v.name === l)) + .sort((a, b) => a - b) + let lastIdx = 0 + for (const old of idxs) { + const idx = Math.max(lastIdx, old - by) + if (idx >= lastIdx) { + arr.splice(idx, 0, arr.splice(old, 1)[0]!) + } + lastIdx = lastIdx + 1 + } + + return arr +} + +export function moveDown(arr: { name: string }[], sel: string[], by = 1) { + const idxs = sel + .map(l => arr.findIndex(v => v.name === l)) + .sort((a, b) => b - a) + let lastIdx = arr.length - 1 + for (const old of idxs) { + const idx = Math.min(lastIdx, old + by) + if (idx <= lastIdx) { + arr.splice(idx, 0, arr.splice(old, 1)[0]!) + } + lastIdx = lastIdx - 1 + } + + return arr +} diff --git a/plugins/variants/src/util.ts b/plugins/variants/src/util.ts new file mode 100644 index 0000000000..1a1bae2da5 --- /dev/null +++ b/plugins/variants/src/util.ts @@ -0,0 +1,65 @@ +export interface Source { + baseUri?: string + name: string + color?: string + group?: string + [key: string]: unknown +} + +// avoid drawing negative width features for SVG exports +export function fillRectCtx( + x: number, + y: number, + width: number, + height: number, + ctx: CanvasRenderingContext2D, + color?: string, +) { + if (width < 0) { + x += width + width = -width + } + if (height < 0) { + y += height + height = -height + } + + if (color) { + ctx.fillStyle = color + } + ctx.fillRect(x, y, width, height) +} + +export function getCol(gt: string) { + if (gt === '0|0' || gt === '0/0') { + return '#ccc' + } else if (gt === '1|0' || gt === '0|1' || gt === '0/1' || gt === '1/0') { + return 'teal' + } else if (gt === '1|1' || gt === '1/1') { + return 'blue' + } else { + return '#CBC3E3' + } +} + +export const colorPaletteDefault = [ + 'red', + 'blue', + 'green', + 'orange', + 'purple', + 'cyan', + 'pink', + 'darkblue', + 'darkred', + 'pink', +] + +export function randomColor(str: string) { + let sum = 0 + + for (let i = 0; i < str.length; i++) { + sum += str.charCodeAt(i) + } + return `hsl(${sum * 10}, 20%, 50%)` +} diff --git a/plugins/wiggle/package.json b/plugins/wiggle/package.json index a5ac53de40..2dbd3ab04c 100644 --- a/plugins/wiggle/package.json +++ b/plugins/wiggle/package.json @@ -36,7 +36,6 @@ "clean": "rimraf dist esm *.tsbuildinfo" }, "dependencies": { - "@floating-ui/react": "^0.26.3", "@gmod/bbi": "^5.0.0", "@mui/icons-material": "^6.0.0", "@mui/x-charts-vendor": "^7.12.0", diff --git a/plugins/wiggle/src/CreateMultiWiggleExtension/index.ts b/plugins/wiggle/src/CreateMultiWiggleExtension/index.ts index ff1828d26d..6e562b4c08 100644 --- a/plugins/wiggle/src/CreateMultiWiggleExtension/index.ts +++ b/plugins/wiggle/src/CreateMultiWiggleExtension/index.ts @@ -6,8 +6,44 @@ import { getSession, isSessionWithAddTracks } from '@jbrowse/core/util' import type PluginManager from '@jbrowse/core/PluginManager' import type { HierarchicalTrackSelectorModel } from '@jbrowse/plugin-data-management' +// lazies const ConfirmDialog = lazy(() => import('./ConfirmDialog')) +function makeTrack({ + model, + arg, +}: { + model: HierarchicalTrackSelectorModel + arg: { + name: string + } +}) { + const tracks = model.selection + const trackIds = tracks.map(c => readConfObject(c, 'name')) + const subadapters = tracks + .map(c => readConfObject(c, 'adapter')) + .map((c, idx) => ({ ...c, source: trackIds[idx] })) + const now = +Date.now() + const trackId = `multitrack-${now}-sessionTrack` + + const session = getSession(model) + if (isSessionWithAddTracks(session)) { + session.addTrackConf({ + type: 'MultiQuantitativeTrack', + trackId, + name: arg.name, + assemblyNames: [ + ...new Set(tracks.flatMap(c => readConfObject(c, 'assemblyNames'))), + ], + adapter: { + type: 'MultiWiggleAdapter', + subadapters, + }, + }) + model.view.showTrack(trackId) + } +} + export default function CreateMultiWiggleExtensionF(pm: PluginManager) { pm.addToExtensionPoint( 'TrackSelector-multiTrackMenuItems', @@ -21,41 +57,14 @@ export default function CreateMultiWiggleExtensionF(pm: PluginManager) { label: 'Create multi-wiggle track', onClick: (model: HierarchicalTrackSelectorModel) => { const tracks = model.selection - const trackIds = tracks.map(c => readConfObject(c, 'name')) - function makeTrack(arg: { name: string }) { - const subadapters = tracks - .map(c => readConfObject(c, 'adapter')) - .map((c, idx) => ({ ...c, source: trackIds[idx] })) - const assemblyNames = [ - ...new Set( - tracks.flatMap(c => readConfObject(c, 'assemblyNames')), - ), - ] - const now = +Date.now() - const trackId = `multitrack-${now}-sessionTrack` - const session = getSession(model) - if (isSessionWithAddTracks(session)) { - session.addTrackConf({ - type: 'MultiQuantitativeTrack', - trackId, - name: arg.name, - assemblyNames, - adapter: { - type: 'MultiWiggleAdapter', - subadapters, - }, - }) - model.view.showTrack(trackId) - } - } getSession(model).queueDialog(handleClose => [ ConfirmDialog, { tracks, onClose: (arg: boolean, arg1?: { name: string }) => { if (arg && arg1) { - makeTrack(arg1) + makeTrack({ model, arg: arg1 }) } handleClose() }, diff --git a/plugins/wiggle/src/LinearWiggleDisplay/renderSvg.tsx b/plugins/wiggle/src/LinearWiggleDisplay/renderSvg.tsx index 92bb09ce65..6589f5bcfe 100644 --- a/plugins/wiggle/src/LinearWiggleDisplay/renderSvg.tsx +++ b/plugins/wiggle/src/LinearWiggleDisplay/renderSvg.tsx @@ -21,7 +21,7 @@ export async function renderSvg( const { offsetPx } = getContainingView(self) as LinearGenomeViewModel return ( <> - {await superRenderSvg(opts)} + {await superRenderSvg(opts)} {needsScalebar && stats ? ( diff --git a/plugins/wiggle/src/MultiDensityRenderer/MultiDensityRenderer.ts b/plugins/wiggle/src/MultiDensityRenderer/MultiDensityRenderer.ts index b5fc7434e6..c63c8dea62 100644 --- a/plugins/wiggle/src/MultiDensityRenderer/MultiDensityRenderer.ts +++ b/plugins/wiggle/src/MultiDensityRenderer/MultiDensityRenderer.ts @@ -6,7 +6,7 @@ import { drawDensity } from '../drawDensity' import type { MultiRenderArgsDeserialized as MultiArgs } from '../WiggleBaseRenderer' import type { Feature } from '@jbrowse/core/util' -export default class MultiXYPlotRenderer extends WiggleBaseRenderer { +export default class MultiDensityPlotRenderer extends WiggleBaseRenderer { // @ts-expect-error async draw(ctx: CanvasRenderingContext2D, props: MultiArgs) { const { sources, features } = props diff --git a/plugins/wiggle/src/MultiLinearWiggleDisplay/components/SetColorDialog.tsx b/plugins/wiggle/src/MultiLinearWiggleDisplay/components/SetColorDialog.tsx index 9636bbfe76..7bc9222577 100644 --- a/plugins/wiggle/src/MultiLinearWiggleDisplay/components/SetColorDialog.tsx +++ b/plugins/wiggle/src/MultiLinearWiggleDisplay/components/SetColorDialog.tsx @@ -29,7 +29,7 @@ export default function SetColorDialog({ const { classes } = useStyles() const { sources } = model const [currLayout, setCurrLayout] = useState(structuredClone(sources || [])) - const [showTips, setShowTips] = useLocalStorage('multiwiggle-showTips', true) + const [showTips, setShowTips] = useLocalStorage('multiwiggle-showTips', false) return ( - {await superRenderSvg(opts)} + {await superRenderSvg(opts)} diff --git a/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts b/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts index d84b0cb29b..71dc5bef5c 100644 --- a/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts +++ b/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts @@ -81,7 +81,10 @@ export default class MultiWiggleAdapter extends BaseFeatureDataAdapter { ).filter(f => !!f) const scoreMin = min(stats.map(s => s.scoreMin)) const scoreMax = max(stats.map(s => s.scoreMax)) - return { scoreMin, scoreMax } + return { + scoreMin, + scoreMax, + } } public getFeatures(region: Region, opts: WiggleOptions = {}) { diff --git a/plugins/wiggle/src/WiggleRPC/MultiWiggleGetSources.ts b/plugins/wiggle/src/WiggleRPC/MultiWiggleGetSources.ts index a2886c628e..b7814f5605 100644 --- a/plugins/wiggle/src/WiggleRPC/MultiWiggleGetSources.ts +++ b/plugins/wiggle/src/WiggleRPC/MultiWiggleGetSources.ts @@ -1,48 +1,12 @@ import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' -import RpcMethodType from '@jbrowse/core/pluggableElementTypes/RpcMethodType' -import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain' -import { renameRegionsIfNeeded } from '@jbrowse/core/util' +import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions' import type { AnyConfigurationModel } from '@jbrowse/core/configuration' -import type { RenderArgs } from '@jbrowse/core/rpc/coreRpcMethods' import type { Region } from '@jbrowse/core/util' -export class MultiWiggleGetSources extends RpcMethodType { +export class MultiWiggleGetSources extends RpcMethodTypeWithFiltersAndRenameRegions { name = 'MultiWiggleGetSources' - async deserializeArguments(args: any, rpcDriverClassName: string) { - const l = await super.deserializeArguments(args, rpcDriverClassName) - return { - ...l, - filters: args.filters - ? new SerializableFilterChain({ - filters: args.filters, - }) - : undefined, - } - } - - async serializeArguments( - args: RenderArgs & { - stopToken?: string - statusCallback?: (arg: string) => void - }, - rpcDriverClassName: string, - ) { - const pm = this.pluginManager - const assemblyManager = pm.rootModel?.session?.assemblyManager - if (!assemblyManager) { - return args - } - - const renamedArgs = await renameRegionsIfNeeded(assemblyManager, { - ...args, - filters: args.filters?.toJSON().filters, - }) - - return super.serializeArguments(renamedArgs, rpcDriverClassName) - } - async execute( args: { adapterConfig: AnyConfigurationModel diff --git a/plugins/wiggle/src/WiggleRPC/WiggleGetGlobalQuantitativeStats.ts b/plugins/wiggle/src/WiggleRPC/WiggleGetGlobalQuantitativeStats.ts index 14a04f40e6..dc9eb3bcb6 100644 --- a/plugins/wiggle/src/WiggleRPC/WiggleGetGlobalQuantitativeStats.ts +++ b/plugins/wiggle/src/WiggleRPC/WiggleGetGlobalQuantitativeStats.ts @@ -1,25 +1,12 @@ import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' -import RpcMethodType from '@jbrowse/core/pluggableElementTypes/RpcMethodType' -import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain' +import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions' import type { AnyConfigurationModel } from '@jbrowse/core/configuration' import type { QuantitativeStats } from '@jbrowse/core/util/stats' -export class WiggleGetGlobalQuantitativeStats extends RpcMethodType { +export class WiggleGetGlobalQuantitativeStats extends RpcMethodTypeWithFiltersAndRenameRegions { name = 'WiggleGetGlobalQuantitativeStats' - async deserializeArguments(args: any, rpcDriverClassName: string) { - const l = await super.deserializeArguments(args, rpcDriverClassName) - return { - ...l, - filters: args.filters - ? new SerializableFilterChain({ - filters: args.filters, - }) - : undefined, - } - } - async execute( args: { adapterConfig: AnyConfigurationModel diff --git a/plugins/wiggle/src/WiggleRPC/WiggleGetMultiRegionQuantitativeStats.ts b/plugins/wiggle/src/WiggleRPC/WiggleGetMultiRegionQuantitativeStats.ts index 788e5777e5..25ba8ce73e 100644 --- a/plugins/wiggle/src/WiggleRPC/WiggleGetMultiRegionQuantitativeStats.ts +++ b/plugins/wiggle/src/WiggleRPC/WiggleGetMultiRegionQuantitativeStats.ts @@ -1,46 +1,11 @@ import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' -import RpcMethodType from '@jbrowse/core/pluggableElementTypes/RpcMethodType' -import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain' -import { renameRegionsIfNeeded } from '@jbrowse/core/util' +import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions' -import type { RenderArgs } from '@jbrowse/core/rpc/coreRpcMethods' import type { Region } from '@jbrowse/core/util' -export class WiggleGetMultiRegionQuantitativeStats extends RpcMethodType { +export class WiggleGetMultiRegionQuantitativeStats extends RpcMethodTypeWithFiltersAndRenameRegions { name = 'WiggleGetMultiRegionQuantitativeStats' - async deserializeArguments(args: any, rpcDriverClassName: string) { - const l = await super.deserializeArguments(args, rpcDriverClassName) - return { - ...l, - filters: args.filters - ? new SerializableFilterChain({ - filters: args.filters, - }) - : undefined, - } - } - - async serializeArguments( - args: RenderArgs & { - statusCallback?: (arg: string) => void - }, - rpcDriverClassName: string, - ) { - const pm = this.pluginManager - const assemblyManager = pm.rootModel?.session?.assemblyManager - if (!assemblyManager) { - return args - } - - const renamedArgs = await renameRegionsIfNeeded(assemblyManager, { - ...args, - filters: args.filters?.toJSON().filters, - }) - - return super.serializeArguments(renamedArgs, rpcDriverClassName) - } - async execute( args: { adapterConfig: Record diff --git a/products/jbrowse-web/package.json b/products/jbrowse-web/package.json index 84aa4e7b73..ffef316537 100644 --- a/products/jbrowse-web/package.json +++ b/products/jbrowse-web/package.json @@ -53,7 +53,7 @@ "crypto-js": "^4.2.0", "date-fns": "^4.1.0", "file-saver": "^2.0.0", - "idb": "^7.1.1", + "idb": "^8.0.0", "mobx": "^6.0.0", "mobx-react": "^9.0.0", "mobx-state-tree": "^5.0.0", diff --git a/products/jbrowse-web/src/__snapshots__/jbrowseModel.test.ts.snap b/products/jbrowse-web/src/__snapshots__/jbrowseModel.test.ts.snap index 2994d52e57..e484959036 100644 --- a/products/jbrowse-web/src/__snapshots__/jbrowseModel.test.ts.snap +++ b/products/jbrowse-web/src/__snapshots__/jbrowseModel.test.ts.snap @@ -504,6 +504,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "volvox_sv_test-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "volvox_sv_test-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "volvox_sv_test-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "volvox_sv_test-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -542,6 +550,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "volvox_sv_test_renamed-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "volvox_sv_test_renamed-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "volvox_sv_test_renamed-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "volvox_sv_test_renamed-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -1194,6 +1210,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "volvox_test_vcf-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "volvox_test_vcf-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "volvox_test_vcf-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "volvox_test_vcf-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -2125,6 +2149,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "volvox_filtered_vcf-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "volvox_filtered_vcf-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "volvox_filtered_vcf-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "volvox_filtered_vcf-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -2163,6 +2195,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "volvox_filtered_vcf_assembly_alias-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "volvox_filtered_vcf_assembly_alias-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "volvox_filtered_vcf_assembly_alias-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "volvox_filtered_vcf_assembly_alias-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -2794,6 +2834,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` }, "type": "LinearVariantDisplay", }, + { + "displayId": "variant_colors-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "variant_colors-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "variant_colors-LinearPairedArcDisplay", "type": "LinearPairedArcDisplay", @@ -3107,6 +3155,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "volvox.inv.vcf-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "volvox.inv.vcf-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "volvox.inv.vcf-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "volvox.inv.vcf-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -3422,6 +3478,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "variant_effect_demo_data-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "variant_effect_demo_data-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "variant_effect_demo_data-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "variant_effect_demo_data-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -3454,6 +3518,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "variant_effect_demo_jannovar-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "variant_effect_demo_jannovar-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "variant_effect_demo_jannovar-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "variant_effect_demo_jannovar-ChordVariantDisplay", "type": "ChordVariantDisplay", @@ -3937,6 +4009,14 @@ exports[`JBrowse model creates with non-empty snapshot 1`] = ` "displayId": "volvox_bedpe-LinearVariantDisplay", "type": "LinearVariantDisplay", }, + { + "displayId": "volvox_bedpe-LinearVariantMatrixDisplay", + "type": "LinearVariantMatrixDisplay", + }, + { + "displayId": "volvox_bedpe-MultiLinearVariantDisplay", + "type": "MultiLinearVariantDisplay", + }, { "displayId": "volvox_bedpe-ChordVariantDisplay", "type": "ChordVariantDisplay", diff --git a/products/jbrowse-web/src/components/ConfigWarningDialog.tsx b/products/jbrowse-web/src/components/ConfigWarningDialog.tsx index 4da2429454..b25648dd4e 100644 --- a/products/jbrowse-web/src/components/ConfigWarningDialog.tsx +++ b/products/jbrowse-web/src/components/ConfigWarningDialog.tsx @@ -26,7 +26,7 @@ function ConfigWarningDialog({ reason: PluginDefinition[] }) { return ( - + diff --git a/products/jbrowse-web/src/components/SessionWarningDialog.tsx b/products/jbrowse-web/src/components/SessionWarningDialog.tsx index 4fd08d1eb7..1c60ef967f 100644 --- a/products/jbrowse-web/src/components/SessionWarningDialog.tsx +++ b/products/jbrowse-web/src/components/SessionWarningDialog.tsx @@ -24,7 +24,7 @@ function SessionWarningDialog({ reason: PluginDefinition[] }) { return ( - + diff --git a/products/jbrowse-web/src/rootModel/__snapshots__/rootModel.test.ts.snap b/products/jbrowse-web/src/rootModel/__snapshots__/rootModel.test.ts.snap index 685653bb79..eefb969dab 100644 --- a/products/jbrowse-web/src/rootModel/__snapshots__/rootModel.test.ts.snap +++ b/products/jbrowse-web/src/rootModel/__snapshots__/rootModel.test.ts.snap @@ -53,10 +53,6 @@ exports[`adds menus 1`] = ` "label": "Duplicate session", "onClick": [Function], }, - { - "label": "Pre-configured sessions...", - "subMenu": [], - }, { "label": "Recent sessions...", "subMenu": [ @@ -225,10 +221,6 @@ exports[`adds menus 2`] = ` "label": "Duplicate session", "onClick": [Function], }, - { - "label": "Pre-configured sessions...", - "subMenu": [], - }, { "label": "Recent sessions...", "subMenu": [ diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/breakpoint_split_view_snapshot.svg b/products/jbrowse-web/src/tests/__image_snapshots__/breakpoint_split_view_snapshot.svg index f03362f043..9fba28b4f4 100644 --- a/products/jbrowse-web/src/tests/__image_snapshots__/breakpoint_split_view_snapshot.svg +++ b/products/jbrowse-web/src/tests/__image_snapshots__/breakpoint_split_view_snapshot.svg @@ -1 +1 @@ -hg19chr3186,698,000186,700,000chr3186,702,000186,704,0000077HG002.hs37d5.11kbpbsv.BND.3:186700648-6:56758392T[6:56758392[pbsv.BND.3:186700648-6:56758392T[6:56758392[HG002.hs37d5.bndshg19chr656,754,00056,756,00056,758,000chr656,758,00056,760,00056,762,0000088HG002.hs37d5.11kbpbsv.BND.6:56758392-3:186700648]3:186700648]Tpbsv.BND.6:56758392-3:186700648]3:186700648]THG002.hs37d5.bnds \ No newline at end of file +hg19chr3186,698,000186,700,000chr3186,702,000186,704,0000077HG002.hs37d5.11kbpbsv.BND.3:186700648-6:56758392T[6:56758392[pbsv.BND.3:186700648-6:56758392T[6:56758392[HG002.hs37d5.bndshg19chr656,754,00056,756,00056,758,000chr656,758,00056,760,00056,762,0000088HG002.hs37d5.11kbpbsv.BND.6:56758392-3:186700648]3:186700648]Tpbsv.BND.6:56758392-3:186700648]3:186700648]THG002.hs37d5.bnds \ No newline at end of file diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/lgv_snapshot.svg b/products/jbrowse-web/src/tests/__image_snapshots__/lgv_snapshot.svg index 2b31b53382..05a22e894d 100644 --- a/products/jbrowse-web/src/tests/__image_snapshots__/lgv_snapshot.svg +++ b/products/jbrowse-web/src/tests/__image_snapshots__/lgv_snapshot.svg @@ -1 +1 @@ -volvox80bpctgA020406080001111GTACAGAGTGACGCTCAAAGCvolvox-sorted.bam (ctgA, canvas) \ No newline at end of file +volvox80bpctgA020406080001111GTACAGAGTGACGCTCAAAGCvolvox-sorted.bam (ctgA, canvas) \ No newline at end of file diff --git a/products/jbrowse-web/src/tests/__snapshots__/ExportSvg.test.tsx.snap b/products/jbrowse-web/src/tests/__snapshots__/ExportSvg.test.tsx.snap index d65176d851..3007327ddb 100644 --- a/products/jbrowse-web/src/tests/__snapshots__/ExportSvg.test.tsx.snap +++ b/products/jbrowse-web/src/tests/__snapshots__/ExportSvg.test.tsx.snap @@ -4,6 +4,6 @@ exports[`export svg of circular 1`] = `"ctgA16,00018,00020,00022,00024,000volvoxctgA13,00014,00015,00016,00017,00018,00019,00020,000volvox_random_inv"`; -exports[`export svg of lgv 1`] = `"volvox80bpctgA020406080001111GTACAGAGTGACGCTCAAAGCvolvox-sorted.bam (ctgA, canvas)"`; +exports[`export svg of lgv 1`] = `"volvox80bpctgA020406080001111GTACAGAGTGACGCTCAAAGCvolvox-sorted.bam (ctgA, canvas)"`; exports[`export svg of synteny 1`] = `"volvoxctgA1,8001,9002,0002,100333619537636283334354210515volvox_inv_indelsvolvox_random_invctgA1,8001,9002,0002,1003345844231067567193728415volvox_inv_indels"`; diff --git a/products/jbrowse-web/src/tests/__snapshots__/ExportSvgBreakpointSplitView.test.tsx.snap b/products/jbrowse-web/src/tests/__snapshots__/ExportSvgBreakpointSplitView.test.tsx.snap index e513a6da6f..223fe376de 100644 --- a/products/jbrowse-web/src/tests/__snapshots__/ExportSvgBreakpointSplitView.test.tsx.snap +++ b/products/jbrowse-web/src/tests/__snapshots__/ExportSvgBreakpointSplitView.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`export svg of breakpoint split view 1`] = `"hg19chr3186,698,000186,700,000chr3186,702,000186,704,0000077HG002.hs37d5.11kbpbsv.BND.3:186700648-6:56758392T[6:56758392[pbsv.BND.3:186700648-6:56758392T[6:56758392[HG002.hs37d5.bndshg19chr656,754,00056,756,00056,758,000chr656,758,00056,760,00056,762,0000088HG002.hs37d5.11kbpbsv.BND.6:56758392-3:186700648]3:186700648]Tpbsv.BND.6:56758392-3:186700648]3:186700648]THG002.hs37d5.bnds"`; +exports[`export svg of breakpoint split view 1`] = `"hg19chr3186,698,000186,700,000chr3186,702,000186,704,0000077HG002.hs37d5.11kbpbsv.BND.3:186700648-6:56758392T[6:56758392[pbsv.BND.3:186700648-6:56758392T[6:56758392[HG002.hs37d5.bndshg19chr656,754,00056,756,00056,758,000chr656,758,00056,760,00056,762,0000088HG002.hs37d5.11kbpbsv.BND.6:56758392-3:186700648]3:186700648]Tpbsv.BND.6:56758392-3:186700648]3:186700648]THG002.hs37d5.bnds"`; diff --git a/test_data/config_demo.json b/test_data/config_demo.json index bbec0de566..3a1e846241 100644 --- a/test_data/config_demo.json +++ b/test_data/config_demo.json @@ -4746,7 +4746,8 @@ "uri": "https://jbrowse.org/demos/bilby/test.bg", "locationType": "UriLocation" } - } + }, + "category": ["BedGraph"] }, { "type": "MultiQuantitativeTrack", @@ -4759,45 +4760,88 @@ "uri": "https://jbrowse.org/demos/bilby/test2.bg", "locationType": "UriLocation" } - } + }, + "category": ["BedGraph"] }, { - "type": "MultiQuantitativeTrack", - "trackId": "bilby3", - "name": "Bilby transition matrix (rust)", - "assemblyNames": ["hg38"], + "type": "VariantTrack", + "trackId": "CCDG_14151_B01_GRM_WGS_2020-08-05_chr1.filtered.shapeit2-duohmm-phased.vcf", + "name": "CCDG_14151_B01_GRM_WGS_2020-08-05_chr1.filtered.shapeit2-duohmm-phased.vcf", "adapter": { - "type": "BedGraphTabixAdapter", - "bedGraphGzLocation": { - "uri": "out.sorted.bed.gz", + "type": "VcfTabixAdapter", + "vcfGzLocation": { + "uri": "http://ftp.1000genomes.ebi.ac.uk/vol1/ftp/data_collections/1000G_2504_high_coverage/working/20201028_3202_phased/CCDG_14151_B01_GRM_WGS_2020-08-05_chr1.filtered.shapeit2-duohmm-phased.vcf.gz", "locationType": "UriLocation" }, "index": { "location": { - "uri": "out.sorted.bed.gz.tbi", + "uri": "http://ftp.1000genomes.ebi.ac.uk/vol1/ftp/data_collections/1000G_2504_high_coverage/working/20201028_3202_phased/CCDG_14151_B01_GRM_WGS_2020-08-05_chr1.filtered.shapeit2-duohmm-phased.vcf.gz.tbi", "locationType": "UriLocation" - } + }, + "indexType": "TBI" } - } + }, + "assemblyNames": ["hg38"] }, { - "type": "MultiQuantitativeTrack", - "trackId": "bilby4", - "name": "Bilby transition matrix (python)", - "assemblyNames": ["hg38"], + "type": "VariantTrack", + "trackId": "1KGP_3202.Illumina_ensemble_callset.freeze_V1.vcf", + "name": "1KGP_3202.Illumina_ensemble_callset.freeze_V1.vcf", "adapter": { - "type": "BedGraphTabixAdapter", - "bedGraphGzLocation": { - "uri": "out.bed.gz", + "type": "VcfTabixAdapter", + "vcfGzLocation": { + "uri": "https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/1000genomes/1KGP_3202.Illumina_ensemble_callset.freeze_V1.vcf.gz", "locationType": "UriLocation" }, "index": { "location": { - "uri": "out.bed.gz.tbi", + "uri": "https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/1000genomes/1KGP_3202.Illumina_ensemble_callset.freeze_V1.vcf.gz.tbi", "locationType": "UriLocation" - } + }, + "indexType": "TBI" } - } + }, + "assemblyNames": ["hg38"] + }, + { + "type": "VariantTrack", + "trackId": "1kGP_high_coverage_Illumina.chr1.filtered.SNV_INDEL_SV_phased_panel.vcf", + "name": "1kGP_high_coverage_Illumina.chr1.filtered.SNV_INDEL_SV_phased_panel.vcf", + "adapter": { + "type": "VcfTabixAdapter", + "vcfGzLocation": { + "uri": "https://ftp-trace.ncbi.nlm.nih.gov/1000genomes/ftp/release/20130502/ALL.chr1.phase3_shapeit2_mvncall_integrated_v5a.20130502.genotypes.vcf.gz", + "locationType": "UriLocation" + }, + "index": { + "location": { + "uri": "https://ftp-trace.ncbi.nlm.nih.gov/1000genomes/ftp/release/20130502/ALL.chr1.phase3_shapeit2_mvncall_integrated_v5a.20130502.genotypes.vcf.gz.tbi", + "locationType": "UriLocation" + }, + "indexType": "TBI" + } + }, + "assemblyNames": ["hg38"] + }, + { + "type": "VariantTrack", + "trackId": "HG02024_VN049_KHVTrio.chr1.vcf", + "name": "HG02024_VN049_KHVTrio.chr1.vcf", + "adapter": { + "type": "VcfTabixAdapter", + "vcfGzLocation": { + "uri": "https://hgdownload.soe.ucsc.edu/gbdb/hg38/1000Genomes/trio/HG02024_VN049_KHV/HG02024_VN049_KHVTrio.chr1.vcf.gz", + "locationType": "UriLocation" + }, + "index": { + "location": { + "uri": "https://hgdownload.soe.ucsc.edu/gbdb/hg38/1000Genomes/trio/HG02024_VN049_KHV/HG02024_VN049_KHVTrio.chr1.vcf.gz.tbi", + "locationType": "UriLocation" + }, + "indexType": "TBI" + } + }, + "assemblyNames": ["hg38"] } ], "connections": [], diff --git a/website/docs/config/LinearVariantMatrixDisplay.md b/website/docs/config/LinearVariantMatrixDisplay.md new file mode 100644 index 0000000000..271422b67b --- /dev/null +++ b/website/docs/config/LinearVariantMatrixDisplay.md @@ -0,0 +1,20 @@ +--- +id: linearvariantmatrixdisplay +title: LinearVariantMatrixDisplay +toplevel: true +--- + +Note: this document is automatically generated from configuration objects in our +source code. See [Config guide](/docs/config_guide) for more info + +## Source file + +[plugins/variants/src/LinearVariantMatrixDisplay/configSchema.ts](https://github.com/GMOD/jbrowse-components/blob/main/plugins/variants/src/LinearVariantMatrixDisplay/configSchema.ts) + +## Docs + +## LinearVariantMatrixDisplay - Derives from + +```js +baseConfiguration: linearBasicDisplayConfigSchemaFactory(pluginManager) +``` diff --git a/website/docs/config/LinearVariantMatrixRenderer.md b/website/docs/config/LinearVariantMatrixRenderer.md new file mode 100644 index 0000000000..9519040016 --- /dev/null +++ b/website/docs/config/LinearVariantMatrixRenderer.md @@ -0,0 +1,14 @@ +--- +id: linearvariantmatrixrenderer +title: LinearVariantMatrixRenderer +toplevel: true +--- + +Note: this document is automatically generated from configuration objects in our +source code. See [Config guide](/docs/config_guide) for more info + +## Source file + +[plugins/variants/src/LinearVariantMatrixRenderer/configSchema.ts](https://github.com/GMOD/jbrowse-components/blob/main/plugins/variants/src/LinearVariantMatrixRenderer/configSchema.ts) + +## Docs diff --git a/website/docs/config/VariantTrack.md b/website/docs/config/VariantTrack.md deleted file mode 100644 index 2d606a2065..0000000000 --- a/website/docs/config/VariantTrack.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: varianttrack -title: VariantTrack ---- - -Note: this document is automatically generated from configuration objects in our -source code. See [Config guide](/docs/config_guide) for more info - -### Source file - -[plugins/variants/src/VariantTrack/configSchema.ts](https://github.com/GMOD/jbrowse-components/blob/main/plugins/variants/src/VariantTrack/configSchema.ts) - -Mostly similar to feature track, but has `ChordDisplayType` registered to it, -and custom feature details in `LinearVariantDisplay` - -### VariantTrack - Derives from - -```js -baseConfiguration: createBaseTrackConfig(pluginManager) -``` diff --git a/website/docs/models/LinearVariantMatrixDisplay.md b/website/docs/models/LinearVariantMatrixDisplay.md new file mode 100644 index 0000000000..a84e86ba72 --- /dev/null +++ b/website/docs/models/LinearVariantMatrixDisplay.md @@ -0,0 +1,72 @@ +--- +id: linearvariantmatrixdisplay +title: LinearVariantMatrixDisplay +toplevel: true +--- + +Note: this document is automatically generated from mobx-state-tree objects in +our source code. See +[Core concepts and intro to pluggable elements](/docs/developer_guide/) for more +info + +## Source file + +[plugins/variants/src/LinearVariantMatrixDisplay/model.ts](https://github.com/GMOD/jbrowse-components/blob/main/plugins/variants/src/LinearVariantMatrixDisplay/model.ts) + +## Docs + +extends `LinearBasicDisplay` + +### LinearVariantMatrixDisplay - Properties + +#### property: type + +```js +// type signature +ISimpleType<"LinearVariantMatrixDisplay"> +// code +type: types.literal('LinearVariantMatrixDisplay') +``` + +#### property: configuration + +```js +// type signature +ITypeUnion +// code +configuration: ConfigurationReference(configSchema) +``` + +### LinearVariantMatrixDisplay - Getters + +#### getter: blockType + +```js +// type +string +``` + +#### getter: renderDelay + +```js +// type +number +``` + +### LinearVariantMatrixDisplay - Methods + +#### method: renderProps + +```js +// type signature +renderProps: () => any +``` + +### LinearVariantMatrixDisplay - Actions + +#### action: setSamples + +```js +// type signature +setSamples: (arg: string[]) => void +``` diff --git a/yarn.lock b/yarn.lock index 7edeb5128f..b6d273ee2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2183,10 +2183,10 @@ dependencies: "@floating-ui/dom" "^1.0.0" -"@floating-ui/react@^0.26.3": - version "0.26.28" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7" - integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw== +"@floating-ui/react@^0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.0.tgz#e0931fd09374ab4b8ce1a1af5cb44d1ccd1bb95a" + integrity sha512-WLEksq7fJapXSJbmfiyq9pAW0a7ZFMEJToFE4oTDESxGjoa+nZu3YMjmZE2KvoUtQhqOK2yMMfWQFZyeWD0wGQ== dependencies: "@floating-ui/react-dom" "^2.1.2" "@floating-ui/utils" "^0.2.8" @@ -2326,10 +2326,10 @@ resolved "https://registry.yarnpkg.com/@gmod/ucsc-hub/-/ucsc-hub-0.3.0.tgz#375eeab1c515605510c706f6e2d76d6b42454f8c" integrity sha512-M8r1rpmNOVH6UHwUTSOR5Z86upWEhvI3BKbyYVq8imTS+nZAq8Gr9RJTBsGlL0sgX7fmLSXms8/pTyp7e3YAQA== -"@gmod/vcf@^5.0.9": - version "5.0.10" - resolved "https://registry.yarnpkg.com/@gmod/vcf/-/vcf-5.0.10.tgz#6c2d7952b15f61642454be90119ea89fd3c227de" - integrity sha512-o7QuPcOeXlJpzwQaFmgojhNvJE4yB9fhrfVEDKpkDjV27pAqwMy89367vtXu4JfBFE9t4zZ6sQRkqYaJ+cIheg== +"@gmod/vcf@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@gmod/vcf/-/vcf-6.0.0.tgz#68f53d2bf7e40c845d69518b950a0ba077ff1e67" + integrity sha512-8rwamVwPO25EfZUpXXf6h4nv2h8oj8GkMMwkeIZh/FNp2r87SC6YwDcyxGJRe2JAWV/zn8BkKyxdEQnJ5VG3DA== "@humanfs/core@^0.19.1": version "0.19.1" @@ -3347,9 +3347,9 @@ which "^4.0.0" "@nx/devkit@>=17.1.2 < 21": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-20.2.0.tgz#b859754b0c151f8bf13105470b32163bcb1be428" - integrity sha512-u3hFafNcTaT793SnzAhstWCTyYfZE93ezbmvxVatAYIZC8Bz8Exw5+R0MIeEZiWDxogcDYzJtZ0U53kebJsoxA== + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-20.2.1.tgz#c6561c5b35b68ebe5a44d85a913b0b546b3214be" + integrity sha512-boNTu7Z7oHkYjrYg5Wzg+cQfbEJ2nntRj1eI99w8mp4qz2B4PEEjJOB0BZafR54ZcKpGEbyp/QBB945GsjTUbw== dependencies: ejs "^3.1.7" enquirer "~2.3.6" @@ -3360,55 +3360,55 @@ tslib "^2.3.0" yargs-parser "21.1.1" -"@nx/nx-darwin-arm64@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.2.0.tgz#066320ee4ffb454d33b4a2335dbbf589e5523ed2" - integrity sha512-Tq5ejMlNGsEuaUxz5mwfaMwWRj/ziygp5dtsSTukpSAzO9iPAs95CvSt7qUO5ZYhLhwAOJ6HPapGbz5WbNu67A== - -"@nx/nx-darwin-x64@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-20.2.0.tgz#84c8f15067276f098cc15fb64e5101ed41e8563c" - integrity sha512-LnFfe3hqVAoY891fUB8AMujOUDCVvRcBd8HuZnER4UwvrPcOLP+e7HFiW8D3A/BsDW5XK95myD8llwQUn329eQ== - -"@nx/nx-freebsd-x64@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.2.0.tgz#de0359d7baa301790cb33210ee3a792f989fdd63" - integrity sha512-bikAwsNO0czoQWxU8ebWFKa+ugVBfFd/XSiT5+G5yJeRdbgBgmUEiK8tMMpwbkVffe4/KC/H/2nx0sgNRUJz4Q== - -"@nx/nx-linux-arm-gnueabihf@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.2.0.tgz#d60f2e8897c67453412b23d14fbec4c1c21106a5" - integrity sha512-faBKLDjSvE+X7HgXMPWTfqX9jDTy9YBtoZBbLZVWMso1T0sfL04ehY7XukBnKQYR3b/knMW077V5gEOrHDT1MQ== - -"@nx/nx-linux-arm64-gnu@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.2.0.tgz#392655b418f7b88fc897026ae55206d932ba9c0d" - integrity sha512-HaosF2EWRM1lRVv9ef/3V44saCSnSjfCqSAsRJ6JviEgozaO8+DgAjpgWCd7UkNN/UArq0lMk1vegKPhMs+qUQ== - -"@nx/nx-linux-arm64-musl@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.2.0.tgz#c08a41febe48467e01b6a963e2bfb32001dda185" - integrity sha512-PmEulUgy/j9usaguyNXkoPJkxSAezVShJgXkSz0oYqfD/8tQyaZSIk9xfLBBQx2w3IdsRQwOMRt3W035F6j6Ww== - -"@nx/nx-linux-x64-gnu@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.2.0.tgz#26b5088af316d427fd180e9e0efd04f2ac7d8d05" - integrity sha512-yW5ICzdtT5nncXraGFfqKqhs2pR+t9ZxoLrM7qwSt9XOtkyVYk/OhkJcdotVG3XiQeDSA86OsnMFlXNs6nKPMg== - -"@nx/nx-linux-x64-musl@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.2.0.tgz#58eb44b38f20d64c77bbfb1517de8640aa8a7078" - integrity sha512-RSqXR7SXOe5UTGDAsOu0VssPyM3G/u0ctSXYAfUj0Gh/JjUjg34gMLs96SUWPnVRAcMRgH2kPm6OBsBFKyB3AQ== - -"@nx/nx-win32-arm64-msvc@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.2.0.tgz#99e2a95b65082d0c4a463f13a8d1b17199e2b060" - integrity sha512-U+rz4fuWnbgqryvxTEdqYDi2keEcY3AdGgzuPUl2WgPchYUJ8UvGSmWFG9eoMxVpCAuvgfse/DntwlQzzxISKg== - -"@nx/nx-win32-x64-msvc@20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.2.0.tgz#f30c725fc6c06b63653993bb2f096db50a5d26e5" - integrity sha512-Yi7dOVNWCRMcMtuhuNI5AQHOgb39wMI4XQWsze9xitj14vgtet4o5xrmUVx2RsoK0oYVQsBpXYxPZeH4oRaFMQ== +"@nx/nx-darwin-arm64@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.2.1.tgz#c6fb53f9fa642dd97f36fefbe3b510116f4b838d" + integrity sha512-nJcyPZfH6Vq4cG6gRnQ8PcnVOLePeT3exzLnQu0I4I2EtCTPyCSRA3gxoGzZ3qZFMQTsCbwv4HYfdx42AXOTAQ== + +"@nx/nx-darwin-x64@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-20.2.1.tgz#8e7be1229cfc6c336b8ba8a1b90e83ae1f9776c9" + integrity sha512-SEiN8fjEs010ME4PRP8O9f8qG8AMZBGz8hOkF6ZrdlC+iEi4iyAGpgWFq8PKBlpVW4G5gxR91Y7eVaTKAsgH5w== + +"@nx/nx-freebsd-x64@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.2.1.tgz#bb63b261fcd3746d93d13cac276bbcbd61e2cb16" + integrity sha512-/yEKS9q17EG2Ci130McvpZM5YUghH1ql9UXWbVmitufn+RQD90hoblkG/B+cxJeZonrdKAjdpLQ+hfVz+FBd/g== + +"@nx/nx-linux-arm-gnueabihf@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.2.1.tgz#0f620a6675b14be845d9a7f96a1b4ba81887f463" + integrity sha512-DPtRjTCJ5++stTGtjqYftCb2c0CNed2s2EZZLQuDP+tikTsLm0d3S3ZaU5eHhqZW35tQuMOVweOfC1nJ3/DTSA== + +"@nx/nx-linux-arm64-gnu@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.2.1.tgz#5380a7d508bfa08a76c97d69eaeb0c4345151680" + integrity sha512-ggGwHOEP6UjXeqv6DtRxizeBnX/zRZi8BRJbEJBwAt1cAUnLlklk8d+Hmjs+j/FlFXBV9f+ylpAqoYkplFR8jg== + +"@nx/nx-linux-arm64-musl@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.2.1.tgz#03554dd415d93bd16d42460a566da0d281578374" + integrity sha512-HZBGxsBJUFbWVTiyJxqt0tS8tlvp+Tp0D533mGKW75cU0rv9dnmbtTwkkkx+LXqerjSRvNS3Qtj0Uh2w92Vtig== + +"@nx/nx-linux-x64-gnu@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.2.1.tgz#6531116a01a28d6e1a40baf9507059c4c3ed9974" + integrity sha512-pTytPwGiPRakqz2PKiWTSRNm9taE1U9n0+kRAAFzbOtzeW+eIoebe5xY5QMoZ+XtIZ6pJM2BUOyMD+/TX57r8Q== + +"@nx/nx-linux-x64-musl@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.2.1.tgz#591b9b7b5b866e95e13113f7783a2bfef803ee45" + integrity sha512-p3egqe5zmwiDl6xSwHi2K9UZWiKbZ/s/j4qV+pZttzMyNPfhohTeP+VwQqjTeQ1hPBl2YhwmmktEPsIPYJG7YA== + +"@nx/nx-win32-arm64-msvc@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.2.1.tgz#c5fc63b2ee8ff3a8b51edb32cca706484b75bb9c" + integrity sha512-Wujist6k08pjgWWQ1pjXrCArmMgnyIXNVmDP14cWo1KHecBuxNWa9i62PrxQ0K8MLYMcAzLHJxN9t54GzBbd+g== + +"@nx/nx-win32-x64-msvc@20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.2.1.tgz#efbcca687cd91f7c8380c6e3d0f838323d73951e" + integrity sha512-tsEYfNV2+CWSQmbh9TM8cX5wk6F2QAH0tfvt4topyOOaR40eszW8qc/eDM/kkJ5nj87BbNEqPBQAYFE0AP1OMA== "@oclif/core@4.0.19": version "4.0.19" @@ -5615,10 +5615,10 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" -agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" - integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.2.tgz#c83b029791b07a5301dce3ef825e6a328b5391cd" + integrity sha512-JVzqkCNRT+VfqzzgPWDPnwvDheSAUdiMUn3NoLXpDJF5lRqeJqyC9iGsAxIOAW+mzIdq+uP1TvcX6bMtrH0agg== dependencies: debug "^4.3.4" @@ -6531,7 +6531,7 @@ call-bind-apply-helpers@^1.0.0: es-errors "^1.3.0" function-bind "^1.1.2" -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7, call-bind@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== @@ -8369,7 +8369,7 @@ es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23 unbox-primitive "^1.0.2" which-typed-array "^1.1.15" -es-define-property@^1.0.0: +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -9389,15 +9389,18 @@ get-caller-file@^2.0.5: integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.5.tgz#dfe7dd1b30761b464fe51bf4bb00ac7c37b681e7" + integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg== dependencies: + call-bind-apply-helpers "^1.0.0" + dunder-proto "^1.0.0" + es-define-property "^1.0.1" es-errors "^1.3.0" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" get-package-type@^0.1.0: version "0.1.0" @@ -9781,14 +9784,14 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1, has-proto@^1.0.3: +has-proto@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== dependencies: dunder-proto "^1.0.0" -has-symbols@^1.0.3: +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== @@ -10048,11 +10051,11 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: debug "4" https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1, https-proxy-agent@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" - integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== dependencies: - agent-base "^7.0.2" + agent-base "^7.1.2" debug "4" human-signals@^2.1.0: @@ -10104,10 +10107,10 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -idb@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" - integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== +idb@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.0.tgz#33d7ed894ed36e23bcb542fb701ad579bfaad41f" + integrity sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw== ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" @@ -12670,9 +12673,9 @@ nwsapi@^2.2.12, nwsapi@^2.2.2: integrity sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ== "nx@>=17.1.2 < 21": - version "20.2.0" - resolved "https://registry.yarnpkg.com/nx/-/nx-20.2.0.tgz#8fa86244414ce429215064ae2fb5fd512ea37b5d" - integrity sha512-JtdTc/jHHoxylBl4DCIOX/dNYQB5+rEIMTHqAFJ3InfsKxjsjFdoHJpOBbRrtnMfgz3GY/hukSb/ggQPuNQW2A== + version "20.2.1" + resolved "https://registry.yarnpkg.com/nx/-/nx-20.2.1.tgz#80406de485ff542fcbc71062f2ff63182a1aea96" + integrity sha512-zUw1DT9BW2ajbDZgcUFKfysGqrbJwsMRjPxT6GthuqcLtDc2iJi3+/UBTLsITSeiw4Za4qPVxjaK4+yma9Ju5w== dependencies: "@napi-rs/wasm-runtime" "0.2.4" "@yarnpkg/lockfile" "^1.1.0" @@ -12704,19 +12707,20 @@ nwsapi@^2.2.12, nwsapi@^2.2.2: tmp "~0.2.1" tsconfig-paths "^4.1.2" tslib "^2.3.0" + yaml "^2.6.0" yargs "^17.6.2" yargs-parser "21.1.1" optionalDependencies: - "@nx/nx-darwin-arm64" "20.2.0" - "@nx/nx-darwin-x64" "20.2.0" - "@nx/nx-freebsd-x64" "20.2.0" - "@nx/nx-linux-arm-gnueabihf" "20.2.0" - "@nx/nx-linux-arm64-gnu" "20.2.0" - "@nx/nx-linux-arm64-musl" "20.2.0" - "@nx/nx-linux-x64-gnu" "20.2.0" - "@nx/nx-linux-x64-musl" "20.2.0" - "@nx/nx-win32-arm64-msvc" "20.2.0" - "@nx/nx-win32-x64-msvc" "20.2.0" + "@nx/nx-darwin-arm64" "20.2.1" + "@nx/nx-darwin-x64" "20.2.1" + "@nx/nx-freebsd-x64" "20.2.1" + "@nx/nx-linux-arm-gnueabihf" "20.2.1" + "@nx/nx-linux-arm64-gnu" "20.2.1" + "@nx/nx-linux-arm64-musl" "20.2.1" + "@nx/nx-linux-x64-gnu" "20.2.1" + "@nx/nx-linux-x64-musl" "20.2.1" + "@nx/nx-win32-arm64-msvc" "20.2.1" + "@nx/nx-win32-x64-msvc" "20.2.1" object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" @@ -14162,17 +14166,18 @@ redent@^3.0.0: strip-indent "^3.0.0" reflect.getprototypeof@^1.0.4, reflect.getprototypeof@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz#04311b33a1b713ca5eb7b5aed9950a86481858e5" - integrity sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g== + version "1.0.8" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz#c58afb17a4007b4d1118c07b92c23fca422c5d82" + integrity sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" define-properties "^1.2.1" + dunder-proto "^1.0.0" es-abstract "^1.23.5" es-errors "^1.3.0" get-intrinsic "^1.2.4" - gopd "^1.0.1" - which-builtin-type "^1.1.4" + gopd "^1.2.0" + which-builtin-type "^1.2.0" regenerate-unicode-properties@^10.2.0: version "10.2.0" @@ -14864,11 +14869,11 @@ socks-proxy-agent@^7.0.0: socks "^2.6.2" socks-proxy-agent@^8.0.3: - version "8.0.4" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz#9071dca17af95f483300316f4b063578fa0db08c" - integrity sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== + version "8.0.5" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" + integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== dependencies: - agent-base "^7.1.1" + agent-base "^7.1.2" debug "^4.3.4" socks "^2.8.3" @@ -16404,7 +16409,7 @@ which-boxed-primitive@^1.0.2: is-string "^1.1.0" is-symbol "^1.1.0" -which-builtin-type@^1.1.4: +which-builtin-type@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.0.tgz#58042ac9602d78a6d117c7e811349df1268ba63c" integrity sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA== @@ -16636,6 +16641,11 @@ yaml@^1.10.0, yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== + yargs-parser@21.1.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"