123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176(* Adapted from reason-react: https://reasonml.github.io/reason-react/docs/en/router *)moduleBrowser=[%js:typehistorytypewindowvalwindow_to_js:window->Ojs.ttypelocationvalhistory:historyoption[@@js.global"history"]valwindow:windowoption[@@js.global"window"]vallocation:window->location[@@js.get]valpathname:location->string[@@js.get]valhash:location->string[@@js.get]valsearch:location->string[@@js.get]valpush_state:history->Ojs.t->string->href:string->unit[@@js.call"pushState"]valreplace_state:history->Ojs.t->string->href:string->unit[@@js.call"replaceState"]]moduleEvent=[%js:typetvalevent:t[@@js.global"Event"]valmake_event_ie11_compatible:string->t[@@js.new"Event"]valcreate_event_non_ie8:string->t[@@js.global"document.createEvent"]valinit_event_non_ie8:t->string->bool->bool->unit[@@js.call"initEvent"](* The cb is t => unit, but access is restricted for now *)valadd_event_listener:Browser.window->string->(unit->unit)->unit[@@js.call"addEventListener"]valremove_event_listener:Browser.window->string->(unit->unit)->unit[@@js.call"removeEventListener"]valdispatch_event:Browser.window->t->unit[@@js.call"dispatchEvent"]]letsafe_make_eventeventName=ifletopenJs_of_ocamlinJs.typeof(Js.Unsafe.injectEvent.event)=Js.string"function"thenEvent.make_event_ie11_compatibleeventNameelseletevent=Event.create_event_non_ie8"Event"inEvent.init_event_non_ie8eventeventNametruetrue;Event.eventletslice_to_ends=matchs=""withtrue->""|false->String.subs1(String.lengths-1)(* if we ever roll our own parser in the future, make sure you test all url combinations
e.g. foo.com/?#bar
*)(* URLSearchParams doesn't work on IE11, edge16, etc. *)(* The library doesn't provide search for now. Users can roll their own solution/data structure.*)letpath()=matchBrowser.windowwith|None->[]|Somew->(matchletopenBrowserinw|>location|>pathnamewith|""|"/"->[]|raw->letraw=slice_to_endrawinletraw=letn=String.lengthrawinmatchn>0&&raw.[n-1]='/'with|true->String.subraw0(n-1)|false->rawinraw|>String.split_on_char'/')lethash()=matchBrowser.windowwith|None->""|Somew->(matchletopenBrowserinw|>location|>hashwith|""|"#"->""|raw->(* remove the preceeding #, which every hash seems to have. *)slice_to_endraw)letsearch()=matchBrowser.windowwith|None->""|Somew->(matchletopenBrowserinw|>location|>searchwith|""|"?"->""|raw->(* remove the preceeding ?, which every search seems to have. *)slice_to_endraw)letpushpath=matchletopenBrowserin(history,window)with|None,_|_,None->()|Somehistory,Somewindow->Browser.push_statehistoryOjs.null""~href:path;Event.dispatch_eventwindow(safe_make_event"popstate")letreplacepath=matchletopenBrowserin(history,window)with|None,_|_,None->()|Somehistory,Somewindow->Browser.replace_statehistoryOjs.null""~href:path;Event.dispatch_eventwindow(safe_make_event"popstate")typeurl={path:stringlist;hash:string;search:string}leturl_not_equalab=letreclist_not_equalxsys=match(xs,ys)with|[],[]->false|[],_::_|_::_,[]->true|x::xs,y::ys->ifx!=ythentrueelselist_not_equalxsysina.hash!=b.hash||a.search!=b.search||list_not_equala.pathb.pathtypewatcher_id=unit->unitleturl()={path=path();hash=hash();search=search()}(* alias exposed publicly *)letdangerously_get_initial_url=urlletwatch_urlcallback=matchBrowser.windowwith|None->fun()->()|Somewindow->letwatcher_id()=callback(url())inEvent.add_event_listenerwindow"popstate"watcher_id;watcher_idletunwatch_urlwatcher_id=matchBrowser.windowwith|None->()|Somewindow->Event.remove_event_listenerwindow"popstate"watcher_idletuse_url?server_url()=leturl,set_url=Core.use_state(fun()->matchserver_urlwith|Someurl->url|None->dangerously_get_initial_url())inCore.use_effect_once(fun()->letwatcher_id=watch_url(funurl->set_url(fun_->url))in(* check for updates that may have occured between the initial state and the subscribe above *)letnew_url=dangerously_get_initial_url()inifurl_not_equalnew_urlurlthenset_url(fun_->new_url);Some(fun()->unwatch_urlwatcher_id));url