1414 * limitations under the License.
1515 */
1616
17+ import './manage-solution.css' ;
18+ import '../../../common/style/antd-overrides.css' ;
1719import { LoadingOutlined } from '@ant-design/icons' ;
1820import { Button , Checkbox , CheckboxChangeEvent , Col , ConfigProvider , Flex , Input , InputNumber , Row , Spin , Tabs , theme } from 'antd' ;
19- import { debounce } from 'lodash' ;
2021import * as React from 'react' ;
2122import { UISection , UISectionChildren } from '../../../../debug/debug-adapters-yaml-file' ;
2223import { CompactDropdown } from '../../../common/components/compact-dropdown' ;
23- import '../../../common/style/antd-overrides.css' ;
2424import { useVSCodeTheme } from '../../../hooks/use-vscode-theme' ;
2525import { MessageHandler } from '../../../message-handler' ;
2626import { IncomingMessage , OutgoingMessage } from '../../messages' ;
2727import { GenericPropertyList } from '../state/manage-solution-state' ;
2828import { SolutionUpdateAction , contextUpdateReducer , initialState , manageSolutionReducer } from '../state/reducer' ;
29- import './manage-solution.css' ;
3029import { ProjectsTable } from './projects-table' ;
3130import { TargetsTable } from './targets-table' ;
3231import { PathType } from '../../types' ;
@@ -35,10 +34,26 @@ export interface ManageSolutionProps {
3534 messageHandler : MessageHandler < IncomingMessage , OutgoingMessage > ;
3635}
3736
37+ type PendingFileSelection = {
38+ service : string | undefined ;
39+ key : string ;
40+ localValueKey : string ;
41+ } ;
42+
43+ type SelectFileContext = {
44+ service : string | undefined ;
45+ key : string ;
46+ localValueKey : string ;
47+ title ?: string ;
48+ defaultUri ?: string ;
49+ pathType ?: PathType ;
50+ } ;
51+
3852export const ManageSolution = ( props : ManageSolutionProps ) => {
3953 const [ state , dispatch ] = React . useReducer ( manageSolutionReducer , initialState ) ;
4054 // Eager local editable values snapshot (display-layer values). Numbers are stored scaled for user editing.
4155 const [ localValues , setLocalValues ] = React . useState < Record < string , string | number > > ( { } ) ;
56+ const pendingFileSelections = React . useRef < Map < string , PendingFileSelection > > ( new Map ( ) ) ;
4257
4358 const adapter = React . useMemo (
4459 ( ) => state . debugAdapters . find ( adapter => adapter . name === state . debugger ) ,
@@ -74,14 +89,28 @@ export const ManageSolution = (props: ManageSolutionProps) => {
7489
7590 React . useEffect ( ( ) => {
7691 const handleFileSelected = ( message : IncomingMessage ) => {
77- if ( message . type === 'FILE_SELECTED' && message . data && message . data . length > 0 ) {
78- const element = document . getElementById ( message . for || '' ) ;
79- if ( element ) {
80- const ymlNode = element . getAttribute ( 'data-yml-node' ) || 'config' ;
81- props . messageHandler . push ( { type : 'SET_DEBUG_ADAPTER_PROPERTY' , service : undefined , key : ymlNode , value : message . data [ 0 ] } ) ;
82- ( element as HTMLInputElement ) . value = message . data [ 0 ] ;
83- }
92+ if ( message . type !== 'FILE_SELECTED' ) {
93+ return ;
94+ }
95+
96+ const pendingSelection = pendingFileSelections . current . get ( message . requestId ) ;
97+ if ( ! pendingSelection ) {
98+ return ;
99+ }
100+ pendingFileSelections . current . delete ( message . requestId ) ;
101+
102+ if ( ! message . data || message . data . length === 0 ) {
103+ return ;
84104 }
105+
106+ const selectedPath = message . data [ 0 ] ;
107+ setLocalValues ( prev => ( { ...prev , [ pendingSelection . localValueKey ] : selectedPath } ) ) ;
108+ props . messageHandler . push ( {
109+ type : 'SET_DEBUG_ADAPTER_PROPERTY' ,
110+ service : pendingSelection . service ,
111+ key : pendingSelection . key ,
112+ value : selectedPath
113+ } ) ;
85114 } ;
86115
87116 const unsubscribe = props . messageHandler . subscribe ( handleFileSelected ) ;
@@ -203,34 +232,26 @@ export const ManageSolution = (props: ManageSolutionProps) => {
203232
204233 const hasDebugger = ! ! state . debugger ;
205234
206- const selectFile = React . useMemo ( ( ) => {
207- const handleSelectFile = ( e : React . MouseEvent < HTMLElement > ) => {
208- const input = ( e . target as HTMLElement ) ?. parentElement ?. parentNode ?. parentNode ?. querySelector ( 'input' ) ;
209- let id = input ?. getAttribute ( 'id' ) ;
210- if ( id === null || id === undefined ) {
211- // eslint-disable-next-line react-hooks/purity
212- id = Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
213- input ?. setAttribute ( 'id' , id ) ;
214- }
215- const title = input ?. getAttribute ( 'title' ) || 'Select File' ;
216- const dataOptionPathType = input ?. getAttribute ( 'data-option-path-type' ) ;
217- const optionPathType : PathType = dataOptionPathType === 'absolute' ? 'absolute' : 'relative' ;
218- const currentValue = input ?. value || '' ;
219- props . messageHandler . push ( {
220- type : 'SELECT_FILE' ,
221- targetElementId : id ,
222- options : {
223- canSelectMany : false ,
224- defaultUri : currentValue ,
225- openLabel : 'Select File' , // title of the dialog button to choose the file
226- title : title , // dialog title
227- filters : { 'All Files' : [ '*' ] } ,
228- pathType : optionPathType
229- }
230- } ) ;
231- } ;
235+ const selectFile = React . useCallback ( ( context : SelectFileContext ) => {
236+ const requestId = `manage-solution-file-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 10 ) } ` ;
237+ pendingFileSelections . current . set ( requestId , {
238+ service : context . service ,
239+ key : context . key ,
240+ localValueKey : context . localValueKey ,
241+ } ) ;
232242
233- return debounce ( handleSelectFile , 300 ) ;
243+ props . messageHandler . push ( {
244+ type : 'SELECT_FILE' ,
245+ requestId,
246+ options : {
247+ canSelectMany : false ,
248+ defaultUri : context . defaultUri ,
249+ openLabel : 'Select File' ,
250+ title : context . title || 'Select File' ,
251+ filters : { 'All Files' : [ '*' ] } ,
252+ pathType : context . pathType ?? 'relative'
253+ }
254+ } ) ;
234255 } , [ props . messageHandler ] ) ;
235256
236257 // Eager initialization/reset of localValues whenever adapter context changes.
@@ -434,7 +455,22 @@ export const ManageSolution = (props: ManageSolutionProps) => {
434455 return (
435456 < Input
436457 addonBefore = { o . name }
437- addonAfter = { < Button type = "primary" className = 'file-button' onClick = { selectFile } > Browse</ Button > }
458+ addonAfter = {
459+ < Button
460+ type = "primary"
461+ className = 'file-button'
462+ onClick = { ( ) => selectFile ( {
463+ service : section [ 'yml-node' ] ,
464+ key : o [ 'yml-node' ] ,
465+ localValueKey : k ,
466+ title : o . description || 'Select File' ,
467+ defaultUri : ( localValues [ k ] as string ) ?? '' ,
468+ pathType : o [ 'path-type' ] ,
469+ } ) }
470+ >
471+ Browse
472+ </ Button >
473+ }
438474 value = { ( localValues [ k ] as string ) ?? '' }
439475 data-yml-node = { o [ 'yml-node' ] }
440476 data-option-path-type = { o [ 'path-type' ] }
0 commit comments