Skip to content

Commit 45e394c

Browse files
Add menu item to duplicate a transcript (#756)
* duplicate transcript * update tabular view * lint fix * fix * fixes * type button for cancel * transcript -> feature, to match other menu items --------- Co-authored-by: Garrett Stevens <stevens.garrett.j@gmail.com>
1 parent 94c047f commit 45e394c

File tree

4 files changed

+201
-20
lines changed

4 files changed

+201
-20
lines changed

packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { alpha } from '@mui/material'
1414

1515
import type { OntologyRecord } from '../../OntologyManager'
1616
import { MergeExons, MergeTranscripts, SplitExon } from '../../components'
17+
import { DuplicateTranscript } from '../../components/DuplicateTranscript'
1718
import {
1819
type MousePosition,
1920
type MousePositionWithFeature,
@@ -950,29 +951,54 @@ function getContextMenuItems(
950951
)
951952
}
952953
if (isTranscriptFeature(feature, session)) {
953-
contextMenuItemsForFeature.push({
954-
label: 'Merge transcript',
955-
onClick: () => {
956-
;(session as unknown as AbstractSessionModel).queueDialog(
957-
(doneCallback) => [
958-
MergeTranscripts,
959-
{
960-
session,
961-
handleClose: () => {
962-
doneCallback()
954+
contextMenuItemsForFeature.push(
955+
{
956+
label: 'Merge transcript',
957+
onClick: () => {
958+
;(session as unknown as AbstractSessionModel).queueDialog(
959+
(doneCallback) => [
960+
MergeTranscripts,
961+
{
962+
session,
963+
handleClose: () => {
964+
doneCallback()
965+
},
966+
changeManager,
967+
sourceFeature: feature,
968+
sourceAssemblyId: currentAssemblyId,
969+
selectedFeature,
970+
setSelectedFeature: (feature?: AnnotationFeature) => {
971+
display.setSelectedFeature(feature)
972+
},
963973
},
964-
changeManager,
965-
sourceFeature: feature,
966-
sourceAssemblyId: currentAssemblyId,
967-
selectedFeature,
968-
setSelectedFeature: (feature?: AnnotationFeature) => {
969-
display.setSelectedFeature(feature)
974+
],
975+
)
976+
},
977+
},
978+
{
979+
label: 'Duplicate feature',
980+
onClick: () => {
981+
;(session as unknown as AbstractSessionModel).queueDialog(
982+
(doneCallback) => [
983+
DuplicateTranscript,
984+
{
985+
session,
986+
handleClose: () => {
987+
doneCallback()
988+
},
989+
changeManager,
990+
sourceFeature: feature,
991+
sourceAssemblyId: currentAssemblyId,
992+
selectedFeature,
993+
setSelectedFeature: (feature?: AnnotationFeature) => {
994+
display.setSelectedFeature(feature)
995+
},
970996
},
971-
},
972-
],
973-
)
997+
],
998+
)
999+
},
9741000
},
975-
})
1001+
)
9761002
if (isSessionModelWithWidgets(session)) {
9771003
contextMenuItemsForFeature.splice(1, 0, {
9781004
label: 'Open transcript editor',

packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/featureContextMenuItems.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
AddChildFeature,
1212
CopyFeature,
1313
DeleteFeature,
14+
DuplicateTranscript,
1415
MergeExons,
1516
MergeTranscripts,
1617
SplitExon,
@@ -214,6 +215,27 @@ export function featureContextMenuItems(
214215
session.showWidget(apolloTranscriptWidget)
215216
},
216217
},
218+
{
219+
label: 'Duplicate feature',
220+
onClick: () => {
221+
;(session as unknown as AbstractSessionModel).queueDialog(
222+
(doneCallback) => [
223+
DuplicateTranscript,
224+
{
225+
session,
226+
handleClose: () => {
227+
doneCallback()
228+
},
229+
changeManager,
230+
sourceFeature: feature,
231+
sourceAssemblyId: currentAssemblyId,
232+
selectedFeature,
233+
setSelectedFeature,
234+
},
235+
],
236+
)
237+
},
238+
},
217239
{
218240
label: 'Visible',
219241
type: 'checkbox',
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/* eslint-disable @typescript-eslint/unbound-method */
2+
import type {
3+
AnnotationFeature,
4+
AnnotationFeatureSnapshot,
5+
} from '@apollo-annotation/mst'
6+
import { AddFeatureChange } from '@apollo-annotation/shared'
7+
import type { AbstractSessionModel } from '@jbrowse/core/util/types'
8+
import { getSnapshot } from '@jbrowse/mobx-state-tree'
9+
import {
10+
Button,
11+
DialogActions,
12+
DialogContent,
13+
DialogContentText,
14+
} from '@mui/material'
15+
import ObjectID from 'bson-objectid'
16+
import React, { useState } from 'react'
17+
18+
import type { ChangeManager } from '../ChangeManager'
19+
import type { ApolloSessionModel } from '../session'
20+
21+
import { Dialog } from './Dialog'
22+
23+
interface DuplicateTranscriptProps {
24+
session: ApolloSessionModel
25+
handleClose(): void
26+
sourceFeature: AnnotationFeature
27+
sourceAssemblyId: string
28+
changeManager: ChangeManager
29+
selectedFeature?: AnnotationFeature
30+
setSelectedFeature(feature?: AnnotationFeature): void
31+
}
32+
33+
export function DuplicateTranscript({
34+
changeManager,
35+
handleClose,
36+
session,
37+
sourceAssemblyId,
38+
sourceFeature,
39+
setSelectedFeature,
40+
}: DuplicateTranscriptProps) {
41+
const [errorMessage, setErrorMessage] = useState('')
42+
const { notify } = session as unknown as AbstractSessionModel
43+
44+
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
45+
event.preventDefault()
46+
setErrorMessage('')
47+
48+
try {
49+
const parentGene = sourceFeature.parent
50+
if (!parentGene) {
51+
setErrorMessage('No parent gene found for this transcript')
52+
return
53+
}
54+
55+
const transcriptSnapshot = getSnapshot(sourceFeature)
56+
const newTranscriptId = new ObjectID().toHexString()
57+
const duplicateTranscript = {
58+
...transcriptSnapshot,
59+
_id: newTranscriptId,
60+
} as AnnotationFeatureSnapshot
61+
62+
if (duplicateTranscript.children) {
63+
const newChildren: Record<string, AnnotationFeatureSnapshot> = {}
64+
for (const [, child] of Object.entries(duplicateTranscript.children)) {
65+
const newChildId = new ObjectID().toHexString()
66+
newChildren[newChildId] = {
67+
...child,
68+
_id: newChildId,
69+
} as AnnotationFeatureSnapshot
70+
}
71+
duplicateTranscript.children = newChildren
72+
}
73+
74+
const change = new AddFeatureChange({
75+
parentFeatureId: parentGene._id,
76+
changedIds: [parentGene._id],
77+
typeName: 'AddFeatureChange',
78+
assembly: sourceAssemblyId,
79+
addedFeature: duplicateTranscript,
80+
})
81+
82+
await changeManager.submit(change).then(() => {
83+
setSelectedFeature(undefined)
84+
session.apolloSetSelectedFeature(newTranscriptId)
85+
notify('Successfully duplicated transcript', 'success')
86+
})
87+
88+
handleClose()
89+
} catch (error) {
90+
setErrorMessage(
91+
error instanceof Error
92+
? error.message
93+
: 'Failed to duplicate transcript',
94+
)
95+
}
96+
}
97+
98+
return (
99+
<Dialog
100+
open
101+
title="Duplicate transcript"
102+
handleClose={handleClose}
103+
maxWidth={false}
104+
data-testid="duplicate-transcript"
105+
>
106+
<form
107+
onSubmit={(event) => {
108+
void onSubmit(event)
109+
}}
110+
>
111+
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
112+
<DialogContentText>
113+
Are you sure you want to create a duplicate of this transcript?
114+
</DialogContentText>
115+
</DialogContent>
116+
<DialogActions>
117+
<Button variant="contained" type="submit">
118+
Yes
119+
</Button>
120+
<Button variant="outlined" type="button" onClick={handleClose}>
121+
Cancel
122+
</Button>
123+
</DialogActions>
124+
</form>
125+
{errorMessage ? (
126+
<DialogContent>
127+
<DialogContentText color="error">{errorMessage}</DialogContentText>
128+
</DialogContent>
129+
) : null}
130+
</Dialog>
131+
)
132+
}

packages/jbrowse-plugin-apollo/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './ViewChangeLog'
1717
export * from './AddRefSeqAliases'
1818
export * from './ViewCheckResults'
1919
export * from './SplitExon'
20+
export * from './DuplicateTranscript'

0 commit comments

Comments
 (0)