@@ -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,9 +52,23 @@ 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 ;
61+ }
62+
63+ interface NavStackType {
64+ prev : NavStack ;
65+ typeGraph : TypeGraph ;
66+ type : GraphQLNamedType ;
67+ selectedEdgeID : string | null ;
68+ searchValue : string ;
69+ }
70+
71+ export type NavStack = NavStackTypeList | NavStackType ;
5772
5873export default function Voyager ( props : VoyagerProps ) {
5974 const initialDisplayOptions = useMemo (
@@ -81,10 +96,10 @@ export default function Voyager(props: VoyagerProps) {
8196 setDisplayOptions ( initialDisplayOptions ) ;
8297 } , [ introspectionResult , initialDisplayOptions ] ) ;
8398
84- const typeGraph = useMemo ( ( ) => {
99+ const [ navStack , setNavStack ] = useState < NavStack | null > ( null ) ;
100+ useEffect ( ( ) => {
85101 if ( introspectionResult . loading || introspectionResult . value == null ) {
86- // FIXME: display introspectionResult.error
87- return null ;
102+ return ; // FIXME: display introspectionResult.error
88103 }
89104
90105 let introspectionSchema ;
@@ -95,24 +110,22 @@ export default function Voyager(props: VoyagerProps) {
95110 introspectionResult . value . errors != null ||
96111 introspectionResult . value . data == null
97112 ) {
98- // FIXME: display errors
99- return null ;
113+ return ; // FIXME: display errors
100114 }
101115 introspectionSchema = buildClientSchema ( introspectionResult . value . data ) ;
102116 }
103117
104118 const schema = getSchema ( introspectionSchema , displayOptions ) ;
105- return getTypeGraph ( schema , displayOptions ) ;
106- } , [ introspectionResult , displayOptions ] ) ;
119+ const typeGraph = getTypeGraph ( schema , displayOptions ) ;
107120
108- useEffect ( ( ) => {
109- setSelected ( { typeID : null , edgeID : null } ) ;
110- } , [ typeGraph ] ) ;
111-
112- const [ selected , setSelected ] = useState < GraphSelection > ( {
113- typeID : null ,
114- edgeID : null ,
115- } ) ;
121+ setNavStack ( ( ) => ( {
122+ prev : null ,
123+ typeGraph,
124+ type : null ,
125+ selectedEdgeID : null ,
126+ searchValue : '' ,
127+ } ) ) ;
128+ } , [ introspectionResult , displayOptions ] ) ;
116129
117130 const {
118131 allowToChangeSchema = false ,
@@ -124,6 +137,70 @@ export default function Voyager(props: VoyagerProps) {
124137
125138 const viewportRef = useRef < GraphViewport > ( null ) ;
126139
140+ const handleNavigationBack = useCallback ( ( ) => {
141+ setNavStack ( ( old ) => {
142+ if ( old ?. prev == null ) {
143+ return old ;
144+ }
145+ return old . prev ;
146+ } ) ;
147+ } , [ ] ) ;
148+
149+ const handleSearch = useCallback ( ( searchValue : string ) => {
150+ setNavStack ( ( old ) => {
151+ if ( old == null ) {
152+ return old ;
153+ }
154+ return { ...old , searchValue } ;
155+ } ) ;
156+ } , [ ] ) ;
157+
158+ const handleSelectNode = useCallback ( ( type : GraphQLNamedType | null ) => {
159+ setNavStack ( ( old ) => {
160+ if ( old == null ) {
161+ return old ;
162+ }
163+ if ( type == null ) {
164+ let first = old ;
165+ while ( first . prev != null ) {
166+ first = first . prev ;
167+ }
168+ return first ;
169+ }
170+ return {
171+ prev : old ,
172+ typeGraph : old . typeGraph ,
173+ type,
174+ selectedEdgeID : null ,
175+ searchValue : '' ,
176+ } ;
177+ } ) ;
178+ } , [ ] ) ;
179+
180+ const handleSelectEdge = useCallback (
181+ ( edgeID : string , fromType : GraphQLNamedType , _toType : GraphQLNamedType ) => {
182+ setNavStack ( ( old ) => {
183+ if ( old == null ) {
184+ return old ;
185+ }
186+ if ( fromType === old . type ) {
187+ // deselect if click again
188+ return edgeID === old . selectedEdgeID
189+ ? { ...old , selectedEdgeID : null }
190+ : { ...old , selectedEdgeID : edgeID } ;
191+ }
192+ return {
193+ prev : old ,
194+ typeGraph : old . typeGraph ,
195+ type : fromType ,
196+ selectedEdgeID : edgeID ,
197+ searchValue : '' ,
198+ } ;
199+ } ) ;
200+ } ,
201+ [ ] ,
202+ ) ;
203+
127204 return (
128205 < ThemeProvider theme = { theme } >
129206 < div className = "graphql-voyager" >
@@ -161,11 +238,12 @@ export default function Voyager(props: VoyagerProps) {
161238 { allowToChangeSchema && renderChangeSchemaButton ( ) }
162239 { panelHeader }
163240 < DocExplorer
164- typeGraph = { typeGraph }
165- selectedTypeID = { selected . typeID }
166- selectedEdgeID = { selected . edgeID }
167- onFocusNode = { ( id ) => viewportRef . current ?. focusNode ( id ) }
168- onSelect = { handleSelect }
241+ navStack = { navStack }
242+ onNavigationBack = { handleNavigationBack }
243+ onSearch = { handleSearch }
244+ onFocusNode = { ( type ) => viewportRef . current ?. focusNode ( type ) }
245+ onSelectNode = { handleSelectNode }
246+ onSelectEdge = { handleSelectEdge }
169247 />
170248 < PoweredBy />
171249 </ div >
@@ -209,31 +287,21 @@ export default function Voyager(props: VoyagerProps) {
209287 { ! hideSettings && (
210288 < Settings
211289 options = { displayOptions }
212- typeGraph = { typeGraph }
290+ typeGraph = { navStack ?. typeGraph }
213291 onChange = { ( options ) =>
214292 setDisplayOptions ( ( oldOptions ) => ( { ...oldOptions , ...options } ) )
215293 }
216294 />
217295 ) }
218296 < GraphViewport
219- typeGraph = { typeGraph }
220- selectedTypeID = { selected . typeID }
221- selectedEdgeID = { selected . edgeID }
222- onSelect = { handleSelect }
297+ navStack = { navStack }
298+ onSelectNode = { handleSelectNode }
299+ onSelectEdge = { handleSelectEdge }
223300 ref = { viewportRef }
224301 />
225302 </ Box >
226303 ) ;
227304 }
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- }
237305}
238306
239307function PanelHeader ( props : { children : ReactNode } ) {
0 commit comments