@apollo-annotation/jbrowse-plugin-apollo 0.3.8 → 0.3.9
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 +10932 -10932
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +10845 -10846
- 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 +18619 -21342
- 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 +7 -7
- package/src/ApolloInternetAccount/model.ts +81 -63
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +49 -18
- package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
- package/src/ChangeManager.ts +3 -1
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +39 -203
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +6 -102
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +31 -230
- package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +62 -386
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +6 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +122 -70
- package/src/components/AddAssembly.tsx +33 -37
- package/src/components/AddFeature.tsx +21 -18
- package/src/components/AddRefSeqAliases.tsx +56 -42
- package/src/components/CopyFeature.tsx +1 -1
- package/src/components/CreateApolloAnnotation.tsx +22 -10
- package/src/components/DeleteAssembly.tsx +2 -9
- package/src/components/DownloadGFF3.tsx +2 -2
- package/src/components/ManageChecks.tsx +2 -9
- package/src/components/ManageUsers.tsx +23 -22
- package/src/components/OntologyTermAutocomplete.tsx +1 -8
- package/src/components/ViewChangeLog.tsx +25 -50
- package/src/components/ViewCheckResults.tsx +1 -7
- package/src/config.ts +3 -3
- package/src/index.ts +17 -16
- package/src/makeDisplayComponent.tsx +9 -13
- package/src/session/ClientDataStore.ts +32 -14
- package/src/session/session.ts +19 -27
- package/src/util/glyphUtils.ts +178 -1
- package/src/util/loadAssemblyIntoClient.ts +3 -2
|
@@ -2,13 +2,16 @@ import {
|
|
|
2
2
|
type AnnotationFeature,
|
|
3
3
|
type TranscriptPartCoding,
|
|
4
4
|
} from '@apollo-annotation/mst'
|
|
5
|
+
import { type BaseDisplayModel } from '@jbrowse/core/pluggableElementTypes'
|
|
5
6
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
6
7
|
import {
|
|
7
8
|
type AbstractSessionModel,
|
|
9
|
+
getContainingView,
|
|
8
10
|
getFrame,
|
|
9
11
|
intersection2,
|
|
10
12
|
measureText,
|
|
11
13
|
} from '@jbrowse/core/util'
|
|
14
|
+
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
12
15
|
import { alpha } from '@mui/material'
|
|
13
16
|
import equal from 'fast-deep-equal/es6'
|
|
14
17
|
import { getSnapshot } from 'mobx-state-tree'
|
|
@@ -18,12 +21,19 @@ import { FilterTranscripts } from '../../components/FilterTranscripts'
|
|
|
18
21
|
import {
|
|
19
22
|
type MousePosition,
|
|
20
23
|
type MousePositionWithFeature,
|
|
24
|
+
getAdjacentExons,
|
|
21
25
|
getContextMenuItemsForFeature,
|
|
22
26
|
getMinAndMaxPx,
|
|
23
27
|
getOverlappingEdge,
|
|
24
28
|
getRelatedFeatures,
|
|
29
|
+
getStreamIcon,
|
|
30
|
+
isCDSFeature,
|
|
31
|
+
isExonFeature,
|
|
25
32
|
isMousePositionWithFeature,
|
|
33
|
+
// isTranscriptFeature,
|
|
26
34
|
isSelectedFeature,
|
|
35
|
+
navToFeatureCenter,
|
|
36
|
+
selectFeatureAndOpenWidget,
|
|
27
37
|
} from '../../util'
|
|
28
38
|
import { type LinearApolloSixFrameDisplay } from '../stateModel'
|
|
29
39
|
import { type LinearApolloSixFrameDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
@@ -109,14 +119,14 @@ function drawTextLabels(
|
|
|
109
119
|
for (let i = labelArray.length - 1; i >= 0; --i) {
|
|
110
120
|
const label = labelArray[i]
|
|
111
121
|
ctx.fillStyle = label.color
|
|
112
|
-
const labelRowX =
|
|
122
|
+
const labelRowX = label.x + 1
|
|
113
123
|
const labelRowY = label.y + label.h
|
|
114
124
|
const textWidth = measureText(label.text, 10)
|
|
115
125
|
if (label.isSelected) {
|
|
116
|
-
ctx.clearRect(labelRowX - 5, labelRowY, textWidth + 10, label.h)
|
|
117
126
|
ctx.font = 'bold '.concat(font)
|
|
118
127
|
}
|
|
119
128
|
if (label.text) {
|
|
129
|
+
ctx.clearRect(labelRowX - 5, labelRowY, textWidth + 10, label.h)
|
|
120
130
|
ctx.fillText(label.text, labelRowX, labelRowY + 11, textWidth)
|
|
121
131
|
ctx.font = font
|
|
122
132
|
}
|
|
@@ -341,6 +351,30 @@ function draw(
|
|
|
341
351
|
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
342
352
|
cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
343
353
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
354
|
+
|
|
355
|
+
// Draw lines to connect CDS features with shared mRNA parent
|
|
356
|
+
if (counter > 1) {
|
|
357
|
+
// Mid-point for intron line "hat"
|
|
358
|
+
const midPoint: [number, number] = [
|
|
359
|
+
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
360
|
+
Math.max(
|
|
361
|
+
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
362
|
+
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
363
|
+
),
|
|
364
|
+
]
|
|
365
|
+
ctx.strokeStyle = 'rgb(0, 128, 128)'
|
|
366
|
+
ctx.beginPath()
|
|
367
|
+
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
368
|
+
ctx.lineTo(...midPoint)
|
|
369
|
+
ctx.stroke()
|
|
370
|
+
ctx.moveTo(...midPoint)
|
|
371
|
+
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
372
|
+
ctx.stroke()
|
|
373
|
+
}
|
|
374
|
+
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
375
|
+
prevCDSTop = cdsTop + rowHeight / 2
|
|
376
|
+
counter += 1
|
|
377
|
+
|
|
344
378
|
if (cdsWidthPx > 2) {
|
|
345
379
|
ctx.clearRect(
|
|
346
380
|
cdsStartPx + 1,
|
|
@@ -365,31 +399,6 @@ function draw(
|
|
|
365
399
|
cdsHeight - 2,
|
|
366
400
|
)
|
|
367
401
|
|
|
368
|
-
// Draw lines to connect CDS features with shared mRNA parent
|
|
369
|
-
if (counter > 1) {
|
|
370
|
-
// Mid-point for intron line "hat"
|
|
371
|
-
const midPoint: [number, number] = [
|
|
372
|
-
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
373
|
-
Math.max(
|
|
374
|
-
frame < 0
|
|
375
|
-
? rowHeight * featureLabelSpacer * highestRow + 1
|
|
376
|
-
: 1, // Avoid render ceiling
|
|
377
|
-
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
378
|
-
),
|
|
379
|
-
]
|
|
380
|
-
ctx.strokeStyle = 'rgb(0, 128, 128)'
|
|
381
|
-
ctx.beginPath()
|
|
382
|
-
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
383
|
-
ctx.lineTo(...midPoint)
|
|
384
|
-
ctx.stroke()
|
|
385
|
-
ctx.moveTo(...midPoint)
|
|
386
|
-
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
387
|
-
ctx.stroke()
|
|
388
|
-
}
|
|
389
|
-
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
390
|
-
prevCDSTop = cdsTop + rowHeight / 2
|
|
391
|
-
counter += 1
|
|
392
|
-
|
|
393
402
|
if (topFill && bottomFill) {
|
|
394
403
|
ctx.fillStyle = topFill
|
|
395
404
|
ctx.fillRect(
|
|
@@ -508,43 +517,42 @@ function drawHover(
|
|
|
508
517
|
let counter = 1
|
|
509
518
|
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
510
519
|
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
520
|
+
const minX =
|
|
521
|
+
(lgv.bpToPx({
|
|
522
|
+
refName,
|
|
523
|
+
coord: cds.min,
|
|
524
|
+
regionNumber: layoutIndex,
|
|
525
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
526
|
+
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
527
|
+
const frame = getFrame(cds.min, cds.max, strand ?? 1, cds.phase)
|
|
528
|
+
const frameAdjust =
|
|
529
|
+
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
530
|
+
const cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
531
|
+
if (counter > 1) {
|
|
532
|
+
// Mid-point for intron line "hat"
|
|
533
|
+
const midPoint: [number, number] = [
|
|
534
|
+
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
535
|
+
Math.max(
|
|
536
|
+
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
537
|
+
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
538
|
+
),
|
|
539
|
+
]
|
|
540
|
+
ctx.strokeStyle = 'rgb(0, 0, 0)'
|
|
541
|
+
ctx.lineWidth = 2
|
|
542
|
+
ctx.beginPath()
|
|
543
|
+
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
544
|
+
ctx.lineTo(...midPoint)
|
|
545
|
+
ctx.stroke()
|
|
546
|
+
ctx.moveTo(...midPoint)
|
|
547
|
+
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
548
|
+
ctx.stroke()
|
|
549
|
+
}
|
|
550
|
+
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
551
|
+
prevCDSTop = cdsTop + rowHeight / 2
|
|
552
|
+
counter += 1
|
|
511
553
|
if (cdsWidthPx > 2) {
|
|
512
|
-
const minX =
|
|
513
|
-
(lgv.bpToPx({
|
|
514
|
-
refName,
|
|
515
|
-
coord: cds.min,
|
|
516
|
-
regionNumber: layoutIndex,
|
|
517
|
-
})?.offsetPx ?? 0) - offsetPx
|
|
518
|
-
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
519
|
-
const frame = getFrame(cds.min, cds.max, strand ?? 1, cds.phase)
|
|
520
|
-
const frameAdjust =
|
|
521
|
-
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
522
|
-
const cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
523
554
|
ctx.fillStyle = 'rgba(255,0,0,0.6)'
|
|
524
555
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
525
|
-
|
|
526
|
-
if (counter > 1) {
|
|
527
|
-
// Mid-point for intron line "hat"
|
|
528
|
-
const midPoint: [number, number] = [
|
|
529
|
-
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
530
|
-
Math.max(
|
|
531
|
-
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
532
|
-
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
533
|
-
),
|
|
534
|
-
]
|
|
535
|
-
ctx.strokeStyle = 'rgb(0, 0, 0)'
|
|
536
|
-
ctx.lineWidth = 2
|
|
537
|
-
ctx.beginPath()
|
|
538
|
-
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
539
|
-
ctx.lineTo(...midPoint)
|
|
540
|
-
ctx.stroke()
|
|
541
|
-
ctx.moveTo(...midPoint)
|
|
542
|
-
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
543
|
-
ctx.stroke()
|
|
544
|
-
}
|
|
545
|
-
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
546
|
-
prevCDSTop = cdsTop + rowHeight / 2
|
|
547
|
-
counter += 1
|
|
548
556
|
}
|
|
549
557
|
}
|
|
550
558
|
}
|
|
@@ -848,8 +856,13 @@ function getContextMenuItems(
|
|
|
848
856
|
}
|
|
849
857
|
if (isMousePositionWithFeature(mousePosition)) {
|
|
850
858
|
const { bp, feature } = mousePosition
|
|
851
|
-
|
|
852
|
-
|
|
859
|
+
let featuresUnderClick = getRelatedFeatures(feature, bp)
|
|
860
|
+
if (isCDSFeature(feature, session)) {
|
|
861
|
+
featuresUnderClick = getRelatedFeatures(feature, bp, true)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
for (const feature of featuresUnderClick) {
|
|
865
|
+
const featureID: string | undefined = feature.attributes
|
|
853
866
|
.get('gff_id')
|
|
854
867
|
?.toString()
|
|
855
868
|
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
@@ -857,9 +870,48 @@ function getContextMenuItems(
|
|
|
857
870
|
}
|
|
858
871
|
const contextMenuItemsForFeature = getContextMenuItemsForFeature(
|
|
859
872
|
display,
|
|
860
|
-
|
|
873
|
+
feature,
|
|
861
874
|
)
|
|
862
|
-
if (
|
|
875
|
+
if (isExonFeature(feature, session)) {
|
|
876
|
+
const adjacentExons = getAdjacentExons(
|
|
877
|
+
feature,
|
|
878
|
+
display,
|
|
879
|
+
mousePosition,
|
|
880
|
+
session,
|
|
881
|
+
)
|
|
882
|
+
const lgv = getContainingView(
|
|
883
|
+
display as BaseDisplayModel,
|
|
884
|
+
) as unknown as LinearGenomeViewModel
|
|
885
|
+
if (adjacentExons.upstream) {
|
|
886
|
+
const exon = adjacentExons.upstream
|
|
887
|
+
contextMenuItemsForFeature.push({
|
|
888
|
+
label: 'Go to upstream exon',
|
|
889
|
+
icon: getStreamIcon(
|
|
890
|
+
feature.strand,
|
|
891
|
+
true,
|
|
892
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
893
|
+
),
|
|
894
|
+
onClick: () => {
|
|
895
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
896
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
897
|
+
},
|
|
898
|
+
})
|
|
899
|
+
}
|
|
900
|
+
if (adjacentExons.downstream) {
|
|
901
|
+
const exon = adjacentExons.downstream
|
|
902
|
+
contextMenuItemsForFeature.push({
|
|
903
|
+
label: 'Go to downstream exon',
|
|
904
|
+
icon: getStreamIcon(
|
|
905
|
+
feature.strand,
|
|
906
|
+
false,
|
|
907
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
908
|
+
),
|
|
909
|
+
onClick: () => {
|
|
910
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
911
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
912
|
+
},
|
|
913
|
+
})
|
|
914
|
+
}
|
|
863
915
|
contextMenuItemsForFeature.push(
|
|
864
916
|
{
|
|
865
917
|
label: 'Merge exons',
|
|
@@ -874,7 +926,7 @@ function getContextMenuItems(
|
|
|
874
926
|
doneCallback()
|
|
875
927
|
},
|
|
876
928
|
changeManager,
|
|
877
|
-
sourceFeature:
|
|
929
|
+
sourceFeature: feature,
|
|
878
930
|
sourceAssemblyId: currentAssemblyId,
|
|
879
931
|
selectedFeature,
|
|
880
932
|
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
@@ -898,7 +950,7 @@ function getContextMenuItems(
|
|
|
898
950
|
doneCallback()
|
|
899
951
|
},
|
|
900
952
|
changeManager,
|
|
901
|
-
sourceFeature:
|
|
953
|
+
sourceFeature: feature,
|
|
902
954
|
sourceAssemblyId: currentAssemblyId,
|
|
903
955
|
selectedFeature,
|
|
904
956
|
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
@@ -911,7 +963,7 @@ function getContextMenuItems(
|
|
|
911
963
|
},
|
|
912
964
|
)
|
|
913
965
|
}
|
|
914
|
-
if (featureTypeOntology.isTypeOf(
|
|
966
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'gene')) {
|
|
915
967
|
contextMenuItemsForFeature.push({
|
|
916
968
|
label: 'Filter alternate transcripts',
|
|
917
969
|
onClick: () => {
|
|
@@ -922,7 +974,7 @@ function getContextMenuItems(
|
|
|
922
974
|
handleClose: () => {
|
|
923
975
|
doneCallback()
|
|
924
976
|
},
|
|
925
|
-
sourceFeature:
|
|
977
|
+
sourceFeature: feature,
|
|
926
978
|
filteredTranscripts: getSnapshot(filteredTranscripts),
|
|
927
979
|
onUpdate: (forms: string[]) => {
|
|
928
980
|
display.updateFilteredTranscripts(forms)
|
|
@@ -934,7 +986,7 @@ function getContextMenuItems(
|
|
|
934
986
|
})
|
|
935
987
|
}
|
|
936
988
|
menuItems.push({
|
|
937
|
-
label:
|
|
989
|
+
label: feature.type,
|
|
938
990
|
subMenu: contextMenuItemsForFeature,
|
|
939
991
|
})
|
|
940
992
|
}
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
} from '@mui/material'
|
|
40
40
|
import ObjectID from 'bson-objectid'
|
|
41
41
|
import { getRoot } from 'mobx-state-tree'
|
|
42
|
-
import React, {
|
|
42
|
+
import React, { useState } from 'react'
|
|
43
43
|
import { makeStyles } from 'tss-react/mui'
|
|
44
44
|
|
|
45
45
|
import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
|
|
@@ -149,25 +149,8 @@ export function AddAssembly({
|
|
|
149
149
|
const [fastaGziIndexUrl, setFastaGziIndexUrl] = useState<string>('')
|
|
150
150
|
|
|
151
151
|
const [loading, setLoading] = useState(false)
|
|
152
|
-
const [
|
|
153
|
-
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
setFastaIndexUrl(fastaUrl ? `${fastaUrl}.fai` : '')
|
|
156
|
-
}, [fastaUrl])
|
|
157
|
-
|
|
158
|
-
useEffect(() => {
|
|
159
|
-
setFastaGziIndexUrl(fastaUrl ? `${fastaUrl}.gzi` : '')
|
|
160
|
-
}, [fastaUrl])
|
|
161
|
-
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
if (sequenceIsEditable || fileType === FileType.GFF3) {
|
|
164
|
-
setIsGzip(
|
|
165
|
-
fastaFile?.name.toLocaleLowerCase().endsWith('.gz') ? true : false,
|
|
166
|
-
)
|
|
167
|
-
} else {
|
|
168
|
-
setIsGzip(true)
|
|
169
|
-
}
|
|
170
|
-
}, [fastaFile, sequenceIsEditable, fileType])
|
|
152
|
+
const [fastaGzipChecked, setFastaGzipChecked] = useState<boolean>(false)
|
|
153
|
+
const [gff3GzipChecked, setGff3GzipChecked] = useState<boolean>(false)
|
|
171
154
|
|
|
172
155
|
function checkAssemblyName(assembly: string) {
|
|
173
156
|
const { assemblies } = session as unknown as AbstractSessionModel
|
|
@@ -194,10 +177,13 @@ export function AddAssembly({
|
|
|
194
177
|
const uri = url.href
|
|
195
178
|
const formData = new FormData()
|
|
196
179
|
let filename = file.name
|
|
180
|
+
const isGzip =
|
|
181
|
+
fileType === FileType.BGZIP_FASTA ||
|
|
182
|
+
(fileType === FileType.FASTA &&
|
|
183
|
+
(!sequenceIsEditable || fastaGzipChecked)) ||
|
|
184
|
+
(fileType === FileType.GFF3 && gff3GzipChecked)
|
|
197
185
|
|
|
198
|
-
if (
|
|
199
|
-
filename = `${filename}.txt`
|
|
200
|
-
} else if (isGzip && !file.name.toLocaleLowerCase().endsWith('.gz')) {
|
|
186
|
+
if (isGzip && !file.name.toLocaleLowerCase().endsWith('.gz')) {
|
|
201
187
|
filename = `${filename}.gz`
|
|
202
188
|
} else if (!isGzip && file.name.toLocaleLowerCase().endsWith('.gz')) {
|
|
203
189
|
filename = `${filename}.txt`
|
|
@@ -370,11 +356,6 @@ export function AddAssembly({
|
|
|
370
356
|
if (newExpanded) {
|
|
371
357
|
setExpanded(panel)
|
|
372
358
|
}
|
|
373
|
-
if (panel === 'panelGffInput') {
|
|
374
|
-
setIsGzip(false)
|
|
375
|
-
} else {
|
|
376
|
-
setIsGzip(true)
|
|
377
|
-
}
|
|
378
359
|
}
|
|
379
360
|
|
|
380
361
|
return (
|
|
@@ -497,12 +478,12 @@ export function AddAssembly({
|
|
|
497
478
|
data-testid="fasta-is-gzip-checkbox"
|
|
498
479
|
control={
|
|
499
480
|
<Checkbox
|
|
500
|
-
checked={
|
|
481
|
+
checked={!sequenceIsEditable || fastaGzipChecked}
|
|
501
482
|
onChange={() => {
|
|
502
483
|
if (sequenceIsEditable) {
|
|
503
|
-
|
|
484
|
+
setFastaGzipChecked(!fastaGzipChecked)
|
|
504
485
|
} else {
|
|
505
|
-
|
|
486
|
+
setFastaGzipChecked(true)
|
|
506
487
|
}
|
|
507
488
|
}}
|
|
508
489
|
disabled={!sequenceIsEditable}
|
|
@@ -533,7 +514,13 @@ export function AddAssembly({
|
|
|
533
514
|
onChange={(
|
|
534
515
|
e: React.ChangeEvent<HTMLInputElement>,
|
|
535
516
|
) => {
|
|
536
|
-
|
|
517
|
+
const file = e.target.files?.item(0)
|
|
518
|
+
if (file) {
|
|
519
|
+
setFastaFile(file)
|
|
520
|
+
if (file.name.endsWith('.gz')) {
|
|
521
|
+
setFastaGzipChecked(true)
|
|
522
|
+
}
|
|
523
|
+
}
|
|
537
524
|
}}
|
|
538
525
|
disabled={submitted && !errorMessage}
|
|
539
526
|
/>
|
|
@@ -606,7 +593,10 @@ export function AddAssembly({
|
|
|
606
593
|
onChange={(
|
|
607
594
|
e: React.ChangeEvent<HTMLInputElement>,
|
|
608
595
|
) => {
|
|
609
|
-
|
|
596
|
+
const { value } = e.target
|
|
597
|
+
setFastaUrl(value)
|
|
598
|
+
setFastaIndexUrl(value ? `${value}.fai` : '')
|
|
599
|
+
setFastaGziIndexUrl(value ? `${value}.gzi` : '')
|
|
610
600
|
}}
|
|
611
601
|
disabled={submitted && !errorMessage}
|
|
612
602
|
slotProps={{
|
|
@@ -727,8 +717,14 @@ export function AddAssembly({
|
|
|
727
717
|
type="file"
|
|
728
718
|
disabled={submitted && !errorMessage}
|
|
729
719
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
730
|
-
|
|
731
|
-
|
|
720
|
+
const file = e.target.files?.item(0)
|
|
721
|
+
if (file) {
|
|
722
|
+
setFastaFile(file)
|
|
723
|
+
setFileType(FileType.GFF3)
|
|
724
|
+
if (file.name.endsWith('.gz')) {
|
|
725
|
+
setGff3GzipChecked(true)
|
|
726
|
+
}
|
|
727
|
+
}
|
|
732
728
|
}}
|
|
733
729
|
/>
|
|
734
730
|
<FormGroup style={{ display: 'grid' }}>
|
|
@@ -748,9 +744,9 @@ export function AddAssembly({
|
|
|
748
744
|
data-testid="gff3-is-gzip-checkbox"
|
|
749
745
|
control={
|
|
750
746
|
<Checkbox
|
|
751
|
-
checked={
|
|
747
|
+
checked={gff3GzipChecked}
|
|
752
748
|
onChange={() => {
|
|
753
|
-
|
|
749
|
+
setGff3GzipChecked(!gff3GzipChecked)
|
|
754
750
|
}}
|
|
755
751
|
disabled={submitted && !errorMessage}
|
|
756
752
|
/>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
3
2
|
|
|
4
3
|
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
5
4
|
import { AddFeatureChange } from '@apollo-annotation/shared'
|
|
@@ -26,6 +25,7 @@ import {
|
|
|
26
25
|
import ObjectID from 'bson-objectid'
|
|
27
26
|
import React, { useState } from 'react'
|
|
28
27
|
|
|
28
|
+
import { CollaborationServerDriver } from '../BackendDrivers'
|
|
29
29
|
import { type ChangeManager } from '../ChangeManager'
|
|
30
30
|
import { isOntologyClass } from '../OntologyManager'
|
|
31
31
|
import { type ApolloSessionModel } from '../session'
|
|
@@ -96,30 +96,32 @@ export function AddFeature({
|
|
|
96
96
|
const [end, setEnd] = useState(String(region.end))
|
|
97
97
|
const [start, setStart] = useState(String(region.start + 1))
|
|
98
98
|
const [type, setType] = useState<NewFeature>(NewFeature.GENE_AND_SUBFEATURES)
|
|
99
|
-
const [customType, setCustomType] = useState<string>()
|
|
99
|
+
const [customType, setCustomType] = useState<string>('')
|
|
100
100
|
const [strand, setStrand] = useState<1 | -1 | undefined>()
|
|
101
101
|
const [errorMessage, setErrorMessage] = useState('')
|
|
102
102
|
|
|
103
|
-
function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
103
|
+
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
104
104
|
event.preventDefault()
|
|
105
105
|
setErrorMessage('')
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
107
|
+
const backendDriver = session.apolloDataStore.getBackendDriver(
|
|
108
|
+
region.assemblyName,
|
|
109
|
+
)
|
|
110
|
+
if (!backendDriver) {
|
|
111
|
+
setErrorMessage('No backend driver found')
|
|
112
|
+
return
|
|
116
113
|
}
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
let refSeqId = region.refName
|
|
115
|
+
if (backendDriver instanceof CollaborationServerDriver) {
|
|
116
|
+
const backendRefSeqId = await backendDriver.getRefSeqId(
|
|
117
|
+
region.assemblyName,
|
|
118
|
+
region.refName,
|
|
121
119
|
)
|
|
122
|
-
|
|
120
|
+
if (!backendRefSeqId) {
|
|
121
|
+
setErrorMessage(`Could not find refSeq for "${region.refName}"`)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
refSeqId = backendRefSeqId
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
if (type === NewFeature.GENE_AND_SUBFEATURES) {
|
|
@@ -248,6 +250,7 @@ export function AddFeature({
|
|
|
248
250
|
maxWidth={false}
|
|
249
251
|
data-testid="add-feature-dialog"
|
|
250
252
|
>
|
|
253
|
+
{/* eslint-disable-next-line @typescript-eslint/no-misused-promises */}
|
|
251
254
|
<form onSubmit={onSubmit} data-testid="submit-form">
|
|
252
255
|
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
|
|
253
256
|
<TextField
|
|
@@ -344,7 +347,7 @@ export function AddFeature({
|
|
|
344
347
|
session={session}
|
|
345
348
|
ontologyName="Sequence Ontology"
|
|
346
349
|
style={{ width: 170 }}
|
|
347
|
-
value=
|
|
350
|
+
value={customType}
|
|
348
351
|
filterTerms={isOntologyClass}
|
|
349
352
|
renderInput={(params) => (
|
|
350
353
|
<TextField
|
|
@@ -16,12 +16,19 @@ import {
|
|
|
16
16
|
Select,
|
|
17
17
|
type SelectChangeEvent,
|
|
18
18
|
} from '@mui/material'
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
DataGrid,
|
|
21
|
+
type GridColDef,
|
|
22
|
+
type GridRowModel,
|
|
23
|
+
type GridRowSelectionModel,
|
|
24
|
+
} from '@mui/x-data-grid'
|
|
25
|
+
import { observer } from 'mobx-react'
|
|
20
26
|
import React, { useEffect, useRef, useState } from 'react'
|
|
21
27
|
|
|
22
28
|
import {
|
|
23
29
|
type ApolloInternetAccount,
|
|
24
30
|
type CollaborationServerDriver,
|
|
31
|
+
type RefNameAliases,
|
|
25
32
|
} from '../BackendDrivers'
|
|
26
33
|
import { type ChangeManager } from '../ChangeManager'
|
|
27
34
|
import { type ApolloSessionModel } from '../session'
|
|
@@ -30,7 +37,7 @@ import { Dialog } from './Dialog'
|
|
|
30
37
|
|
|
31
38
|
const columns: GridColDef[] = [
|
|
32
39
|
{ field: 'refName', headerName: 'Ref Name' },
|
|
33
|
-
{ field: 'aliases', headerName: 'Aliases', editable: true },
|
|
40
|
+
{ field: 'aliases', headerName: 'Aliases', editable: true, flex: 1 },
|
|
34
41
|
]
|
|
35
42
|
|
|
36
43
|
interface AddChildFeatureProps {
|
|
@@ -44,7 +51,7 @@ const isGeneratedObjectId = (key: string): boolean => {
|
|
|
44
51
|
return pattern.test(key)
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
export function AddRefSeqAliases({
|
|
54
|
+
export const AddRefSeqAliases = observer(function AddRefSeqAliases({
|
|
48
55
|
changeManager,
|
|
49
56
|
handleClose,
|
|
50
57
|
session,
|
|
@@ -75,44 +82,50 @@ export function AddRefSeqAliases({
|
|
|
75
82
|
const assemblies = collaborationServerDriver.getAssemblies()
|
|
76
83
|
|
|
77
84
|
useEffect(() => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!selectedAssembly.refNames) {
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
const refNameAliasess = selectedAssembly.refNameAliases
|
|
93
|
-
for (const key in refNameAliasess) {
|
|
94
|
-
const value = refNameAliasess[key]
|
|
95
|
-
if (!value || isGeneratedObjectId(key)) {
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
98
|
-
if (initialMap.has(value)) {
|
|
99
|
-
const aliases = initialMap.get(value) ?? []
|
|
100
|
-
initialMap.set(value, [...aliases, key])
|
|
101
|
-
} else {
|
|
102
|
-
initialMap.set(value, [key])
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
setRefNameAliasMap(initialMap)
|
|
85
|
+
if (assemblies.length > 0) {
|
|
86
|
+
setSelectedAssembly(assemblies[0])
|
|
87
|
+
collaborationServerDriver
|
|
88
|
+
.getRefNameAliases(assemblies[0].name)
|
|
89
|
+
.then((refNameAliases) => {
|
|
90
|
+
initializeRefNameAliasMap(refNameAliases)
|
|
91
|
+
})
|
|
92
|
+
.catch(() => {
|
|
93
|
+
setRefNameAliasMap(new Map())
|
|
94
|
+
setErrorMessage('Error fetching refName aliases for assembly')
|
|
95
|
+
})
|
|
106
96
|
}
|
|
97
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
98
|
+
}, [])
|
|
107
99
|
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
const initializeRefNameAliasMap = (refNameAliasesList: RefNameAliases[]) => {
|
|
101
|
+
const initialMap = new Map<string, string[]>()
|
|
102
|
+
for (const refNameAliases of refNameAliasesList) {
|
|
103
|
+
const key = refNameAliases.refName
|
|
104
|
+
if (isGeneratedObjectId(key)) {
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
initialMap.set(key, refNameAliases.aliases)
|
|
108
|
+
}
|
|
109
|
+
setRefNameAliasMap(initialMap)
|
|
110
|
+
}
|
|
110
111
|
|
|
111
112
|
const handleChangeAssembly = (e: SelectChangeEvent) => {
|
|
112
113
|
const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
|
|
113
114
|
setSelectedAssembly(newAssembly)
|
|
115
|
+
if (!newAssembly?.name) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
collaborationServerDriver
|
|
119
|
+
.getRefNameAliases(newAssembly.name)
|
|
120
|
+
.then((refNameAliases) => {
|
|
121
|
+
initializeRefNameAliasMap(refNameAliases)
|
|
122
|
+
setErrorMessage('')
|
|
123
|
+
})
|
|
124
|
+
.catch(() => {
|
|
125
|
+
setRefNameAliasMap(new Map())
|
|
126
|
+
setErrorMessage('Error fetching refName aliases for assembly')
|
|
127
|
+
})
|
|
114
128
|
setEnableSubmit(false)
|
|
115
|
-
setErrorMessage('')
|
|
116
129
|
if (fileRef.current) {
|
|
117
130
|
fileRef.current.value = ''
|
|
118
131
|
}
|
|
@@ -145,11 +158,12 @@ export function AddRefSeqAliases({
|
|
|
145
158
|
})
|
|
146
159
|
}
|
|
147
160
|
|
|
148
|
-
const rowSelectionChange = (
|
|
149
|
-
|
|
161
|
+
const rowSelectionChange = (gridRowSelectionModel: GridRowSelectionModel) => {
|
|
162
|
+
const { ids } = gridRowSelectionModel
|
|
163
|
+
if (ids.size > 0) {
|
|
150
164
|
setEnableSubmit(true)
|
|
151
|
-
const selectedRows = ids.flatMap((id) =>
|
|
152
|
-
getTableRows().filter((row) => row.id === id),
|
|
165
|
+
const selectedRows = [...ids.values()].flatMap((id) =>
|
|
166
|
+
getTableRows().filter((row) => String(row.id) === String(id)),
|
|
153
167
|
)
|
|
154
168
|
setSelectedRows(selectedRows)
|
|
155
169
|
} else {
|
|
@@ -222,6 +236,7 @@ export function AddRefSeqAliases({
|
|
|
222
236
|
label="Assembly"
|
|
223
237
|
value={selectedAssembly?.name ?? ''}
|
|
224
238
|
onChange={handleChangeAssembly}
|
|
239
|
+
style={{ minWidth: 150 }}
|
|
225
240
|
>
|
|
226
241
|
{assemblies.map((option) => (
|
|
227
242
|
<MenuItem key={option.name} value={option.name}>
|
|
@@ -255,11 +270,10 @@ export function AddRefSeqAliases({
|
|
|
255
270
|
},
|
|
256
271
|
}}
|
|
257
272
|
pageSizeOptions={[5, 10]}
|
|
258
|
-
onRowSelectionModelChange={
|
|
259
|
-
rowSelectionChange(ids as unknown as number[])
|
|
260
|
-
}}
|
|
273
|
+
onRowSelectionModelChange={rowSelectionChange}
|
|
261
274
|
processRowUpdate={processRowUpdate}
|
|
262
275
|
checkboxSelection
|
|
276
|
+
disableRowSelectionExcludeModel
|
|
263
277
|
></DataGrid>
|
|
264
278
|
</div>
|
|
265
279
|
) : null}
|
|
@@ -284,4 +298,4 @@ export function AddRefSeqAliases({
|
|
|
284
298
|
) : null}
|
|
285
299
|
</Dialog>
|
|
286
300
|
)
|
|
287
|
-
}
|
|
301
|
+
})
|