diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index 67b3237ba76305ccc559d7291af12009e0319802..162bd5f87ab1e83da0c8a7628b532ab1787ae6ba 100644 --- a/apps/web/src/app/app.tsx +++ b/apps/web/src/app/app.tsx @@ -24,6 +24,7 @@ import { GraphQueryResultFromBackend, GraphQueryResultFromBackendPayload, resetGraphQueryResults, + queryingBackend, } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice'; import { Query2BackendQuery, QueryBuilder, QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder'; import { Schema } from '@graphpolaris/shared/lib/schema/panel'; @@ -109,6 +110,7 @@ export function App(props: App) { if (query.nodes.length === 0) { dispatch(resetGraphQueryResults()); } else { + dispatch(queryingBackend()); api_query.execute(Query2BackendQuery(session.currentDatabase, query, queryBuilderSettings, ml)); } } diff --git a/apps/web/src/app/panels/Visualization.tsx b/apps/web/src/app/panels/Visualization.tsx index b41d5d8813182fddc383ceb9b166fc149cab5c19..a0c52c073288df769c05c3aa3b676867e1867df9 100644 --- a/apps/web/src/app/panels/Visualization.tsx +++ b/apps/web/src/app/panels/Visualization.tsx @@ -1,57 +1,60 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { RawJSONVis, NodeLinkVis, PaohVis, SemanticSubstrates } from '@graphpolaris/shared/lib/vis'; -import { useVisualizationCache } from '@graphpolaris/shared/lib/data-access'; +import { useGraphQueryResult, useQuerybuilderGraph, useVisualizationState } from '@graphpolaris/shared/lib/data-access'; +import { Visualizations } from '@graphpolaris/shared/lib/data-access/store/visualizationSlice'; +import { LoadingSpinner } from '@graphpolaris/shared/lib/components/LoadingSpinner'; export const VisualizationPanel = () => { - const vis = useVisualizationCache() + const vis = useVisualizationState(); + const graphQueryResult = useGraphQueryResult(); + const query = useQuerybuilderGraph(); + + const visualizationComponent = useMemo(() => { + switch (vis.activeVisualization) { + case Visualizations.NodeLink: + return ( + <div id={Visualizations.NodeLink} className="tabContent w-full h-full"> + <NodeLinkVis /> + </div> + ); + case Visualizations.RawJSON: + return ( + <div id={Visualizations.RawJSON} className="tabContent w-full h-full"> + <RawJSONVis /> + </div> + ); + case Visualizations.Paohvis: + return ( + <div id={Visualizations.Paohvis} className="tabContent w-full h-full"> + <PaohVis rowHeight={30} hyperedgeColumnWidth={30} gapBetweenRanges={3} /> + </div> + ); + default: + return null; + } + }, [graphQueryResult]); + return ( - <div className="vis-panel h-full w-full overflow-y-auto"> - <h1>Visualization Panel</h1> - <p className='text-sm'>{vis.visual}</p> - {/* <div> - <button - onClick={() => - dispatch( - assignNewGraphQueryResult({ - nodes: [ - { - id: 'agent/007', - attributes: { name: 'Daniel Craig' }, - }, - ], - edges: [], - }) - ) - } - > - Load in mock result - </button> - <button - onClick={() => - dispatch(assignNewGraphQueryResult({ nodes: [], edges: [] })) - } - > - Remove mock result - </button> - </div> */} - {/* width: '83%', - height: '95vh', */} - <div className="w-full h-[calc(100%-2rem)]"> - <div id="NodeLinkVis" className='tabContent'> - {vis.visual === "NodeLinkVis" ? <><NodeLinkVis /></> : null} - </div> - <div id="RawJSONVis" className='tabContent'> - {vis.visual === "RawJSONVis" ? <><RawJSONVis /></> : null} - </div> - <div id="PaohVis" className='tabContent'> - {vis.visual === "PaohVis" ? <><PaohVis rowHeight={30} hyperedgeColumnWidth={30} gapBetweenRanges={3} /></> : null} - </div> - {/* <div id="SemSubVis" className='tabContent'> - <SemanticSubstrates /> - </div> */} + <div className="vis-panel h-full w-full overflow-y-auto" style={graphQueryResult.nodes.length === 0 ? { overflow: 'hidden' } : {}}> + <h1> + <span>Visualization Panel | </span> + <span className="text-sm">{vis.activeVisualization}</span> + </h1> + <div className="h-[calc(100%-2rem)]"> + {graphQueryResult.queryingBackend && ( + <div className="w-full h-full flex flex-col items-center justify-center overflow-hidden"> + <LoadingSpinner>Querying backend...</LoadingSpinner> + </div> + )} + {!graphQueryResult.queryingBackend && graphQueryResult.nodes.length === 0 ? ( + <div className="w-full h-full flex flex-col items-center justify-center"> + <p>No data available to be shown</p> + {query.nodes.length > 0 ? <p>Query resulted in empty dataset</p> : <p>Query for data to visualize</p>} + </div> + ) : ( + <div className="w-full h-full">{visualizationComponent}</div> + )} </div> - - {/* <div /> */} </div> ); }; diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx index fcc67696792be497cac9bfe7c1608647f550b4d0..ff78645393585300a4c19323b59c36366a898c95 100644 --- a/apps/web/src/components/navbar/navbar.tsx +++ b/apps/web/src/components/navbar/navbar.tsx @@ -21,7 +21,7 @@ import { useDatabaseAPI, useSchemaAPI, useSessionCache, - useVisualizationCache, + useVisualizationState, } from '@graphpolaris/shared/lib/data-access'; import { DatabaseMenu } from './databasemenu'; import { NewDatabaseForm } from './AddDatabaseForm/newdatabaseform'; @@ -29,7 +29,7 @@ import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice import { SearchBar } from './search/SearchBar'; /** NavbarComponentProps is an interface containing the NavbarViewModel. */ -import { setVisual } from '@graphpolaris/shared/lib/data-access/store/visualisationSlice'; +import { Visualizations, setActiveVisualization } from '@graphpolaris/shared/lib/data-access/store/visualizationSlice'; export interface NavbarComponentProps { // changeColourPalette: () => void; FIXME move to redux } @@ -54,7 +54,7 @@ export interface NavbarSubComponentState { export const Navbar = (props: NavbarComponentProps) => { const auth = useAuthorizationCache(); const session = useSessionCache(); - const vis = useVisualizationCache(); + const vis = useVisualizationState(); const api = useDatabaseAPI(); const schemaApi = useSchemaAPI(); const dispatch = useAppDispatch(); @@ -77,7 +77,7 @@ export const Navbar = (props: NavbarComponentProps) => { /> <div title="GraphPolaris" className="navbar w-full"> <a href="https://graphpolaris.com/" className="mr-auto" target="_blank"> - <img src={currentLogo} alt="GraphPolaris" className="h-9"/> + <img src={currentLogo} alt="GraphPolaris" className="h-9" /> </a> <SearchBar /> <div className="dropdown"> @@ -85,25 +85,13 @@ export const Navbar = (props: NavbarComponentProps) => { Vis </label> <ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52"> - <li - onClick={() => { - dispatch(setVisual('NodeLinkVis')); - }} - > + <li onClick={() => dispatch(setActiveVisualization(Visualizations.NodeLink))}> <a>Node Link</a> </li> - <li - onClick={() => { - dispatch(setVisual('PaohVis')); - }} - > + <li onClick={() => dispatch(setActiveVisualization(Visualizations.Paohvis))}> <a>PaohVis</a> </li> - <li - onClick={() => { - dispatch(setVisual('RawJSONVis')); - }} - > + <li onClick={() => dispatch(setActiveVisualization(Visualizations.RawJSON))}> <a>JSON Structure</a> </li> {/* <li><a>Semantic Substrates</a></li> */} diff --git a/docker-compose.yml b/docker-compose.yml index e728d6c41511927e08839b2d6f8a5ed9d1635917..33d9dc555c0ab8d5510990d60b6bcfeb6ee51dcb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,6 @@ services: - BACKEND_URL=http:// - BACKEND_WSS_URL=ws://client-updater-service/ - STAGING=dev - - SKIP_LOGIN=true - BACKEND_USER=user-management-service - BACKEND_QUERY=query-service - BACKEND_SCHEMA=schema-service diff --git a/libs/shared/lib/components/LoadingSpinner.tsx b/libs/shared/lib/components/LoadingSpinner.tsx new file mode 100644 index 0000000000000000000000000000000000000000..69e5c8101edc64b69f9c654bde67d123dbcbb129 --- /dev/null +++ b/libs/shared/lib/components/LoadingSpinner.tsx @@ -0,0 +1,25 @@ +import { PropsWithChildren } from 'react'; + +export const LoadingSpinner = (props: PropsWithChildren) => { + return ( + <div className="text-sm"> + <svg + aria-hidden="true" + className="inline w-6 h-6 mr-2 text-gray-200 animate-spin fill-entity-600" + viewBox="0 0 100 101" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" + fill="currentColor" + /> + <path + d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" + fill="currentFill" + /> + </svg> + {props.children ? props.children : 'Connecting...'} + </div> + ); +}; diff --git a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts index 634d35068f6853984a64ee61c080317d1d7b0c51..773e5891de6af6555e9ca92eba3b4f3627f6940c 100755 --- a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts +++ b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts @@ -51,6 +51,7 @@ export interface GraphQueryResult { // Describes what entities there are in this graph query result. nodeTypes: string[]; + queryingBackend: boolean; } // Define the initial state using that type @@ -58,6 +59,7 @@ export const initialState: GraphQueryResult = { nodes: [], edges: [], nodeTypes: [], + queryingBackend: false, }; export const graphQueryResultSlice = createSlice({ @@ -102,17 +104,22 @@ export const graphQueryResultSlice = createSlice({ state.nodes = nodes; state.edges = edges; state.nodeTypes = nodeTypes; + state.queryingBackend = false; }, resetGraphQueryResults: (state) => { // Assign new state state.nodes = []; state.edges = []; state.nodeTypes = []; + state.queryingBackend = false; + }, + queryingBackend: (state) => { + state.queryingBackend = true; }, }, }); -export const { assignNewGraphQueryResult, resetGraphQueryResults } = graphQueryResultSlice.actions; +export const { assignNewGraphQueryResult, resetGraphQueryResults, queryingBackend } = graphQueryResultSlice.actions; // Other code such as selectors can use the imported `RootState` type export const selectGraphQueryResult = (state: RootState) => state.graphQueryResult; diff --git a/libs/shared/lib/data-access/store/hooks.ts b/libs/shared/lib/data-access/store/hooks.ts index 35aca4601667e5691a8490a99395dff726f360b2..c6da6ad308ab98f04b0e82e1891d5c62817d94f0 100644 --- a/libs/shared/lib/data-access/store/hooks.ts +++ b/libs/shared/lib/data-access/store/hooks.ts @@ -10,7 +10,7 @@ import { } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { sessionCacheState } from './sessionSlice'; import { authState } from './authSlice'; -import { visualState } from './visualisationSlice'; +import { visualizationState } from './visualizationSlice'; import { allMLEnabled, selectML } from './mlSlice'; import { searchResultState, searchResultData, searchResultSchema, searchResultQB } from './searchResultSlice'; @@ -36,7 +36,6 @@ export const useQuerybuilderSettings = () => useAppSelector(queryBuilderSettings export const useConfig = () => useAppSelector(configState); export const useSessionCache = () => useAppSelector(sessionCacheState); export const useAuthorizationCache = () => useAppSelector(authState); -export const useVisualizationCache = () =>useAppSelector(visualState); // Machine Learning Slices export const useML = () => useAppSelector(selectML); @@ -47,3 +46,6 @@ export const useSearchResult = () => useAppSelector(searchResultState); export const useSearchResultData = () => useAppSelector(searchResultData); export const useSearchResultSchema = () => useAppSelector(searchResultSchema); export const useSearchResultQB = () => useAppSelector(searchResultQB); + +// Visualization Slices +export const useVisualizationState = () => useAppSelector(visualizationState); diff --git a/libs/shared/lib/data-access/store/sessionSlice.ts b/libs/shared/lib/data-access/store/sessionSlice.ts index bc9e98a8b5aa61faadfcbd92565b8f2552ce36ff..02c7201e9c65bbe9c1595aa76c71102d31be4ef7 100644 --- a/libs/shared/lib/data-access/store/sessionSlice.ts +++ b/libs/shared/lib/data-access/store/sessionSlice.ts @@ -13,26 +13,6 @@ export type SessionCacheI = { currentDatabase?: string; databases: string[]; error?: ErrorMessage; - - // queryListOpen: boolean; - // queryStatusList: { - // queries: Record<string, QueryStatusListItem>; - // queryIDsOrder: string[]; - // }; - // functionsMenuOpen: boolean; - // currentDatabaseKey: string; - // currentColours: { key: string; index: number }; - // elementsperDatabaseObject: Record<string, (AnyNode | Edge<any>)[]>; - // autoSendQueries: boolean; - // panelWidthHeights: { - // windowinnerHeight: number; - // windowinnerWidth: number; - // navBarHeight: number; - // schemaDrawerHeight: number; - // queryDrawerHeight: number; - // schemaDrawerWidth: number; - // queryDrawerWidth: number; - // }; }; // Define the initial state using that type diff --git a/libs/shared/lib/data-access/store/store.ts b/libs/shared/lib/data-access/store/store.ts index f54cdc8d2d1e58e42a5a7d590e72752e7f28e21d..af20b8beff50401d69e4fc6f369e1cf3f4e46014 100644 --- a/libs/shared/lib/data-access/store/store.ts +++ b/libs/shared/lib/data-access/store/store.ts @@ -7,7 +7,7 @@ import sessionSlice from './sessionSlice'; import authSlice from './authSlice'; import mlSlice from './mlSlice'; import searchResultSlice from './searchResultSlice'; -import visualisationSlice from './visualisationSlice'; +import visualizationSlice from './visualizationSlice'; export const store = configureStore({ reducer: { @@ -19,7 +19,7 @@ export const store = configureStore({ config: configSlice, ml: mlSlice, searchResults: searchResultSlice, - visualize: visualisationSlice + visualize: visualizationSlice, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ diff --git a/libs/shared/lib/data-access/store/visualisationSlice.ts b/libs/shared/lib/data-access/store/visualisationSlice.ts deleted file mode 100644 index 75d27ae7604946c34bb0345b9f2847c08cc39b97..0000000000000000000000000000000000000000 --- a/libs/shared/lib/data-access/store/visualisationSlice.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import type { RootState } from './store'; -import { dispatch } from 'd3'; - -// Define the initial state using that type -export const initialState = { - visual: 'NodeLinkVis', - tab: 'NodeLinkTab' - -}; - -export const visualisationSlice = createSlice({ - name: 'visualisation', - // `createSlice` will infer the state type from the `initialState` argument - initialState, - reducers: { - setVisualTab: (state, action) => { - state.visual = action.payload[0] - state.tab = action.payload[1] - }, - setTab: (state, action) => { - state.tab=action.payload - }, - setVisual: (state,action) => { - state.visual=action.payload - } - } - }, -); - -export const {setVisualTab, setTab, setVisual} = visualisationSlice.actions; - -// Other code such as selectors can use the imported `RootState` type -// export const configState = (state: RootState) => state.config; -export const visualState = (state:RootState)=> state.visualize; -export default visualisationSlice.reducer; diff --git a/libs/shared/lib/data-access/store/visualizationSlice.ts b/libs/shared/lib/data-access/store/visualizationSlice.ts new file mode 100644 index 0000000000000000000000000000000000000000..a84757a0dc86f808ec6edeabb71ec93d0fa9177f --- /dev/null +++ b/libs/shared/lib/data-access/store/visualizationSlice.ts @@ -0,0 +1,32 @@ +import { RawJSONVis } from '@graphpolaris/shared/lib/vis'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { RootState } from './store'; + +export enum Visualizations { + NodeLink = 'NodeLink', + Paohvis = 'Paohvis', + RawJSON = 'RawJSON', +} + +type VisState = { + activeVisualization: Visualizations; +}; + +export const initialState: VisState = { + activeVisualization: Visualizations.NodeLink, +}; + +export const visualizationSlice = createSlice({ + name: 'visualization', + initialState, + reducers: { + setActiveVisualization: (state, action: PayloadAction<Visualizations>) => { + state.activeVisualization = action.payload; + }, + }, +}); + +export const { setActiveVisualization } = visualizationSlice.actions; + +export const visualizationState = (state: RootState) => state.visualize; +export default visualizationSlice.reducer; diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx index 3e5b32eadd18792e0500436487252b46a30456bc..1d7a311926d89259c528425ac93282d4e011a66b 100644 --- a/libs/shared/lib/schema/panel/schema.tsx +++ b/libs/shared/lib/schema/panel/schema.tsx @@ -36,6 +36,7 @@ import SelfEdge from '../pills/edges/self-edge'; import { useSchemaAPI } from '../../data-access'; import { SchemaDialog } from './schemaDialog'; import { toSchemaGraphology } from '../../data-access/store/schemaSlice'; +import { LoadingSpinner } from '../../components/LoadingSpinner'; interface Props { content?: string; @@ -166,88 +167,83 @@ export const Schema = (props: Props) => { <div className="schema-panel w-full h-full"> <SchemaDialog open={toggleSchemaSettings} onClose={() => setToggleSchemaSettings(false)} ref={dialogRef} /> <div className="flex flex-col h-[1rem]"> - <h1>Schema</h1> - {loading ? ( - <p className="text-sm"> - <svg - aria-hidden="true" - className="inline w-6 h-6 mr-2 text-gray-200 animate-spin fill-entity-600" - viewBox="0 0 100 101" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" - fill="currentColor" - /> - <path - d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" - fill="currentFill" - /> - </svg> - Connecting... - </p> - ) : ( - <p className="text-sm">{`Connected to: ${session.currentDatabase}`}</p> - )} - - {nodes.length === 0 && !loading && <p className="text-sm">No Elements</p>} + <h1> + Schema{' '} + {loading ? ( + <span> + <LoadingSpinner>Connecting to {session.currentDatabase}...</LoadingSpinner> + </span> + ) : ( + <> + {session.currentDatabase && ( + <> + {' | '} + <span className="text-sm"> Connected to: {session.currentDatabase}</span> + </> + )} + </> + )} + </h1> </div> - <ReactFlowProvider> - <div className="h-[calc(100%-.8rem)] w-full"> - <ReactFlow - snapGrid={[10, 10]} - snapToGrid - onlyRenderVisibleElements={false} - nodesDraggable={true} - nodeTypes={nodeTypes} - edgeTypes={edgeTypes} - connectionLineComponent={ConnectionDragLine} - onNodesChange={onNodeChanged} - onEdgesChange={onEdgeChanged} - nodes={nodes} - edges={edges} - onInit={onInit} - proOptions={{ hideAttribution: true }} - > - <Controls showInteractive={false} showZoom={false} showFitView={true} className={`${styles.controls} schema-settings`}> - {/* <ControlButton - className={styles.exportButton} - title={'Export graph schema'} - onClick={(event) => { - event.stopPropagation(); - // this.setState({ - // ...this.state, - // exportMenuAnchor: event.currentTarget, - // }); - }} + {nodes.length === 0 && !loading ? ( + <p className="text-sm">No Elements</p> + ) : ( + <ReactFlowProvider> + <div className="h-[calc(100%-.8rem)] w-full"> + <ReactFlow + snapGrid={[10, 10]} + snapToGrid + onlyRenderVisibleElements={false} + nodesDraggable={true} + nodeTypes={nodeTypes} + edgeTypes={edgeTypes} + connectionLineComponent={ConnectionDragLine} + onNodesChange={onNodeChanged} + onEdgesChange={onEdgeChanged} + nodes={nodes} + edges={edges} + onInit={onInit} + proOptions={{ hideAttribution: true }} > - <img src={exportIcon} width={21}></img> - </ControlButton> */} - <ControlButton - className={styles.exportButton} - title={'Refresh graph schema'} - onClick={(event) => { - event.stopPropagation(); - api_schema.RequestSchema(session.currentDatabase); - }} - > - <CachedIcon /> - </ControlButton> - <ControlButton - className={styles.exportButton} - title={'Open Settings'} - onClick={(event) => { - event.stopPropagation(); - setToggleSchemaSettings(!toggleSchemaSettings); - }} - > - <SettingsIcon ref={settingsIconRef} /> - </ControlButton> - </Controls> - </ReactFlow> - </div> - </ReactFlowProvider> + <Controls showInteractive={false} showZoom={false} showFitView={true} className={`${styles.controls} schema-settings`}> + {/* <ControlButton + className={styles.exportButton} + title={'Export graph schema'} + onClick={(event) => { + event.stopPropagation(); + // this.setState({ + // ...this.state, + // exportMenuAnchor: event.currentTarget, + // }); + }} + > + <img src={exportIcon} width={21}></img> + </ControlButton> */} + <ControlButton + className={styles.exportButton} + title={'Refresh graph schema'} + onClick={(event) => { + event.stopPropagation(); + api_schema.RequestSchema(session.currentDatabase); + }} + > + <CachedIcon /> + </ControlButton> + <ControlButton + className={styles.exportButton} + title={'Open Settings'} + onClick={(event) => { + event.stopPropagation(); + setToggleSchemaSettings(!toggleSchemaSettings); + }} + > + <SettingsIcon ref={settingsIconRef} /> + </ControlButton> + </Controls> + </ReactFlow> + </div> + </ReactFlowProvider> + )} </div> ); };