Skip to content

Commit 7810e12

Browse files
feat: Migrate utilities from UrlContextProvider to Zustand stores (resolves #269; fixes #294; resolves #306). (#286)
1 parent 3585745 commit 7810e12

File tree

15 files changed

+537
-564
lines changed

15 files changed

+537
-564
lines changed

src/App.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import AppController from "./components/AppController";
22
import Layout from "./components/Layout";
3-
import UrlContextProvider from "./contexts/UrlContextProvider";
43

54

65
/**
@@ -10,11 +9,9 @@ import UrlContextProvider from "./contexts/UrlContextProvider";
109
*/
1110
const App = () => {
1211
return (
13-
<UrlContextProvider>
14-
<AppController>
15-
<Layout/>
16-
</AppController>
17-
</UrlContextProvider>
12+
<AppController>
13+
<Layout/>
14+
</AppController>
1815
);
1916
};
2017

src/components/AppController.tsx

Lines changed: 116 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import 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";
146
import useLogFileManagerStore from "../stores/logFileManagerProxyStore";
157
import useLogFileStore from "../stores/logFileStore";
168
import {handleErrorWithNotification} from "../stores/notificationStore";
179
import useQueryStore from "../stores/queryStore";
1810
import useUiStore from "../stores/uiStore";
1911
import useViewStore from "../stores/viewStore";
12+
import {Nullable} from "../typings/common";
2013
import {UI_STATE} from "../typings/states";
14+
import {UrlHashParams} from "../typings/url";
2115
import {
2216
CURSOR_CODE,
2317
CursorType,
@@ -27,6 +21,13 @@ import {
2721
isWithinBounds,
2822
} from "../utils/data";
2923
import {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+
70138
interface AppControllerProps {
71139
children: React.ReactNode;
72140
}
@@ -79,127 +147,74 @@ interface AppControllerProps {
79147
* @return
80148
*/
81149
const 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
};

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/QueryInputBox.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, {useCallback} from "react";
22

33
import {
44
LinearProgress,
@@ -11,6 +11,7 @@ import useUiStore from "../../../../../stores/uiStore";
1111
import {QUERY_PROGRESS_VALUE_MAX} from "../../../../../typings/query";
1212
import {UI_ELEMENT} from "../../../../../typings/states";
1313
import {isDisabled} from "../../../../../utils/states";
14+
import {updateWindowUrlHashParams} from "../../../../../utils/url";
1415
import ToggleIconButton from "./ToggleIconButton";
1516

1617
import "./QueryInputBox.css";
@@ -25,27 +26,32 @@ const QueryInputBox = () => {
2526
const isCaseSensitive = useQueryStore((state) => state.queryIsCaseSensitive);
2627
const isRegex = useQueryStore((state) => state.queryIsRegex);
2728
const querystring = useQueryStore((state) => state.queryString);
28-
const setQueryIsCaseSensitive = useQueryStore((state) => state.setQueryIsCaseSensitive);
29-
const setQueryIsRegex = useQueryStore((state) => state.setQueryIsRegex);
30-
const setQueryString = useQueryStore((state) => state.setQueryString);
3129
const queryProgress = useQueryStore((state) => state.queryProgress);
32-
const startQuery = useQueryStore((state) => state.startQuery);
3330
const uiState = useUiStore((state) => state.uiState);
3431

35-
const handleQueryInputChange = (ev: React.ChangeEvent<HTMLTextAreaElement>) => {
36-
setQueryString(ev.target.value);
32+
const handleQueryInputChange = useCallback((ev: React.ChangeEvent<HTMLTextAreaElement>) => {
33+
const newQueryString = ev.target.value;
34+
updateWindowUrlHashParams({queryString: newQueryString});
35+
const {setQueryString, startQuery} = useQueryStore.getState();
36+
setQueryString(newQueryString);
3737
startQuery();
38-
};
38+
}, []);
3939

40-
const handleCaseSensitivityButtonClick = () => {
41-
setQueryIsCaseSensitive(!isCaseSensitive);
40+
const handleCaseSensitivityButtonClick = useCallback(() => {
41+
const newQueryIsSensitive = !isCaseSensitive;
42+
updateWindowUrlHashParams({queryIsCaseSensitive: newQueryIsSensitive});
43+
const {setQueryIsCaseSensitive, startQuery} = useQueryStore.getState();
44+
setQueryIsCaseSensitive(newQueryIsSensitive);
4245
startQuery();
43-
};
46+
}, [isCaseSensitive]);
4447

45-
const handleRegexButtonClick = () => {
46-
setQueryIsRegex(!isRegex);
48+
const handleRegexButtonClick = useCallback(() => {
49+
const newQueryIsRegex = !isRegex;
50+
updateWindowUrlHashParams({queryIsRegex: newQueryIsRegex});
51+
const {setQueryIsRegex, startQuery} = useQueryStore.getState();
52+
setQueryIsRegex(newQueryIsRegex);
4753
startQuery();
48-
};
54+
}, [isRegex]);
4955

5056
const isQueryInputBoxDisabled = isDisabled(uiState, UI_ELEMENT.QUERY_INPUT_BOX);
5157

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import {useCallback} from "react";
2+
13
import {
24
ListItemButton,
35
Typography,
46
} from "@mui/joy";
57

6-
import {updateWindowUrlHashParams} from "../../../../../contexts/UrlContextProvider";
8+
import useViewStore from "../../../../../stores/viewStore";
9+
import {updateWindowUrlHashParams} from "../../../../../utils/url";
710

811
import "./Result.css";
912

@@ -36,9 +39,12 @@ const Result = ({logEventNum, message, matchRange}: ResultProps) => {
3639
message.slice(...matchRange),
3740
message.slice(matchRange[1]),
3841
];
39-
const handleResultButtonClick = () => {
42+
43+
const handleResultButtonClick = useCallback(() => {
4044
updateWindowUrlHashParams({logEventNum});
41-
};
45+
const {setLogEventNum} = useViewStore.getState();
46+
setLogEventNum(logEventNum);
47+
}, [logEventNum]);
4248

4349
return (
4450
<ListItemButton

0 commit comments

Comments
 (0)