@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 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 +11212 -10483
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +11251 -10509
- 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 +7726 -9014
- 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 +18 -18
- package/src/ApolloInternetAccount/model.ts +123 -70
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +72 -20
- package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
- package/src/ChangeManager.ts +36 -14
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +72 -234
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +23 -131
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +279 -217
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
- package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
- package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +157 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +101 -38
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +334 -262
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
- package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/components/AddAssembly.tsx +34 -38
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddChildFeature.tsx +5 -2
- package/src/components/AddFeature.tsx +30 -21
- package/src/components/AddRefSeqAliases.tsx +64 -50
- package/src/components/CopyFeature.tsx +4 -2
- package/src/components/CreateApolloAnnotation.tsx +22 -9
- package/src/components/DeleteAssembly.tsx +3 -10
- package/src/components/DownloadGFF3.tsx +2 -2
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +6 -6
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +3 -10
- package/src/components/ManageUsers.tsx +23 -22
- package/src/components/MergeTranscripts.tsx +12 -15
- package/src/components/OntologyTermAutocomplete.tsx +1 -8
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/ViewChangeLog.tsx +25 -50
- package/src/components/ViewCheckResults.tsx +2 -8
- package/src/components/index.ts +1 -0
- package/src/config.ts +6 -0
- package/src/index.ts +53 -115
- package/src/makeDisplayComponent.tsx +9 -14
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/ClientDataStore.ts +32 -14
- package/src/session/session.ts +159 -121
- package/src/util/annotationFeatureUtils.ts +15 -21
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +329 -0
- package/src/util/loadAssemblyIntoClient.ts +3 -2
- package/src/util/mouseEventsUtils.ts +32 -0
package/src/ChangeManager.ts
CHANGED
|
@@ -30,6 +30,7 @@ export class ChangeManager {
|
|
|
30
30
|
constructor(private dataStore: ClientDataStore & IAnyStateTreeNode) {}
|
|
31
31
|
|
|
32
32
|
recentChanges: Change[] = []
|
|
33
|
+
undoneChanges: Change[] = []
|
|
33
34
|
|
|
34
35
|
async submit(change: Change, opts: SubmitOpts = {}) {
|
|
35
36
|
const {
|
|
@@ -41,10 +42,15 @@ export class ChangeManager {
|
|
|
41
42
|
const session = getSession(this.dataStore)
|
|
42
43
|
const controller = new AbortController()
|
|
43
44
|
|
|
44
|
-
const { jobsManager } = getSession(
|
|
45
|
+
const { jobsManager, isLocked } = getSession(
|
|
45
46
|
this.dataStore,
|
|
46
47
|
) as unknown as ApolloSessionModel
|
|
47
48
|
|
|
49
|
+
if (isLocked) {
|
|
50
|
+
session.notify('Cannot submit changes in locked mode')
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
const job = {
|
|
49
55
|
name: change.typeName,
|
|
50
56
|
statusMessage: 'Pre-validating',
|
|
@@ -90,7 +96,7 @@ export class ChangeManager {
|
|
|
90
96
|
)
|
|
91
97
|
if (!results2.ok) {
|
|
92
98
|
// notify of invalid change and revert
|
|
93
|
-
await this.
|
|
99
|
+
await this.undo(change)
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
if (submitToBackend) {
|
|
@@ -100,7 +106,9 @@ export class ChangeManager {
|
|
|
100
106
|
// submit to driver
|
|
101
107
|
const { collaborationServerDriver, getBackendDriver } = this.dataStore
|
|
102
108
|
const backendDriver = isAssemblySpecificChange(change)
|
|
103
|
-
?
|
|
109
|
+
? // for assembly-specific change, fall back in case it's an
|
|
110
|
+
// add-assembly change, since that won't exist in the driver yet
|
|
111
|
+
getBackendDriver(change.assembly) ?? collaborationServerDriver
|
|
104
112
|
: collaborationServerDriver
|
|
105
113
|
let backendResult: ValidationResultSet
|
|
106
114
|
try {
|
|
@@ -111,7 +119,7 @@ export class ChangeManager {
|
|
|
111
119
|
}
|
|
112
120
|
console.error(error)
|
|
113
121
|
session.notify(String(error), 'error')
|
|
114
|
-
await this.
|
|
122
|
+
await this.undo(change, false)
|
|
115
123
|
return
|
|
116
124
|
}
|
|
117
125
|
if (!backendResult.ok) {
|
|
@@ -120,15 +128,15 @@ export class ChangeManager {
|
|
|
120
128
|
jobsManager.abortJob(job.name, msg)
|
|
121
129
|
}
|
|
122
130
|
session.notify(msg, 'error')
|
|
123
|
-
await this.
|
|
131
|
+
await this.undo(change, false)
|
|
124
132
|
return
|
|
125
133
|
}
|
|
126
134
|
if (change.notification) {
|
|
127
135
|
session.notify(change.notification, 'success')
|
|
128
136
|
}
|
|
129
137
|
if (addToRecents) {
|
|
130
|
-
// Push the change into array
|
|
131
138
|
this.recentChanges.push(change)
|
|
139
|
+
this.undoneChanges = []
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -137,22 +145,36 @@ export class ChangeManager {
|
|
|
137
145
|
}
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
async
|
|
148
|
+
async undo(change: Change, submitToBackend = true) {
|
|
141
149
|
const inverseChange = change.getInverse()
|
|
142
150
|
const opts = { submitToBackend, addToRecents: false }
|
|
143
151
|
return this.submit(inverseChange, opts)
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
async redo(change: Change, submitToBackend = true) {
|
|
155
|
+
const opts = { submitToBackend, addToRecents: false }
|
|
156
|
+
return this.submit(change, opts)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async undoLastChange() {
|
|
160
|
+
const session = getSession(this.dataStore)
|
|
150
161
|
const lastChange = this.recentChanges.pop()
|
|
151
162
|
if (!lastChange) {
|
|
152
|
-
|
|
153
|
-
|
|
163
|
+
session.notify('No changes to undo!', 'info')
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
this.undoneChanges.push(lastChange)
|
|
167
|
+
return this.undo(lastChange)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async redoLastChange() {
|
|
171
|
+
const session = getSession(this.dataStore)
|
|
172
|
+
const lastChange = this.undoneChanges.pop()
|
|
173
|
+
if (!lastChange) {
|
|
174
|
+
session.notify('No changes to redo!', 'info')
|
|
154
175
|
return
|
|
155
176
|
}
|
|
156
|
-
|
|
177
|
+
this.recentChanges.push(lastChange)
|
|
178
|
+
return this.redo(lastChange)
|
|
157
179
|
}
|
|
158
180
|
}
|
|
@@ -58,7 +58,7 @@ export const ApolloTranscriptDetailsWidget = observer(
|
|
|
58
58
|
model: ApolloTranscriptDetailsWidgetState
|
|
59
59
|
}) {
|
|
60
60
|
const { classes } = useStyles()
|
|
61
|
-
const DEFAULT_PANELS = ['summary', 'location'
|
|
61
|
+
const DEFAULT_PANELS = ['summary', 'location']
|
|
62
62
|
const [panelState, setPanelState] = useState<string[]>(DEFAULT_PANELS)
|
|
63
63
|
|
|
64
64
|
const { model } = props
|
|
@@ -106,10 +106,53 @@ export const ApolloTranscriptDetailsWidget = observer(
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const
|
|
110
|
-
'Apollo-TranscriptDetailsCustomComponent',
|
|
109
|
+
const CustomComponentInsideSummary = pluginManager.evaluateExtensionPoint(
|
|
110
|
+
'Apollo-TranscriptDetailsCustomComponent-InsideSummary',
|
|
111
111
|
NoOpCustomComponent,
|
|
112
|
-
|
|
112
|
+
{ feature, session },
|
|
113
|
+
) as React.ElementType<CustomComponentProps>
|
|
114
|
+
|
|
115
|
+
const CustomComponentAfterSummary = pluginManager.evaluateExtensionPoint(
|
|
116
|
+
'Apollo-TranscriptDetailsCustomComponent-AfterSummary',
|
|
117
|
+
NoOpCustomComponent,
|
|
118
|
+
{ feature, session },
|
|
119
|
+
) as React.ElementType<CustomComponentProps>
|
|
120
|
+
|
|
121
|
+
const CustomComponentInsideLocation = pluginManager.evaluateExtensionPoint(
|
|
122
|
+
'Apollo-TranscriptDetailsCustomComponent-InsideLocation',
|
|
123
|
+
NoOpCustomComponent,
|
|
124
|
+
{ feature, session },
|
|
125
|
+
) as React.ElementType<CustomComponentProps>
|
|
126
|
+
|
|
127
|
+
const CustomComponentAfterLocation = pluginManager.evaluateExtensionPoint(
|
|
128
|
+
'Apollo-TranscriptDetailsCustomComponent-AfterLocation',
|
|
129
|
+
NoOpCustomComponent,
|
|
130
|
+
{ feature, session },
|
|
131
|
+
) as React.ElementType<CustomComponentProps>
|
|
132
|
+
|
|
133
|
+
const CustomComponentInsideAttributes =
|
|
134
|
+
pluginManager.evaluateExtensionPoint(
|
|
135
|
+
'Apollo-TranscriptDetailsCustomComponent-InsideAttributes',
|
|
136
|
+
NoOpCustomComponent,
|
|
137
|
+
{ feature, session },
|
|
138
|
+
) as React.ElementType<CustomComponentProps>
|
|
139
|
+
|
|
140
|
+
const CustomComponentAfterAttributes = pluginManager.evaluateExtensionPoint(
|
|
141
|
+
'Apollo-TranscriptDetailsCustomComponent-AfterAttributes',
|
|
142
|
+
NoOpCustomComponent,
|
|
143
|
+
{ feature, session },
|
|
144
|
+
) as React.ElementType<CustomComponentProps>
|
|
145
|
+
|
|
146
|
+
const CustomComponentInsideSequence = pluginManager.evaluateExtensionPoint(
|
|
147
|
+
'Apollo-TranscriptDetailsCustomComponent-InsideSequence',
|
|
148
|
+
NoOpCustomComponent,
|
|
149
|
+
{ feature, session },
|
|
150
|
+
) as React.ElementType<CustomComponentProps>
|
|
151
|
+
|
|
152
|
+
const CustomComponentAfterSequence = pluginManager.evaluateExtensionPoint(
|
|
153
|
+
'Apollo-TranscriptDetailsCustomComponent-AfterSequence',
|
|
154
|
+
NoOpCustomComponent,
|
|
155
|
+
{ feature, session },
|
|
113
156
|
) as React.ElementType<CustomComponentProps>
|
|
114
157
|
|
|
115
158
|
return (
|
|
@@ -131,9 +174,10 @@ export const ApolloTranscriptDetailsWidget = observer(
|
|
|
131
174
|
</StyledAccordionSummary>
|
|
132
175
|
<AccordionDetails>
|
|
133
176
|
<TranscriptWidgetSummary feature={feature} refName={refName} />
|
|
177
|
+
<CustomComponentInsideSummary session={session} feature={feature} />
|
|
134
178
|
</AccordionDetails>
|
|
135
179
|
</Accordion>
|
|
136
|
-
<
|
|
180
|
+
<CustomComponentAfterSummary session={session} feature={feature} />
|
|
137
181
|
<Accordion
|
|
138
182
|
style={{ marginTop: 5 }}
|
|
139
183
|
expanded={panelState.includes('location')}
|
|
@@ -157,8 +201,13 @@ export const ApolloTranscriptDetailsWidget = observer(
|
|
|
157
201
|
session={apolloSession}
|
|
158
202
|
assembly={currentAssembly._id || ''}
|
|
159
203
|
/>
|
|
204
|
+
<CustomComponentInsideLocation
|
|
205
|
+
session={session}
|
|
206
|
+
feature={feature}
|
|
207
|
+
/>
|
|
160
208
|
</AccordionDetails>
|
|
161
209
|
</Accordion>
|
|
210
|
+
<CustomComponentAfterLocation session={session} feature={feature} />
|
|
162
211
|
<Accordion
|
|
163
212
|
style={{ marginTop: 5 }}
|
|
164
213
|
expanded={panelState.includes('attrs')}
|
|
@@ -189,8 +238,13 @@ export const ApolloTranscriptDetailsWidget = observer(
|
|
|
189
238
|
assembly={currentAssembly._id || ''}
|
|
190
239
|
editable={editable}
|
|
191
240
|
/>
|
|
241
|
+
<CustomComponentInsideAttributes
|
|
242
|
+
session={session}
|
|
243
|
+
feature={feature}
|
|
244
|
+
/>
|
|
192
245
|
</AccordionDetails>
|
|
193
246
|
</Accordion>
|
|
247
|
+
<CustomComponentAfterAttributes session={session} feature={feature} />
|
|
194
248
|
<Accordion
|
|
195
249
|
style={{ marginTop: 5 }}
|
|
196
250
|
expanded={panelState.includes('sequence')}
|
|
@@ -216,8 +270,13 @@ export const ApolloTranscriptDetailsWidget = observer(
|
|
|
216
270
|
refName={refName}
|
|
217
271
|
/>
|
|
218
272
|
)}
|
|
273
|
+
<CustomComponentInsideSequence
|
|
274
|
+
session={session}
|
|
275
|
+
feature={feature}
|
|
276
|
+
/>
|
|
219
277
|
</AccordionDetails>
|
|
220
278
|
</Accordion>
|
|
279
|
+
<CustomComponentAfterSequence feature={feature} session={session} />
|
|
221
280
|
</div>
|
|
222
281
|
)
|
|
223
282
|
},
|
|
@@ -66,7 +66,7 @@ export const BasicInformation = observer(function BasicInformation({
|
|
|
66
66
|
return changeManager.submit(change)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function handleStartChange(newStart: number) {
|
|
69
|
+
function handleStartChange(newStart: number): boolean {
|
|
70
70
|
newStart--
|
|
71
71
|
const change = new LocationStartChange({
|
|
72
72
|
typeName: 'LocationStartChange',
|
|
@@ -76,10 +76,11 @@ export const BasicInformation = observer(function BasicInformation({
|
|
|
76
76
|
newStart,
|
|
77
77
|
assembly,
|
|
78
78
|
})
|
|
79
|
-
|
|
79
|
+
void changeManager.submit(change)
|
|
80
|
+
return true
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
function handleEndChange(newEnd: number) {
|
|
83
|
+
function handleEndChange(newEnd: number): boolean {
|
|
83
84
|
const change = new LocationEndChange({
|
|
84
85
|
typeName: 'LocationEndChange',
|
|
85
86
|
changedIds: [_id],
|
|
@@ -88,7 +89,8 @@ export const BasicInformation = observer(function BasicInformation({
|
|
|
88
89
|
newEnd,
|
|
89
90
|
assembly,
|
|
90
91
|
})
|
|
91
|
-
|
|
92
|
+
void changeManager.submit(change)
|
|
93
|
+
return true
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
async function fetchValidTerms(
|
|
@@ -15,7 +15,7 @@ interface NumberTextFieldProps
|
|
|
15
15
|
| 'error'
|
|
16
16
|
| 'helperText'
|
|
17
17
|
> {
|
|
18
|
-
onChangeCommitted(newValue: number):
|
|
18
|
+
onChangeCommitted(newValue: number): boolean
|
|
19
19
|
value: unknown
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -65,7 +65,10 @@ export const NumberTextField = observer(function NumberTextField({
|
|
|
65
65
|
if (Number.isNaN(valueAsNumber)) {
|
|
66
66
|
setValue(String(initialValue))
|
|
67
67
|
} else {
|
|
68
|
-
onChangeCommitted(valueAsNumber)
|
|
68
|
+
const success = onChangeCommitted(valueAsNumber)
|
|
69
|
+
if (!success) {
|
|
70
|
+
setValue(String(initialValue))
|
|
71
|
+
}
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
}}
|
|
@@ -28,10 +28,18 @@ type SegmentListType = 'CDS' | 'cDNA' | 'genomic' | 'protein'
|
|
|
28
28
|
|
|
29
29
|
interface SequenceSegment {
|
|
30
30
|
type: SegmentType
|
|
31
|
-
|
|
31
|
+
sequence: string
|
|
32
32
|
locs: { min: number; max: number }[]
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function getSequenceLength(segments: SequenceSegment[]): number {
|
|
36
|
+
let length = 0
|
|
37
|
+
for (const segment of segments) {
|
|
38
|
+
length += segment.sequence.length
|
|
39
|
+
}
|
|
40
|
+
return length
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
function getSequenceSegments(
|
|
36
44
|
segmentType: SegmentListType,
|
|
37
45
|
feature: AnnotationFeature,
|
|
@@ -57,51 +65,20 @@ function getSequenceSegments(
|
|
|
57
65
|
: loc.type
|
|
58
66
|
const previousSegment = segments.at(-1)
|
|
59
67
|
if (!previousSegment) {
|
|
60
|
-
const sequenceLines = splitStringIntoChunks(
|
|
61
|
-
sequence,
|
|
62
|
-
SEQUENCE_WRAP_LENGTH,
|
|
63
|
-
)
|
|
64
68
|
segments.push({
|
|
65
69
|
type,
|
|
66
|
-
|
|
70
|
+
sequence,
|
|
67
71
|
locs: [{ min: loc.min, max: loc.max }],
|
|
68
72
|
})
|
|
69
73
|
continue
|
|
70
74
|
}
|
|
71
75
|
if (previousSegment.type === type) {
|
|
72
|
-
|
|
73
|
-
previousSegment.sequenceLines
|
|
74
|
-
const newSequence = previousSegmentFollowingLines.join('') + sequence
|
|
75
|
-
previousSegment.sequenceLines = [
|
|
76
|
-
previousSegmentFirstLine,
|
|
77
|
-
...splitStringIntoChunks(newSequence, SEQUENCE_WRAP_LENGTH),
|
|
78
|
-
]
|
|
76
|
+
previousSegment.sequence += sequence
|
|
79
77
|
previousSegment.locs.push({ min: loc.min, max: loc.max })
|
|
80
78
|
} else {
|
|
81
|
-
const count = segments.reduce(
|
|
82
|
-
(accumulator, currentSegment) =>
|
|
83
|
-
accumulator +
|
|
84
|
-
currentSegment.sequenceLines.reduce(
|
|
85
|
-
(subAccumulator, currentLine) =>
|
|
86
|
-
subAccumulator + currentLine.length,
|
|
87
|
-
0,
|
|
88
|
-
),
|
|
89
|
-
0,
|
|
90
|
-
)
|
|
91
|
-
const previousLineLength = count % SEQUENCE_WRAP_LENGTH
|
|
92
|
-
const newSegmentFirstLineLength =
|
|
93
|
-
SEQUENCE_WRAP_LENGTH - previousLineLength
|
|
94
|
-
const newSegmentFirstLine = sequence.slice(
|
|
95
|
-
0,
|
|
96
|
-
newSegmentFirstLineLength,
|
|
97
|
-
)
|
|
98
|
-
const newSegmentRemainderLines = splitStringIntoChunks(
|
|
99
|
-
sequence.slice(newSegmentFirstLineLength),
|
|
100
|
-
SEQUENCE_WRAP_LENGTH,
|
|
101
|
-
)
|
|
102
79
|
segments.push({
|
|
103
80
|
type,
|
|
104
|
-
|
|
81
|
+
sequence,
|
|
105
82
|
locs: [{ min: loc.min, max: loc.max }],
|
|
106
83
|
})
|
|
107
84
|
}
|
|
@@ -113,17 +90,14 @@ function getSequenceSegments(
|
|
|
113
90
|
const [firstLocation] = cdsLocations
|
|
114
91
|
const locs: { min: number; max: number }[] = []
|
|
115
92
|
for (const loc of firstLocation) {
|
|
116
|
-
|
|
93
|
+
let locSeq = getSequence(loc.min, loc.max)
|
|
94
|
+
if (strand === -1) {
|
|
95
|
+
locSeq = revcom(locSeq)
|
|
96
|
+
}
|
|
97
|
+
wholeSequence += locSeq
|
|
117
98
|
locs.push({ min: loc.min, max: loc.max })
|
|
118
99
|
}
|
|
119
|
-
|
|
120
|
-
wholeSequence = revcom(wholeSequence)
|
|
121
|
-
}
|
|
122
|
-
const sequenceLines = splitStringIntoChunks(
|
|
123
|
-
wholeSequence,
|
|
124
|
-
SEQUENCE_WRAP_LENGTH,
|
|
125
|
-
)
|
|
126
|
-
segments.push({ type: 'CDS', sequenceLines, locs })
|
|
100
|
+
segments.push({ type: 'CDS', sequence: wholeSequence, locs })
|
|
127
101
|
return segments
|
|
128
102
|
}
|
|
129
103
|
case 'protein': {
|
|
@@ -131,20 +105,20 @@ function getSequenceSegments(
|
|
|
131
105
|
const [firstLocation] = cdsLocations
|
|
132
106
|
const locs: { min: number; max: number }[] = []
|
|
133
107
|
for (const loc of firstLocation) {
|
|
134
|
-
|
|
108
|
+
let locSeq = getSequence(loc.min, loc.max)
|
|
109
|
+
if (strand === -1) {
|
|
110
|
+
locSeq = revcom(locSeq)
|
|
111
|
+
}
|
|
112
|
+
wholeSequence += locSeq
|
|
135
113
|
locs.push({ min: loc.min, max: loc.max })
|
|
136
114
|
}
|
|
137
|
-
if (strand === -1) {
|
|
138
|
-
wholeSequence = revcom(wholeSequence)
|
|
139
|
-
}
|
|
140
115
|
let protein = ''
|
|
141
116
|
for (let i = 0; i < wholeSequence.length; i += 3) {
|
|
142
117
|
const codonSeq: string = wholeSequence.slice(i, i + 3).toUpperCase()
|
|
143
118
|
protein +=
|
|
144
119
|
defaultCodonTable[codonSeq as keyof typeof defaultCodonTable] || '&'
|
|
145
120
|
}
|
|
146
|
-
|
|
147
|
-
segments.push({ type: 'protein', sequenceLines, locs })
|
|
121
|
+
segments.push({ type: 'protein', sequence: protein, locs })
|
|
148
122
|
return segments
|
|
149
123
|
}
|
|
150
124
|
}
|
|
@@ -282,6 +256,43 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
282
256
|
void copyToClipboard(seqDiv)
|
|
283
257
|
}
|
|
284
258
|
|
|
259
|
+
function wrapSequence(
|
|
260
|
+
sequenceSegments: SequenceSegment[],
|
|
261
|
+
sequenceWrapLength: number,
|
|
262
|
+
): React.ReactNode[] {
|
|
263
|
+
const seqElements: React.ReactNode[] = []
|
|
264
|
+
let processedChars = 0
|
|
265
|
+
for (const [index, segment] of sequenceSegments.entries()) {
|
|
266
|
+
const lastLineLength = processedChars % sequenceWrapLength
|
|
267
|
+
const segmentLineBreak =
|
|
268
|
+
processedChars > 0 && lastLineLength === 0 ? '\n' : ''
|
|
269
|
+
processedChars += segment.sequence.length
|
|
270
|
+
const firstLine =
|
|
271
|
+
segmentLineBreak +
|
|
272
|
+
segment.sequence.slice(0, sequenceWrapLength - lastLineLength)
|
|
273
|
+
const remainingLines = splitStringIntoChunks(
|
|
274
|
+
segment.sequence.slice(firstLine.length),
|
|
275
|
+
sequenceWrapLength,
|
|
276
|
+
)
|
|
277
|
+
const printLines = [firstLine, ...remainingLines]
|
|
278
|
+
|
|
279
|
+
const span = (
|
|
280
|
+
<span
|
|
281
|
+
key={`${segment.type}-${index}`}
|
|
282
|
+
style={{
|
|
283
|
+
background: getSegmentColor(segment.type),
|
|
284
|
+
color: theme.palette.getContrastText(getSegmentColor(segment.type)),
|
|
285
|
+
whiteSpace: 'pre-line',
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
{printLines.join('\n')}
|
|
289
|
+
</span>
|
|
290
|
+
)
|
|
291
|
+
seqElements.push(span)
|
|
292
|
+
}
|
|
293
|
+
return seqElements
|
|
294
|
+
}
|
|
295
|
+
|
|
285
296
|
return (
|
|
286
297
|
<>
|
|
287
298
|
<Select
|
|
@@ -289,9 +300,14 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
289
300
|
value={selectedOption}
|
|
290
301
|
onChange={handleChangeSeqOption}
|
|
291
302
|
size="small"
|
|
303
|
+
data-testid="sequenceOptionSelector"
|
|
292
304
|
>
|
|
293
305
|
{sequenceOptions.map((option) => (
|
|
294
|
-
<MenuItem
|
|
306
|
+
<MenuItem
|
|
307
|
+
key={option}
|
|
308
|
+
value={option}
|
|
309
|
+
data-testid={`sequenceOption-${option}`}
|
|
310
|
+
>
|
|
295
311
|
{option}
|
|
296
312
|
</MenuItem>
|
|
297
313
|
))}
|
|
@@ -320,29 +336,10 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
320
336
|
: `${interval.max}-${interval.min + 1}`,
|
|
321
337
|
)
|
|
322
338
|
.join(';')}
|
|
323
|
-
({feature.strand === 1 ? '+' : '-'}
|
|
339
|
+
(strand={feature.strand === 1 ? '+' : '-'};length=
|
|
340
|
+
{getSequenceLength(sequenceSegments)})
|
|
324
341
|
<br />
|
|
325
|
-
{sequenceSegments
|
|
326
|
-
<span
|
|
327
|
-
key={`${segment.type}-${index}`}
|
|
328
|
-
style={{
|
|
329
|
-
background: getSegmentColor(segment.type),
|
|
330
|
-
color: theme.palette.getContrastText(
|
|
331
|
-
getSegmentColor(segment.type),
|
|
332
|
-
),
|
|
333
|
-
}}
|
|
334
|
-
>
|
|
335
|
-
{segment.sequenceLines.map((sequenceLine, idx) => (
|
|
336
|
-
<React.Fragment key={`${sequenceLine.slice(0, 5)}-${idx}`}>
|
|
337
|
-
{sequenceLine}
|
|
338
|
-
{idx === segment.sequenceLines.length - 1 &&
|
|
339
|
-
sequenceLine.length !== SEQUENCE_WRAP_LENGTH ? null : (
|
|
340
|
-
<br />
|
|
341
|
-
)}
|
|
342
|
-
</React.Fragment>
|
|
343
|
-
))}
|
|
344
|
-
</span>
|
|
345
|
-
))}
|
|
342
|
+
{wrapSequence(sequenceSegments, SEQUENCE_WRAP_LENGTH)}
|
|
346
343
|
</Paper>
|
|
347
344
|
</>
|
|
348
345
|
)
|