@apollo-annotation/jbrowse-plugin-apollo 0.1.0
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/README.md +76 -0
- package/dist/index.esm.js +10248 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +7 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
- package/package.json +130 -0
- package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
- package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
- package/src/ApolloInternetAccount/configSchema.ts +26 -0
- package/src/ApolloInternetAccount/index.ts +2 -0
- package/src/ApolloInternetAccount/model.ts +448 -0
- package/src/ApolloJobModel.ts +117 -0
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
- package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
- package/src/ApolloSequenceAdapter/index.ts +21 -0
- package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
- package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
- package/src/ApolloSixFrameRenderer/index.ts +3 -0
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
- package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
- package/src/ApolloTextSearchAdapter/index.ts +18 -0
- package/src/BackendDrivers/BackendDriver.ts +31 -0
- package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
- package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
- package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
- package/src/BackendDrivers/index.ts +4 -0
- package/src/ChangeManager.ts +148 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
- package/src/LinearApolloDisplay/components/index.ts +1 -0
- package/src/LinearApolloDisplay/configSchema.ts +16 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
- package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
- package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
- package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
- package/src/LinearApolloDisplay/index.ts +2 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
- package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
- package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
- package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
- package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
- package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
- package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
- package/src/LinearApolloDisplay/types.ts +1 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
- package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
- package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
- package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
- package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
- package/src/OntologyManager/OntologyStore/index.ts +526 -0
- package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
- package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
- package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
- package/src/OntologyManager/index.ts +173 -0
- package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
- package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
- package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
- package/src/SixFrameFeatureDisplay/index.ts +2 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
- package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
- package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
- package/src/TabularEditor/HybridGrid/index.ts +1 -0
- package/src/TabularEditor/TabularEditorPane.tsx +34 -0
- package/src/TabularEditor/index.ts +3 -0
- package/src/TabularEditor/model.ts +44 -0
- package/src/TabularEditor/types.ts +3 -0
- package/src/components/AddAssembly.tsx +464 -0
- package/src/components/AddChildFeature.tsx +247 -0
- package/src/components/AddFeature.tsx +252 -0
- package/src/components/CopyFeature.tsx +328 -0
- package/src/components/DeleteAssembly.tsx +185 -0
- package/src/components/DeleteFeature.tsx +90 -0
- package/src/components/Dialog.tsx +47 -0
- package/src/components/DownloadGFF3.tsx +213 -0
- package/src/components/ImportFeatures.tsx +295 -0
- package/src/components/ManageChecks.tsx +280 -0
- package/src/components/ManageUsers.tsx +218 -0
- package/src/components/ModifyFeatureAttribute.tsx +457 -0
- package/src/components/OntologyTermAutocomplete.tsx +240 -0
- package/src/components/OntologyTermMultiSelect.tsx +349 -0
- package/src/components/OpenLocalFile.tsx +178 -0
- package/src/components/ViewChangeLog.tsx +208 -0
- package/src/components/ViewCheckResults.tsx +151 -0
- package/src/components/index.ts +12 -0
- package/src/config.ts +10 -0
- package/src/declare.d.ts +3 -0
- package/src/extensions/annotationFromPileup.ts +208 -0
- package/src/extensions/index.ts +1 -0
- package/src/index.ts +394 -0
- package/src/makeDisplayComponent.tsx +244 -0
- package/src/session/ClientDataStore.ts +282 -0
- package/src/session/index.ts +1 -0
- package/src/session/session.ts +373 -0
- package/src/types.ts +10 -0
- package/src/util/index.ts +31 -0
- package/src/util/loadAssemblyIntoClient.ts +291 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { readConfObject } from '@jbrowse/core/configuration'
|
|
2
|
+
import { AbstractSessionModel } from '@jbrowse/core/util'
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
DialogActions,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogContentText,
|
|
8
|
+
MenuItem,
|
|
9
|
+
Select,
|
|
10
|
+
SelectChangeEvent,
|
|
11
|
+
TextField,
|
|
12
|
+
} from '@mui/material'
|
|
13
|
+
import { AnnotationFeatureI, AnnotationFeatureSnapshot } from 'apollo-mst'
|
|
14
|
+
import { AddFeatureChange } from 'apollo-shared'
|
|
15
|
+
import ObjectID from 'bson-objectid'
|
|
16
|
+
import { IKeyValueMap } from 'mobx'
|
|
17
|
+
import { getSnapshot } from 'mobx-state-tree'
|
|
18
|
+
import React, { useEffect, useState } from 'react'
|
|
19
|
+
|
|
20
|
+
import { ChangeManager } from '../ChangeManager'
|
|
21
|
+
import { ApolloSessionModel } from '../session'
|
|
22
|
+
import { Dialog } from './Dialog'
|
|
23
|
+
|
|
24
|
+
interface CopyFeatureProps {
|
|
25
|
+
session: ApolloSessionModel
|
|
26
|
+
handleClose(): void
|
|
27
|
+
sourceFeature: AnnotationFeatureI
|
|
28
|
+
sourceAssemblyId: string
|
|
29
|
+
changeManager: ChangeManager
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Collection {
|
|
33
|
+
_id: string
|
|
34
|
+
name: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Recursively assign new IDs to a feature
|
|
39
|
+
* @param feature - Parent feature
|
|
40
|
+
* @param featureIds -
|
|
41
|
+
*/
|
|
42
|
+
function generateNewIds(
|
|
43
|
+
// feature: AnnotationFeatureSnapshot,
|
|
44
|
+
feature: AnnotationFeatureSnapshot,
|
|
45
|
+
featureIds: string[],
|
|
46
|
+
): AnnotationFeatureSnapshot {
|
|
47
|
+
const newId = new ObjectID().toHexString()
|
|
48
|
+
featureIds.push(newId)
|
|
49
|
+
|
|
50
|
+
const children: Record<string, AnnotationFeatureSnapshot> = {}
|
|
51
|
+
if (feature.children) {
|
|
52
|
+
for (const child of Object.values(feature.children)) {
|
|
53
|
+
const newChild = generateNewIds(child, featureIds)
|
|
54
|
+
children[newChild._id] = newChild
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const referenceSeq =
|
|
58
|
+
typeof feature.refSeq === 'string'
|
|
59
|
+
? feature.refSeq
|
|
60
|
+
: (feature.refSeq as unknown as ObjectID).toHexString()
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...feature,
|
|
64
|
+
refSeq: referenceSeq,
|
|
65
|
+
children: feature.children && children,
|
|
66
|
+
_id: newId,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function CopyFeature({
|
|
71
|
+
changeManager,
|
|
72
|
+
handleClose,
|
|
73
|
+
session,
|
|
74
|
+
sourceAssemblyId,
|
|
75
|
+
sourceFeature,
|
|
76
|
+
}: CopyFeatureProps) {
|
|
77
|
+
const { assemblyManager, notify } = session as unknown as AbstractSessionModel
|
|
78
|
+
const assemblies = assemblyManager.assemblyList
|
|
79
|
+
|
|
80
|
+
const [selectedAssemblyId, setSelectedAssemblyId] = useState<
|
|
81
|
+
string | undefined
|
|
82
|
+
>(assemblies.find((a) => a.name !== sourceAssemblyId)?.name)
|
|
83
|
+
const [refNames, setRefNames] = useState<Collection[]>([])
|
|
84
|
+
const [selectedRefSeqId, setSelectedRefSeqId] = useState('')
|
|
85
|
+
const [start, setStart] = useState(sourceFeature.start)
|
|
86
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
87
|
+
|
|
88
|
+
async function handleChangeAssembly(e: SelectChangeEvent<string>) {
|
|
89
|
+
setSelectedAssemblyId(e.target.value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
setSelectedRefSeqId('')
|
|
94
|
+
async function getRefNames() {
|
|
95
|
+
if (!selectedAssemblyId) {
|
|
96
|
+
setErrorMessage('No assemblies to copy to')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId)
|
|
100
|
+
if (!assembly) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
const { refNameAliases } = assembly
|
|
104
|
+
if (!refNameAliases) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
const newRefNames = [...Object.entries(refNameAliases)]
|
|
108
|
+
.filter(([id, refName]) => id !== refName)
|
|
109
|
+
.map(([id, refName]) => ({ _id: id, name: refName ?? '' }))
|
|
110
|
+
setRefNames(newRefNames)
|
|
111
|
+
setSelectedRefSeqId(newRefNames[0]?._id || '')
|
|
112
|
+
}
|
|
113
|
+
getRefNames().catch((error) => setErrorMessage(String(error)))
|
|
114
|
+
}, [selectedAssemblyId, assemblyManager])
|
|
115
|
+
|
|
116
|
+
async function handleChangeRefSeq(e: SelectChangeEvent<string>) {
|
|
117
|
+
const refSeq = e.target.value as string
|
|
118
|
+
setSelectedRefSeqId(refSeq)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
122
|
+
if (!selectedAssemblyId) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
event.preventDefault()
|
|
126
|
+
setErrorMessage('')
|
|
127
|
+
const featureLength = sourceFeature.length
|
|
128
|
+
const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId)
|
|
129
|
+
if (!assembly) {
|
|
130
|
+
setErrorMessage(`Assembly not found: ${selectedAssemblyId}.`)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const canonicalRefName = assembly?.getCanonicalRefName(selectedRefSeqId)
|
|
134
|
+
const region = assembly.regions?.find((r) => r.refName === canonicalRefName)
|
|
135
|
+
if (!region) {
|
|
136
|
+
setErrorMessage(`RefSeq not found: ${selectedRefSeqId}.`)
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const newEnd = start + featureLength
|
|
141
|
+
if (newEnd > region.end) {
|
|
142
|
+
setErrorMessage(
|
|
143
|
+
`Feature would extend beyond the bounds of the selected reference sequence. (Feature would end at ${newEnd}, but reference sequence ends at ${region.end})`,
|
|
144
|
+
)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
if (start < region.start) {
|
|
148
|
+
setErrorMessage(
|
|
149
|
+
`Reference sequence starts at ${region.start}, feature cannot start before that.`,
|
|
150
|
+
)
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const featureIds: string[] = []
|
|
155
|
+
// Let's add featureId to each child recursively
|
|
156
|
+
const newFeatureLine = generateNewIds(
|
|
157
|
+
getSnapshot(sourceFeature) as unknown as AnnotationFeatureSnapshot,
|
|
158
|
+
featureIds,
|
|
159
|
+
)
|
|
160
|
+
// Clear possible parentId -attribute.
|
|
161
|
+
const attributeMap: IKeyValueMap<string[]> = {
|
|
162
|
+
...(newFeatureLine.attributes as unknown as IKeyValueMap<string[]>),
|
|
163
|
+
}
|
|
164
|
+
if ('Parent' in attributeMap) {
|
|
165
|
+
delete attributeMap.Parent
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Update gffId value if it's ObjectId
|
|
169
|
+
if (
|
|
170
|
+
newFeatureLine.gffId &&
|
|
171
|
+
ObjectID.isValid(newFeatureLine.gffId.toString())
|
|
172
|
+
) {
|
|
173
|
+
newFeatureLine.gffId = newFeatureLine._id
|
|
174
|
+
}
|
|
175
|
+
newFeatureLine.refSeq = selectedRefSeqId
|
|
176
|
+
const locationMove = start - newFeatureLine.start
|
|
177
|
+
newFeatureLine.start = start
|
|
178
|
+
newFeatureLine.end = start + featureLength
|
|
179
|
+
// Updates children start, end and gffId values
|
|
180
|
+
const updatedChildren = updateRefSeqStartEndAndGffId(
|
|
181
|
+
newFeatureLine,
|
|
182
|
+
locationMove,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const change = new AddFeatureChange({
|
|
186
|
+
changedIds: [newFeatureLine._id],
|
|
187
|
+
typeName: 'AddFeatureChange',
|
|
188
|
+
assembly: selectedAssemblyId,
|
|
189
|
+
addedFeature: {
|
|
190
|
+
_id: newFeatureLine._id,
|
|
191
|
+
refSeq: newFeatureLine.refSeq,
|
|
192
|
+
start: newFeatureLine.start,
|
|
193
|
+
end: newFeatureLine.end,
|
|
194
|
+
type: newFeatureLine.type,
|
|
195
|
+
children: updatedChildren.children as unknown as Record<
|
|
196
|
+
string,
|
|
197
|
+
AnnotationFeatureSnapshot
|
|
198
|
+
>,
|
|
199
|
+
attributes: attributeMap,
|
|
200
|
+
discontinuousLocations: newFeatureLine.discontinuousLocations,
|
|
201
|
+
strand: newFeatureLine.strand,
|
|
202
|
+
score: newFeatureLine.score,
|
|
203
|
+
phase: newFeatureLine.phase,
|
|
204
|
+
},
|
|
205
|
+
copyFeature: true,
|
|
206
|
+
allIds: featureIds,
|
|
207
|
+
})
|
|
208
|
+
await changeManager.submit?.(change)
|
|
209
|
+
|
|
210
|
+
notify('Feature copied successfully', 'success')
|
|
211
|
+
handleClose()
|
|
212
|
+
event.preventDefault()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Recursively loop children and update refSeq, start, end and gffId values
|
|
217
|
+
* @param feature - parent feature
|
|
218
|
+
* @param locationMove - how much location has been moved from original
|
|
219
|
+
* @returns
|
|
220
|
+
*/
|
|
221
|
+
function updateRefSeqStartEndAndGffId(
|
|
222
|
+
feature: AnnotationFeatureSnapshot,
|
|
223
|
+
locationMove: number,
|
|
224
|
+
): AnnotationFeatureSnapshot {
|
|
225
|
+
const children: Record<string, AnnotationFeatureSnapshot> = {}
|
|
226
|
+
if (feature.children) {
|
|
227
|
+
for (const child of Object.values(feature.children)) {
|
|
228
|
+
const newChild = updateRefSeqStartEndAndGffId(child, locationMove)
|
|
229
|
+
// Update gffId value if it's ObjectId
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
231
|
+
if (ObjectID.isValid(newChild.gffId!.toString())) {
|
|
232
|
+
newChild.gffId = newChild._id
|
|
233
|
+
}
|
|
234
|
+
newChild.refSeq = selectedRefSeqId
|
|
235
|
+
newChild.start = newChild.start + locationMove
|
|
236
|
+
newChild.end = newChild.end + locationMove
|
|
237
|
+
children[newChild._id] = newChild
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const refSeq =
|
|
241
|
+
typeof feature.refSeq === 'string'
|
|
242
|
+
? feature.refSeq
|
|
243
|
+
: (feature.refSeq as unknown as ObjectID).toHexString()
|
|
244
|
+
|
|
245
|
+
const id =
|
|
246
|
+
typeof feature._id === 'string'
|
|
247
|
+
? feature._id
|
|
248
|
+
: (feature._id as unknown as ObjectID).toHexString()
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
...feature,
|
|
252
|
+
refSeq,
|
|
253
|
+
children: feature.children && children,
|
|
254
|
+
_id: id,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<Dialog
|
|
260
|
+
open
|
|
261
|
+
title="Copy features and annotations"
|
|
262
|
+
handleClose={handleClose}
|
|
263
|
+
maxWidth={false}
|
|
264
|
+
data-testid="copy-feature"
|
|
265
|
+
>
|
|
266
|
+
<form onSubmit={onSubmit}>
|
|
267
|
+
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
|
|
268
|
+
<DialogContentText>Target assembly</DialogContentText>
|
|
269
|
+
<Select
|
|
270
|
+
labelId="label"
|
|
271
|
+
value={selectedAssemblyId}
|
|
272
|
+
onChange={handleChangeAssembly}
|
|
273
|
+
>
|
|
274
|
+
{assemblies
|
|
275
|
+
.filter((option) => option.name !== sourceAssemblyId)
|
|
276
|
+
.map((option) => (
|
|
277
|
+
<MenuItem key={option.name} value={option.name}>
|
|
278
|
+
{readConfObject(option, 'displayName')}
|
|
279
|
+
</MenuItem>
|
|
280
|
+
))}
|
|
281
|
+
</Select>
|
|
282
|
+
<DialogContentText>Target reference sequence</DialogContentText>
|
|
283
|
+
<Select
|
|
284
|
+
labelId="label"
|
|
285
|
+
value={selectedRefSeqId}
|
|
286
|
+
onChange={handleChangeRefSeq}
|
|
287
|
+
>
|
|
288
|
+
{refNames.map((option) => (
|
|
289
|
+
<MenuItem key={option._id} value={option._id}>
|
|
290
|
+
{option.name}
|
|
291
|
+
</MenuItem>
|
|
292
|
+
))}
|
|
293
|
+
</Select>
|
|
294
|
+
<DialogContentText>
|
|
295
|
+
Start position in target reference sequence
|
|
296
|
+
</DialogContentText>
|
|
297
|
+
<TextField
|
|
298
|
+
margin="dense"
|
|
299
|
+
type="number"
|
|
300
|
+
fullWidth
|
|
301
|
+
variant="outlined"
|
|
302
|
+
value={start}
|
|
303
|
+
onChange={(e) => {
|
|
304
|
+
setStart(Number(e.target.value))
|
|
305
|
+
}}
|
|
306
|
+
/>
|
|
307
|
+
</DialogContent>
|
|
308
|
+
<DialogActions>
|
|
309
|
+
<Button
|
|
310
|
+
disabled={!selectedAssemblyId || !selectedRefSeqId || !start}
|
|
311
|
+
variant="contained"
|
|
312
|
+
type="submit"
|
|
313
|
+
>
|
|
314
|
+
Submit
|
|
315
|
+
</Button>
|
|
316
|
+
<Button variant="outlined" type="submit" onClick={handleClose}>
|
|
317
|
+
Cancel
|
|
318
|
+
</Button>
|
|
319
|
+
</DialogActions>
|
|
320
|
+
</form>
|
|
321
|
+
{errorMessage ? (
|
|
322
|
+
<DialogContent>
|
|
323
|
+
<DialogContentText color="error">{errorMessage}</DialogContentText>
|
|
324
|
+
</DialogContent>
|
|
325
|
+
) : null}
|
|
326
|
+
</Dialog>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Checkbox,
|
|
5
|
+
DialogActions,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogContentText,
|
|
8
|
+
FormControlLabel,
|
|
9
|
+
FormGroup,
|
|
10
|
+
MenuItem,
|
|
11
|
+
Select,
|
|
12
|
+
SelectChangeEvent,
|
|
13
|
+
} from '@mui/material'
|
|
14
|
+
import { DeleteAssemblyChange } from 'apollo-shared'
|
|
15
|
+
import { getRoot } from 'mobx-state-tree'
|
|
16
|
+
import React, { useEffect, useState } from 'react'
|
|
17
|
+
|
|
18
|
+
import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
|
|
19
|
+
import {
|
|
20
|
+
ApolloInternetAccount,
|
|
21
|
+
CollaborationServerDriver,
|
|
22
|
+
} from '../BackendDrivers'
|
|
23
|
+
import { ChangeManager } from '../ChangeManager'
|
|
24
|
+
import { ApolloSessionModel } from '../session'
|
|
25
|
+
import { ApolloRootModel } from '../types'
|
|
26
|
+
import { Dialog } from './Dialog'
|
|
27
|
+
|
|
28
|
+
interface DeleteAssemblyProps {
|
|
29
|
+
session: ApolloSessionModel
|
|
30
|
+
handleClose(): void
|
|
31
|
+
changeManager: ChangeManager
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function DeleteAssembly({
|
|
35
|
+
changeManager,
|
|
36
|
+
handleClose,
|
|
37
|
+
session,
|
|
38
|
+
}: DeleteAssemblyProps) {
|
|
39
|
+
const { internetAccounts } = getRoot<ApolloRootModel>(session)
|
|
40
|
+
const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
|
|
41
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
42
|
+
const [confirmDelete, setconfirmDelete] = useState(false)
|
|
43
|
+
const [submitted, setSubmitted] = useState(false)
|
|
44
|
+
const apolloInternetAccounts = internetAccounts.filter(
|
|
45
|
+
(ia) => ia.type === 'ApolloInternetAccount',
|
|
46
|
+
) as ApolloInternetAccountModel[]
|
|
47
|
+
if (apolloInternetAccounts.length === 0) {
|
|
48
|
+
throw new Error('No Apollo internet account found')
|
|
49
|
+
}
|
|
50
|
+
const [selectedInternetAccount, setSelectedInternetAccount] = useState(
|
|
51
|
+
apolloInternetAccounts[0],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const { collaborationServerDriver } = session.apolloDataStore as {
|
|
55
|
+
collaborationServerDriver: CollaborationServerDriver
|
|
56
|
+
getInternetAccount(
|
|
57
|
+
assemblyName?: string,
|
|
58
|
+
internetAccountId?: string,
|
|
59
|
+
): ApolloInternetAccount
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const assemblies = collaborationServerDriver.getAssemblies()
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (assemblies.length > 0 && selectedAssembly === undefined) {
|
|
66
|
+
setSelectedAssembly(assemblies[0])
|
|
67
|
+
}
|
|
68
|
+
}, [assemblies, selectedAssembly])
|
|
69
|
+
|
|
70
|
+
function handleChangeInternetAccount(e: SelectChangeEvent<string>) {
|
|
71
|
+
setSubmitted(false)
|
|
72
|
+
const newlySelectedInternetAccount = apolloInternetAccounts.find(
|
|
73
|
+
(ia) => ia.internetAccountId === e.target.value,
|
|
74
|
+
)
|
|
75
|
+
if (!newlySelectedInternetAccount) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Could not find internetAccount with ID "${e.target.value}"`,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
setSelectedInternetAccount(newlySelectedInternetAccount)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleChangeAssembly(e: SelectChangeEvent<string>) {
|
|
84
|
+
const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
|
|
85
|
+
setSelectedAssembly(newAssembly)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
setSubmitted(true)
|
|
91
|
+
setErrorMessage('')
|
|
92
|
+
if (!selectedAssembly) {
|
|
93
|
+
setErrorMessage('Must select assembly!')
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
const change = new DeleteAssemblyChange({
|
|
97
|
+
typeName: 'DeleteAssemblyChange',
|
|
98
|
+
assembly: selectedAssembly.name,
|
|
99
|
+
})
|
|
100
|
+
await changeManager.submit?.(change, {
|
|
101
|
+
internetAccountId: selectedInternetAccount.internetAccountId,
|
|
102
|
+
})
|
|
103
|
+
handleClose()
|
|
104
|
+
event.preventDefault()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Dialog
|
|
109
|
+
open
|
|
110
|
+
title="Delete Assembly"
|
|
111
|
+
handleClose={handleClose}
|
|
112
|
+
maxWidth={false}
|
|
113
|
+
data-testid="delete-assembly"
|
|
114
|
+
>
|
|
115
|
+
<form onSubmit={onSubmit}>
|
|
116
|
+
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
|
|
117
|
+
{apolloInternetAccounts.length > 1 ? (
|
|
118
|
+
<>
|
|
119
|
+
<DialogContentText>Select account</DialogContentText>
|
|
120
|
+
<Select
|
|
121
|
+
value={selectedInternetAccount.internetAccountId}
|
|
122
|
+
onChange={handleChangeInternetAccount}
|
|
123
|
+
disabled={submitted && !errorMessage}
|
|
124
|
+
>
|
|
125
|
+
{internetAccounts.map((option) => (
|
|
126
|
+
<MenuItem key={option.id} value={option.internetAccountId}>
|
|
127
|
+
{option.name}
|
|
128
|
+
</MenuItem>
|
|
129
|
+
))}
|
|
130
|
+
</Select>
|
|
131
|
+
</>
|
|
132
|
+
) : null}
|
|
133
|
+
<DialogContentText>Select assembly</DialogContentText>
|
|
134
|
+
<Select
|
|
135
|
+
labelId="label"
|
|
136
|
+
value={selectedAssembly?.name ?? ''}
|
|
137
|
+
onChange={handleChangeAssembly}
|
|
138
|
+
disabled={assemblies.length === 0}
|
|
139
|
+
>
|
|
140
|
+
{assemblies.map((option) => (
|
|
141
|
+
<MenuItem key={option.name} value={option.name}>
|
|
142
|
+
{option.displayName ?? option.name}
|
|
143
|
+
</MenuItem>
|
|
144
|
+
))}
|
|
145
|
+
</Select>
|
|
146
|
+
<DialogContentText>
|
|
147
|
+
<strong style={{ color: 'red' }}>
|
|
148
|
+
NOTE: All assembly data will be deleted and this operation cannot
|
|
149
|
+
be undone!
|
|
150
|
+
</strong>
|
|
151
|
+
</DialogContentText>
|
|
152
|
+
<FormGroup>
|
|
153
|
+
<FormControlLabel
|
|
154
|
+
control={
|
|
155
|
+
<Checkbox
|
|
156
|
+
checked={confirmDelete}
|
|
157
|
+
onChange={() => setconfirmDelete(!confirmDelete)}
|
|
158
|
+
/>
|
|
159
|
+
}
|
|
160
|
+
label="I understand that all assembly data will be deleted"
|
|
161
|
+
/>
|
|
162
|
+
</FormGroup>
|
|
163
|
+
</DialogContent>
|
|
164
|
+
|
|
165
|
+
<DialogActions>
|
|
166
|
+
<Button
|
|
167
|
+
disabled={!selectedAssembly || !confirmDelete}
|
|
168
|
+
variant="contained"
|
|
169
|
+
type="submit"
|
|
170
|
+
>
|
|
171
|
+
Delete
|
|
172
|
+
</Button>
|
|
173
|
+
<Button variant="outlined" type="submit" onClick={handleClose}>
|
|
174
|
+
Cancel
|
|
175
|
+
</Button>
|
|
176
|
+
</DialogActions>
|
|
177
|
+
</form>
|
|
178
|
+
{errorMessage ? (
|
|
179
|
+
<DialogContent>
|
|
180
|
+
<DialogContentText color="error">{errorMessage}</DialogContentText>
|
|
181
|
+
</DialogContent>
|
|
182
|
+
) : null}
|
|
183
|
+
</Dialog>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { AbstractSessionModel } from '@jbrowse/core/util'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DialogActions,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogContentText,
|
|
7
|
+
} from '@mui/material'
|
|
8
|
+
import { AnnotationFeatureI } from 'apollo-mst'
|
|
9
|
+
import { DeleteFeatureChange } from 'apollo-shared'
|
|
10
|
+
import { getSnapshot } from 'mobx-state-tree'
|
|
11
|
+
import React, { useState } from 'react'
|
|
12
|
+
|
|
13
|
+
import { ChangeManager } from '../ChangeManager'
|
|
14
|
+
import { ApolloSessionModel } from '../session'
|
|
15
|
+
import { Dialog } from './Dialog'
|
|
16
|
+
|
|
17
|
+
interface DeleteFeatureProps {
|
|
18
|
+
session: ApolloSessionModel
|
|
19
|
+
handleClose(): void
|
|
20
|
+
sourceFeature: AnnotationFeatureI
|
|
21
|
+
sourceAssemblyId: string
|
|
22
|
+
changeManager: ChangeManager
|
|
23
|
+
selectedFeature?: AnnotationFeatureI
|
|
24
|
+
setSelectedFeature(feature?: AnnotationFeatureI): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function DeleteFeature({
|
|
28
|
+
changeManager,
|
|
29
|
+
handleClose,
|
|
30
|
+
selectedFeature,
|
|
31
|
+
session,
|
|
32
|
+
setSelectedFeature,
|
|
33
|
+
sourceAssemblyId,
|
|
34
|
+
sourceFeature,
|
|
35
|
+
}: DeleteFeatureProps) {
|
|
36
|
+
const { notify } = session as unknown as AbstractSessionModel
|
|
37
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
38
|
+
|
|
39
|
+
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
40
|
+
event.preventDefault()
|
|
41
|
+
setErrorMessage('')
|
|
42
|
+
if (selectedFeature?._id === sourceFeature._id) {
|
|
43
|
+
setSelectedFeature()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Delete features
|
|
47
|
+
const change = new DeleteFeatureChange({
|
|
48
|
+
changedIds: [sourceFeature._id],
|
|
49
|
+
typeName: 'DeleteFeatureChange',
|
|
50
|
+
assembly: sourceAssemblyId,
|
|
51
|
+
deletedFeature: getSnapshot(sourceFeature),
|
|
52
|
+
parentFeatureId: sourceFeature.parent?._id,
|
|
53
|
+
})
|
|
54
|
+
await changeManager.submit?.(change)
|
|
55
|
+
notify('Feature deleted successfully', 'success')
|
|
56
|
+
handleClose()
|
|
57
|
+
event.preventDefault()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Dialog
|
|
62
|
+
open
|
|
63
|
+
title="Delete feature"
|
|
64
|
+
handleClose={handleClose}
|
|
65
|
+
maxWidth={false}
|
|
66
|
+
data-testid="delete-feature"
|
|
67
|
+
>
|
|
68
|
+
<form onSubmit={onSubmit}>
|
|
69
|
+
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
|
|
70
|
+
<DialogContentText>
|
|
71
|
+
Are you sure you want to delete the selected feature?
|
|
72
|
+
</DialogContentText>
|
|
73
|
+
</DialogContent>
|
|
74
|
+
<DialogActions>
|
|
75
|
+
<Button variant="contained" type="submit">
|
|
76
|
+
Yes
|
|
77
|
+
</Button>
|
|
78
|
+
<Button variant="outlined" type="submit" onClick={handleClose}>
|
|
79
|
+
Cancel
|
|
80
|
+
</Button>
|
|
81
|
+
</DialogActions>
|
|
82
|
+
</form>
|
|
83
|
+
{errorMessage ? (
|
|
84
|
+
<DialogContent>
|
|
85
|
+
<DialogContentText color="error">{errorMessage}</DialogContentText>
|
|
86
|
+
</DialogContent>
|
|
87
|
+
) : null}
|
|
88
|
+
</Dialog>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Dialog as JBDialog } from '@jbrowse/core/ui'
|
|
2
|
+
import CloseIcon from '@mui/icons-material/Close'
|
|
3
|
+
import { DialogProps, DialogTitle, IconButton } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import { makeStyles } from 'tss-react/mui'
|
|
7
|
+
|
|
8
|
+
const useStyles = makeStyles()((theme) => ({
|
|
9
|
+
dialogTitle: {
|
|
10
|
+
background: theme.palette.primary.main,
|
|
11
|
+
color: theme.palette.primary.contrastText,
|
|
12
|
+
padding: theme.spacing(2),
|
|
13
|
+
},
|
|
14
|
+
closeButton: {
|
|
15
|
+
position: 'absolute',
|
|
16
|
+
right: theme.spacing(1),
|
|
17
|
+
top: theme.spacing(1.5),
|
|
18
|
+
color: theme.palette.primary.contrastText,
|
|
19
|
+
},
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
interface Props extends DialogProps {
|
|
23
|
+
handleClose(): void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Dialog = observer(function JBrowseDialog(props: Props) {
|
|
27
|
+
const { classes } = useStyles()
|
|
28
|
+
const { handleClose, title, ...other } = props
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<JBDialog
|
|
32
|
+
{...other}
|
|
33
|
+
header={
|
|
34
|
+
<>
|
|
35
|
+
<DialogTitle className={classes.dialogTitle}>{title}</DialogTitle>
|
|
36
|
+
<IconButton
|
|
37
|
+
aria-label="close"
|
|
38
|
+
onClick={handleClose}
|
|
39
|
+
className={classes.closeButton}
|
|
40
|
+
>
|
|
41
|
+
<CloseIcon />
|
|
42
|
+
</IconButton>
|
|
43
|
+
</>
|
|
44
|
+
}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
})
|