11import { Accessor , createContext , createSignal , ParentComponent , Setter , useContext } from 'solid-js' ;
2+ import { createStore } from 'solid-js/store' ;
23
3- import { Action , Entity , follow , parse , submit , Target } from '@siren-js/client' ;
4+ import * as Siren from '@siren-js/client' ;
5+
6+ interface HistoryItem {
7+ url : string ;
8+ content : string ;
9+ entity ?: Siren . Entity ;
10+ error ?: Error ;
11+ }
412
513interface BrowserContextValue {
6- entity : Accessor < Entity | undefined > ;
14+ canGoBack : Accessor < boolean > ;
15+ canGoForward : Accessor < boolean > ;
16+ entity : Accessor < Siren . Entity | undefined > ;
17+ error : Accessor < Error | undefined > ;
718 loading : Accessor < boolean > ;
819 location : Accessor < string > ;
920 rawContent : Accessor < string | undefined > ;
1021 setLocation : Setter < string > ;
11- error : Accessor < Error | undefined > ;
12- follow : ( target : Target ) => void ;
13- submit : ( action : Action ) => void ;
22+ follow ( target : Siren . Target ) : void ;
23+ goBack ( ) : void ;
24+ goForward ( ) : void ;
25+ submit ( action : Siren . Action ) : void ;
1426}
1527
1628const BrowserContext = createContext ( { } as BrowserContextValue ) ;
1729
1830export const BrowserProvider : ParentComponent = ( props ) => {
1931 const [ loading , setLoading ] = createSignal ( false ) ;
2032 const [ location , setLocation ] = createSignal ( '' ) ;
21- const [ entity , setEntity ] = createSignal < Entity > ( ) ;
33+ const [ history , setHistory ] = createStore < HistoryItem [ ] > ( [ ] ) ;
34+ const [ historyIndex , setHistoryIndex ] = createSignal ( - 1 ) ;
2235 const [ rawContent , setRawContent ] = createSignal < string > ( ) ;
36+ const [ entity , setEntity ] = createSignal < Siren . Entity > ( ) ;
2337 const [ error , setError ] = createSignal < Error > ( ) ;
2438
2539 const responseHandler = async ( res : Response ) : Promise < void > => {
2640 setLocation ( res . url ) ;
41+
2742 const text = await res . text ( ) ;
2843 setRawContent ( text ) ;
44+
45+ const historyItem : HistoryItem = {
46+ url : res . url ,
47+ content : text ,
48+ } ;
49+
2950 const contentType = res . headers . get ( 'Content-Type' ) ?? '' ;
3051 if ( / ^ a p p l i c a t i o n \/ v n d \. s i r e n \+ j s o n ( ; | $ ) / . test ( contentType ) ) {
52+ const entity = await Siren . parse ( text ) ;
3153 setError ( undefined ) ;
32- setEntity ( await parse ( text ) ) ;
54+ setEntity ( entity ) ;
55+ historyItem . entity = entity ;
3356 } else {
57+ const error = new Error ( `Unable to parse unrecognized Content-Type: ${ contentType } ` ) ;
3458 setEntity ( undefined ) ;
35- setError ( new Error ( `Unable to parse unrecognized Content-Type: ${ contentType } ` ) ) ;
59+ setError ( error ) ;
60+ historyItem . error = error ;
3661 }
62+
63+ setHistory ( [ ...history . slice ( 0 , historyIndex ( ) + 1 ) , historyItem ] ) ;
64+ setHistoryIndex ( ( value ) => value + 1 ) ;
3765 } ;
3866
3967 const navigate = ( req : ( ) => Promise < Response > ) => {
@@ -44,18 +72,59 @@ export const BrowserProvider: ParentComponent = (props) => {
4472 . finally ( ( ) => setLoading ( false ) ) ;
4573 } ;
4674
47- const value : BrowserContextValue = {
48- entity,
49- error,
50- loading,
51- location,
52- rawContent,
53- setLocation,
54- follow : ( url ) => navigate ( ( ) => follow ( url ) ) ,
55- submit : ( action : Action ) => navigate ( ( ) => submit ( action ) ) ,
75+ const follow = ( target : Siren . Target ) => navigate ( ( ) => Siren . follow ( target ) ) ;
76+ const submit = ( action : Siren . Action ) => navigate ( ( ) => Siren . submit ( action ) ) ;
77+
78+ const setStateFrom = ( item : HistoryItem ) => {
79+ setLocation ( item . url ) ;
80+ setRawContent ( item . content ) ;
81+ setEntity ( item . entity ) ;
82+ setError ( item . error ) ;
83+ } ;
84+
85+ const go = ( indexFn : ( currentIndex : number ) => number ) => {
86+ setHistoryIndex ( ( currentIndex ) => {
87+ const index = indexFn ( currentIndex ) ;
88+ const historyItem = history [ index ] ;
89+ setStateFrom ( historyItem ) ;
90+ return index ;
91+ } ) ;
92+ } ;
93+
94+ const canGoBack = ( ) => historyIndex ( ) > 0 ;
95+ const goBack = ( ) => {
96+ if ( canGoBack ( ) ) {
97+ go ( ( i ) => i - 1 ) ;
98+ }
99+ } ;
100+
101+ const canGoForward = ( ) => historyIndex ( ) < history . length - 1 ;
102+ const goForward = ( ) => {
103+ if ( canGoForward ( ) ) {
104+ go ( ( i ) => i + 1 ) ;
105+ }
56106 } ;
57107
58- return < BrowserContext . Provider value = { value } > { props . children } </ BrowserContext . Provider > ;
108+ return (
109+ < BrowserContext . Provider
110+ value = { {
111+ canGoBack,
112+ canGoForward,
113+ entity,
114+ error,
115+ follow,
116+ goBack,
117+ goForward,
118+ loading,
119+ location,
120+ rawContent,
121+ setLocation,
122+ submit,
123+ } }
124+ >
125+ { props . children }
126+ </ BrowserContext . Provider >
127+ ) ;
59128} ;
60129
61130export const useBrowserContext = ( ) => useContext ( BrowserContext ) ;
0 commit comments