@@ -6,18 +6,19 @@ import Button from '@mui/material/Button';
66import Stack from '@mui/material/Stack' ;
77import { ThemeProvider } from '@mui/material/styles' ;
88import { ExecutionResult } from 'graphql/execution' ;
9- import { GraphQLSchema } from 'graphql/type' ;
9+ import { GraphQLNamedType , GraphQLSchema } from 'graphql/type' ;
1010import { buildClientSchema , IntrospectionQuery } from 'graphql/utilities' ;
1111import {
1212 Children ,
1313 type ReactNode ,
14+ useCallback ,
1415 useEffect ,
1516 useMemo ,
1617 useRef ,
1718 useState ,
1819} from 'react' ;
1920
20- import { getTypeGraph } from '../graph/type-graph.ts' ;
21+ import { getTypeGraph , TypeGraph } from '../graph/type-graph.ts' ;
2122import { getSchema } from '../introspection/introspection.ts' ;
2223import { MaybePromise , usePromise } from '../utils/usePromise.ts' ;
2324import DocExplorer from './doc-explorer/DocExplorer.tsx' ;
@@ -51,11 +52,26 @@ export interface VoyagerProps {
5152 children ?: ReactNode ;
5253}
5354
54- export type GraphSelection =
55- | { typeID : null ; edgeID : null }
56- | { typeID : string ; edgeID : string | null } ;
55+ interface NavStackTypeList {
56+ prev : null ;
57+ typeGraph : TypeGraph ;
58+ type : null ;
59+ selectedEdgeID : null ;
60+ searchValue : string | null ;
61+ }
62+
63+ interface NavStackType {
64+ prev : NavStack ;
65+ typeGraph : TypeGraph ;
66+ type : GraphQLNamedType ;
67+ selectedEdgeID : string | null ;
68+ searchValue : string | null ;
69+ }
70+
71+ export type NavStack = NavStackTypeList | NavStackType ;
5772
5873export default function Voyager ( props : VoyagerProps ) {
74+ console . log ( 'Voyager' ) ;
5975 const initialDisplayOptions = useMemo (
6076 ( ) => ( {
6177 rootType : undefined ,
@@ -78,13 +94,14 @@ export default function Voyager(props: VoyagerProps) {
7894 const [ displayOptions , setDisplayOptions ] = useState ( initialDisplayOptions ) ;
7995
8096 useEffect ( ( ) => {
97+ console . log ( 'useEffect' ) ;
8198 setDisplayOptions ( initialDisplayOptions ) ;
8299 } , [ introspectionResult , initialDisplayOptions ] ) ;
83100
84- const typeGraph = useMemo ( ( ) => {
101+ const [ navStack , setNavStack ] = useState < NavStack | null > ( null ) ;
102+ useEffect ( ( ) => {
85103 if ( introspectionResult . loading || introspectionResult . value == null ) {
86- // FIXME: display introspectionResult.error
87- return null ;
104+ return ; // FIXME: display introspectionResult.error
88105 }
89106
90107 let introspectionSchema ;
@@ -95,24 +112,22 @@ export default function Voyager(props: VoyagerProps) {
95112 introspectionResult . value . errors != null ||
96113 introspectionResult . value . data == null
97114 ) {
98- // FIXME: display errors
99- return null ;
115+ return ; // FIXME: display errors
100116 }
101117 introspectionSchema = buildClientSchema ( introspectionResult . value . data ) ;
102118 }
103119
104120 const schema = getSchema ( introspectionSchema , displayOptions ) ;
105- return getTypeGraph ( schema , displayOptions ) ;
106- } , [ introspectionResult , displayOptions ] ) ;
121+ const typeGraph = getTypeGraph ( schema , displayOptions ) ;
107122
108- useEffect ( ( ) => {
109- setSelected ( { typeID : null , edgeID : null } ) ;
110- } , [ typeGraph ] ) ;
111-
112- const [ selected , setSelected ] = useState < GraphSelection > ( {
113- typeID : null ,
114- edgeID : null ,
115- } ) ;
123+ setNavStack ( ( ) => ( {
124+ prev : null ,
125+ typeGraph,
126+ type : null ,
127+ selectedEdgeID : null ,
128+ searchValue : null ,
129+ } ) ) ;
130+ } , [ introspectionResult , displayOptions ] ) ;
116131
117132 const {
118133 allowToChangeSchema = false ,
@@ -124,6 +139,70 @@ export default function Voyager(props: VoyagerProps) {
124139
125140 const viewportRef = useRef < GraphViewport > ( null ) ;
126141
142+ const handleNavigationBack = useCallback ( ( ) => {
143+ setNavStack ( ( old ) => {
144+ if ( old ?. prev == null ) {
145+ return old ;
146+ }
147+ return old . prev ;
148+ } ) ;
149+ } , [ ] ) ;
150+
151+ const handleSearch = useCallback ( ( searchValue : string | null ) => {
152+ setNavStack ( ( old ) => {
153+ if ( old == null ) {
154+ return old ;
155+ }
156+ return { ...old , searchValue } ;
157+ } ) ;
158+ } , [ ] ) ;
159+
160+ const handleSelectNode = useCallback ( ( type : GraphQLNamedType | null ) => {
161+ setNavStack ( ( old ) => {
162+ if ( old == null ) {
163+ return old ;
164+ }
165+ if ( type == null ) {
166+ let first = old ;
167+ while ( first . prev != null ) {
168+ first = first . prev ;
169+ }
170+ return first ;
171+ }
172+ return {
173+ prev : old ,
174+ typeGraph : old . typeGraph ,
175+ type,
176+ selectedEdgeID : null ,
177+ searchValue : null ,
178+ } ;
179+ } ) ;
180+ } , [ ] ) ;
181+
182+ const handleSelectEdge = useCallback (
183+ ( edgeID : string , fromType : GraphQLNamedType , _toType : GraphQLNamedType ) => {
184+ setNavStack ( ( old ) => {
185+ if ( old == null ) {
186+ return old ;
187+ }
188+ if ( fromType === old . type ) {
189+ // deselect if click again
190+ return edgeID === old . selectedEdgeID
191+ ? { ...old , selectedEdgeID : null }
192+ : { ...old , selectedEdgeID : edgeID } ;
193+ }
194+ return {
195+ prev : old ,
196+ typeGraph : old . typeGraph ,
197+ type : fromType ,
198+ selectedEdgeID : edgeID ,
199+ searchValue : null ,
200+ } ;
201+ } ) ;
202+ } ,
203+ [ ] ,
204+ ) ;
205+
127206 return (
128207 < ThemeProvider theme = { theme } >
129208 < div className = "graphql-voyager" >
@@ -161,11 +240,12 @@ export default function Voyager(props: VoyagerProps) {
161240 { allowToChangeSchema && renderChangeSchemaButton ( ) }
162241 { panelHeader }
163242 < DocExplorer
164- typeGraph = { typeGraph }
165- selectedTypeID = { selected . typeID }
166- selectedEdgeID = { selected . edgeID }
167- onFocusNode = { ( id ) => viewportRef . current ?. focusNode ( id ) }
168- onSelect = { handleSelect }
243+ navStack = { navStack }
244+ onNavigationBack = { handleNavigationBack }
245+ onSearch = { handleSearch }
246+ onFocusNode = { ( type ) => viewportRef . current ?. focusNode ( type ) }
247+ onSelectNode = { handleSelectNode }
248+ onSelectEdge = { handleSelectEdge }
169249 />
170250 < PoweredBy />
171251 </ div >
@@ -209,31 +289,21 @@ export default function Voyager(props: VoyagerProps) {
209289 { ! hideSettings && (
210290 < Settings
211291 options = { displayOptions }
212- typeGraph = { typeGraph }
292+ typeGraph = { navStack ?. typeGraph }
213293 onChange = { ( options ) =>
214294 setDisplayOptions ( ( oldOptions ) => ( { ...oldOptions , ...options } ) )
215295 }
216296 />
217297 ) }
218298 < GraphViewport
219- typeGraph = { typeGraph }
220- selectedTypeID = { selected . typeID }
221- selectedEdgeID = { selected . edgeID }
222- onSelect = { handleSelect }
299+ navStack = { navStack }
300+ onSelectNode = { handleSelectNode }
301+ onSelectEdge = { handleSelectEdge }
223302 ref = { viewportRef }
224303 />
225304 </ Box >
226305 ) ;
227306 }
228-
229- function handleSelect ( newSel : GraphSelection ) {
230- setSelected ( ( oldSel ) => {
231- if ( newSel . typeID === oldSel . typeID && newSel . edgeID === oldSel . edgeID ) {
232- return { typeID : newSel . typeID , edgeID : null } ; // deselect if click again
233- }
234- return newSel ;
235- } ) ;
236- }
237307}
238308
239309function PanelHeader ( props : { children : ReactNode } ) {
0 commit comments