@apollo-annotation/jbrowse-plugin-apollo 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +6964 -4598
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +6610 -4261
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +11563 -7493
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +4 -4
- package/src/ApolloInternetAccount/addMenuItems.ts +23 -2
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +1 -1
- package/src/ApolloInternetAccount/components/LoginIcons.tsx +1 -1
- package/src/ApolloInternetAccount/configSchema.ts +1 -1
- package/src/ApolloInternetAccount/model.ts +11 -10
- package/src/ApolloJobModel.ts +1 -1
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +8 -6
- package/src/ApolloRefNameAliasAdapter/index.ts +2 -2
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/index.ts +1 -1
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +8 -7
- package/src/ApolloTextSearchAdapter/index.ts +1 -1
- package/src/BackendDrivers/BackendDriver.ts +7 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +14 -10
- package/src/BackendDrivers/DesktopFileDriver.ts +11 -10
- package/src/BackendDrivers/InMemoryFileDriver.ts +10 -6
- package/src/ChangeManager.ts +15 -11
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +8 -7
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +35 -14
- package/src/FeatureDetailsWidget/AttributeKey.tsx +50 -0
- package/src/FeatureDetailsWidget/AttributeKeySelector.tsx +104 -0
- package/src/FeatureDetailsWidget/Attributes.tsx +215 -367
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -5
- package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +104 -0
- package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +22 -0
- package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +4 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -1
- package/src/FeatureDetailsWidget/Sequence.tsx +2 -2
- package/src/FeatureDetailsWidget/StringTextField.tsx +1 -1
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +15 -23
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +950 -196
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +8 -4
- package/src/FeatureDetailsWidget/model.ts +8 -3
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +7 -7
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +59 -72
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +253 -60
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +52 -6
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +16 -8
- package/src/LinearApolloDisplay/stateModel/base.ts +81 -10
- package/src/LinearApolloDisplay/stateModel/index.ts +4 -3
- package/src/LinearApolloDisplay/stateModel/layouts.ts +8 -39
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +63 -46
- package/src/LinearApolloDisplay/stateModel/rendering.ts +60 -31
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +226 -0
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +32 -0
- package/src/LinearApolloSixFrameDisplay/components/index.ts +2 -0
- package/src/LinearApolloSixFrameDisplay/configSchema.ts +7 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +940 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +63 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/index.ts +1 -0
- package/src/LinearApolloSixFrameDisplay/index.ts +2 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +302 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/index.ts +27 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +252 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +368 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +201 -0
- package/src/LinearApolloSixFrameDisplay/types.ts +1 -0
- package/src/OntologyManager/OntologyStore/fulltext.test.ts +1 -1
- package/src/OntologyManager/OntologyStore/fulltext.ts +8 -3
- package/src/OntologyManager/OntologyStore/index.test.ts +3 -1
- package/src/OntologyManager/OntologyStore/index.ts +19 -14
- package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +6 -5
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +11 -5
- package/src/OntologyManager/index.ts +12 -7
- package/src/OntologyManager/util.ts +3 -2
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
- package/src/TabularEditor/HybridGrid/Feature.tsx +13 -7
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +1 -1
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +3 -2
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +1 -1
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +114 -22
- package/src/TabularEditor/TabularEditorPane.tsx +1 -1
- package/src/TabularEditor/model.ts +2 -2
- package/src/TabularEditor/types.ts +5 -2
- package/src/components/AddAssembly.tsx +182 -179
- package/src/components/AddAssemblyAliases.tsx +114 -0
- package/src/components/AddChildFeature.tsx +8 -10
- package/src/components/AddFeature.tsx +216 -44
- package/src/components/AddRefSeqAliases.tsx +14 -12
- package/src/components/CopyFeature.tsx +10 -11
- package/src/components/CreateApolloAnnotation.tsx +342 -158
- package/src/components/DeleteAssembly.tsx +9 -8
- package/src/components/DeleteFeature.tsx +362 -14
- package/src/components/Dialog.tsx +1 -1
- package/src/components/DownloadGFF3.tsx +31 -11
- package/src/components/FilterFeatures.tsx +6 -4
- package/src/components/FilterTranscripts.tsx +86 -0
- package/src/components/ImportFeatures.tsx +7 -6
- package/src/components/LogOut.tsx +5 -4
- package/src/components/ManageChecks.tsx +9 -8
- package/src/components/ManageUsers.tsx +11 -10
- package/src/components/MergeExons.tsx +193 -0
- package/src/components/MergeTranscripts.tsx +185 -0
- package/src/components/OntologyTermAutocomplete.tsx +5 -5
- package/src/components/OntologyTermMultiSelect.tsx +6 -6
- package/src/components/OpenLocalFile.tsx +4 -3
- package/src/components/SplitExon.tsx +134 -0
- package/src/components/ViewChangeLog.tsx +7 -6
- package/src/components/ViewCheckResults.tsx +8 -7
- package/src/components/index.ts +3 -0
- package/src/config.ts +5 -0
- package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +13 -10
- package/src/extensions/annotationFromPileup.ts +104 -94
- package/src/index.ts +33 -50
- package/src/makeDisplayComponent.tsx +90 -37
- package/src/session/ClientDataStore.ts +21 -17
- package/src/session/session.ts +46 -39
- package/src/types.ts +4 -4
- package/src/util/annotationFeatureUtils.ts +66 -1
- package/src/util/copyToClipboard.ts +21 -0
- package/src/util/glyphUtils.ts +49 -0
- package/src/util/index.ts +5 -3
- package/src/util/loadAssemblyIntoClient.ts +10 -3
- package/src/util/mouseEventsUtils.ts +113 -0
- package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +0 -13
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +0 -707
- package/src/ApolloSixFrameRenderer/configSchema.ts +0 -7
- package/src/ApolloSixFrameRenderer/index.ts +0 -3
- package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +0 -19
- package/src/SixFrameFeatureDisplay/components/index.ts +0 -1
- package/src/SixFrameFeatureDisplay/configSchema.ts +0 -21
- package/src/SixFrameFeatureDisplay/index.ts +0 -2
- package/src/SixFrameFeatureDisplay/stateModel.ts +0 -443
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
import styled from '@emotion/styled'
|
|
3
|
+
import { Table, TableBody, TableCell, TableRow } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
1
5
|
import React from 'react'
|
|
2
6
|
|
|
3
|
-
import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
4
|
-
import { observer } from 'mobx-react'
|
|
5
7
|
import { getFeatureId, getFeatureName, getStrand } from '../util'
|
|
6
|
-
import { Table, TableBody, TableCell, TableRow } from '@mui/material'
|
|
7
|
-
import styled from '@emotion/styled'
|
|
8
8
|
|
|
9
9
|
const HeaderTableCell = styled(TableCell)(() => ({
|
|
10
10
|
fontWeight: 'bold',
|
|
@@ -37,6 +37,10 @@ export const TranscriptWidgetSummary = observer(
|
|
|
37
37
|
<TableCell>{getFeatureId(feature)}</TableCell>
|
|
38
38
|
</TableRow>
|
|
39
39
|
)}
|
|
40
|
+
<TableRow>
|
|
41
|
+
<HeaderTableCell>Type</HeaderTableCell>
|
|
42
|
+
<TableCell>{feature.type}</TableCell>
|
|
43
|
+
</TableRow>
|
|
40
44
|
<TableRow>
|
|
41
45
|
<HeaderTableCell>Location</HeaderTableCell>
|
|
42
46
|
<TableCell>
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
3
3
|
import {
|
|
4
|
-
AnnotationFeature,
|
|
4
|
+
type AnnotationFeature,
|
|
5
5
|
AnnotationFeatureModel,
|
|
6
6
|
} from '@apollo-annotation/mst'
|
|
7
7
|
import { getSession } from '@jbrowse/core/util'
|
|
8
8
|
import { ElementId } from '@jbrowse/core/util/types/mst'
|
|
9
9
|
import { autorun } from 'mobx'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type Instance,
|
|
12
|
+
type SnapshotIn,
|
|
13
|
+
addDisposer,
|
|
14
|
+
types,
|
|
15
|
+
} from 'mobx-state-tree'
|
|
11
16
|
|
|
12
|
-
import { ApolloSessionModel } from '../session'
|
|
17
|
+
import { type ApolloSessionModel } from '../session'
|
|
13
18
|
|
|
14
19
|
export const ApolloFeatureDetailsWidgetModel = types
|
|
15
20
|
.model('ApolloFeatureDetailsWidget', {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
|
-
import { Menu, MenuItem } from '@jbrowse/core/ui'
|
|
4
|
+
import { Menu, type MenuItem } from '@jbrowse/core/ui'
|
|
5
5
|
import {
|
|
6
|
-
AbstractSessionModel,
|
|
6
|
+
type AbstractSessionModel,
|
|
7
7
|
doesIntersect2,
|
|
8
8
|
getContainingView,
|
|
9
9
|
} from '@jbrowse/core/util'
|
|
10
|
-
import type
|
|
10
|
+
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
11
11
|
import ErrorIcon from '@mui/icons-material/Error'
|
|
12
12
|
import {
|
|
13
13
|
Alert,
|
|
@@ -20,7 +20,7 @@ import { observer } from 'mobx-react'
|
|
|
20
20
|
import React, { useEffect, useState } from 'react'
|
|
21
21
|
import { makeStyles } from 'tss-react/mui'
|
|
22
22
|
|
|
23
|
-
import { LinearApolloDisplay as LinearApolloDisplayI } from '../stateModel'
|
|
23
|
+
import { type LinearApolloDisplay as LinearApolloDisplayI } from '../stateModel'
|
|
24
24
|
|
|
25
25
|
interface LinearApolloDisplayProps {
|
|
26
26
|
model: LinearApolloDisplayI
|
|
@@ -95,7 +95,7 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
95
95
|
const { assemblyManager } = session as unknown as AbstractSessionModel
|
|
96
96
|
return (
|
|
97
97
|
<>
|
|
98
|
-
{lgv.bpPerPx
|
|
98
|
+
{3 / lgv.bpPerPx >= 1 ? (
|
|
99
99
|
<div
|
|
100
100
|
className={classes.canvasContainer}
|
|
101
101
|
style={{
|
|
@@ -139,7 +139,7 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
139
139
|
} else {
|
|
140
140
|
const coord: [number, number] = [event.clientX, event.clientY]
|
|
141
141
|
setContextCoord(coord)
|
|
142
|
-
setContextMenuItems(getContextMenuItems(
|
|
142
|
+
setContextMenuItems(getContextMenuItems(event))
|
|
143
143
|
}
|
|
144
144
|
}}
|
|
145
145
|
>
|
|
@@ -238,7 +238,7 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
238
238
|
pointerEvents: apolloDragging ? 'none' : 'auto',
|
|
239
239
|
}}
|
|
240
240
|
>
|
|
241
|
-
<ErrorIcon />
|
|
241
|
+
<ErrorIcon data-testid="ErrorIcon" />
|
|
242
242
|
</Avatar>
|
|
243
243
|
</Tooltip>
|
|
244
244
|
)
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
AbstractSessionModel,
|
|
7
|
-
isSessionModelWithWidgets,
|
|
8
|
-
SessionWithWidgets,
|
|
9
|
-
} from '@jbrowse/core/util'
|
|
1
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
import { type MenuItem } from '@jbrowse/core/ui'
|
|
3
|
+
import { type AbstractSessionModel } from '@jbrowse/core/util'
|
|
4
|
+
import { type Theme, alpha } from '@mui/material'
|
|
10
5
|
|
|
11
6
|
import { AddChildFeature, CopyFeature, DeleteFeature } from '../../components'
|
|
12
|
-
|
|
13
|
-
import { LinearApolloDisplay } from '../stateModel'
|
|
7
|
+
import { type LinearApolloDisplay } from '../stateModel'
|
|
14
8
|
import {
|
|
9
|
+
type LinearApolloDisplayMouseEvents,
|
|
10
|
+
type MousePosition,
|
|
11
|
+
type MousePositionWithFeatureAndGlyph,
|
|
15
12
|
isMousePositionWithFeatureAndGlyph,
|
|
16
|
-
LinearApolloDisplayMouseEvents,
|
|
17
|
-
MousePosition,
|
|
18
|
-
MousePositionWithFeatureAndGlyph,
|
|
19
13
|
} from '../stateModel/mouseEvents'
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
14
|
+
import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
|
|
15
|
+
import { type CanvasMouseEvent } from '../types'
|
|
16
|
+
|
|
17
|
+
import { type Glyph } from './Glyph'
|
|
23
18
|
|
|
24
19
|
function drawBoxOutline(
|
|
25
20
|
ctx: CanvasRenderingContext2D,
|
|
@@ -258,9 +253,39 @@ export function drawBox(
|
|
|
258
253
|
|
|
259
254
|
function getContextMenuItems(
|
|
260
255
|
display: LinearApolloDisplayMouseEvents,
|
|
256
|
+
): MenuItem[] {
|
|
257
|
+
const { apolloHover } = display
|
|
258
|
+
if (!apolloHover) {
|
|
259
|
+
return []
|
|
260
|
+
}
|
|
261
|
+
const { feature: sourceFeature } = apolloHover
|
|
262
|
+
return getContextMenuItemsForFeature(display, sourceFeature)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function makeFeatureLabel(feature: AnnotationFeature) {
|
|
266
|
+
let name: string | undefined
|
|
267
|
+
if (feature.attributes.get('gff_name')) {
|
|
268
|
+
name = feature.attributes.get('gff_name')?.join(',')
|
|
269
|
+
} else if (feature.attributes.get('gff_id')) {
|
|
270
|
+
name = feature.attributes.get('gff_id')?.join(',')
|
|
271
|
+
} else {
|
|
272
|
+
name = feature._id
|
|
273
|
+
}
|
|
274
|
+
const coords = `(${(feature.min + 1).toLocaleString('en')}..${feature.max.toLocaleString('en')})`
|
|
275
|
+
const maxLen = 60
|
|
276
|
+
if (name && name.length + coords.length > maxLen + 5) {
|
|
277
|
+
const trim = maxLen - coords.length
|
|
278
|
+
name = trim > 0 ? name.slice(0, trim) : ''
|
|
279
|
+
name = `${name}[...]`
|
|
280
|
+
}
|
|
281
|
+
return `${name} ${coords}`
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getContextMenuItemsForFeature(
|
|
285
|
+
display: LinearApolloDisplayMouseEvents,
|
|
286
|
+
sourceFeature: AnnotationFeature,
|
|
261
287
|
): MenuItem[] {
|
|
262
288
|
const {
|
|
263
|
-
apolloHover,
|
|
264
289
|
apolloInternetAccount: internetAccount,
|
|
265
290
|
changeManager,
|
|
266
291
|
regions,
|
|
@@ -268,17 +293,23 @@ function getContextMenuItems(
|
|
|
268
293
|
session,
|
|
269
294
|
} = display
|
|
270
295
|
const menuItems: MenuItem[] = []
|
|
271
|
-
if (!apolloHover) {
|
|
272
|
-
return menuItems
|
|
273
|
-
}
|
|
274
|
-
const { feature: sourceFeature } = apolloHover
|
|
275
296
|
const role = internetAccount ? internetAccount.role : 'admin'
|
|
276
297
|
const admin = role === 'admin'
|
|
277
298
|
const readOnly = !(role && ['admin', 'user'].includes(role))
|
|
278
299
|
const [region] = regions
|
|
279
300
|
const sourceAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
280
301
|
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
302
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
303
|
+
if (!featureTypeOntology) {
|
|
304
|
+
throw new Error('featureTypeOntology is undefined')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Add only relevant options
|
|
281
308
|
menuItems.push(
|
|
309
|
+
{
|
|
310
|
+
label: makeFeatureLabel(sourceFeature),
|
|
311
|
+
type: 'subHeader',
|
|
312
|
+
},
|
|
282
313
|
{
|
|
283
314
|
label: 'Add child feature',
|
|
284
315
|
disabled: readOnly,
|
|
@@ -344,55 +375,7 @@ function getContextMenuItems(
|
|
|
344
375
|
)
|
|
345
376
|
},
|
|
346
377
|
},
|
|
347
|
-
{
|
|
348
|
-
label: 'Edit feature details',
|
|
349
|
-
onClick: () => {
|
|
350
|
-
const apolloFeatureWidget = (
|
|
351
|
-
session as unknown as SessionWithWidgets
|
|
352
|
-
).addWidget(
|
|
353
|
-
'ApolloFeatureDetailsWidget',
|
|
354
|
-
'apolloFeatureDetailsWidget',
|
|
355
|
-
{
|
|
356
|
-
feature: sourceFeature,
|
|
357
|
-
assembly: currentAssemblyId,
|
|
358
|
-
refName: region.refName,
|
|
359
|
-
},
|
|
360
|
-
)
|
|
361
|
-
;(session as unknown as SessionWithWidgets).showWidget(
|
|
362
|
-
apolloFeatureWidget,
|
|
363
|
-
)
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
378
|
)
|
|
367
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
368
|
-
if (!featureTypeOntology) {
|
|
369
|
-
throw new Error('featureTypeOntology is undefined')
|
|
370
|
-
}
|
|
371
|
-
if (
|
|
372
|
-
(featureTypeOntology.isTypeOf(sourceFeature.type, 'transcript') ||
|
|
373
|
-
featureTypeOntology.isTypeOf(
|
|
374
|
-
sourceFeature.type,
|
|
375
|
-
'pseudogenic_transcript',
|
|
376
|
-
)) &&
|
|
377
|
-
isSessionModelWithWidgets(session)
|
|
378
|
-
) {
|
|
379
|
-
menuItems.push({
|
|
380
|
-
label: 'Edit transcript details',
|
|
381
|
-
onClick: () => {
|
|
382
|
-
const apolloTranscriptWidget = session.addWidget(
|
|
383
|
-
'ApolloTranscriptDetails',
|
|
384
|
-
'apolloTranscriptDetails',
|
|
385
|
-
{
|
|
386
|
-
feature: sourceFeature,
|
|
387
|
-
assembly: currentAssemblyId,
|
|
388
|
-
changeManager,
|
|
389
|
-
refName: region.refName,
|
|
390
|
-
},
|
|
391
|
-
)
|
|
392
|
-
session.showWidget(apolloTranscriptWidget)
|
|
393
|
-
},
|
|
394
|
-
})
|
|
395
|
-
}
|
|
396
379
|
return menuItems
|
|
397
380
|
}
|
|
398
381
|
|
|
@@ -460,9 +443,12 @@ function onMouseUp(
|
|
|
460
443
|
return
|
|
461
444
|
}
|
|
462
445
|
const { featureAndGlyphUnderMouse } = mousePosition
|
|
463
|
-
if (featureAndGlyphUnderMouse
|
|
464
|
-
|
|
446
|
+
if (!featureAndGlyphUnderMouse) {
|
|
447
|
+
return
|
|
465
448
|
}
|
|
449
|
+
const { feature } = featureAndGlyphUnderMouse
|
|
450
|
+
stateModel.setSelectedFeature(feature)
|
|
451
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
466
452
|
}
|
|
467
453
|
|
|
468
454
|
/** @returns undefined if mouse not on the edge of this feature, otherwise 'start' or 'end' depending on which edge */
|
|
@@ -497,6 +483,7 @@ export const boxGlyph: Glyph = {
|
|
|
497
483
|
drawDragPreview,
|
|
498
484
|
drawHover,
|
|
499
485
|
drawTooltip,
|
|
486
|
+
getContextMenuItemsForFeature,
|
|
500
487
|
getContextMenuItems,
|
|
501
488
|
getFeatureFromLayout,
|
|
502
489
|
getRowCount,
|
|
@@ -1,24 +1,39 @@
|
|
|
1
|
-
import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
-
import {
|
|
1
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
import { type MenuItem } from '@jbrowse/core/ui'
|
|
3
|
+
import {
|
|
4
|
+
type AbstractSessionModel,
|
|
5
|
+
getFrame,
|
|
6
|
+
intersection2,
|
|
7
|
+
isSessionModelWithWidgets,
|
|
8
|
+
} from '@jbrowse/core/util'
|
|
3
9
|
import { alpha } from '@mui/material'
|
|
4
10
|
|
|
5
|
-
import {
|
|
11
|
+
import { type OntologyRecord } from '../../OntologyManager'
|
|
12
|
+
import { MergeExons, MergeTranscripts, SplitExon } from '../../components'
|
|
13
|
+
import { type ApolloSessionModel } from '../../session'
|
|
14
|
+
import { getMinAndMaxPx, getOverlappingEdge } from '../../util'
|
|
15
|
+
import { getFeaturesUnderClick } from '../../util/annotationFeatureUtils'
|
|
16
|
+
import { type LinearApolloDisplay } from '../stateModel'
|
|
6
17
|
import {
|
|
18
|
+
type LinearApolloDisplayMouseEvents,
|
|
19
|
+
type MousePosition,
|
|
20
|
+
type MousePositionWithFeatureAndGlyph,
|
|
7
21
|
isMousePositionWithFeatureAndGlyph,
|
|
8
|
-
MousePosition,
|
|
9
|
-
MousePositionWithFeatureAndGlyph,
|
|
10
22
|
} from '../stateModel/mouseEvents'
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
23
|
+
import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
|
|
24
|
+
import { type CanvasMouseEvent } from '../types'
|
|
25
|
+
|
|
13
26
|
import { boxGlyph } from './BoxGlyph'
|
|
14
|
-
import {
|
|
15
|
-
import { OntologyRecord } from '../../OntologyManager'
|
|
27
|
+
import { type Glyph } from './Glyph'
|
|
16
28
|
|
|
17
29
|
let forwardFillLight: CanvasPattern | null = null
|
|
18
30
|
let backwardFillLight: CanvasPattern | null = null
|
|
19
31
|
let forwardFillDark: CanvasPattern | null = null
|
|
20
32
|
let backwardFillDark: CanvasPattern | null = null
|
|
21
|
-
|
|
33
|
+
const canvas = globalThis.document.createElement('canvas')
|
|
34
|
+
// @ts-expect-error getContext is undefined in the web worker
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
36
|
+
if (canvas?.getContext) {
|
|
22
37
|
for (const direction of ['forward', 'backward']) {
|
|
23
38
|
for (const themeMode of ['light', 'dark']) {
|
|
24
39
|
const canvas = document.createElement('canvas')
|
|
@@ -225,7 +240,6 @@ function draw(
|
|
|
225
240
|
apolloSelectedFeature && _id === apolloSelectedFeature._id
|
|
226
241
|
? 'rgb(0,0,0)'
|
|
227
242
|
: cdsColorCode
|
|
228
|
-
ctx.fillStyle = cdsColorCode
|
|
229
243
|
ctx.fillRect(
|
|
230
244
|
cdsStartPx + 1,
|
|
231
245
|
cdsTop + 1,
|
|
@@ -619,6 +633,7 @@ function onMouseDown(
|
|
|
619
633
|
currentMousePosition,
|
|
620
634
|
draggableFeature.feature,
|
|
621
635
|
draggableFeature.edge,
|
|
636
|
+
true,
|
|
622
637
|
)
|
|
623
638
|
}
|
|
624
639
|
}
|
|
@@ -652,8 +667,39 @@ function onMouseUp(
|
|
|
652
667
|
return
|
|
653
668
|
}
|
|
654
669
|
const { featureAndGlyphUnderMouse } = mousePosition
|
|
655
|
-
if (featureAndGlyphUnderMouse
|
|
656
|
-
|
|
670
|
+
if (!featureAndGlyphUnderMouse) {
|
|
671
|
+
return
|
|
672
|
+
}
|
|
673
|
+
const { feature } = featureAndGlyphUnderMouse
|
|
674
|
+
stateModel.setSelectedFeature(feature)
|
|
675
|
+
const { session } = stateModel
|
|
676
|
+
const { apolloDataStore } = session
|
|
677
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
678
|
+
if (!featureTypeOntology) {
|
|
679
|
+
throw new Error('featureTypeOntology is undefined')
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let containsCDSOrExon = false
|
|
683
|
+
for (const [, child] of feature.children ?? []) {
|
|
684
|
+
if (
|
|
685
|
+
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
686
|
+
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
687
|
+
) {
|
|
688
|
+
containsCDSOrExon = true
|
|
689
|
+
break
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (
|
|
693
|
+
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
694
|
+
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
695
|
+
containsCDSOrExon
|
|
696
|
+
) {
|
|
697
|
+
stateModel.showFeatureDetailsWidget(feature, [
|
|
698
|
+
'ApolloTranscriptDetails',
|
|
699
|
+
'apolloTranscriptDetails',
|
|
700
|
+
])
|
|
701
|
+
} else {
|
|
702
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
657
703
|
}
|
|
658
704
|
}
|
|
659
705
|
|
|
@@ -674,31 +720,18 @@ function getDraggableFeatureInfo(
|
|
|
674
720
|
const isTranscript =
|
|
675
721
|
featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
676
722
|
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')
|
|
677
|
-
const
|
|
723
|
+
const isCDS = featureTypeOntology.isTypeOf(feature.type, 'CDS')
|
|
678
724
|
if (isGene || isTranscript) {
|
|
725
|
+
// For gene glyphs, the sizes of genes and transcripts are determined by
|
|
726
|
+
// their child exons, so we don't make them draggable
|
|
679
727
|
return
|
|
680
728
|
}
|
|
729
|
+
// So now the type of feature is either CDS or exon. If an exon and CDS edge
|
|
730
|
+
// are in the same place, we want to prioritize dragging the exon. If the
|
|
731
|
+
// feature we're on is a CDS, let's find any exon it may overlap.
|
|
681
732
|
const { bp, refName, regionNumber, x } = mousePosition
|
|
682
733
|
const { lgv } = stateModel
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const minPxInfo = lgv.bpToPx({ refName, coord: feature.min, regionNumber })
|
|
686
|
-
const maxPxInfo = lgv.bpToPx({ refName, coord: feature.max, regionNumber })
|
|
687
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
688
|
-
return
|
|
689
|
-
}
|
|
690
|
-
const minPx = minPxInfo.offsetPx - offsetPx
|
|
691
|
-
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
692
|
-
if (Math.abs(maxPx - minPx) < 8) {
|
|
693
|
-
return
|
|
694
|
-
}
|
|
695
|
-
if (Math.abs(minPx - x) < 4) {
|
|
696
|
-
return { feature, edge: 'min' }
|
|
697
|
-
}
|
|
698
|
-
if (Math.abs(maxPx - x) < 4) {
|
|
699
|
-
return { feature, edge: 'max' }
|
|
700
|
-
}
|
|
701
|
-
if (isCds) {
|
|
734
|
+
if (isCDS) {
|
|
702
735
|
const transcript = feature.parent
|
|
703
736
|
if (!transcript?.children) {
|
|
704
737
|
return
|
|
@@ -710,46 +743,205 @@ function getDraggableFeatureInfo(
|
|
|
710
743
|
exonChildren.push(child)
|
|
711
744
|
}
|
|
712
745
|
}
|
|
713
|
-
|
|
714
746
|
const overlappingExon = exonChildren.find((child) => {
|
|
715
747
|
const [start, end] = intersection2(bp - 1, bp, child.min, child.max)
|
|
716
748
|
return start !== undefined && end !== undefined
|
|
717
749
|
})
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const maxPxInfo = lgv.bpToPx({
|
|
728
|
-
refName,
|
|
729
|
-
coord: overlappingExon.max,
|
|
730
|
-
regionNumber,
|
|
731
|
-
})
|
|
732
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
733
|
-
return
|
|
750
|
+
if (overlappingExon) {
|
|
751
|
+
// We are on an exon, are we on the edge of it?
|
|
752
|
+
const minMax = getMinAndMaxPx(overlappingExon, refName, regionNumber, lgv)
|
|
753
|
+
if (minMax) {
|
|
754
|
+
const overlappingEdge = getOverlappingEdge(overlappingExon, x, minMax)
|
|
755
|
+
if (overlappingEdge) {
|
|
756
|
+
return overlappingEdge
|
|
757
|
+
}
|
|
758
|
+
}
|
|
734
759
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
760
|
+
}
|
|
761
|
+
// End of special cases, let's see if we're on the edge of this CDS or exon
|
|
762
|
+
const minMax = getMinAndMaxPx(feature, refName, regionNumber, lgv)
|
|
763
|
+
if (minMax) {
|
|
764
|
+
const overlappingEdge = getOverlappingEdge(feature, x, minMax)
|
|
765
|
+
if (overlappingEdge) {
|
|
766
|
+
return overlappingEdge
|
|
739
767
|
}
|
|
740
|
-
|
|
741
|
-
|
|
768
|
+
}
|
|
769
|
+
return
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function isTranscriptFeature(
|
|
773
|
+
feature: AnnotationFeature,
|
|
774
|
+
session: ApolloSessionModel,
|
|
775
|
+
): boolean {
|
|
776
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
777
|
+
if (!featureTypeOntology) {
|
|
778
|
+
throw new Error('featureTypeOntology is undefined')
|
|
779
|
+
}
|
|
780
|
+
return (
|
|
781
|
+
featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
782
|
+
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')
|
|
783
|
+
)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function isExonFeature(
|
|
787
|
+
feature: AnnotationFeature,
|
|
788
|
+
session: ApolloSessionModel,
|
|
789
|
+
): boolean {
|
|
790
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
791
|
+
if (!featureTypeOntology) {
|
|
792
|
+
throw new Error('featureTypeOntology is undefined')
|
|
793
|
+
}
|
|
794
|
+
return featureTypeOntology.isTypeOf(feature.type, 'exon')
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function isCDSFeature(
|
|
798
|
+
feature: AnnotationFeature,
|
|
799
|
+
session: ApolloSessionModel,
|
|
800
|
+
): boolean {
|
|
801
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
802
|
+
if (!featureTypeOntology) {
|
|
803
|
+
throw new Error('featureTypeOntology is undefined')
|
|
804
|
+
}
|
|
805
|
+
return featureTypeOntology.isTypeOf(feature.type, 'CDS')
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function getContextMenuItems(
|
|
809
|
+
display: LinearApolloDisplayMouseEvents,
|
|
810
|
+
mousePosition: MousePositionWithFeatureAndGlyph,
|
|
811
|
+
): MenuItem[] {
|
|
812
|
+
const {
|
|
813
|
+
apolloInternetAccount: internetAccount,
|
|
814
|
+
apolloHover,
|
|
815
|
+
changeManager,
|
|
816
|
+
regions,
|
|
817
|
+
selectedFeature,
|
|
818
|
+
session,
|
|
819
|
+
} = display
|
|
820
|
+
const [region] = regions
|
|
821
|
+
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
822
|
+
const menuItems: MenuItem[] = []
|
|
823
|
+
const role = internetAccount ? internetAccount.role : 'admin'
|
|
824
|
+
const admin = role === 'admin'
|
|
825
|
+
if (!apolloHover) {
|
|
826
|
+
return menuItems
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
let featuresUnderClick = getFeaturesUnderClick(mousePosition)
|
|
830
|
+
if (isCDSFeature(mousePosition.featureAndGlyphUnderMouse.feature, session)) {
|
|
831
|
+
featuresUnderClick = getFeaturesUnderClick(mousePosition, true)
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
for (const feature of featuresUnderClick) {
|
|
835
|
+
const contextMenuItemsForFeature = boxGlyph.getContextMenuItemsForFeature(
|
|
836
|
+
display,
|
|
837
|
+
feature,
|
|
838
|
+
)
|
|
839
|
+
if (isExonFeature(feature, session)) {
|
|
840
|
+
contextMenuItemsForFeature.push(
|
|
841
|
+
{
|
|
842
|
+
label: 'Merge exons',
|
|
843
|
+
disabled: !admin,
|
|
844
|
+
onClick: () => {
|
|
845
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
846
|
+
(doneCallback) => [
|
|
847
|
+
MergeExons,
|
|
848
|
+
{
|
|
849
|
+
session,
|
|
850
|
+
handleClose: () => {
|
|
851
|
+
doneCallback()
|
|
852
|
+
},
|
|
853
|
+
changeManager,
|
|
854
|
+
sourceFeature: feature,
|
|
855
|
+
sourceAssemblyId: currentAssemblyId,
|
|
856
|
+
selectedFeature,
|
|
857
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
858
|
+
display.setSelectedFeature(feature)
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
],
|
|
862
|
+
)
|
|
863
|
+
},
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
label: 'Split exon',
|
|
867
|
+
disabled: !admin,
|
|
868
|
+
onClick: () => {
|
|
869
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
870
|
+
(doneCallback) => [
|
|
871
|
+
SplitExon,
|
|
872
|
+
{
|
|
873
|
+
session,
|
|
874
|
+
handleClose: () => {
|
|
875
|
+
doneCallback()
|
|
876
|
+
},
|
|
877
|
+
changeManager,
|
|
878
|
+
sourceFeature: feature,
|
|
879
|
+
sourceAssemblyId: currentAssemblyId,
|
|
880
|
+
selectedFeature,
|
|
881
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
882
|
+
display.setSelectedFeature(feature)
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
)
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
)
|
|
742
890
|
}
|
|
743
|
-
if (
|
|
744
|
-
|
|
891
|
+
if (isTranscriptFeature(feature, session)) {
|
|
892
|
+
contextMenuItemsForFeature.push({
|
|
893
|
+
label: 'Merge transcript',
|
|
894
|
+
onClick: () => {
|
|
895
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
896
|
+
(doneCallback) => [
|
|
897
|
+
MergeTranscripts,
|
|
898
|
+
{
|
|
899
|
+
session,
|
|
900
|
+
handleClose: () => {
|
|
901
|
+
doneCallback()
|
|
902
|
+
},
|
|
903
|
+
changeManager,
|
|
904
|
+
sourceFeature: feature,
|
|
905
|
+
sourceAssemblyId: currentAssemblyId,
|
|
906
|
+
selectedFeature,
|
|
907
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
908
|
+
display.setSelectedFeature(feature)
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
],
|
|
912
|
+
)
|
|
913
|
+
},
|
|
914
|
+
})
|
|
915
|
+
if (isSessionModelWithWidgets(session)) {
|
|
916
|
+
contextMenuItemsForFeature.push({
|
|
917
|
+
label: 'Open transcript details',
|
|
918
|
+
onClick: () => {
|
|
919
|
+
const apolloTranscriptWidget = session.addWidget(
|
|
920
|
+
'ApolloTranscriptDetails',
|
|
921
|
+
'apolloTranscriptDetails',
|
|
922
|
+
{
|
|
923
|
+
feature,
|
|
924
|
+
assembly: currentAssemblyId,
|
|
925
|
+
changeManager,
|
|
926
|
+
refName: region.refName,
|
|
927
|
+
},
|
|
928
|
+
)
|
|
929
|
+
session.showWidget(apolloTranscriptWidget)
|
|
930
|
+
},
|
|
931
|
+
})
|
|
932
|
+
}
|
|
745
933
|
}
|
|
934
|
+
menuItems.push({
|
|
935
|
+
label: feature.type,
|
|
936
|
+
subMenu: contextMenuItemsForFeature,
|
|
937
|
+
})
|
|
746
938
|
}
|
|
747
|
-
return
|
|
939
|
+
return menuItems
|
|
748
940
|
}
|
|
749
941
|
|
|
750
942
|
// False positive here, none of these functions use "this"
|
|
751
943
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
752
|
-
const { drawTooltip,
|
|
944
|
+
const { drawTooltip, getContextMenuItemsForFeature, onMouseLeave } = boxGlyph
|
|
753
945
|
/* eslint-enable @typescript-eslint/unbound-method */
|
|
754
946
|
|
|
755
947
|
export const geneGlyph: Glyph = {
|
|
@@ -758,6 +950,7 @@ export const geneGlyph: Glyph = {
|
|
|
758
950
|
drawHover,
|
|
759
951
|
drawTooltip,
|
|
760
952
|
getContextMenuItems,
|
|
953
|
+
getContextMenuItemsForFeature,
|
|
761
954
|
getFeatureFromLayout,
|
|
762
955
|
getRowCount,
|
|
763
956
|
getRowForFeature,
|