1
1
import React , { useEffect , useState , Suspense } from 'react' ;
2
2
import { createDockerDesktopClient } from '@docker/extension-api-client' ;
3
- import { Stack , Typography , Button , IconButton , Alert , DialogTitle , Dialog , DialogContent , CircularProgress , Paper , Box } from '@mui/material' ;
3
+ import { Stack , Typography , Button , IconButton , Alert , DialogTitle , Dialog , DialogContent , CircularProgress , Paper , Box , SvgIcon , useTheme } from '@mui/material' ;
4
4
import { CatalogItemWithName } from './components/tile/Tile' ;
5
5
import { Close } from '@mui/icons-material' ;
6
6
import { CatalogGrid } from './components/CatalogGrid' ;
7
7
import { POLL_INTERVAL } from './Constants' ;
8
- import MCPCatalogLogo from './MCP Catalog.svg'
9
- import { getMCPClientStates , MCPClientState } from './MCPClients' ;
10
8
import { CatalogProvider , useCatalogContext } from './context/CatalogContext' ;
9
+ import { MCPClientProvider } from './context/MCPClientContext' ;
11
10
import ConfigurationModal from './components/ConfigurationModal' ;
11
+ import { Settings as SettingsIcon } from '@mui/icons-material' ;
12
12
13
13
const Settings = React . lazy ( ( ) => import ( './components/Settings' ) ) ;
14
14
15
+ // Create lazy-loaded logo components
16
+ const LazyDarkLogo = React . lazy ( ( ) => import ( './components/DarkLogo' ) ) ;
17
+ const LazyLightLogo = React . lazy ( ( ) => import ( './components/LightLogo' ) ) ;
18
+
19
+ // Logo component that uses Suspense for conditional loading
20
+ const Logo = ( ) => {
21
+ const theme = useTheme ( ) ;
22
+ const isDarkMode = theme . palette . mode === 'dark' ;
23
+
24
+ return (
25
+ < Suspense fallback = { < Box sx = { { height : '5em' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } } > < CircularProgress size = { 24 } /> </ Box > } >
26
+ < Box sx = { { display : 'flex' , alignItems : 'center' , width : '100%' } } >
27
+ { isDarkMode ? < LazyDarkLogo /> : < LazyLightLogo /> }
28
+ </ Box >
29
+ </ Suspense >
30
+ ) ;
31
+ }
32
+
15
33
export const client = createDockerDesktopClient ( ) ;
16
34
17
35
const DEFAULT_SETTINGS = {
@@ -21,58 +39,31 @@ const DEFAULT_SETTINGS = {
21
39
22
40
export function App ( ) {
23
41
const [ settings , setSettings ] = useState < { showModal : boolean , pollIntervalSeconds : number } > ( localStorage . getItem ( 'settings' ) ? JSON . parse ( localStorage . getItem ( 'settings' ) || '{}' ) : DEFAULT_SETTINGS ) ;
24
- const [ mcpClientStates , setMcpClientStates ] = useState < { [ name : string ] : MCPClientState } > ( { } ) ;
25
42
const [ configuringItem , setConfiguringItem ] = useState < CatalogItemWithName | null > ( null ) ;
26
-
27
- const updateMCPClientStates = async ( ) => {
28
- const oldStates = mcpClientStates ;
29
- const states = await getMCPClientStates ( client )
30
- setMcpClientStates ( states ) ;
31
- // Whenever a client connection changes, show toast to user
32
- if ( Object . values ( oldStates ) . some ( state => state . exists && ! state . configured ) && Object . values ( states ) . every ( state => state . configured ) ) {
33
- client . desktopUI . toast . success ( 'MCP Client Connected. Restart it to load the Catalog.' ) ;
34
- }
35
- if ( Object . values ( oldStates ) . some ( state => state . exists && state . configured ) && Object . values ( states ) . every ( state => ! state . configured ) ) {
36
- client . desktopUI . toast . error ( 'MCP Client Disconnected. Restart it to remove the Catalog.' ) ;
37
- }
38
- }
39
-
40
- useEffect ( ( ) => {
41
- let interval : NodeJS . Timeout ;
42
- updateMCPClientStates ( ) ;
43
- interval = setInterval ( ( ) => {
44
- updateMCPClientStates ( ) ;
45
- } , POLL_INTERVAL ) ;
46
- return ( ) => clearInterval ( interval ) ;
47
- } , [ ] ) ;
48
-
49
- // Wrap the entire application with our CatalogProvider
43
+ // Wrap the entire application with our providers
50
44
return (
51
45
< CatalogProvider client = { client } >
52
- < AppContent
53
- settings = { settings }
54
- setSettings = { setSettings }
55
- mcpClientStates = { mcpClientStates }
56
- configuringItem = { configuringItem }
57
- setConfiguringItem = { setConfiguringItem }
58
- updateMCPClientStates = { updateMCPClientStates }
59
- / >
46
+ < MCPClientProvider client = { client } >
47
+ < AppContent
48
+ settings = { settings }
49
+ setSettings = { setSettings }
50
+ configuringItem = { configuringItem }
51
+ setConfiguringItem = { setConfiguringItem }
52
+ />
53
+ </ MCPClientProvider >
60
54
</ CatalogProvider >
61
55
) ;
62
56
}
63
57
64
58
interface AppContentProps {
65
59
settings : { showModal : boolean , pollIntervalSeconds : number } ;
66
60
setSettings : React . Dispatch < React . SetStateAction < { showModal : boolean , pollIntervalSeconds : number } > > ;
67
- mcpClientStates : { [ name : string ] : MCPClientState } ;
68
61
configuringItem : CatalogItemWithName | null ;
69
62
setConfiguringItem : React . Dispatch < React . SetStateAction < CatalogItemWithName | null > > ;
70
- updateMCPClientStates : ( ) => Promise < void > ;
71
63
}
72
64
73
- function AppContent ( { settings, setSettings, mcpClientStates, configuringItem, setConfiguringItem, updateMCPClientStates } : AppContentProps ) {
74
- const { imagesLoadingResults, loadImagesIfNeeded, secrets, catalogItems, registryItems, tryLoadSecrets } = useCatalogContext ( ) ;
75
-
65
+ function AppContent ( { settings, setSettings, configuringItem, setConfiguringItem } : AppContentProps ) {
66
+ const { imagesLoadingResults, loadImagesIfNeeded, secrets, catalogItems, registryItems, } = useCatalogContext ( ) ;
76
67
if ( ! imagesLoadingResults || imagesLoadingResults . stderr ) {
77
68
return < Paper sx = { { padding : 2 , height : '90vh' , display : 'flex' , flexDirection : 'column' , justifyContent : 'center' , alignItems : 'center' } } >
78
69
{ ! imagesLoadingResults && < CircularProgress sx = { { marginBottom : 2 } } /> }
@@ -103,8 +94,6 @@ function AppContent({ settings, setSettings, mcpClientStates, configuringItem, s
103
94
</ Paper >
104
95
}
105
96
106
- const hasMCPConfigured = Object . values ( mcpClientStates ) . some ( state => state . exists && state . configured ) ;
107
-
108
97
return (
109
98
< >
110
99
{ settings . showModal && (
@@ -124,8 +113,6 @@ function AppContent({ settings, setSettings, mcpClientStates, configuringItem, s
124
113
< Settings
125
114
settings = { settings }
126
115
setSettings = { setSettings }
127
- mcpClientStates = { mcpClientStates }
128
- onUpdate = { updateMCPClientStates }
129
116
/>
130
117
</ Suspense >
131
118
</ DialogContent >
@@ -143,20 +130,13 @@ function AppContent({ settings, setSettings, mcpClientStates, configuringItem, s
143
130
) }
144
131
145
132
< Stack direction = "column" spacing = { 1 } justifyContent = 'center' alignItems = 'center' >
146
- < img src = { MCPCatalogLogo } alt = "MCP Catalog" height = { 100 } />
147
- { hasMCPConfigured ? < > </ > : < Alert action = { < Button variant = 'outlined' color = 'secondary' onClick = { ( ) => setSettings ( { ...settings , showModal : true } ) } > Configure</ Button > } severity = "error" sx = { { fontWeight : 'bold' } } > MCP Clients are not configured. Please configure MCP Clients to use the MCP Catalog.</ Alert > }
133
+ < Stack direction = "row" spacing = { 1 } justifyContent = 'space-evenly' alignItems = 'center' sx = { { width : '100%' , maxWidth : '1000px' } } >
134
+ < Logo />
135
+ < IconButton sx = { { ml : 2 , alignSelf : 'flex-end' , justifyContent : 'flex-end' } } onClick = { ( ) => setSettings ( { ...settings , showModal : true } ) } >
136
+ < SettingsIcon sx = { { fontSize : '1.5em' } } />
137
+ </ IconButton >
138
+ </ Stack >
148
139
< CatalogGrid
149
- settingsBadgeProps = { hasMCPConfigured ? { } : {
150
- color : hasMCPConfigured ? 'default' : 'error' ,
151
- badgeContent : '0 MCP Clients' ,
152
- sx : {
153
- width : 80 ,
154
- height : '100%' ,
155
- display : 'flex' ,
156
- justifyContent : 'center' ,
157
- alignItems : 'center' ,
158
- } ,
159
- } }
160
140
setConfiguringItem = { setConfiguringItem }
161
141
showSettings = { ( ) => setSettings ( { ...settings , showModal : true } ) }
162
142
/>
0 commit comments