@@ -24,7 +24,7 @@ import { useHistory } from '../../lib/history';
2424import { keepEditorMouseMovesInShadowRoot } from '../../lib/monaco-shadow-dom' ;
2525import { useOperations } from '../../lib/operations' ;
2626import { LaboratoryPluginTab , usePlugins } from '../../lib/plugins' ;
27- import { usePreflight } from '../../lib/preflight' ;
27+ import { usePreflight , usePreflightPrompt } from '../../lib/preflight' ;
2828import { useSettings } from '../../lib/settings' ;
2929import { LaboratoryTabCustom , useTabs } from '../../lib/tabs' ;
3030import { useTests } from '../../lib/tests' ;
@@ -76,6 +76,7 @@ import { History } from './history';
7676import { HistoryItem } from './history-item' ;
7777import { Operation } from './operation' ;
7878import { Preflight } from './preflight' ;
79+ import { PreflightPromptModal } from './preflight-prompt-modal' ;
7980import { Settings } from './settings' ;
8081import { Tabs } from './tabs' ;
8182
@@ -130,91 +131,6 @@ const addTestFormSchema = z.object({
130131 name : z . string ( ) . min ( 1 , 'Name is required' ) ,
131132} ) ;
132133
133- const PreflightPromptModal = ( props : {
134- open : boolean ;
135- onOpenChange : ( open : boolean ) => void ;
136- placeholder : string ;
137- defaultValue ?: string ;
138- onSubmit ?: ( value : string | null ) => void ;
139- } ) => {
140- const form = useForm ( {
141- defaultValues : {
142- value : props . defaultValue || null ,
143- } ,
144- validators : {
145- onSubmit : z . object ( {
146- value : z . string ( ) . min ( 1 , 'Value is required' ) . nullable ( ) ,
147- } ) ,
148- } ,
149- onSubmit : ( { value } ) => {
150- props . onSubmit ?.( value . value || null ) ;
151- props . onOpenChange ( false ) ;
152- form . reset ( ) ;
153- } ,
154- } ) ;
155-
156- return (
157- < Dialog
158- open = { props . open }
159- onOpenChange = { open => {
160- if ( ! form . state . isSubmitted ) {
161- void form . handleSubmit ( ) ;
162- }
163-
164- props . onOpenChange ( open ) ;
165- } }
166- >
167- < DialogContent >
168- < DialogHeader >
169- < DialogTitle > Preflight prompt</ DialogTitle >
170- </ DialogHeader >
171- < DialogDescription > Enter values for the preflight script.</ DialogDescription >
172- < form
173- id = "preflight-prompt-form"
174- onSubmit = { e => {
175- e . preventDefault ( ) ;
176- void form . handleSubmit ( ) ;
177- } }
178- >
179- < FieldGroup >
180- < form . Field name = "value" >
181- { field => {
182- const isInvalid = field . state . meta . isTouched && ! field . state . meta . isValid ;
183- return (
184- < Field data-invalid = { isInvalid } >
185- < Input
186- id = { field . name }
187- name = { field . name }
188- value = { field . state . value || '' }
189- onBlur = { field . handleBlur }
190- onChange = { e => field . handleChange ( e . target . value ) }
191- aria-invalid = { isInvalid }
192- placeholder = { props . placeholder }
193- autoComplete = "off"
194- />
195- { isInvalid && < FieldError errors = { field . state . meta . errors } /> }
196- </ Field >
197- ) ;
198- } }
199- </ form . Field >
200- </ FieldGroup >
201- </ form >
202- < DialogFooter >
203- < Button
204- type = "submit"
205- form = "preflight-prompt-form"
206- onClick = { ( ) => {
207- void form . handleSubmit ( ) ;
208- } }
209- >
210- Submit
211- </ Button >
212- </ DialogFooter >
213- </ DialogContent >
214- </ Dialog >
215- ) ;
216- } ;
217-
218134const LaboratoryContent = ( ) => {
219135 const {
220136 activeTab,
@@ -572,13 +488,30 @@ export const Laboratory = (
572488 [ props . permissions ] ,
573489 ) ;
574490
491+ // Called before the API hooks so `usePreflight` can answer `lab.prompt()` calls from
492+ // scripts running as part of an operation, not just from the preflight Test button.
493+ const {
494+ isPreflightPromptModalOpen,
495+ setIsPreflightPromptModalOpen,
496+ preflightPromptModalProps,
497+ openPreflightPromptModal,
498+ closePreflightPromptModal,
499+ } = usePreflightPrompt ( ) ;
500+
575501 const settingsApi = useSettings ( props ) ;
576502 const envApi = useEnv ( props ) ;
577503 const preflightApi = usePreflight ( {
578504 ...props ,
579505 envApi,
506+ openPreflightPromptModal,
507+ closePreflightPromptModal,
580508 } ) ;
581509
510+ const { abortPreflight } = preflightApi ;
511+
512+ // A run outlives the lab otherwise: the worker keeps going after the host unmounts.
513+ useEffect ( ( ) => abortPreflight , [ abortPreflight ] ) ;
514+
582515 const pluginsApi = usePlugins ( props ) ;
583516 const testsApi = useTests ( props ) ;
584517 const tabsApi = useTabs ( props ) ;
@@ -669,37 +602,6 @@ export const Laboratory = (
669602 } ,
670603 } ) ;
671604
672- const [ isPreflightPromptModalOpen , setIsPreflightPromptModalOpen ] = useState ( false ) ;
673-
674- const [ preflightPromptModalProps , setPreflightPromptModalProps ] = useState < {
675- placeholder : string ;
676- defaultValue ?: string ;
677- onSubmit ?: ( value : string | null ) => void ;
678- } > ( {
679- placeholder : '' ,
680- defaultValue : undefined ,
681- onSubmit : undefined ,
682- } ) ;
683-
684- const openPreflightPromptModal = useCallback (
685- ( props : {
686- placeholder : string ;
687- defaultValue ?: string ;
688- onSubmit ?: ( value : string | null ) => void ;
689- } ) => {
690- setPreflightPromptModalProps ( {
691- placeholder : props . placeholder ,
692- defaultValue : props . defaultValue ,
693- onSubmit : props . onSubmit ,
694- } ) ;
695-
696- setTimeout ( ( ) => {
697- setIsPreflightPromptModalOpen ( true ) ;
698- } , 200 ) ;
699- } ,
700- [ ] ,
701- ) ;
702-
703605 const [ container , setContainer ] = useState < HTMLDivElement | null > ( null ) ;
704606
705607 const [ isFullScreen , setIsFullScreen ] = useState ( false ) ;
0 commit comments