@@ -2,8 +2,7 @@ import React from "react"
22import { ChevronUp , Check , X } from "lucide-react"
33import { cn } from "@/lib/utils"
44import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
5- import { Popover , PopoverContent , PopoverTrigger , StandardTooltip , Button } from "@/components/ui"
6- import { IconButton } from "./IconButton"
5+ import { Popover , PopoverContent , PopoverTrigger , StandardTooltip } from "@/components/ui"
76import { vscode } from "@/utils/vscode"
87import { useExtensionState } from "@/context/ExtensionStateContext"
98import { useAppTranslation } from "@/i18n/TranslationContext"
@@ -12,6 +11,9 @@ import { ModeConfig, CustomModePrompts } from "@roo-code/types"
1211import { telemetryClient } from "@/utils/TelemetryClient"
1312import { TelemetryEventName } from "@roo-code/types"
1413import { Fzf } from "fzf"
14+ import { ImportModeDialog } from "@/components/common/ImportModeDialog"
15+ import { ModeSelectorFooter } from "./ModeSelectorFooter"
16+ import { useModeSelectorExportImport } from "./useModeSelectorExportImport"
1517
1618// Minimum number of modes required to show search functionality
1719const SEARCH_THRESHOLD = 6
@@ -45,8 +47,17 @@ export const ModeSelector = ({
4547 const portalContainer = useRooPortal ( "roo-portal" )
4648 const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState ( )
4749 const { t } = useAppTranslation ( )
48- const [ showImportDialog , setShowImportDialog ] = React . useState ( false )
49- const [ isImporting , setIsImporting ] = React . useState ( false )
50+ const {
51+ showImportDialog,
52+ isImporting,
53+ exportError,
54+ importError,
55+ handleExport,
56+ handleImport,
57+ openImportDialog,
58+ closeImportDialog,
59+ clearErrors,
60+ } = useModeSelectorExportImport ( )
5061
5162 const trackModeSelectorOpened = React . useCallback ( ( ) => {
5263 // Track telemetry every time the mode selector is opened
@@ -155,23 +166,6 @@ export const ModeSelector = ({
155166 }
156167 } , [ open ] )
157168
158- // Handle import/export result messages
159- React . useEffect ( ( ) => {
160- const handler = ( event : MessageEvent ) => {
161- const message = event . data
162- if ( message . type === "importModeResult" ) {
163- setIsImporting ( false )
164- setShowImportDialog ( false )
165- if ( ! message . success && message . error !== "cancelled" ) {
166- console . error ( "Failed to import mode:" , message . error )
167- }
168- }
169- }
170-
171- window . addEventListener ( "message" , handler )
172- return ( ) => window . removeEventListener ( "message" , handler )
173- } , [ ] )
174-
175169 // Determine if search should be shown
176170 const showSearch = ! disableSearch && modes . length > SEARCH_THRESHOLD
177171
@@ -273,131 +267,44 @@ export const ModeSelector = ({
273267 </ div >
274268
275269 { /* Bottom bar with buttons on left and title on right */ }
276- < div className = "flex flex-row items-center justify-between px-2 py-2 border-t border-vscode-dropdown-border" >
277- < div className = "flex flex-row gap-1" >
278- < IconButton
279- iconClass = "codicon-extensions"
280- title = { t ( "chat:modeSelector.marketplace" ) }
281- onClick = { ( ) => {
282- window . postMessage (
283- {
284- type : "action" ,
285- action : "marketplaceButtonClicked" ,
286- values : { marketplaceTab : "mode" } ,
287- } ,
288- "*" ,
289- )
290- setOpen ( false )
291- } }
292- />
293- < IconButton
294- iconClass = "codicon-export"
295- title = { t ( "prompts:exportMode.title" ) }
296- onClick = { ( ) => {
297- if ( value ) {
298- vscode . postMessage ( {
299- type : "exportMode" ,
300- slug : value ,
301- } )
302- }
303- setOpen ( false )
304- } }
305- />
306- < IconButton
307- iconClass = "codicon-import"
308- title = { t ( "prompts:modes.importMode" ) }
309- onClick = { ( ) => {
310- setShowImportDialog ( true )
311- setOpen ( false )
312- } }
313- />
314- < IconButton
315- iconClass = "codicon-settings-gear"
316- title = { t ( "chat:modeSelector.settings" ) }
317- onClick = { ( ) => {
318- vscode . postMessage ( {
319- type : "switchTab" ,
320- tab : "modes" ,
321- } )
322- setOpen ( false )
323- } }
324- />
325- </ div >
326-
327- { /* Info icon and title on the right - only show info icon when search bar is visible */ }
328- < div className = "flex items-center gap-1 pr-1" >
329- { showSearch && (
330- < StandardTooltip content = { instructionText } >
331- < span className = "codicon codicon-info text-xs text-vscode-descriptionForeground opacity-70 hover:opacity-100 cursor-help" />
332- </ StandardTooltip >
333- ) }
334- < h4 className = "m-0 font-medium text-sm text-vscode-descriptionForeground" >
335- { t ( "chat:modeSelector.title" ) }
336- </ h4 >
337- </ div >
338- </ div >
270+ < ModeSelectorFooter
271+ selectedMode = { value }
272+ showSearch = { showSearch }
273+ instructionText = { instructionText }
274+ onExport = { ( ) => handleExport ( value ) }
275+ onImport = { openImportDialog }
276+ onClose = { ( ) => setOpen ( false ) }
277+ />
339278 </ div >
340279 </ PopoverContent >
341280 </ Popover >
342281
343282 { /* Import Mode Dialog */ }
344- { showImportDialog && (
345- < div className = "fixed inset-0 flex items-center justify-center bg-black/50 z-[1000]" >
346- < div className = "bg-vscode-editor-background border border-vscode-editor-lineHighlightBorder rounded-lg shadow-lg p-6 max-w-md w-full" >
347- < h3 className = "text-lg font-semibold mb-4" > { t ( "prompts:modes.importMode" ) } </ h3 >
348- < p className = "text-sm text-vscode-descriptionForeground mb-4" >
349- { t ( "prompts:importMode.selectLevel" ) }
350- </ p >
351- < div className = "space-y-3 mb-6" >
352- < label className = "flex items-start gap-2 cursor-pointer" >
353- < input
354- type = "radio"
355- name = "importLevel"
356- value = "project"
357- className = "mt-1"
358- defaultChecked
359- />
360- < div >
361- < div className = "font-medium" > { t ( "prompts:importMode.project.label" ) } </ div >
362- < div className = "text-xs text-vscode-descriptionForeground" >
363- { t ( "prompts:importMode.project.description" ) }
364- </ div >
365- </ div >
366- </ label >
367- < label className = "flex items-start gap-2 cursor-pointer" >
368- < input type = "radio" name = "importLevel" value = "global" className = "mt-1" />
369- < div >
370- < div className = "font-medium" > { t ( "prompts:importMode.global.label" ) } </ div >
371- < div className = "text-xs text-vscode-descriptionForeground" >
372- { t ( "prompts:importMode.global.description" ) }
373- </ div >
374- </ div >
375- </ label >
376- </ div >
377- < div className = "flex justify-end gap-2" >
378- < Button variant = "secondary" onClick = { ( ) => setShowImportDialog ( false ) } >
379- { t ( "prompts:createModeDialog.buttons.cancel" ) }
380- </ Button >
381- < Button
382- variant = "default"
383- onClick = { ( ) => {
384- if ( ! isImporting ) {
385- const selectedLevel = (
386- document . querySelector (
387- 'input[name="importLevel"]:checked' ,
388- ) as HTMLInputElement
389- ) ?. value as "global" | "project"
390- setIsImporting ( true )
391- vscode . postMessage ( {
392- type : "importMode" ,
393- source : selectedLevel || "project" ,
394- } )
395- }
396- } }
397- disabled = { isImporting } >
398- { isImporting ? t ( "prompts:importMode.importing" ) : t ( "prompts:importMode.import" ) }
399- </ Button >
283+ < ImportModeDialog
284+ isOpen = { showImportDialog }
285+ onClose = { closeImportDialog }
286+ onImport = { handleImport }
287+ isImporting = { isImporting }
288+ />
289+
290+ { /* Error notifications */ }
291+ { ( exportError || importError ) && (
292+ < div className = "fixed bottom-4 right-4 max-w-sm bg-vscode-notifications-background border border-vscode-notifications-border rounded-md shadow-lg p-4 z-[1001]" >
293+ < div className = "flex items-start gap-2" >
294+ < span className = "codicon codicon-error text-vscode-errorForeground flex-shrink-0 mt-0.5" > </ span >
295+ < div className = "flex-1" >
296+ < div className = "text-sm font-medium text-vscode-notifications-foreground" >
297+ { exportError ? t ( "prompts:exportMode.errorTitle" ) : t ( "prompts:importMode.errorTitle" ) }
298+ </ div >
299+ < div className = "text-xs text-vscode-descriptionForeground mt-1" >
300+ { exportError || importError }
301+ </ div >
400302 </ div >
303+ < button
304+ onClick = { clearErrors }
305+ className = "text-vscode-icon-foreground hover:text-vscode-foreground" >
306+ < X className = "h-4 w-4" />
307+ </ button >
401308 </ div >
402309 </ div >
403310 ) }
0 commit comments