From 9f6a267fd43815c9a3f36935b0e96342cbc9f3bb Mon Sep 17 00:00:00 2001 From: Shashank Budhanuru Ramaraju Date: Tue, 3 Feb 2026 15:29:21 +0000 Subject: [PATCH] duplicate transcript --- .../LinearApolloDisplay/glyphs/GeneGlyph.ts | 66 ++++++--- .../src/components/DuplicateTranscript.tsx | 132 ++++++++++++++++++ 2 files changed, 178 insertions(+), 20 deletions(-) create mode 100644 packages/jbrowse-plugin-apollo/src/components/DuplicateTranscript.tsx diff --git a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts index 7123111f6..7c99a80f9 100644 --- a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +++ b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts @@ -14,6 +14,7 @@ import { alpha } from '@mui/material' import { type OntologyRecord } from '../../OntologyManager' import { MergeExons, MergeTranscripts, SplitExon } from '../../components' +import { DuplicateTranscript } from '../../components/DuplicateTranscript' import { type MousePosition, type MousePositionWithFeature, @@ -950,29 +951,54 @@ function getContextMenuItems( ) } if (isTranscriptFeature(feature, session)) { - contextMenuItemsForFeature.push({ - label: 'Merge transcript', - onClick: () => { - ;(session as unknown as AbstractSessionModel).queueDialog( - (doneCallback) => [ - MergeTranscripts, - { - session, - handleClose: () => { - doneCallback() + contextMenuItemsForFeature.push( + { + label: 'Merge transcript', + onClick: () => { + ;(session as unknown as AbstractSessionModel).queueDialog( + (doneCallback) => [ + MergeTranscripts, + { + session, + handleClose: () => { + doneCallback() + }, + changeManager, + sourceFeature: feature, + sourceAssemblyId: currentAssemblyId, + selectedFeature, + setSelectedFeature: (feature?: AnnotationFeature) => { + display.setSelectedFeature(feature) + }, }, - changeManager, - sourceFeature: feature, - sourceAssemblyId: currentAssemblyId, - selectedFeature, - setSelectedFeature: (feature?: AnnotationFeature) => { - display.setSelectedFeature(feature) + ], + ) + }, + }, + { + label: 'Duplicate transcript', + onClick: () => { + ;(session as unknown as AbstractSessionModel).queueDialog( + (doneCallback) => [ + DuplicateTranscript, + { + session, + handleClose: () => { + doneCallback() + }, + changeManager, + sourceFeature: feature, + sourceAssemblyId: currentAssemblyId, + selectedFeature, + setSelectedFeature: (feature?: AnnotationFeature) => { + display.setSelectedFeature(feature) + }, }, - }, - ], - ) + ], + ) + }, }, - }) + ) if (isSessionModelWithWidgets(session)) { contextMenuItemsForFeature.splice(1, 0, { label: 'Open transcript editor', diff --git a/packages/jbrowse-plugin-apollo/src/components/DuplicateTranscript.tsx b/packages/jbrowse-plugin-apollo/src/components/DuplicateTranscript.tsx new file mode 100644 index 000000000..8d68df06c --- /dev/null +++ b/packages/jbrowse-plugin-apollo/src/components/DuplicateTranscript.tsx @@ -0,0 +1,132 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import { + type AnnotationFeature, + type AnnotationFeatureSnapshot, +} from '@apollo-annotation/mst' +import { AddFeatureChange } from '@apollo-annotation/shared' +import { type AbstractSessionModel } from '@jbrowse/core/util/types' +import { + Button, + DialogActions, + DialogContent, + DialogContentText, +} from '@mui/material' +import ObjectID from 'bson-objectid' +import { getSnapshot } from 'mobx-state-tree' +import React, { useState } from 'react' + +import { type ChangeManager } from '../ChangeManager' +import { type ApolloSessionModel } from '../session' + +import { Dialog } from './Dialog' + +interface DuplicateTranscriptProps { + session: ApolloSessionModel + handleClose(): void + sourceFeature: AnnotationFeature + sourceAssemblyId: string + changeManager: ChangeManager + selectedFeature?: AnnotationFeature + setSelectedFeature(feature?: AnnotationFeature): void +} + +export function DuplicateTranscript({ + changeManager, + handleClose, + session, + sourceAssemblyId, + sourceFeature, + setSelectedFeature, +}: DuplicateTranscriptProps) { + const [errorMessage, setErrorMessage] = useState('') + const { notify } = session as unknown as AbstractSessionModel + + async function onSubmit(event: React.FormEvent) { + event.preventDefault() + setErrorMessage('') + + try { + const parentGene = sourceFeature.parent + if (!parentGene) { + setErrorMessage('No parent gene found for this transcript') + return + } + + const transcriptSnapshot = getSnapshot(sourceFeature) + const newTranscriptId = new ObjectID().toHexString() + const duplicateTranscript = { + ...transcriptSnapshot, + _id: newTranscriptId, + } as AnnotationFeatureSnapshot + + if (duplicateTranscript.children) { + const newChildren: Record = {} + for (const [, child] of Object.entries(duplicateTranscript.children)) { + const newChildId = new ObjectID().toHexString() + newChildren[newChildId] = { + ...child, + _id: newChildId, + } as AnnotationFeatureSnapshot + } + duplicateTranscript.children = newChildren + } + + const change = new AddFeatureChange({ + parentFeatureId: parentGene._id, + changedIds: [parentGene._id], + typeName: 'AddFeatureChange', + assembly: sourceAssemblyId, + addedFeature: duplicateTranscript, + }) + + await changeManager.submit(change).then(() => { + setSelectedFeature(undefined) + session.apolloSetSelectedFeature(newTranscriptId) + notify('Successfully duplicated transcript', 'success') + }) + + handleClose() + } catch (error) { + setErrorMessage( + error instanceof Error + ? error.message + : 'Failed to duplicate transcript', + ) + } + } + + return ( + +
{ + void onSubmit(event) + }} + > + + + Are you sure you want to create a duplicate of this transcript? + + + + + + +
+ {errorMessage ? ( + + {errorMessage} + + ) : null} +
+ ) +}