@@ -12,7 +12,9 @@ import type { NormalizedDagNode } from '../types.js'
1212
1313interface ExploreContextProps {
1414 exploreState : ExploreState
15- // explorePathFromHash: string | null
15+ explorePathPrefix : string
16+ isLoading : boolean
17+ setExplorePath ( path : string | null ) : void
1618 doExploreLink ( link : any ) : void
1719 doExploreUserProvidedPath ( path : string ) : void
1820 doUploadUserProvidedCar ( file : File , uploadImage : string ) : Promise < void >
@@ -37,7 +39,6 @@ export interface ExploreState {
3739 nodes : any [ ]
3840 pathBoundaries : any [ ]
3941 error : IpldExploreError | null
40- explorePathFromHash : string | null
4142}
4243
4344export const ExploreContext = createContext < ExploreContextProps | undefined > ( undefined )
@@ -58,51 +59,108 @@ const getCidFromCidOrFqdn = (cidOrFqdn: CID | string): CID => {
5859 return CID . parse ( cidOrFqdn )
5960}
6061
62+ const processPath = ( path : string | null , pathPrefix : string ) : string | null => {
63+ let newPath = path
64+ if ( newPath != null ) {
65+ if ( newPath . includes ( pathPrefix ) ) {
66+ newPath = newPath . slice ( pathPrefix . length )
67+ }
68+ if ( newPath . startsWith ( '/' ) ) {
69+ newPath = newPath . slice ( 1 )
70+ }
71+ if ( newPath === '' ) {
72+ newPath = null
73+ } else {
74+ newPath = decodeURIComponent ( newPath )
75+ }
76+ }
77+ return newPath
78+ }
79+
6180const defaultState : ExploreState = {
6281 path : null ,
6382 targetNode : null ,
6483 canonicalPath : '' ,
6584 localPath : '' ,
6685 nodes : [ ] ,
6786 pathBoundaries : [ ] ,
68- error : null ,
69- explorePathFromHash : null
87+ error : null
88+ }
89+
90+ export interface ExploreProviderProps {
91+ children ?: ReactNode | ReactNode [ ]
92+ state ?: Partial < ExploreState >
93+ explorePathPrefix ?: string
7094}
7195
72- export const ExploreProvider = ( { children, state = defaultState } : { children ?: ReactNode , state ?: ExploreState } ) : React . ReactNode => {
73- const [ exploreState , setExploreState ] = useState < ExploreState > ( { ...state , explorePathFromHash : window . location . hash . slice ( '#/explore' . length ) } )
96+ export const ExploreProvider = ( { children, state, explorePathPrefix = '#/explore' } : ExploreProviderProps ) : React . ReactNode => {
97+ if ( state == null ) {
98+ state = {
99+ path : processPath ( window . location . hash , explorePathPrefix )
100+ }
101+ } else {
102+ if ( state . path === '' ) {
103+ state . path = null
104+ } else if ( state . path != null ) {
105+ state . path = processPath ( state . path , explorePathPrefix )
106+ }
107+ }
108+ const [ exploreState , setExploreState ] = useState < ExploreState > ( { ...defaultState , ...state } )
74109 const { helia } = useHelia ( )
75- const { explorePathFromHash } = exploreState
110+ const [ isLoading , setIsLoading ] = useState < boolean > ( false )
111+ const { path } = exploreState
76112
77- const fetchExploreData = useCallback ( async ( path : string ) : Promise < void > => {
78- // Clear the target node when a new path is requested
79- setExploreState ( ( exploreState ) => ( {
80- ...exploreState ,
81- targetNode : null
82- } ) )
83- const pathParts = parseIpldPath ( path )
84- if ( pathParts == null || helia == null ) return
113+ useEffect ( ( ) => {
114+ setIsLoading ( true ) ;
85115
86- const { cidOrFqdn, rest } = pathParts
87- try {
88- const cid = getCidFromCidOrFqdn ( cidOrFqdn )
89- const { targetNode, canonicalPath, localPath, nodes, pathBoundaries } = await resolveIpldPath ( helia , cid , rest )
90-
91- setExploreState ( ( { explorePathFromHash } ) => ( {
92- explorePathFromHash,
93- path,
94- targetNode,
95- canonicalPath,
96- localPath,
97- nodes,
98- pathBoundaries,
99- error : null
116+ ( async ( ) => {
117+ if ( path == null || helia == null ) {
118+ return
119+ }
120+ // Clear the target node when a new path is requested
121+ setExploreState ( ( exploreState ) => ( {
122+ ...exploreState ,
123+ targetNode : null
100124 } ) )
101- } catch ( error : any ) {
102- console . warn ( 'Failed to resolve path' , path , error )
103- setExploreState ( ( prevState ) => ( { ...prevState , error } ) )
125+ const pathParts = parseIpldPath ( path )
126+ if ( pathParts == null || helia == null ) return
127+
128+ const { cidOrFqdn, rest } = pathParts
129+ try {
130+ const cid = getCidFromCidOrFqdn ( cidOrFqdn )
131+ const { targetNode, canonicalPath, localPath, nodes, pathBoundaries } = await resolveIpldPath ( helia , cid , rest )
132+
133+ setExploreState ( ( curr ) => ( {
134+ ...curr ,
135+ targetNode,
136+ canonicalPath,
137+ localPath,
138+ nodes,
139+ pathBoundaries,
140+ error : null
141+ } ) )
142+ } catch ( error : any ) {
143+ console . warn ( 'Failed to resolve path' , path , error )
144+ setExploreState ( ( prevState ) => ( { ...prevState , error } ) )
145+ }
146+ } ) ( ) . catch ( ( err ) => {
147+ console . error ( 'Error fetching explore data' , err )
148+ setExploreState ( ( prevState ) => ( { ...prevState , error : err } ) )
149+ } ) . finally ( ( ) => {
150+ setIsLoading ( false )
151+ } )
152+ } , [ helia , path ] )
153+
154+ const setExplorePath = ( path : string | null ) : void => {
155+ const newPath = processPath ( path , explorePathPrefix )
156+ if ( newPath != null && ! window . location . href . includes ( newPath ) ) {
157+ throw new Error ( 'setExplorePath should only be used to update the state, not the URL. If you are using a routing library that doesn\'t allow you to listen to hashchange events, ensure the URL is updated prior to calling setExplorePath.' )
104158 }
105- } , [ helia ] )
159+ setExploreState ( ( exploreState ) => ( {
160+ ...exploreState ,
161+ path : newPath
162+ } ) )
163+ }
106164
107165 const doExploreLink = ( link : LinkObject ) : void => {
108166 const { nodes, pathBoundaries } = exploreState
@@ -114,12 +172,16 @@ export const ExploreProvider = ({ children, state = defaultState }: { children?:
114172 }
115173 pathParts . unshift ( cid )
116174 const path = pathParts . map ( ( part ) => encodeURIComponent ( part ) ) . join ( '/' )
117- const hash = `#/explore /${ path } `
175+ const hash = `${ explorePathPrefix } /${ path } `
118176 window . location . hash = hash
177+ setExplorePath ( path )
119178 }
120179
180+ /**
181+ * @deprecated - use setExplorePath instead
182+ */
121183 const doExploreUserProvidedPath = ( path : string ) : void => {
122- const hash = path != null ? `#/explore ${ ensureLeadingSlash ( path ) } ` : '#/explore'
184+ const hash = path != null ? `${ explorePathPrefix } ${ ensureLeadingSlash ( path ) } ` : explorePathPrefix
123185 window . location . hash = hash
124186 }
125187
@@ -130,7 +192,7 @@ export const ExploreProvider = ({ children, state = defaultState }: { children?:
130192 }
131193 try {
132194 const rootCid = await importCar ( file , helia )
133- const hash = rootCid . toString ( ) != null ? `#/explore ${ ensureLeadingSlash ( rootCid . toString ( ) ) } ` : '#/explore'
195+ const hash = rootCid . toString ( ) != null ? `${ explorePathPrefix } ${ ensureLeadingSlash ( rootCid . toString ( ) ) } ` : explorePathPrefix
134196 window . location . hash = hash
135197
136198 const imageFileLoader = document . getElementById ( 'car-loader-image' ) as HTMLImageElement
@@ -140,42 +202,14 @@ export const ExploreProvider = ({ children, state = defaultState }: { children?:
140202 } catch ( err ) {
141203 console . error ( 'Could not import car file' , err )
142204 }
143- } , [ helia ] )
144-
145- useEffect ( ( ) => {
146- const handleHashChange = ( ) : void => {
147- const explorePathFromHash = window . location . hash . slice ( '#/explore' . length )
148-
149- setExploreState ( ( state ) => ( {
150- ...state ,
151- explorePathFromHash
152- } ) )
153- }
154-
155- window . addEventListener ( 'hashchange' , handleHashChange )
156- handleHashChange ( )
157-
158- return ( ) => {
159- window . removeEventListener ( 'hashchange' , handleHashChange )
160- }
161- } , [ ] )
162-
163- useEffect ( ( ) => {
164- // if explorePathFromHash or helia change and are not null, fetch the data
165- // We need to check for helia because the helia provider is async and may not be ready yet
166- if ( explorePathFromHash != null && helia != null ) {
167- void ( async ( ) => {
168- await fetchExploreData ( decodeURIComponent ( explorePathFromHash ) )
169- } ) ( )
170- }
171- } , [ helia , explorePathFromHash ] )
205+ } , [ explorePathPrefix , helia ] )
172206
173207 if ( helia == null ) {
174208 return < Loader color = 'dark' />
175209 }
176210
177211 return (
178- < ExploreContext . Provider value = { { exploreState, doExploreLink, doExploreUserProvidedPath, doUploadUserProvidedCar } } >
212+ < ExploreContext . Provider value = { { exploreState, explorePathPrefix , isLoading , doExploreLink, doExploreUserProvidedPath, doUploadUserProvidedCar, setExplorePath } } key = { path } >
179213 { children }
180214 </ ExploreContext . Provider >
181215 )
0 commit comments