1- import { useEffect , useRef , useState } from 'react'
1+ import { useState } from 'react'
22
3- import { Dialog } from '@jbrowse/core/ui'
3+ import { Dialog , ErrorMessage } from '@jbrowse/core/ui'
44import { getSession } from '@jbrowse/core/util'
55import { createStopToken , stopStopToken } from '@jbrowse/core/util/stopToken'
66import {
@@ -11,9 +11,9 @@ import {
1111 Typography ,
1212} from '@mui/material'
1313import { transaction } from 'mobx'
14- import { getSnapshot } from 'mobx-state-tree'
1514import { observer } from 'mobx-react'
1615
16+ import type { AbstractSessionModel } from '@jbrowse/core/util'
1717import type { DotplotViewModel } from '../model'
1818
1919interface Region {
@@ -34,6 +34,96 @@ interface DiagonalizationResult {
3434 }
3535}
3636
37+ interface RunDiagonalizationArgs {
38+ model : Pick <
39+ DotplotViewModel ,
40+ 'tracks' | 'hview' | 'vview' | 'id' | 'type' | 'displayName'
41+ >
42+ session : AbstractSessionModel
43+ stopToken : string
44+ setProgress : ( progress : number ) => void
45+ setMessage : ( message : string ) => void
46+ }
47+
48+ async function runDiagonalization ( {
49+ model,
50+ session,
51+ stopToken,
52+ setProgress,
53+ setMessage,
54+ } : RunDiagonalizationArgs ) {
55+ setProgress ( 0 )
56+ setMessage ( 'Preparing diagonalization...' )
57+
58+ // Get first track's adapter config
59+ const track = model . tracks [ 0 ]
60+ if ( ! track ) {
61+ throw new Error ( 'No tracks found' )
62+ }
63+
64+ const display = track . displays [ 0 ]
65+ if ( ! display ) {
66+ throw new Error ( 'No display found' )
67+ }
68+
69+ // Call RPC method to run diagonalization on worker
70+ const result = ( await session . rpcManager . call (
71+ model . id ,
72+ 'DiagonalizeDotplot' ,
73+ {
74+ sessionId : `diagonalize-${ Date . now ( ) } ` ,
75+ view : {
76+ hview : model . hview ,
77+ vview : model . vview ,
78+ } ,
79+ adapterConfig : display . adapterConfig ,
80+ stopToken,
81+ statusCallback : ( msg : string ) => {
82+ setMessage ( msg )
83+ // Estimate progress based on message
84+ if ( msg . includes ( 'Initializing' ) ) {
85+ setProgress ( 5 )
86+ } else if ( msg . includes ( 'Getting renderer' ) ) {
87+ setProgress ( 10 )
88+ } else if ( msg . includes ( 'Fetching features' ) ) {
89+ setProgress ( 20 )
90+ } else if ( msg . includes ( 'Extracting' ) ) {
91+ setProgress ( 30 )
92+ } else if ( msg . includes ( 'Running diagonalization' ) ) {
93+ setProgress ( 40 )
94+ } else if ( msg . includes ( 'Grouping' ) ) {
95+ setProgress ( 50 )
96+ } else if ( msg . includes ( 'Determining' ) ) {
97+ setProgress ( 65 )
98+ } else if ( msg . includes ( 'Sorting' ) ) {
99+ setProgress ( 80 )
100+ } else if ( msg . includes ( 'Building' ) ) {
101+ setProgress ( 90 )
102+ } else if ( msg . includes ( 'complete' ) ) {
103+ setProgress ( 100 )
104+ }
105+ } ,
106+ } ,
107+ ) ) as DiagonalizationResult
108+
109+ setMessage ( 'Applying new layout...' )
110+ setProgress ( 95 )
111+
112+ // Apply the new ordering
113+ if ( result . newRegions . length > 0 ) {
114+ transaction ( ( ) => {
115+ model . vview . setDisplayedRegions ( result . newRegions )
116+ } )
117+ setProgress ( 100 )
118+ setMessage (
119+ `Diagonalization complete! Reordered ${ result . stats . regionsReordered } regions, reversed ${ result . stats . regionsReversed } ` ,
120+ )
121+ return result
122+ } else {
123+ throw new Error ( 'No regions to reorder' )
124+ }
125+ }
126+
37127const DiagonalizationProgressDialog = observer ( function ( {
38128 handleClose,
39129 model,
@@ -45,158 +135,104 @@ const DiagonalizationProgressDialog = observer(function ({
45135 >
46136} ) {
47137 const [ progress , setProgress ] = useState ( 0 )
48- const [ message , setMessage ] = useState ( 'Initializing...' )
49- const [ error , setError ] = useState < string > ( )
50- const [ isComplete , setIsComplete ] = useState ( false )
51- const stopTokenRef = useRef ( createStopToken ( ) )
52-
53- useEffect ( ( ) => {
54- // Run diagonalization on mount
55- const runDiagonalization = async ( ) => {
56- const session = getSession ( model )
57- const stopToken = stopTokenRef . current
58-
59- try {
60- setMessage ( 'Preparing diagonalization...' )
61-
62- // Get first track's adapter config
63- const track = model . tracks [ 0 ]
64- if ( ! track ) {
65- setError ( 'No tracks found' )
66- setIsComplete ( true )
67- session . notify ( 'No tracks found to diagonalize' , 'warning' )
68- return
69- }
70-
71- const display = track . displays [ 0 ]
72- if ( ! display ) {
73- setError ( 'No display found' )
74- setIsComplete ( true )
75- session . notify ( 'No display found' , 'warning' )
76- return
77- }
78-
79- // Call RPC method to run diagonalization on worker
80- const result = ( await session . rpcManager . call (
81- model . id ,
82- 'DiagonalizeDotplot' ,
83- {
84- sessionId : `diagonalize-${ Date . now ( ) } ` ,
85- view : {
86- hview : getSnapshot ( model . hview ) ,
87- vview : getSnapshot ( model . vview ) ,
88- } ,
89- adapterConfig : getSnapshot ( display . adapterConfig ) ,
90- stopToken,
91- statusCallback : ( msg : string ) => {
92- setMessage ( msg )
93- // Estimate progress based on message
94- if ( msg . includes ( 'Initializing' ) ) {
95- setProgress ( 5 )
96- } else if ( msg . includes ( 'Getting renderer' ) ) {
97- setProgress ( 10 )
98- } else if ( msg . includes ( 'Fetching features' ) ) {
99- setProgress ( 20 )
100- } else if ( msg . includes ( 'Extracting' ) ) {
101- setProgress ( 30 )
102- } else if ( msg . includes ( 'Running diagonalization' ) ) {
103- setProgress ( 40 )
104- } else if ( msg . includes ( 'Grouping' ) ) {
105- setProgress ( 50 )
106- } else if ( msg . includes ( 'Determining' ) ) {
107- setProgress ( 65 )
108- } else if ( msg . includes ( 'Sorting' ) ) {
109- setProgress ( 80 )
110- } else if ( msg . includes ( 'Building' ) ) {
111- setProgress ( 90 )
112- } else if ( msg . includes ( 'complete' ) ) {
113- setProgress ( 100 )
114- }
115- } ,
116- } ,
117- ) ) as DiagonalizationResult
118-
119- setMessage ( 'Applying new layout...' )
120- setProgress ( 95 )
121-
122- // Apply the new ordering
123- if ( result . newRegions . length > 0 ) {
124- transaction ( ( ) => {
125- model . vview . setDisplayedRegions ( result . newRegions )
126- } )
127- setProgress ( 100 )
128- setMessage (
129- `Diagonalization complete! Reordered ${ result . stats . regionsReordered } regions, reversed ${ result . stats . regionsReversed } ` ,
130- )
131- setIsComplete ( true )
132-
133- // Auto-close after success
134- setTimeout ( ( ) => {
135- handleClose ( )
136- } , 2000 )
137- } else {
138- setError ( 'No regions to reorder' )
139- setIsComplete ( true )
140- session . notify ( 'No regions found to reorder' , 'warning' )
141- }
142- } catch ( err ) {
143- console . error ( 'Diagonalization error:' , err )
144- const errMsg = `${ err } `
145- setError ( errMsg . includes ( 'aborted' ) ? 'Cancelled by user' : `Error: ${ err } ` )
146- setIsComplete ( true )
147- if ( ! errMsg . includes ( 'aborted' ) ) {
148- session . notify ( `Diagonalization failed: ${ err } ` , 'error' )
149- }
150- }
151- }
152-
153- // eslint-disable-next-line @typescript-eslint/no-floating-promises
154- runDiagonalization ( )
155-
156- // Cleanup stop token on unmount
157- return ( ) => {
158- stopStopToken ( stopTokenRef . current )
138+ const [ message , setMessage ] = useState ( 'Ready to start diagonalization' )
139+ const [ error , setError ] = useState < unknown > ( )
140+ const [ isRunning , setIsRunning ] = useState ( false )
141+ const [ stopToken , setStopToken ] = useState < string > ( )
142+
143+ const handleStart = async ( ) => {
144+ const session = getSession ( model )
145+ const token = createStopToken ( )
146+ setStopToken ( token )
147+
148+ try {
149+ setIsRunning ( true )
150+ await runDiagonalization ( {
151+ model,
152+ session,
153+ stopToken : token ,
154+ setProgress,
155+ setMessage,
156+ } )
157+
158+ // Auto-close after success
159+ setTimeout ( ( ) => {
160+ handleClose ( )
161+ } , 2000 )
162+ } catch ( err ) {
163+ console . error ( err )
164+ setError ( err )
165+ } finally {
166+ setIsRunning ( false )
159167 }
160- } , [ model , handleClose ] )
168+ }
161169
162170 const handleCancel = ( ) => {
163- stopStopToken ( stopTokenRef . current )
171+ if ( stopToken ) {
172+ stopStopToken ( stopToken )
173+ setStopToken ( undefined )
174+ }
164175 handleClose ( )
165176 }
166177
178+ const handleDialogClose = ( ) => {
179+ // Only allow closing if not running
180+ if ( ! isRunning ) {
181+ handleClose ( )
182+ }
183+ }
184+
167185 return (
168- < Dialog open title = "Diagonalizing" onClose = { handleCancel } >
186+ < Dialog
187+ open
188+ title = "Diagonalize Dotplot"
189+ onClose = { handleDialogClose }
190+ maxWidth = "lg"
191+ >
169192 < DialogContent style = { { minWidth : 400 } } >
170- < Typography
171- variant = "body1"
172- gutterBottom
173- color = { error ? 'error' : 'inherit' }
174- >
175- { error || message }
176- </ Typography >
177- < LinearProgress
178- variant = "determinate"
179- value = { progress }
180- style = { { marginTop : 16 } }
181- color = { error ? 'error' : 'primary' }
182- />
183- < Typography
184- variant = "caption"
185- color = "textSecondary"
186- style = { { marginTop : 8 , display : 'block' } }
187- >
188- { Math . round ( progress ) } % complete
189- </ Typography >
193+ { message ? < Typography > { message } </ Typography > : null }
194+ { error ? < ErrorMessage error = { error } /> : null }
195+ { isRunning ? (
196+ < >
197+ < LinearProgress
198+ variant = "determinate"
199+ value = { progress }
200+ style = { { marginTop : 16 } }
201+ color = { error ? 'error' : 'primary' }
202+ />
203+ < Typography
204+ variant = "caption"
205+ color = "textSecondary"
206+ style = { { marginTop : 8 , display : 'block' } }
207+ >
208+ { Math . round ( progress ) } % complete
209+ </ Typography >
210+ </ >
211+ ) : null }
190212 </ DialogContent >
191213 < DialogActions >
192- { ! isComplete ? (
193- < Button onClick = { handleCancel } color = "secondary" >
214+ { ! isRunning ? (
215+ < >
216+ < Button onClick = { handleClose } color = "secondary" variant = "contained" >
217+ Cancel
218+ </ Button >
219+ < Button
220+ onClick = { ( ) => {
221+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
222+ handleStart ( )
223+ } }
224+ color = "primary"
225+ variant = "contained"
226+ >
227+ Start
228+ </ Button >
229+ </ >
230+ ) : null }
231+ { isRunning ? (
232+ < Button onClick = { handleCancel } color = "secondary" variant = "contained" >
194233 Cancel
195234 </ Button >
196235 ) : null }
197- < Button onClick = { handleClose } color = "primary" disabled = { ! isComplete } >
198- { isComplete ? 'Done' : 'Processing...' }
199- </ Button >
200236 </ DialogActions >
201237 </ Dialog >
202238 )
0 commit comments