11import React , {
2- useContext ,
32 useEffect ,
43 useRef ,
54} from "react" ;
65
7- import {
8- updateWindowUrlHashParams ,
9- URL_HASH_PARAMS_DEFAULT ,
10- URL_SEARCH_PARAMS_DEFAULT ,
11- UrlContext ,
12- } from "../contexts/UrlContextProvider" ;
13- import useContextStore from "../stores/contextStore" ;
146import useLogFileManagerStore from "../stores/logFileManagerProxyStore" ;
157import useLogFileStore from "../stores/logFileStore" ;
168import { handleErrorWithNotification } from "../stores/notificationStore" ;
179import useQueryStore from "../stores/queryStore" ;
1810import useUiStore from "../stores/uiStore" ;
1911import useViewStore from "../stores/viewStore" ;
12+ import { Nullable } from "../typings/common" ;
2013import { UI_STATE } from "../typings/states" ;
14+ import { UrlHashParams } from "../typings/url" ;
2115import {
2216 CURSOR_CODE ,
2317 CursorType ,
@@ -27,6 +21,13 @@ import {
2721 isWithinBounds ,
2822} from "../utils/data" ;
2923import { clamp } from "../utils/math" ;
24+ import {
25+ getWindowUrlHashParams ,
26+ getWindowUrlSearchParams ,
27+ updateWindowUrlHashParams ,
28+ URL_HASH_PARAMS_DEFAULT ,
29+ URL_SEARCH_PARAMS_DEFAULT ,
30+ } from "../utils/url" ;
3031
3132
3233/**
@@ -67,6 +68,73 @@ const updateUrlIfEventOnPage = (
6768 return true ;
6869} ;
6970
71+ /**
72+ * Updates view-related parameters from URL hash.
73+ *
74+ * @param hashParams
75+ */
76+ const updateViewHashParams = ( hashParams : UrlHashParams ) : void => {
77+ const { isPrettified, logEventNum} = hashParams ;
78+ const { updateIsPrettified, setLogEventNum} = useViewStore . getState ( ) ;
79+
80+ updateIsPrettified ( isPrettified ) ;
81+ setLogEventNum ( logEventNum ) ;
82+ } ;
83+
84+ /**
85+ * Updates query-related parameters from URL hash.
86+ *
87+ * @param hashParams
88+ * @return Whether any query parameters were modified.
89+ */
90+ const updateQueryHashParams = ( hashParams : UrlHashParams ) : boolean => {
91+ const { queryIsCaseSensitive, queryIsRegex, queryString} = hashParams ;
92+ const {
93+ queryIsCaseSensitive : currentQueryIsCaseSensitive ,
94+ queryIsRegex : currentQueryIsRegex ,
95+ queryString : currentQueryString ,
96+ setQueryIsCaseSensitive,
97+ setQueryIsRegex,
98+ setQueryString,
99+ } = useQueryStore . getState ( ) ;
100+
101+ let isQueryModified = false ;
102+ isQueryModified ||= queryIsCaseSensitive !== currentQueryIsCaseSensitive ;
103+ setQueryIsCaseSensitive ( queryIsCaseSensitive ) ;
104+
105+ isQueryModified ||= queryIsRegex !== currentQueryIsRegex ;
106+ setQueryIsRegex ( queryIsRegex ) ;
107+
108+ isQueryModified ||= queryString !== currentQueryString ;
109+ setQueryString ( queryString ) ;
110+
111+ return isQueryModified ;
112+ } ;
113+
114+ /**
115+ * Handles hash change events by updating the application state based on the URL hash parameters.
116+ *
117+ * @param [ev] The hash change event, or `null` when called on application initialization.
118+ * @return The parsed URL hash parameters.
119+ */
120+ const handleHashChange = ( ev : Nullable < HashChangeEvent > ) : UrlHashParams => {
121+ const hashParams = getWindowUrlHashParams ( ) ;
122+ updateViewHashParams ( hashParams ) ;
123+ const isQueryModified = updateQueryHashParams ( hashParams ) ;
124+ const isTriggeredByHashChange = null !== ev ;
125+ if ( isTriggeredByHashChange && isQueryModified ) {
126+ const { startQuery} = useQueryStore . getState ( ) ;
127+ startQuery ( ) ;
128+ }
129+
130+ // Remove empty or falsy parameters.
131+ updateWindowUrlHashParams ( {
132+ ...hashParams ,
133+ } ) ;
134+
135+ return hashParams ;
136+ } ;
137+
70138interface AppControllerProps {
71139 children : React . ReactNode ;
72140}
@@ -79,127 +147,74 @@ interface AppControllerProps {
79147 * @return
80148 */
81149const AppController = ( { children} : AppControllerProps ) => {
82- const {
83- filePath, isPrettified, logEventNum, queryString, queryIsRegex, queryIsCaseSensitive,
84- } = useContext ( UrlContext ) ;
85-
86150 // States
87- const setLogEventNum = useContextStore ( ( state ) => state . setLogEventNum ) ;
88- const logFileManagerProxy = useLogFileManagerStore ( ( state ) => state . logFileManagerProxy ) ;
89- const loadFile = useLogFileStore ( ( state ) => state . loadFile ) ;
90- const numEvents = useLogFileStore ( ( state ) => state . numEvents ) ;
91- const beginLineNumToLogEventNum = useViewStore ( ( state ) => state . beginLineNumToLogEventNum ) ;
92- const setIsPrettified = useViewStore ( ( state ) => state . updateIsPrettified ) ;
93- const updatePageData = useViewStore ( ( state ) => state . updatePageData ) ;
94- const uiState = useUiStore ( ( state ) => state . uiState ) ;
95- const setUiState = useUiStore ( ( state ) => state . setUiState ) ;
151+ const logEventNum = useViewStore ( ( state ) => state . logEventNum ) ;
96152
97153 // Refs
98- const isPrettifiedRef = useRef < boolean > ( isPrettified ?? false ) ;
99- const logEventNumRef = useRef ( logEventNum ) ;
154+ const isInitialized = useRef < boolean > ( false ) ;
100155
101- // Synchronize `logEventNumRef` with `logEventNum` .
156+ // On app init, register hash change handler, and handle hash and search parameters .
102157 useEffect ( ( ) => {
103- if ( null !== logEventNum ) {
104- logEventNumRef . current = logEventNum ;
105- setLogEventNum ( logEventNum ) ;
158+ window . addEventListener ( "hashchange" , handleHashChange ) ;
159+
160+ // Prevent re-initialization on re-renders.
161+ if ( isInitialized . current ) {
162+ return ( ) => null ;
163+ }
164+ isInitialized . current = true ;
165+
166+ // Handle initial page load and maintain full URL state
167+ const hashParams = handleHashChange ( null ) ;
168+ const searchParams = getWindowUrlSearchParams ( ) ;
169+ if ( URL_SEARCH_PARAMS_DEFAULT . filePath !== searchParams . filePath ) {
170+ let cursor : CursorType = { code : CURSOR_CODE . LAST_EVENT , args : null } ;
171+
172+ if ( URL_HASH_PARAMS_DEFAULT . logEventNum !== hashParams . logEventNum ) {
173+ cursor = {
174+ code : CURSOR_CODE . EVENT_NUM ,
175+ args : { eventNum : hashParams . logEventNum } ,
176+ } ;
177+ }
178+ const { loadFile} = useLogFileStore . getState ( ) ;
179+ loadFile ( searchParams . filePath , cursor ) ;
106180 }
107- } , [
108- logEventNum ,
109- setLogEventNum ,
110- ] ) ;
111181
112- // Synchronize `isPrettifiedRef` with `isPrettified`.
113- useEffect ( ( ) => {
114- isPrettifiedRef . current = isPrettified ?? false ;
115- setIsPrettified ( isPrettifiedRef . current ) ;
116- } , [
117- isPrettified ,
118- setIsPrettified ,
119- ] ) ;
182+ return ( ) => {
183+ window . removeEventListener ( "hashchange" , handleHashChange ) ;
184+ } ;
185+ } , [ ] ) ;
120186
121187 // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL.
122188 useEffect ( ( ) => {
189+ const { numEvents} = useLogFileStore . getState ( ) ;
123190 if ( 0 === numEvents || URL_HASH_PARAMS_DEFAULT . logEventNum === logEventNum ) {
124191 return ;
125192 }
126193
127194 const clampedLogEventNum = clamp ( logEventNum , 1 , numEvents ) ;
128- const logEventNumsOnPage : number [ ] =
129- Array . from ( beginLineNumToLogEventNum . values ( ) ) ;
130-
195+ const { beginLineNumToLogEventNum} = useViewStore . getState ( ) ;
196+ const logEventNumsOnPage : number [ ] = Array . from ( beginLineNumToLogEventNum . values ( ) ) ;
131197 if ( updateUrlIfEventOnPage ( clampedLogEventNum , logEventNumsOnPage ) ) {
132198 // No need to request a new page since the log event is on the current page.
133199 return ;
134200 }
135201
202+ // If the log event is not on the current page, request a new page.
203+ const { setUiState} = useUiStore . getState ( ) ;
136204 setUiState ( UI_STATE . FAST_LOADING ) ;
137-
138205 ( async ( ) => {
206+ const { logFileManagerProxy} = useLogFileManagerStore . getState ( ) ;
139207 const cursor : CursorType = {
140208 code : CURSOR_CODE . EVENT_NUM ,
141209 args : { eventNum : clampedLogEventNum } ,
142210 } ;
143- const pageData = await logFileManagerProxy . loadPage ( cursor , isPrettifiedRef . current ) ;
211+ const { isPrettified} = useViewStore . getState ( ) ;
212+
213+ const pageData = await logFileManagerProxy . loadPage ( cursor , isPrettified ) ;
214+ const { updatePageData} = useViewStore . getState ( ) ;
144215 updatePageData ( pageData ) ;
145216 } ) ( ) . catch ( handleErrorWithNotification ) ;
146- } , [
147- beginLineNumToLogEventNum ,
148- logEventNum ,
149- logFileManagerProxy ,
150- numEvents ,
151- setUiState ,
152- updatePageData ,
153- ] ) ;
154-
155- // On `filePath` update, load file.
156- useEffect ( ( ) => {
157- if ( URL_SEARCH_PARAMS_DEFAULT . filePath === filePath ) {
158- return ;
159- }
160-
161- let cursor : CursorType = { code : CURSOR_CODE . LAST_EVENT , args : null } ;
162- if ( URL_HASH_PARAMS_DEFAULT . logEventNum !== logEventNumRef . current ) {
163- cursor = {
164- code : CURSOR_CODE . EVENT_NUM ,
165- args : { eventNum : logEventNumRef . current } ,
166- } ;
167- }
168- loadFile ( filePath , cursor ) ;
169- } , [
170- filePath ,
171- loadFile ,
172- ] ) ;
173-
174- // Synchronize `queryIsCaseSensitive` with the Zustand QueryStore.
175- useEffect ( ( ) => {
176- if ( null !== queryIsCaseSensitive ) {
177- const { setQueryIsCaseSensitive} = useQueryStore . getState ( ) ;
178- setQueryIsCaseSensitive ( queryIsCaseSensitive ) ;
179- }
180- } , [ queryIsCaseSensitive ] ) ;
181-
182- // Synchronize `queryIsRegex` with the Zustand QueryStore.
183- useEffect ( ( ) => {
184- if ( null !== queryIsRegex ) {
185- const { setQueryIsRegex} = useQueryStore . getState ( ) ;
186- setQueryIsRegex ( queryIsRegex ) ;
187- }
188- } , [ queryIsRegex ] ) ;
189-
190- useEffect ( ( ) => {
191- if ( null !== queryString ) {
192- const { setQueryString} = useQueryStore . getState ( ) ;
193- setQueryString ( queryString ) ;
194- }
195- if ( UI_STATE . READY === uiState ) {
196- const { startQuery} = useQueryStore . getState ( ) ;
197- startQuery ( ) ;
198- }
199- } , [
200- uiState ,
201- queryString ,
202- ] ) ;
217+ } , [ logEventNum ] ) ;
203218
204219 return children ;
205220} ;
0 commit comments