Browse Source

Chore: upgrade page and components for mui v5 (#885)

* remove makestyle for search.tsx

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update search page

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* remove more makeStyles

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* remove makeStyle for play.tsx

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* remove makeStyle for database/tree

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update makestyle

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* bug fix

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

---------

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 1 month ago
parent
commit
1789fe97f2

+ 1 - 0
client/src/components/customInput/CustomInput.tsx

@@ -275,6 +275,7 @@ const CustomInput = (props: ICustomInputProps) => {
     adornmentConfig,
     checkValid,
     validInfo,
+    ...others
   } = props;
 
   let template: ReactElement | null;

+ 94 - 89
client/src/pages/databases/Databases.tsx

@@ -6,71 +6,79 @@ import { dataContext } from '@/context';
 import StatusIcon from '@/components/status/StatusIcon';
 import { ConsistencyLevelEnum, DYNAMIC_FIELD } from '@/consts';
 import { ALL_ROUTER_TYPES } from '@/router/consts';
-import { makeStyles } from '@mui/styles';
 import { LoadingType } from '@/components/status/StatusIcon';
-import type { Theme } from '@mui/material/styles';
 import type { SearchParams, QueryState } from './types';
 import { DatabasesTab } from './DatabasesTab';
 import { CollectionsTabs } from './CollectionsTab';
+import { styled } from '@mui/material/styles';
+import { Box } from '@mui/material';
 
 const DEFAULT_TREE_WIDTH = 230;
 
-const useStyles = makeStyles((theme: Theme) => ({
-  wrapper: {
-    flexDirection: 'row',
-    padding: theme.spacing(0),
-    '&.dragging': {
-      cursor: 'ew-resize',
-      '& $tree': {
-        pointerEvents: 'none',
-        userSelect: 'none',
-      },
-      '& $tab': {
-        pointerEvents: 'none',
-        userSelect: 'none',
-      },
-      '& $dragger': {
-        background: theme.palette.divider,
-      },
-    },
-  },
-  tree: {
-    boxShadow: 'none',
-    flexGrow: 0,
-    flexShrink: 0,
-    height: 'calc(100vh - 90px)',
-    overflowY: 'auto',
-    overflowX: 'hidden',
-    boxSizing: 'border-box',
-    paddingRight: 8,
-    position: 'relative',
-  },
-  dragger: {
-    pointerEvents: 'auto',
-    position: 'absolute',
-    top: 0,
-    right: 0,
-    width: 2,
-    height: '100%',
-    background: 'transparent',
+const PageWrapper = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'row',
+  padding: theme.spacing(2),
+  height: 'calc(100vh - 64px)',
+  overflow: 'hidden',
+  '&.dragging': {
     cursor: 'ew-resize',
-    '&:hover': {
-      width: 2,
-      cursor: 'ew-resize',
+    '& .tree': {
+      pointerEvents: 'none',
+      userSelect: 'none',
+    },
+    '& .tab': {
+      pointerEvents: 'none',
+      userSelect: 'none',
+    },
+    '& .dragger': {
       background: theme.palette.divider,
     },
   },
-  tab: {
-    flexGrow: 1,
-    flexShrink: 1,
-    overflowX: 'auto',
-    padding: theme.spacing(0, 2),
-    border: `1px solid ${theme.palette.divider}`,
-    borderRadius: 8,
-    boxShadow: '0px 6px 30px rgba(0, 0, 0, 0.1)',
+}));
+
+const TreeSection = styled(Box)(({ theme }) => ({
+  boxShadow: 'none',
+  flexGrow: 0,
+  flexShrink: 0,
+  height: '100%',
+  overflowY: 'auto',
+  overflowX: 'hidden',
+  boxSizing: 'border-box',
+  paddingRight: 8,
+  position: 'relative',
+}));
+
+const Dragger = styled(Box)(({ theme }) => ({
+  pointerEvents: 'auto',
+  position: 'absolute',
+  top: 0,
+  right: 0,
+  width: 2,
+  height: '100%',
+  background: 'transparent',
+  cursor: 'ew-resize',
+  '&:hover': {
+    width: 2,
+    cursor: 'ew-resize',
+    background: theme.palette.divider,
   },
 }));
 
+const TabSection = styled(Box)(({ theme }) => ({
+  flexGrow: 1,
+  flexShrink: 1,
+  overflow: 'hidden',
+  padding: theme.spacing(0, 2),
+  border: `1px solid ${theme.palette.divider}`,
+  borderRadius: 8,
+  boxShadow: '0px 6px 30px rgba(0, 0, 0, 0.1)',
+  height: '100%',
+  display: 'flex',
+  flexDirection: 'column',
+  backgroundColor: theme.palette.background.paper,
+}));
+
 // Databases page(tree and tabs)
 const Databases = () => {
   // context
@@ -276,9 +284,6 @@ const Databases = () => {
     databasePage = '',
   } = params;
 
-  // get style
-  const classes = useStyles();
-
   // update navigation
   useNavigationHook(ALL_ROUTER_TYPES.DATABASES, {
     collectionName,
@@ -311,13 +316,9 @@ const Databases = () => {
 
   // render
   return (
-    <section
-      className={`page-wrapper ${classes.wrapper} ${
-        isDragging ? 'dragging' : ''
-      }`}
-    >
-      <section
-        className={classes.tree}
+    <PageWrapper className={isDragging ? 'dragging' : ''}>
+      <TreeSection
+        className="tree"
         ref={treeRef}
         style={{ width: ui.tree.width }}
       >
@@ -331,41 +332,45 @@ const Databases = () => {
             params={params}
           />
         )}
-        <div
-          className={classes.dragger}
+        <Dragger
+          className="dragger"
           data-id="dragger"
           onDoubleClick={handleDoubleClick}
           ref={draggerRef}
-        ></div>
-      </section>
-      {!collectionName && (
-        <DatabasesTab
-          databasePage={databasePage}
-          databaseName={databaseName}
-          tabClass={classes.tab}
         />
+      </TreeSection>
+      {!collectionName && (
+        <TabSection className="tab">
+          <DatabasesTab
+            databasePage={databasePage}
+            databaseName={databaseName}
+            tabClass="tab"
+          />
+        </TabSection>
       )}
       {collectionName && (
-        <CollectionsTabs
-          collectionPage={collectionPage}
-          collectionName={collectionName}
-          tabClass={classes.tab}
-          searchParams={
-            searchParams.find(
-              s => s.collection.collection_name === collectionName
-            )!
-          }
-          setSearchParams={setCollectionSearchParams}
-          queryState={
-            queryState.find(
-              q => q.collection.collection_name === collectionName
-            )!
-          }
-          setQueryState={setCollectionQueryState}
-          collections={collections}
-        />
+        <TabSection className="tab">
+          <CollectionsTabs
+            collectionPage={collectionPage}
+            collectionName={collectionName}
+            tabClass="tab"
+            searchParams={
+              searchParams.find(
+                s => s.collection.collection_name === collectionName
+              )!
+            }
+            setSearchParams={setCollectionSearchParams}
+            queryState={
+              queryState.find(
+                q => q.collection.collection_name === collectionName
+              )!
+            }
+            setQueryState={setCollectionQueryState}
+            collections={collections}
+          />
+        </TabSection>
       )}
-    </section>
+    </PageWrapper>
   );
 };
 

+ 67 - 0
client/src/pages/databases/StyledComponents.ts

@@ -0,0 +1,67 @@
+import { styled } from '@mui/material/styles';
+import { Box } from '@mui/material';
+
+export const Root = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'column',
+  height: 'calc(100% - 16px)',
+}));
+
+export const Toolbar = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  justifyContent: 'space-between',
+  alignItems: 'center',
+  backgroundColor: theme.palette.background.paper,
+  padding: theme.spacing(0, 0, 1),
+  gap: theme.spacing(1),
+
+  '& .left': {
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    gap: theme.spacing(1),
+    flex: 1,
+
+    '& .textarea': {
+      width: '100%',
+      '& .MuiFormHelperText-root': {
+        display: 'none',
+      },
+    },
+  },
+
+  '& .right': {
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    gap: theme.spacing(1),
+  },
+
+  '& .selector': {
+    width: 160,
+  },
+
+  '& .outputs': {
+    height: 56,
+    width: 140,
+  },
+
+  '& .btn': {
+    height: 56,
+    width: 80,
+  },
+}));
+
+export const Selector = styled(Box)({
+  width: 160,
+});
+
+export const Outputs = styled(Box)({
+  height: 56,
+  width: 140,
+});
+
+export const Button = styled(Box)({
+  height: 56,
+  width: 80,
+});

+ 3 - 2
client/src/pages/databases/collections/Collections.tsx

@@ -28,6 +28,7 @@ import type {
   ToolBarConfig,
 } from '@/components/grid/Types';
 import type { CollectionObject } from '@server/types';
+import { Root } from '../StyledComponents';
 
 const Collections = () => {
   const { isManaged } = useContext(authContext);
@@ -469,7 +470,7 @@ const Collections = () => {
   }, [collectionList, batchRefreshCollections]);
 
   return (
-    <Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
+    <Root sx={{ height: 'calc(100% - 24px)' }}>
       {collections.length > 0 || loading ? (
         <AttuGrid
           toolbarConfigs={toolbarConfigs}
@@ -514,7 +515,7 @@ const Collections = () => {
           />
         </>
       )}
-    </Box>
+    </Root>
   );
 };
 

+ 9 - 11
client/src/pages/databases/collections/data/CollectionData.tsx

@@ -14,7 +14,7 @@ import CustomToolBar from '@/components/grid/ToolBar';
 import InsertDialog from '@/pages/dialogs/insert/Dialog';
 import EditJSONDialog from '@/pages/dialogs/EditJSONDialog';
 import { getLabelDisplayedRows } from '@/pages/search/Utils';
-import { getQueryStyles } from './Styles';
+import { Root, Toolbar } from '../../StyledComponents';
 import {
   DYNAMIC_FIELD,
   DataTypeStringEnum,
@@ -66,8 +66,6 @@ const CollectionData = (props: CollectionDataProps) => {
   const { t: collectionTrans } = useTranslation('collection');
   const { t: btnTrans } = useTranslation('btn');
   const { t: commonTrans } = useTranslation();
-  // classes
-  const classes = getQueryStyles();
 
   // UI ref
   const filterRef = useRef();
@@ -409,11 +407,11 @@ const CollectionData = (props: CollectionDataProps) => {
   }, [collection.collection_name]);
 
   return (
-    <div className={classes.root}>
+    <Root>
       {collection && (
         <>
           <CustomToolBar toolbarConfigs={toolbarConfigs} hideOnDisable={true} />
-          <div className={classes.toolbar}>
+          <Toolbar>
             <div className="left">
               <CustomInput
                 type="text"
@@ -463,7 +461,7 @@ const CollectionData = (props: CollectionDataProps) => {
                 options={CONSISTENCY_LEVEL_OPTIONS}
                 value={queryState.consistencyLevel}
                 label={collectionTrans('consistency')}
-                wrapperClass={classes.selector}
+                wrapperClass="selector"
                 disabled={!collection.loaded}
                 variant="filled"
                 onChange={(e: { target: { value: unknown } }) => {
@@ -478,7 +476,7 @@ const CollectionData = (props: CollectionDataProps) => {
 
             <div className="right">
               <CustomMultiSelector
-                className={classes.outputs}
+                className="outputs"
                 options={queryState.fields.map(f => {
                   return {
                     label:
@@ -529,7 +527,7 @@ const CollectionData = (props: CollectionDataProps) => {
                 }}
               />
               <CustomButton
-                className={classes.btn}
+                className="btn"
                 onClick={handleFilterReset}
                 disabled={!collection.loaded}
                 startIcon={<ResetIcon classes={{ root: 'icon' }} />}
@@ -537,7 +535,7 @@ const CollectionData = (props: CollectionDataProps) => {
                 {btnTrans('reset')}
               </CustomButton>
               <CustomButton
-                className={classes.btn}
+                className="btn"
                 variant="contained"
                 onClick={() => {
                   setCurrentPage(0);
@@ -553,7 +551,7 @@ const CollectionData = (props: CollectionDataProps) => {
                 {btnTrans('query')}
               </CustomButton>
             </div>
-          </div>
+          </Toolbar>
           <AttuGrid
             toolbarConfigs={[]}
             colDefinitions={queryState.outputFields.map(i => {
@@ -615,7 +613,7 @@ const CollectionData = (props: CollectionDataProps) => {
           />
         </>
       )}
-    </div>
+    </Root>
   );
 };
 

+ 0 - 55
client/src/pages/databases/collections/data/Styles.ts

@@ -1,55 +0,0 @@
-import { Theme } from '@mui/material';
-import { makeStyles } from '@mui/styles';
-
-export const getQueryStyles = makeStyles((theme: Theme) => ({
-  root: {
-    display: 'flex',
-    flexDirection: 'column',
-    height: '100%',
-  },
-  emptyCard: {
-    height: '100%',
-    boxShadow: 'none',
-  },
-  toolbar: {
-    display: 'flex',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    backgroundColor: theme.palette.background.paper,
-    padding: theme.spacing(0, 0, 1),
-    gap: theme.spacing(1),
-
-    '& .left': {
-      display: 'flex',
-      justifyContent: 'space-between',
-      alignItems: 'center',
-      gap: theme.spacing(1),
-      flex: 1,
-
-      '& .textarea': {
-        width: '100%',
-        '& .MuiFormHelperText-root': {
-          display: 'none',
-        },
-      },
-    },
-
-    '& .right': {
-      display: 'flex',
-      justifyContent: 'space-between',
-      alignItems: 'center',
-      gap: theme.spacing(1),
-    },
-  },
-  selector: {
-    width: 160,
-  },
-  outputs: {
-    height: 56,
-    width: 140,
-  },
-  btn: {
-    height: 56,
-    width: 80,
-  },
-}));

+ 5 - 7
client/src/pages/databases/collections/search/DataExplorer.tsx

@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
 import * as d3 from 'd3';
 import { useTheme } from '@mui/material';
 import { cloneObj } from '@/utils';
-import { getDataExplorerStyle } from './Styles';
+import { DataExplorerRoot, SelectedNodes } from './StyledComponents';
 import DataPanel from './DataPanel';
 import type { GraphData, GraphNode, GraphLink } from '../../types';
 import type { SearchResultData } from '@server/types';
@@ -109,8 +109,6 @@ const DataExplorer = ({
   const [render, setRender] = useState<number>(0);
   // theme
   const theme = useTheme();
-  // classes
-  const classes = getDataExplorerStyle();
 
   // ref
   const rootRef = useRef<HTMLDivElement>(null);
@@ -285,21 +283,21 @@ const DataExplorer = ({
   }, [JSON.stringify(data), width, height, theme]);
 
   return (
-    <div className={classes.root} ref={rootRef}>
+    <DataExplorerRoot ref={rootRef}>
       <svg ref={svgRef} width={width} height={height}>
         <g ref={gRef} />
       </svg>
       {selectedNodesRef.current.length > 0 && (
-        <div className={classes.selectedNodes} data-render={render}>
+        <SelectedNodes data-render={render}>
           {selectedNodesRef.current.map(node => (
             <DataPanel key={node.id} node={node} color={color} />
           ))}
-        </div>
+        </SelectedNodes>
       )}
       {hoveredNode && (
         <DataPanel key={hoveredNode.id} node={hoveredNode} color={color} />
       )}
-    </div>
+    </DataExplorerRoot>
   );
 };
 

+ 52 - 54
client/src/pages/databases/collections/search/Search.tsx

@@ -1,11 +1,5 @@
 import { useState, useMemo, ChangeEvent, useCallback } from 'react';
-import {
-  Typography,
-  Accordion,
-  AccordionSummary,
-  AccordionDetails,
-  Checkbox,
-} from '@mui/material';
+import { Typography, AccordionSummary, Checkbox } from '@mui/material';
 import { useTranslation } from 'react-i18next';
 import { DataService, CollectionService } from '@/http';
 import Icons from '@/components/icons/Icons';
@@ -15,7 +9,6 @@ import EmptyCard from '@/components/cards/EmptyCard';
 import CustomButton from '@/components/customButton/CustomButton';
 import { getLabelDisplayedRows } from '@/pages/search/Utils';
 import { useSearchResult, usePaginationHook } from '@/hooks';
-import { getQueryStyles } from './Styles';
 import SearchGlobalParams from './SearchGlobalParams';
 import VectorInputBox from './SearchInputBox';
 import StatusIcon, { LoadingType } from '@/components/status/StatusIcon';
@@ -46,6 +39,19 @@ import type {
   CollectionFullObject,
   VectorSearchResults,
 } from '@server/types';
+import {
+  SearchRoot,
+  InputArea,
+  AccordionsContainer,
+  StyledAccordion,
+  StyledAccordionDetails,
+  SearchControls,
+  SearchResults,
+  Toolbar,
+  Explorer,
+  CloseButton,
+  CheckboxRow,
+} from './StyledComponents';
 
 export interface CollectionDataProps {
   collectionName: string;
@@ -74,8 +80,6 @@ const Search = (props: CollectionDataProps) => {
   // translations
   const { t: searchTrans } = useTranslation('search');
   const { t: btnTrans } = useTranslation('btn');
-  // classes
-  const classes = getQueryStyles();
 
   // UI functions
   const handleExpand = useCallback(
@@ -373,13 +377,13 @@ const Search = (props: CollectionDataProps) => {
 
   if (!hasVectorIndex || !loaded) {
     return (
-      <div className={classes.root}>
+      <SearchRoot>
         <EmptyCard
           wrapperClass={`page-empty-card`}
           icon={<Icons.load />}
           text={searchTrans('loadCollectionFirst')}
         />
-      </div>
+      </SearchRoot>
     );
   }
 
@@ -405,27 +409,25 @@ const Search = (props: CollectionDataProps) => {
   const enablePartitionsFilter = !collection.schema.enablePartitionKey;
 
   return (
-    <div className={classes.root}>
+    <SearchRoot>
       {collection && (
-        <div className={classes.inputArea}>
-          <div className={classes.accordions}>
+        <InputArea>
+          <AccordionsContainer>
             {searchParams.searchParams.map((s, index: number) => {
               const field = s.field;
               return (
-                <Accordion
+                <StyledAccordion
                   key={`${collection.collection_name}-${field.name}`}
                   expanded={s.expanded}
                   onChange={handleExpand(field.name)}
-                  className={`${classes.accordion} ${
-                    highlightField === field.name && 'highlight'
-                  }`}
+                  className={highlightField === field.name ? 'highlight' : ''}
                 >
                   <AccordionSummary
                     expandIcon={<ExpandMoreIcon />}
                     aria-controls={`${field.name}-content`}
                     id={`${field.name}-header`}
                   >
-                    <div className={classes.checkbox}>
+                    <CheckboxRow>
                       {searchParams.searchParams.length > 1 && (
                         <Checkbox
                           size="small"
@@ -435,9 +437,7 @@ const Search = (props: CollectionDataProps) => {
                       )}
                       <div className="label">
                         <Typography
-                          className={`field-name ${
-                            s.data.length > 0 ? 'bold' : ''
-                          }`}
+                          className={`field-name ${s.selected ? 'bold' : ''}`}
                         >
                           {field.is_function_output
                             ? `${field.name}<=${
@@ -450,9 +450,9 @@ const Search = (props: CollectionDataProps) => {
                           <i>{field.index && field.index.metricType}</i>
                         </Typography>
                       </div>
-                    </div>
+                    </CheckboxRow>
                   </AccordionSummary>
-                  <AccordionDetails className={classes.accordionDetail}>
+                  <StyledAccordionDetails>
                     <VectorInputBox
                       searchParams={s}
                       onChange={onSearchInputChange}
@@ -460,12 +460,8 @@ const Search = (props: CollectionDataProps) => {
                       type={field.is_function_output ? 'text' : 'vector'}
                     />
 
-                    <Typography className="text">
-                      {searchTrans('thirdTip')}
-                    </Typography>
-
                     <SearchParams
-                      wrapperClass="paramsWrapper"
+                      sx={{ pt: 1 }}
                       consistency_level={'Strong'}
                       handleConsistencyChange={(level: string) => {}}
                       indexType={field.index.indexType}
@@ -481,8 +477,8 @@ const Search = (props: CollectionDataProps) => {
                         return false;
                       }}
                     />
-                  </AccordionDetails>
-                </Accordion>
+                  </StyledAccordionDetails>
+                </StyledAccordion>
               );
             })}
 
@@ -493,9 +489,9 @@ const Search = (props: CollectionDataProps) => {
                 setSelected={onPartitionsChange}
               />
             )}
-          </div>
+          </AccordionsContainer>
 
-          <div className={classes.searchControls}>
+          <SearchControls>
             <SearchGlobalParams
               onSlideChange={(field: string) => {
                 setHighlightField(field);
@@ -514,7 +510,10 @@ const Search = (props: CollectionDataProps) => {
               onClick={genRandomVectors}
               size="small"
               disabled={false}
-              className={classes.genBtn}
+              className="genBtn"
+              sx={{
+                mb: 1,
+              }}
             >
               {btnTrans('example')}
             </CustomButton>
@@ -522,7 +521,7 @@ const Search = (props: CollectionDataProps) => {
             <CustomButton
               variant="contained"
               size="small"
-              className={classes.genBtn}
+              className="genBtn"
               disabled={disableSearch}
               tooltip={disableSearchTooltip}
               tooltipPlacement="top"
@@ -535,10 +534,10 @@ const Search = (props: CollectionDataProps) => {
                     : '',
               })}
             </CustomButton>
-          </div>
+          </SearchControls>
 
-          <div className={classes.searchResults}>
-            <section className={classes.toolbar}>
+          <SearchResults>
+            <Toolbar>
               <div className="left">
                 <CustomInput
                   type="text"
@@ -586,14 +585,14 @@ const Search = (props: CollectionDataProps) => {
                     !searchParams.searchResult ||
                     searchParams.searchResult!.length === 0
                   }
-                  className={classes.btn}
+                  className="btn"
                   startIcon={<Icons.magic classes={{ root: 'icon' }} />}
                 >
                   {btnTrans('explore')}
                 </CustomButton>
 
                 <CustomButton
-                  className={classes.btn}
+                  className="btn"
                   disabled={result.length === 0}
                   onClick={() => {
                     saveCsvAs(
@@ -606,7 +605,7 @@ const Search = (props: CollectionDataProps) => {
                   {btnTrans('export')}
                 </CustomButton>
                 <CustomButton
-                  className={classes.btn}
+                  className="btn"
                   onClick={
                     explorerOpen ? onExplorerResetClicked : onResetClicked
                   }
@@ -615,26 +614,25 @@ const Search = (props: CollectionDataProps) => {
                   {btnTrans('reset')}
                 </CustomButton>
               </div>
-            </section>
+            </Toolbar>
 
             {explorerOpen ? (
-              <div className={classes.explorer}>
+              <Explorer>
                 <DataExplorer
                   data={searchParams.graphData}
                   onNodeClick={onNodeClicked}
                 />
-                <CustomButton
+                <CloseButton
                   onClick={() => {
                     setExplorerOpen(false);
                   }}
                   size="small"
                   startIcon={<Icons.clear classes={{ root: 'icon' }} />}
-                  className={classes.closeBtn}
                   variant="contained"
                 >
                   {btnTrans('close')}
-                </CustomButton>
-              </div>
+                </CloseButton>
+              </Explorer>
             ) : (searchParams.searchResult &&
                 searchParams.searchResult.length > 0) ||
               tableLoading ? (
@@ -647,17 +645,17 @@ const Search = (props: CollectionDataProps) => {
                 page={currentPage}
                 tableHeaderHeight={46}
                 rowHeight={39}
+                openCheckBox={false}
                 onPageChange={handlePageChange}
                 rowsPerPage={pageSize}
                 setRowsPerPage={handlePageSize}
-                openCheckBox={false}
-                isLoading={tableLoading}
+                handleSort={handleGridSort}
                 orderBy={orderBy}
                 order={order}
                 labelDisplayedRows={getLabelDisplayedRows(
                   `(${searchParams.searchLatency} ms)`
                 )}
-                handleSort={handleGridSort}
+                isLoading={tableLoading}
               />
             ) : (
               <EmptyCard
@@ -670,10 +668,10 @@ const Search = (props: CollectionDataProps) => {
                 }
               />
             )}
-          </div>
-        </div>
+          </SearchResults>
+        </InputArea>
       )}
-    </div>
+    </SearchRoot>
   );
 };
 

+ 21 - 5
client/src/pages/databases/collections/search/SearchInputBox.tsx

@@ -9,13 +9,32 @@ import { javascript } from '@codemirror/lang-javascript';
 import { linter, Diagnostic } from '@codemirror/lint';
 import { CollectionService } from '@/http';
 import { DataTypeStringEnum } from '@/consts';
-import { getQueryStyles } from './Styles';
 import { useTheme } from '@mui/material';
 import { githubLight } from '@ddietr/codemirror-themes/github-light';
 import { githubDark } from '@ddietr/codemirror-themes/github-dark';
 import { Validator } from './utils';
 import type { CollectionFullObject } from '@server/types';
 import type { SearchSingleParams } from '../../types';
+import { styled } from '@mui/material/styles';
+
+const SearchInputBoxWrapper = styled('div')(({ theme }) => ({
+  height: '124px',
+  margin: '0 0 8px 0',
+  overflow: 'auto',
+  backgroundColor: theme.palette.background.default,
+  cursor: 'text',
+  boxShadow: '0 1px 0 transparent',
+  transition: 'box-shadow 0.3s ease',
+  '&:hover': {
+    boxShadow: '0 1px 0 #000',
+  },
+  '&:active': {
+    boxShadow: `0 1px 0 ${theme.palette.primary.main}`,
+  },
+  '&.focused': {
+    boxShadow: `0 2px 0 ${theme.palette.primary.main}`,
+  },
+}));
 
 export type SearchInputBoxProps = {
   onChange: (anns_field: string, value: string) => void;
@@ -34,9 +53,6 @@ export default function SearchInputBox(props: SearchInputBoxProps) {
   const { searchParams, onChange, collection, type } = props;
   const { field, data } = searchParams;
 
-  // classes
-  const classes = getQueryStyles();
-
   // refs
   const editorEl = useRef<HTMLDivElement>(null);
   const editor = useRef<EditorView>();
@@ -238,5 +254,5 @@ export default function SearchInputBox(props: SearchInputBoxProps) {
     }
   }, [theme.palette.mode]);
 
-  return <div className={classes.searchInputBox} ref={editorEl}></div>;
+  return <SearchInputBoxWrapper ref={editorEl}></SearchInputBoxWrapper>;
 }

+ 320 - 0
client/src/pages/databases/collections/search/StyledComponents.ts

@@ -0,0 +1,320 @@
+import { styled } from '@mui/material/styles';
+import { Box, Paper, Accordion, AccordionDetails, Button } from '@mui/material';
+
+export const SearchRoot = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'column',
+  height: '100%',
+}));
+
+export const InputArea = styled(Box)({
+  display: 'flex',
+  flexDirection: 'row',
+  width: '100%',
+  height: 'max-content',
+  padding: 0,
+});
+
+export const AccordionsContainer = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  width: '230px',
+  flexDirection: 'column',
+  flexShrink: 0,
+  padding: '0 8px 8px 0',
+  minHeight: 'calc(100vh - 164px)',
+  height: 'calc(100vh - 164px)',
+  overflow: 'auto',
+  borderRight: `1px solid ${theme.palette.divider}`,
+  '& .MuiAccordion-root.Mui-expanded': {
+    margin: 0,
+  },
+  '& .MuiAccordion-root.Mui-expanded:before': {
+    opacity: 1,
+  },
+}));
+
+export const StyledAccordion = styled(Accordion)(({ theme }) => ({
+  boxShadow: 'none',
+  padding: '0',
+  border: '1px solid transparent',
+
+  '&.highlight': {
+    border: `1px solid ${theme.palette.secondary.main}`,
+    borderRadius: 8,
+  },
+
+  '& .MuiAccordionSummary-root': {
+    minHeight: '48px',
+    height: '48px',
+    padding: '0 12px 0 0',
+    transition: 'none',
+    '&.Mui-expanded': {
+      minHeight: '48px',
+      height: '48px',
+      margin: 0,
+    },
+    '& .MuiAccordionSummary-expandIcon': {
+      padding: 4,
+      alignSelf: 'flex-start',
+      position: 'relative',
+      top: '4px',
+    },
+    '& .MuiAccordionSummary-content': {
+      margin: 0,
+      padding: '8px 0',
+      '&.Mui-expanded': {
+        margin: 0,
+      },
+    },
+  },
+  '& .MuiAccordionSummary-content': {
+    margin: 0,
+    padding: '8px 0',
+    '&.Mui-expanded': {
+      margin: 0,
+    },
+  },
+  '& .MuiAccordionSummary-expandIcon': {
+    alignSelf: 'flex-start',
+    position: 'relative',
+    top: '4px',
+  },
+}));
+
+export const StyledAccordionDetails = styled(AccordionDetails)({
+  display: 'flex',
+  flexDirection: 'column',
+  padding: '0',
+  '& .textarea': {
+    height: '100px',
+    marginBottom: '8px',
+  },
+  '& .paramsWrapper': {
+    '& .MuiFormControl-root': {
+      width: '100%',
+    },
+  },
+});
+
+export const SearchInputBox = styled(Box)(({ theme }) => ({
+  height: '124px',
+  margin: '0 0 8px 0',
+  overflow: 'auto',
+  backgroundColor: theme.palette.background.default,
+  cursor: 'text',
+  boxShadow: '0 1px 0 transparent',
+  transition: 'box-shadow 0.3s ease',
+  '&:hover': {
+    boxShadow: '0 1px 0 #000',
+  },
+  '&:active': {
+    boxShadow: `0 1px 0 ${theme.palette.primary.main}`,
+  },
+  '&.focused': {
+    boxShadow: `0 2px 0 ${theme.palette.primary.main}`,
+  },
+}));
+
+export const SearchControls = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'column',
+  width: 120,
+  minWidth: 120,
+  padding: '0 8px',
+  borderRight: `1px solid ${theme.palette.divider}`,
+
+  '& .selector': {
+    marginBottom: '8px',
+  },
+  '& span button': {
+    width: '100%',
+    height: '100%',
+  },
+}));
+
+export const SearchResults = styled(Box)({
+  display: 'flex',
+  flexDirection: 'column',
+  flexGrow: 1,
+  paddingLeft: 8,
+  overflow: 'auto',
+});
+
+export const Toolbar = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  justifyContent: 'space-between',
+  alignItems: 'center',
+  backgroundColor: theme.palette.background.paper,
+  padding: theme.spacing(0, 0, 1),
+  gap: theme.spacing(1),
+
+  '& .left': {
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    gap: theme.spacing(1),
+    flex: 1,
+
+    '& .textarea': {
+      width: '100%',
+      '& .MuiFormHelperText-root': {
+        display: 'none',
+      },
+    },
+  },
+
+  '& .right': {
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    gap: theme.spacing(1),
+  },
+}));
+
+export const StyledButton = styled(Button)({
+  height: 56,
+  width: 80,
+});
+
+export const Explorer = styled(Box)({
+  display: 'flex',
+  flexDirection: 'column',
+  height: '100%',
+  position: 'relative',
+  flexGrow: 1,
+});
+
+export const CloseButton = styled(Button)({
+  position: 'absolute',
+  top: 8,
+  left: 8,
+  zIndex: 1,
+  padding: '4px 8px',
+});
+
+export const ResetButton = styled(Button)({
+  position: 'absolute',
+  top: 8,
+  left: 90,
+  zIndex: 1,
+  padding: '4px 8px',
+});
+
+export const NodeInfo = styled(Paper)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'column',
+  padding: '8px',
+  backgroundColor: theme.palette.background.paper,
+  border: `1px solid ${theme.palette.divider}`,
+  borderRadius: 8,
+  boxShadow: '0px 6px 30px rgba(0, 0, 0, 0.1)',
+  maxWidth: 240,
+  overflow: 'auto',
+  zIndex: 1,
+  '& .wrapper': {
+    display: 'flex',
+    flexDirection: 'row',
+    flexWrap: 'wrap',
+    gap: 4,
+    justifyContent: 'center',
+    '& img': {
+      display: 'inline-block',
+      maxWidth: 120,
+      maxHeight: 120,
+      objectFit: 'contain',
+    },
+  },
+  '& .tip': {
+    color: theme.palette.text.secondary,
+    fontSize: 12,
+    textAlign: 'center',
+  },
+}));
+
+export const SelectedNodes = styled(Box)(({ theme }) => ({
+  position: 'fixed',
+  display: 'flex',
+  flexDirection: 'column',
+  top: 200,
+  right: 36,
+  borderRadius: 8,
+  gap: 8,
+  height: '70%',
+  overflow: 'auto',
+  backgroundColor: theme.palette.background.paper,
+  '& .nodeInfo': {
+    boxShadow: 'none',
+    flexShrink: 0,
+  },
+}));
+
+export const CheckboxRow = styled('div')(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'row',
+  width: '100%',
+  '& .MuiCheckbox-root': {
+    padding: 0,
+    marginRight: 4,
+    alignSelf: 'flex-start',
+    position: 'relative',
+    top: '2px',
+  },
+  '& .label': {
+    display: 'flex',
+    flexDirection: 'column',
+    width: '100%',
+  },
+  '& .field-name': {
+    fontSize: '13px',
+    fontWeight: 400,
+    lineHeight: '20px',
+    wordBreak: 'break-all',
+    '&.bold': {
+      fontWeight: 600,
+    },
+  },
+  '& .vector-type': {
+    color: theme.palette.text.secondary,
+    fontSize: '12px',
+    lineHeight: '20px',
+    '& i': {
+      marginLeft: '4px',
+      fontSize: '10px',
+      fontWeight: 600,
+      color: theme.palette.secondary.main,
+    },
+  },
+}));
+
+export const DataExplorerRoot = styled(Box)(({ theme }) => ({
+  '& .nodeInfo': {
+    display: 'flex',
+    flexDirection: 'column',
+    padding: '8px',
+    backgroundColor: theme.palette.background.paper,
+    border: `1px solid ${theme.palette.divider}`,
+    borderRadius: 8,
+    boxShadow: '0px 6px 30px rgba(0, 0, 0, 0.1)',
+    maxWidth: 240,
+    overflow: 'auto',
+    zIndex: 1,
+    '& .wrapper': {
+      display: 'flex',
+      flexDirection: 'row',
+      flexWrap: 'wrap',
+      gap: 4,
+      justifyContent: 'center',
+      '& img': {
+        display: 'inline-block',
+        maxWidth: 120,
+        maxHeight: 120,
+        objectFit: 'contain',
+      },
+    },
+    '& .tip': {
+      color: theme.palette.text.secondary,
+      fontSize: 12,
+      textAlign: 'center',
+    },
+  },
+}));

+ 0 - 272
client/src/pages/databases/collections/search/Styles.ts

@@ -1,272 +0,0 @@
-import { Theme } from '@mui/material';
-import { makeStyles } from '@mui/styles';
-
-export const getQueryStyles = makeStyles((theme: Theme) => ({
-  root: {
-    display: 'flex',
-    flexDirection: 'column',
-    height: '100%',
-  },
-
-  inputArea: {
-    display: 'flex',
-    flexDirection: 'row',
-    width: '100%',
-    height: 'max-content',
-    padding: 0,
-  },
-
-  accordions: {
-    display: 'flex',
-    width: '230px',
-    flexDirection: 'column',
-    flexShrink: 0,
-    padding: '0 8px 8px 0',
-    minHeight: 'calc(100vh - 164px)',
-    height: 'calc(100vh - 164px)',
-    overflow: 'auto',
-
-    borderRight: `1px solid ${theme.palette.divider}`,
-    '& .MuiAccordion-root.Mui-expanded': {
-      margin: 0,
-    },
-    '& .MuiAccordion-root.Mui-expanded:before': {
-      opacity: 1,
-    },
-  },
-
-  accordion: {
-    boxShadow: 'none',
-    padding: '0',
-    border: '1px solid transparent',
-
-    '&.highlight': {
-      border: `1px solid ${theme.palette.secondary.main}`,
-      borderRadius: 8,
-    },
-
-    // borderBottom: `1px solid ${theme.palette.divider}`,
-    '& .MuiAccordionSummary-root': {
-      minHeight: '48px',
-      padding: '0 12px 0 0',
-      '& .MuiAccordionSummary-expandIcon': {
-        padding: 4,
-      },
-    },
-    '& .MuiAccordionSummary-content': {
-      margin: 0,
-      padding: '8px 0',
-    },
-    '& .MuiAccordionSummary-expandIcon': {
-      alignSelf: 'flex-start',
-      position: 'relative',
-      top: '4px',
-    },
-  },
-  accordionDetail: {
-    display: 'flex',
-    flexDirection: 'column',
-    padding: '0',
-    '& .textarea': {
-      height: '100px',
-      marginBottom: '8px',
-    },
-    '& .paramsWrapper': {
-      '& .MuiFormControl-root': {
-        width: '100%',
-      },
-    },
-  },
-  heading: {
-    flexShrink: 0,
-  },
-  checkbox: {
-    display: 'flex',
-    flexDirection: 'row',
-    '& .MuiCheckbox-root': {
-      padding: 0,
-      marginRight: 4,
-      alignSelf: 'flex-start',
-      postion: 'relative',
-      top: '2px',
-    },
-    '& .field-name': {
-      fontSize: '13px',
-      fontWeight: 400,
-      lineHeight: '20px',
-      wordBreak: 'break-all',
-    },
-    '& .bold': {
-      fontWeight: 600,
-    },
-    '& .vector-type': {
-      color: theme.palette.text.secondary,
-      fontSize: '12px',
-      lineHeight: '20px',
-      '& i': {
-        marginLeft: '4px',
-        fontSize: '10px',
-        fontWeight: 600,
-        color: theme.palette.secondary.main,
-      },
-    },
-  },
-
-  searchInputBox: {
-    height: '124px',
-    margin: '0 0 8px 0',
-    overflow: 'auto',
-    backgroundColor: theme.palette.background.default,
-    cursor: 'text',
-    boxShadow: '0 1px 0 transparent',
-    transition: `box-shadow 0.3s ease`,
-    '&:hover': {
-      boxShadow: '0 1px 0 #000',
-    },
-    '&:active': {
-      boxShadow: `0 1px 0 ${theme.palette.primary.main}`,
-    },
-    '&.focused': {
-      boxShadow: `0 2px 0 ${theme.palette.primary.main}`,
-    },
-  },
-
-  searchControls: {
-    display: 'flex',
-    flexDirection: 'column',
-    width: 120,
-    minWidth: 120,
-    padding: '0 8px',
-    borderRight: `1px solid ${theme.palette.divider}`,
-
-    '& .selector': {
-      marginBottom: '8px',
-    },
-    '& span button': {
-      width: '100%',
-      height: '100%',
-    },
-  },
-
-  searchResults: {
-    display: 'flex',
-    flexDirection: 'column',
-    flexGrow: 1,
-    paddingLeft: 8,
-    overflow: 'auto',
-  },
-
-  toolbar: {
-    display: 'flex',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    backgroundColor: theme.palette.background.paper,
-    padding: theme.spacing(0, 0, 1),
-    gap: theme.spacing(1),
-
-    '& .left': {
-      display: 'flex',
-      justifyContent: 'space-between',
-      alignItems: 'center',
-      gap: theme.spacing(1),
-      flex: 1,
-
-      '& .textarea': {
-        width: '100%',
-        '& .MuiFormHelperText-root': {
-          display: 'none',
-        },
-      },
-    },
-
-    '& .right': {
-      display: 'flex',
-      justifyContent: 'space-between',
-      alignItems: 'center',
-      gap: theme.spacing(1),
-    },
-  },
-
-  genBtn: {
-    marginBottom: 8,
-  },
-
-  btn: {
-    height: 56,
-    width: 80,
-  },
-
-  explorer: {
-    display: 'flex',
-    flexDirection: 'column',
-    height: '100%',
-    position: 'relative',
-    flexGrow: 1,
-  },
-  closeBtn: {
-    position: 'absolute',
-    top: 8,
-    left: 8,
-    zIndex: 1,
-    padding: '4px 8px',
-  },
-  resetBtn: {
-    position: 'absolute',
-    top: 8,
-    left: 90,
-    zIndex: 1,
-    padding: '4px 8px',
-  },
-}));
-
-export const getDataExplorerStyle = makeStyles((theme: Theme) => ({
-  root: {
-    '& .nodeInfo': {
-      display: 'flex',
-      flexDirection: 'column',
-      padding: '8px',
-      backgroundColor: theme.palette.background.paper,
-      border: `1px solid ${theme.palette.divider}`,
-      borderRadius: 8,
-      boxShadow: '0px 6px 30px rgba(0, 0, 0, 0.1)',
-      maxWidth: 240,
-      overflow: 'auto',
-      zIndex: 1,
-      '& .wrapper': {
-        display: 'flex',
-        flexDirection: 'row',
-        flexWrap: 'wrap',
-        gap: 4,
-
-        justifyContent: 'center',
-        '& img': {
-          display: 'inline-block',
-          maxWidth: 120,
-          maxHeight: 120,
-          objectFit: 'contain',
-        },
-      },
-      '& .tip': {
-        color: theme.palette.text.secondary,
-        fontSize: 12,
-        textAlign: 'center',
-      },
-    },
-  },
-  selectedNodes: {
-    position: 'fixed',
-    display: 'flex',
-    flexDirection: 'column',
-    top: 200,
-    right: 36,
-    borderRadius: 8,
-    gap: 8,
-    height: '70%',
-    overflow: 'auto',
-    backgroundColor: theme.palette.background.paper,
-    '& .nodeInfo': {
-      boxShadow: 'none',
-      flexShrink: 0,
-    },
-  },
-}));

+ 3 - 4
client/src/pages/databases/collections/segments/Segments.tsx

@@ -1,6 +1,7 @@
 import { useEffect, useState, useContext } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useParams } from 'react-router-dom';
+import { Box } from '@mui/material';
 import { SegmentService } from '@/http';
 import { usePaginationHook } from '@/hooks';
 import { rootContext } from '@/context';
@@ -8,7 +9,6 @@ import AttuGrid from '@/components/grid/Grid';
 import CustomToolBar from '@/components/grid/ToolBar';
 import CompactDialog from '@/pages/dialogs/CompactDialog';
 import FlushDialog from '@/pages/dialogs/FlushDialog';
-import { getQueryStyles } from '../data/Styles';
 import { getLabelDisplayedRows } from '../../../search/Utils';
 import type { ColDefinitionsType } from '@/components/grid/Types';
 import type { ToolBarConfig } from '@/components/grid/Types';
@@ -16,7 +16,6 @@ import type { Segment } from './Types';
 
 const Segments = () => {
   const { collectionName = '' } = useParams<{ collectionName: string }>();
-  const classes = getQueryStyles();
   const { setDialog } = useContext(rootContext);
 
   const [segments, setSegments] = useState<Segment[]>([]);
@@ -213,7 +212,7 @@ const Segments = () => {
   };
 
   return (
-    <div className={classes.root}>
+    <Box sx={{ width: '100%', height: '100%' }}>
       <CustomToolBar toolbarConfigs={toolbarConfigs} />
 
       <AttuGrid
@@ -238,7 +237,7 @@ const Segments = () => {
           commonTrans(data.length > 1 ? 'grid.segments' : 'grid.segment')
         )}
       />
-    </div>
+    </Box>
   );
 };
 

+ 65 - 0
client/src/pages/databases/tree/StyledComponents.ts

@@ -0,0 +1,65 @@
+import { styled } from '@mui/material/styles';
+import { Box, MenuItem, Divider } from '@mui/material';
+
+export const CollectionNodeWrapper = styled(Box)({
+  minHeight: '24px',
+  lineHeight: '24px',
+  display: 'flex',
+  minWidth: 190,
+});
+
+export const CollectionNameWrapper = styled(Box)({
+  display: 'flex',
+  alignItems: 'center',
+  width: 'calc(100% - 45px)',
+  '& .collectionName': {
+    minWidth: 36,
+  },
+});
+
+export const Count = styled('span')(({ theme }) => ({
+  fontSize: '13px',
+  fontWeight: 500,
+  marginLeft: theme.spacing(0.5),
+  color: theme.palette.text.secondary,
+  pointerEvents: 'none',
+}));
+
+export const StatusDot = styled(Box, {
+  shouldForwardProp: prop => prop !== 'status',
+})<{ status: 'loaded' | 'unloaded' | 'loading' | 'noIndex' }>(
+  ({ theme, status }) => ({
+    width: '8px',
+    height: '8px',
+    borderRadius: '50%',
+    position: 'absolute',
+    right: 6,
+    top: 10,
+    zIndex: 1,
+    ...(status === 'loaded' && {
+      border: `1px solid ${theme.palette.primary.main}`,
+      backgroundColor: theme.palette.primary.main,
+    }),
+    ...(status === 'unloaded' && {
+      border: `1px solid ${theme.palette.primary.main}`,
+      background: '#fff !important',
+    }),
+    ...(status === 'loading' && {
+      border: `1px solid ${theme.palette.primary.light}`,
+      backgroundColor: `${theme.palette.primary.light} !important`,
+    }),
+    ...(status === 'noIndex' && {
+      border: `1px solid ${theme.palette.text.disabled}`,
+      backgroundColor: theme.palette.text.disabled,
+    }),
+  })
+);
+
+export const StyledMenuItem = styled(MenuItem)(({ theme }) => ({
+  fontSize: '14px',
+  padding: '6px 16px',
+}));
+
+export const StyledDivider = styled(Divider)(({ theme }) => ({
+  margin: 0,
+}));

+ 18 - 41
client/src/pages/databases/tree/TreeContextMenu.tsx

@@ -10,8 +10,7 @@ import RenameCollectionDialog from '@/pages/dialogs/RenameCollectionDialog';
 import InsertDialog from '@/pages/dialogs/insert/Dialog';
 import ImportSampleDialog from '@/pages/dialogs/ImportSampleDialog';
 import EmptyDataDialog from '@/pages/dialogs/EmptyDataDialog';
-import { MenuItem, Divider } from '@mui/material';
-import { useStyles } from './style';
+import { StyledMenuItem, StyledDivider } from './StyledComponents';
 import type { ContextMenu } from './types';
 import type { CollectionObject } from '@server/types';
 
@@ -24,7 +23,6 @@ export const TreeContextMenu = (props: {
   const { fetchCollection, database } = useContext(dataContext);
   const navigate = useNavigate();
 
-  const classes = useStyles();
   // props
   const { contextMenu, onClick } = props;
   // i18n
@@ -39,8 +37,7 @@ export const TreeContextMenu = (props: {
     case 'db':
       return (
         <>
-          <MenuItem
-            className={classes.menuItem}
+          <StyledMenuItem
             onClick={() => {
               setDialog({
                 open: true,
@@ -60,23 +57,14 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('createCollection')}
-          </MenuItem>
-          {/* 
-          <MenuItem
-            className={classes.menuItem}
-            disabled={true}
-            onClick={() => handleMenuClick('Delete')}
-          >
-            {actionTrans('dropDatabase')}
-          </MenuItem> */}
+          </StyledMenuItem>
         </>
       );
 
     case 'collection':
       return (
         <>
-          <MenuItem
-            className={classes.menuItem}
+          <StyledMenuItem
             disabled={(contextMenu.object as CollectionObject).loaded}
             onClick={() => {
               setDialog({
@@ -94,9 +82,8 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('loadCollection')}
-          </MenuItem>
-          <MenuItem
-            className={classes.menuItem}
+          </StyledMenuItem>
+          <StyledMenuItem
             disabled={!(contextMenu.object as CollectionObject).loaded}
             onClick={() => {
               setDialog({
@@ -114,9 +101,8 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('releaseCollection')}
-          </MenuItem>
-          <MenuItem
-            className={classes.menuItem}
+          </StyledMenuItem>
+          <StyledMenuItem
             onClick={() => {
               setDialog({
                 open: true,
@@ -138,10 +124,8 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('renameCollection')}
-          </MenuItem>
-
-          <MenuItem
-            className={classes.menuItem}
+          </StyledMenuItem>
+          <StyledMenuItem
             onClick={() => {
               setDialog({
                 open: true,
@@ -160,12 +144,9 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('dropCollection')}
-          </MenuItem>
-
-          <Divider />
-
-          <MenuItem
-            className={classes.menuItem}
+          </StyledMenuItem>
+          <StyledDivider />
+          <StyledMenuItem
             onClick={() => {
               const collection = contextMenu.object as CollectionObject;
               setDialog({
@@ -188,10 +169,8 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('importFile')}
-          </MenuItem>
-
-          <MenuItem
-            className={classes.menuItem}
+          </StyledMenuItem>
+          <StyledMenuItem
             onClick={() => {
               const collection = contextMenu.object as CollectionObject;
               setDialog({
@@ -211,10 +190,8 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('insertSampleData')}
-          </MenuItem>
-
-          <MenuItem
-            className={classes.menuItem}
+          </StyledMenuItem>
+          <StyledMenuItem
             onClick={() => {
               const collection = contextMenu.object as CollectionObject;
               setDialog({
@@ -234,7 +211,7 @@ export const TreeContextMenu = (props: {
             }}
           >
             {actionTrans('emptyCollection')}
-          </MenuItem>
+          </StyledMenuItem>
         </>
       );
   }

+ 26 - 21
client/src/pages/databases/tree/index.tsx

@@ -19,9 +19,7 @@ import {
 } from '@mui/material'; // Added Box, IconButton
 import { useNavigate } from 'react-router-dom';
 import { CollectionObject } from '@server/types';
-import clcx from 'clsx';
 import { formatNumber } from '@/utils';
-import { useStyles } from './style';
 import {
   DatabaseTreeItem as OriginalDatabaseTreeItem, // Rename original type
   TreeNodeType,
@@ -32,6 +30,12 @@ import {
 import { TreeContextMenu } from './TreeContextMenu';
 import { useVirtualizer } from '@tanstack/react-virtual'; // Import virtualizer
 import { dataContext } from '@/context';
+import {
+  CollectionNodeWrapper,
+  CollectionNameWrapper,
+  Count,
+  StatusDot,
+} from './StyledComponents';
 
 // Define a type for the flattened list item
 interface FlatTreeItem {
@@ -47,15 +51,7 @@ interface FlatTreeItem {
 
 // ... existing CollectionNode component (can be reused or integrated) ...
 const CollectionNode: React.FC<{ data: CollectionObject }> = ({ data }) => {
-  // i18n collectionTrans
   const { t: commonTrans } = useTranslation();
-  const classes = useStyles();
-  const loadClass = clcx(classes.dot, {
-    [classes.loaded]: data.loaded,
-    [classes.unloaded]: !data.loaded,
-    [classes.loading]: data.status === 'loading',
-    [classes.noIndex]: !data.schema || !data.schema.hasVectorIndex,
-  });
   const hasIndex = data.schema && data.schema.hasVectorIndex;
   const loadStatus = hasIndex
     ? data.loaded
@@ -63,22 +59,26 @@ const CollectionNode: React.FC<{ data: CollectionObject }> = ({ data }) => {
       : commonTrans('status.unloaded')
     : commonTrans('status.noVectorIndex');
 
+  const getStatus = () => {
+    if (!data.schema || !data.schema.hasVectorIndex) return 'noIndex';
+    if (data.status === 'loading') return 'loading';
+    return data.loaded ? 'loaded' : 'unloaded';
+  };
+
   return (
-    <div className={classes.collectionNode}>
-      <div className={classes.collectionName}>
+    <CollectionNodeWrapper>
+      <CollectionNameWrapper>
         <Tooltip title={data.collection_name} placement="top">
           <Typography noWrap className="collectionName">
             {data.collection_name}
           </Typography>
         </Tooltip>
-        <span className={classes.count}>
-          ({formatNumber(data.rowCount || 0)})
-        </span>
-      </div>
+        <Count>({formatNumber(data.rowCount || 0)})</Count>
+      </CollectionNameWrapper>
       <Tooltip title={loadStatus} placement="top">
-        <div className={loadClass}></div>
+        <StatusDot status={getStatus()} />
       </Tooltip>
-    </div>
+    </CollectionNodeWrapper>
   );
 };
 
@@ -91,7 +91,6 @@ const DatabaseTree: React.FC<DatabaseTreeProps> = props => {
   } = props;
 
   // context
-  const classes = useStyles();
   const navigate = useNavigate();
   const { collectionName = '' } = useParams<{ collectionName: string }>();
   const { batchRefreshCollections } = useContext(dataContext);
@@ -346,10 +345,16 @@ const DatabaseTree: React.FC<DatabaseTreeProps> = props => {
     <>
       <Box
         ref={parentRef}
-        className={classes.root} // Apply root styles (ensure height and overflow)
         sx={{
           height: treeHeight, // Adjust this height based on your layout requirements
           overflow: 'auto',
+          fontSize: '15px',
+          color: theme => theme.palette.text.primary,
+          backgroundColor: theme => theme.palette.background.default,
+          '& .MuiSvgIcon-root': {
+            fontSize: '14px',
+            color: theme => theme.palette.text.primary,
+          },
         }}
       >
         <Box
@@ -434,7 +439,7 @@ const DatabaseTree: React.FC<DatabaseTreeProps> = props => {
                   <CollectionNode data={node.data as CollectionObject} />
                 ) : (
                   <Tooltip title={node.name} placement="top">
-                    <Typography noWrap className={classes.dbName}>
+                    <Typography noWrap sx={{ width: 'calc(100% - 30px)' }}>
                       {/* Reuse dbName style or create a generic one */}
                       {node.name}
                       {node.type === 'db' && (

+ 0 - 67
client/src/pages/databases/tree/style.ts

@@ -1,67 +0,0 @@
-import { makeStyles } from '@mui/styles';
-import { Theme } from '@mui/material';
-
-export const useStyles = makeStyles((theme: Theme) => ({
-  root: {
-    fontSize: '15px',
-    color: theme.palette.text.primary,
-    backgroundColor: theme.palette.background.default,
-    '& .MuiSvgIcon-root': {
-      fontSize: '14px',
-      color: theme.palette.text.primary,
-    },
-  },
-  collectionNode: {
-    minHeight: '24px',
-    lineHeight: '24px',
-    display: 'flex',
-    minWidth: 190,
-  },
-  collectionName: {
-    display: 'flex',
-    alignItems: 'center',
-    width: 'calc(100% - 45px)',
-    '& .collectionName': {
-      minWidth: 36,
-    },
-  },
-  dbName: {
-    width: 'calc(100% - 30px)',
-  },
-  count: {
-    fontSize: '13px',
-    fontWeight: 500,
-    marginLeft: theme.spacing(0.5),
-    color: theme.palette.text.secondary,
-    pointerEvents: 'none',
-  },
-  dot: {
-    width: '8px',
-    height: '8px',
-    borderRadius: '50%',
-    position: 'absolute',
-    right: 6,
-    top: 10,
-    zIndex: 1,
-  },
-  loaded: {
-    border: `1px solid ${theme.palette.primary.main}`,
-    backgroundColor: theme.palette.primary.main,
-  },
-  unloaded: {
-    border: `1px solid ${theme.palette.primary.main}`,
-    background: '#fff !important',
-  },
-  loading: {
-    border: `1px solid ${theme.palette.primary.light}`,
-    backgroundColor: `${theme.palette.primary.light} !important`,
-  },
-  noIndex: {
-    border: `1px solid ${theme.palette.text.disabled}`,
-    backgroundColor: theme.palette.text.disabled,
-  },
-  menuItem: {
-    fontSize: '14px',
-    boderBottom: `1px solid ${theme.palette.divider}`,
-  },
-}));

+ 74 - 0
client/src/pages/databases/tree/styles.tsx

@@ -0,0 +1,74 @@
+import { Box, styled } from '@mui/material';
+
+export const TreeRoot = styled(Box)(({ theme }) => ({
+  fontSize: '15px',
+  color: theme.palette.text.primary,
+  backgroundColor: theme.palette.background.default,
+  '& .MuiSvgIcon-root': {
+    fontSize: '14px',
+    color: theme.palette.text.primary,
+  },
+}));
+
+export const CollectionNode = styled(Box)(({ theme }) => ({
+  minHeight: '24px',
+  lineHeight: '24px',
+  display: 'flex',
+  minWidth: 190,
+}));
+
+export const CollectionName = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  alignItems: 'center',
+  width: 'calc(100% - 45px)',
+  '& .collectionName': {
+    minWidth: 36,
+  },
+}));
+
+export const DbName = styled(Box)(({ theme }) => ({
+  width: 'calc(100% - 30px)',
+}));
+
+export const Count = styled(Box)(({ theme }) => ({
+  fontSize: '13px',
+  fontWeight: 500,
+  marginLeft: theme.spacing(0.5),
+  color: theme.palette.text.secondary,
+  pointerEvents: 'none',
+}));
+
+export const Dot = styled(Box)(({ theme }) => ({
+  width: '8px',
+  height: '8px',
+  borderRadius: '50%',
+  position: 'absolute',
+  right: 6,
+  top: 10,
+  zIndex: 1,
+}));
+
+export const LoadedDot = styled(Dot)(({ theme }) => ({
+  border: `1px solid ${theme.palette.primary.main}`,
+  backgroundColor: theme.palette.primary.main,
+}));
+
+export const UnloadedDot = styled(Dot)(({ theme }) => ({
+  border: `1px solid ${theme.palette.primary.main}`,
+  background: '#fff !important',
+}));
+
+export const LoadingDot = styled(Dot)(({ theme }) => ({
+  border: `1px solid ${theme.palette.primary.light}`,
+  backgroundColor: `${theme.palette.primary.light} !important`,
+}));
+
+export const NoIndexDot = styled(Dot)(({ theme }) => ({
+  border: `1px solid ${theme.palette.text.disabled}`,
+  backgroundColor: theme.palette.text.disabled,
+}));
+
+export const MenuItem = styled(Box)(({ theme }) => ({
+  fontSize: '14px',
+  borderBottom: `1px solid ${theme.palette.divider}`,
+}));

+ 14 - 3
client/src/pages/play/JSONEditor.tsx

@@ -6,9 +6,21 @@ import { linter } from '@codemirror/lint';
 import { githubLight } from '@ddietr/codemirror-themes/github-light';
 import { githubDark } from '@ddietr/codemirror-themes/github-dark';
 import { Compartment } from '@codemirror/state';
+import { styled } from '@mui/material/styles';
 
 import { useCodeMirror } from './hooks/use-codemirror';
-import { getCMStyle, getStyles } from './style';
+import { getCMStyle } from './style';
+
+const EditorContainer = styled('div')({
+  height: '100%',
+  overflow: 'auto',
+  '& .cm-editor': {
+    height: '100%',
+  },
+  '& .cm-scroller': {
+    overflow: 'auto',
+  },
+});
 
 type Props = {
   value: string;
@@ -21,7 +33,6 @@ export const JSONEditor: FC<Props> = props => {
   const theme = useTheme();
   const themeCompartment = new Compartment();
   const container = useRef<HTMLDivElement>(null);
-  const classes = getStyles();
 
   const isDarkMode = theme.palette.mode === 'dark';
 
@@ -43,5 +54,5 @@ export const JSONEditor: FC<Props> = props => {
     onChange,
   });
 
-  return <div ref={container} className={classes.editor}></div>;
+  return <EditorContainer ref={container} />;
 };

+ 42 - 8
client/src/pages/play/Play.tsx

@@ -24,7 +24,7 @@ import { useCodelensShortcuts } from './hooks/use-codelens-shortcuts';
 import { Autocomplete } from './language/extensions/autocomplete';
 import { KeyMap } from './language/extensions/keymap';
 import { MilvusHTTP } from './language/milvus.http';
-import { getCMStyle, getStyles } from './style';
+import { getCMStyle } from './style';
 import { CustomEventNameEnum, PlaygroundCustomEventDetail } from './Types';
 import { DocumentEventManager } from './utils/event';
 import { JSONEditor } from './JSONEditor';
@@ -49,7 +49,6 @@ const Play: FC = () => {
   const { isManaged, authReq } = useContext(authContext);
 
   // styles
-  const classes = getStyles();
   const [code, setCode] = useState(() => {
     const savedCode = localStorage.getItem(ATTU_PLAY_CODE);
     return savedCode || DEFAULT_CODE_VALUE;
@@ -160,16 +159,51 @@ const Play: FC = () => {
   }, []);
 
   return (
-    <Box className={classes.root}>
-      <Paper elevation={0} className={classes.leftPane}>
-        <div
+    <Box
+      sx={{
+        margin: '0',
+        position: 'relative',
+        display: 'flex',
+        overflow: 'hidden',
+        borderRadius: 8,
+        height: '100vh',
+        padding: theme.spacing(2),
+      }}
+    >
+      <Paper
+        elevation={0}
+        sx={{
+          flex: 1,
+          padding: 0,
+          display: 'flex',
+          flexDirection: 'column',
+        }}
+      >
+        <Box
           ref={container}
           defaultValue={code}
-          className={classes.editor}
-        ></div>
+          sx={{
+            width: '100%',
+            height: '100%',
+            border: 'none',
+            outline: 'none',
+            resize: 'none',
+            fontSize: '16px',
+            fontFamily: 'monospace',
+            backgroundColor: 'transparent',
+          }}
+        ></Box>
       </Paper>
 
-      <Paper elevation={0} className={classes.rightPane}>
+      <Paper
+        elevation={0}
+        sx={{
+          flex: 1,
+          marginLeft: theme.spacing(2),
+          display: 'flex',
+          overflow: 'hidden',
+        }}
+      >
         <JSONEditor value={content || `{}`} editable={false} />
       </Paper>
     </Box>

+ 0 - 35
client/src/pages/play/style.tsx

@@ -1,40 +1,5 @@
-import { makeStyles } from '@mui/styles';
 import { Theme } from '@mui/material';
 
-export const getStyles = makeStyles((theme: Theme) => ({
-  root: {
-    margin: '0',
-    position: 'relative',
-    display: 'flex',
-    overflow: 'hidden',
-    borderRadius: 8,
-    height: '100vh',
-    padding: theme.spacing(2),
-  },
-  leftPane: {
-    flex: 1,
-    padding: 0,
-    display: 'flex',
-    flexDirection: 'column',
-  },
-  rightPane: {
-    flex: 1,
-    marginLeft: theme.spacing(2),
-    display: 'flex',
-    overflow: 'hidden',
-  },
-  editor: {
-    width: '100%',
-    height: '100%',
-    border: 'none',
-    outline: 'none',
-    resize: 'none',
-    fontSize: '16px',
-    fontFamily: 'monospace',
-    backgroundColor: 'transparent',
-  },
-}));
-
 export const getCMStyle = (theme: Theme) => {
   const isDark = theme.palette.mode === 'dark';
   return {

+ 43 - 14
client/src/pages/search/SearchParams.tsx

@@ -21,7 +21,7 @@ const SearchParams: FC<SearchParamsProps> = ({
   handleFormChange,
   topK,
   setParamsDisabled,
-  wrapperClass = '',
+  sx = {},
 }) => {
   const { t: warningTrans } = useTranslation('warning');
 
@@ -375,19 +375,48 @@ const SearchParams: FC<SearchParamsProps> = ({
   }, [disabled, setParamsDisabled]);
 
   return (
-    <Box className={wrapperClass}>
-      <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
-        {/* dynamic params, now every type only has one param except metric type */}
-        {searchParams.map(param => (
-          <CustomInput
-            key={param}
-            type="text"
-            textConfig={getSearchInputConfig(param)}
-            checkValid={checkIsValid}
-            validInfo={validation}
-          />
-        ))}
-      </Box>
+    <Box
+      sx={{
+        ...sx,
+        display: 'flex',
+        flexDirection: 'column',
+        gap: 2,
+        '& .inline-input': {
+          marginBottom: '20px',
+        },
+      }}
+    >
+      {/* dynamic params, now every type only has one param except metric type */}
+      {searchParams.map(param => (
+        <CustomInput
+          key={param}
+          type="text"
+          textConfig={{
+            ...getSearchInputConfig(param),
+            className: 'inline-input',
+            sx: {
+              width: '100%',
+              '& .MuiFormHelperText-root': {
+                position: 'absolute',
+                margin: 0,
+                padding: '0 14px',
+                fontSize: '0.75rem',
+                lineHeight: '1.66',
+                letterSpacing: '0.03333em',
+                textAlign: 'left',
+                marginTop: '3px',
+                marginRight: '14px',
+                marginBottom: '0',
+                marginLeft: '14px',
+              },
+            },
+            variant: 'outlined',
+            InputLabelProps: { shrink: true },
+          }}
+          checkValid={checkIsValid}
+          validInfo={validation}
+        />
+      ))}
     </Box>
   );
 };

+ 2 - 1
client/src/pages/search/Types.ts

@@ -2,6 +2,7 @@ import { searchKeywordsType } from '@/consts';
 import { DataTypeEnum } from '@/consts';
 import type { FieldObject, KeyValuePair } from '@server/types';
 import type { Option } from '@/components/customSelector/Types';
+import type { SxProps, Theme } from '@mui/material/styles';
 
 export interface SearchParamsProps {
   // default index type is FLAT
@@ -14,9 +15,9 @@ export interface SearchParamsProps {
   handleFormChange: (form: { [key in string]: number | string }) => void;
   topK: number;
   handleConsistencyChange: (type: string) => void;
-  wrapperClass?: string;
   setParamsDisabled: (isDisabled: boolean) => void;
   consistency_level: string;
+  sx: SxProps<Theme>;
 }
 
 export interface SearchResultView {

+ 35 - 38
client/src/pages/system/MiniTopology.tsx

@@ -1,44 +1,42 @@
 import { FC } from 'react';
-import { Theme, useTheme } from '@mui/material';
-import { makeStyles } from '@mui/styles';
+import { Theme, useTheme, styled } from '@mui/material';
 import type { MiniTopoProps } from './Types';
 
-const getStyles = makeStyles((theme: Theme) => ({
-  container: {},
-  childNode: {
-    transition: 'all .25s',
-    cursor: 'pointer',
-    transformOrigin: '50% 50%',
-    transformBox: 'fill-box',
+const StyledSvg = styled('svg')(({ theme }: { theme: Theme }) => ({}));
 
-    '& circle': {
-      transition: 'all .25s',
-    },
+const StyledG = styled('g')(({ theme }: { theme: Theme }) => ({
+  transition: 'all .25s',
+  cursor: 'pointer',
+  transformOrigin: '50% 50%',
+  transformBox: 'fill-box',
 
-    '& text': {
-      transition: 'all .25s',
-    },
+  '& circle': {
+    transition: 'all .25s',
+  },
 
-    '&:hover, &:focus': {
-      transform: 'scale(1.1)',
-      filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
-    },
+  '& text': {
+    transition: 'all .25s',
+  },
+
+  '&:hover, &:focus': {
+    transform: 'scale(1.1)',
+    filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
+  },
 
-    '&:focus': {
-      outline: 'none',
+  '&:focus': {
+    outline: 'none',
 
-      '& svg path': {
-        fill: theme.palette.background.paper,
-      },
+    '& svg path': {
+      fill: theme.palette.background.paper,
+    },
 
-      '& circle': {
-        fill: theme.palette.primary.main,
-        stroke: theme.palette.primary.main,
-      },
+    '& circle': {
+      fill: theme.palette.primary.main,
+      stroke: theme.palette.primary.main,
+    },
 
-      '& text': {
-        fill: theme.palette.background.paper,
-      },
+    '& text': {
+      fill: theme.palette.background.paper,
     },
   },
 }));
@@ -48,7 +46,6 @@ const capitalize = (s: string) => {
 };
 
 const MiniTopo: FC<MiniTopoProps> = props => {
-  const classes = getStyles();
   const theme = useTheme();
   const { selectedCord, selectedChildNode, setCord, setShowChildView } = props;
 
@@ -65,8 +62,8 @@ const MiniTopo: FC<MiniTopoProps> = props => {
     HEIGHT / 2 + LINE * Math.sin((ANGLE * Math.PI) / 180);
 
   return (
-    <svg
-      className={classes.container}
+    <StyledSvg
+      theme={theme}
       width={WIDTH}
       height={HEIGHT}
       viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
@@ -80,8 +77,8 @@ const MiniTopo: FC<MiniTopoProps> = props => {
         y2={childNodeCenterY}
         stroke={theme.palette.primary.main}
       />
-      <g
-        className={classes.childNode}
+      <StyledG
+        theme={theme}
         onClick={() => {
           setShowChildView(false);
         }}
@@ -104,7 +101,7 @@ const MiniTopo: FC<MiniTopoProps> = props => {
         >
           {selectedCord ? capitalize(selectedCord.infos?.name) : ''}
         </text>
-      </g>
+      </StyledG>
       <g>
         <svg
           width="60"
@@ -137,7 +134,7 @@ const MiniTopo: FC<MiniTopoProps> = props => {
           y={childNodeCenterY + 50}
         >{`${selectedChildNode ? selectedChildNode.infos?.name : ''}`}</text>
       </g>
-    </svg>
+    </StyledSvg>
   );
 };
 

+ 9 - 9
client/src/pages/system/Topology.tsx

@@ -1,14 +1,8 @@
-import { Theme, useTheme } from '@mui/material';
+import { Box, Theme, useTheme } from '@mui/material';
 import { useEffect } from 'react';
 import { makeStyles } from '@mui/styles';
 
 const getStyles = makeStyles((theme: Theme) => ({
-  container: {
-    overflow: 'auto',
-    backgroundColor: theme.palette.background.paper,
-    // height: 'auto',
-    // width: '100%',
-  },
   rootNode: {
     transition: 'all .25s',
     cursor: 'pointer',
@@ -159,7 +153,13 @@ const Topo = (props: any) => {
   let centerNode: any;
 
   return (
-    <div className={classes.container}>
+    <Box
+      sx={{
+        display: 'flex',
+        overflow: 'auto',
+        backgroundColor: theme.palette.background.paper,
+      }}
+    >
       <svg
         width={WIDTH}
         height={HEIGHT}
@@ -556,7 +556,7 @@ const Topo = (props: any) => {
           </text>
         </g>
       </svg>
-    </div>
+    </Box>
   );
 };