From 112d486e9c398651bd505941d860f48582b1ae3c Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Tue, 22 Jul 2025 18:09:03 -0400 Subject: [PATCH 1/8] convert `updateLogEventNum` to `setLogEventNum`, `updateIsPrettified` to `setIsPrettified`; remove `setPageData` and refactor `updatePageData`. --- src/components/AppController.tsx | 18 ++++---- .../SidebarTabs/SearchTabPanel/Result.tsx | 5 +- src/components/Editor/index.tsx | 9 ++-- src/components/StatusBar/index.tsx | 4 +- src/stores/logFileStore.ts | 6 +-- src/stores/viewStore/createViewEventSlice.ts | 46 ++----------------- src/stores/viewStore/createViewFilterSlice.ts | 1 + .../viewStore/createViewFormattingSlice.ts | 41 ++--------------- src/stores/viewStore/createViewPageSlice.ts | 20 +++----- src/stores/viewStore/types.ts | 5 +- 10 files changed, 37 insertions(+), 118 deletions(-) diff --git a/src/components/AppController.tsx b/src/components/AppController.tsx index 9b0ec4af0..d03ec48f1 100644 --- a/src/components/AppController.tsx +++ b/src/components/AppController.tsx @@ -7,13 +7,15 @@ import useLogFileManagerProxyStore from "../stores/logFileManagerProxyStore"; import useLogFileStore from "../stores/logFileStore"; import {handleErrorWithNotification} from "../stores/notificationStore"; import useQueryStore from "../stores/queryStore"; +import useUiStore from "../stores/uiStore"; import useViewStore from "../stores/viewStore"; import {Nullable} from "../typings/common"; +import {UI_STATE} from "../typings/states"; import { CURSOR_CODE, CursorType, } from "../typings/worker"; -import {clamp} from "../utils/math.ts"; +import {clamp} from "../utils/math"; import { getWindowUrlHashParams, getWindowUrlSearchParams, @@ -44,12 +46,12 @@ const getCursorFromHashParams = ({isPrettified, logEventNum, timestamp}: { } const { - isPrettified: prevIsPrettified, updateIsPrettified, updateLogEventNum, + isPrettified: prevIsPrettified, setIsPrettified, setLogEventNum, } = useViewStore.getState(); const clampedLogEventNum = clamp(logEventNum, 1, numEvents); if (isPrettified !== prevIsPrettified) { - updateIsPrettified(isPrettified); + setIsPrettified(isPrettified); return { code: CURSOR_CODE.EVENT_NUM, @@ -63,7 +65,8 @@ const getCursorFromHashParams = ({isPrettified, logEventNum, timestamp}: { args: {timestamp: timestamp}, }; } else if (logEventNum !== URL_HASH_PARAMS_DEFAULT.logEventNum) { - updateLogEventNum(logEventNum); + setLogEventNum(clampedLogEventNum); + updateWindowUrlHashParams({logEventNum: clampedLogEventNum}); const {beginLineNumToLogEventNum} = useViewStore.getState(); const logEventNumsOnPage: number[] = Array.from(beginLineNumToLogEventNum.values()); if (updateUrlIfEventOnPage(clampedLogEventNum, logEventNumsOnPage)) { @@ -103,6 +106,8 @@ const updateViewHashParams = () => { const {updatePageData} = useViewStore.getState(); const pageData = await logFileManagerProxy.loadPage(cursor, isPrettified); updatePageData(pageData); + const {setUiState} = useUiStore.getState(); + setUiState(UI_STATE.READY); })().catch(handleErrorWithNotification); }; @@ -150,9 +155,6 @@ const handleHashChange = (ev: Nullable) => { const {startQuery} = useQueryStore.getState(); startQuery(); } - - // eslint-disable-next-line no-warning-comments - // TODO: Remove empty or falsy parameters. }; interface AppControllerProps { @@ -210,5 +212,5 @@ const AppController = ({children}: AppControllerProps) => { return children; }; - +export {updateViewHashParams}; export default AppController; diff --git a/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx b/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx index 450eac076..99497c690 100644 --- a/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx +++ b/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx @@ -5,8 +5,8 @@ import { Typography, } from "@mui/joy"; -import useViewStore from "../../../../../stores/viewStore"; import {updateWindowUrlHashParams} from "../../../../../utils/url"; +import {updateViewHashParams} from "../../../../AppController.tsx"; import "./Result.css"; @@ -42,8 +42,7 @@ const Result = ({logEventNum, message, matchRange}: ResultProps) => { const handleResultButtonClick = useCallback(() => { updateWindowUrlHashParams({logEventNum}); - const {updateLogEventNum} = useViewStore.getState(); - updateLogEventNum(logEventNum); + updateViewHashParams(); }, [logEventNum]); return ( diff --git a/src/components/Editor/index.tsx b/src/components/Editor/index.tsx index 6ac587d78..6c35ac9fa 100644 --- a/src/components/Editor/index.tsx +++ b/src/components/Editor/index.tsx @@ -34,6 +34,7 @@ import { getMapValueWithNearestLessThanOrEqualKey, } from "../../utils/data"; import {updateWindowUrlHashParams} from "../../utils/url"; +import {updateViewHashParams} from "../AppController.tsx"; import MonacoInstance from "./MonacoInstance"; import {goToPositionAndCenter} from "./MonacoInstance/utils"; @@ -163,12 +164,13 @@ const handleEditorCustomAction = ( break; } case ACTION_NAME.TOGGLE_PRETTIFY: { - const {isPrettified, updateIsPrettified} = useViewStore.getState(); + const {isPrettified, setIsPrettified} = useViewStore.getState(); const newIsPrettified = !isPrettified; updateWindowUrlHashParams({ [HASH_PARAM_NAMES.IS_PRETTIFIED]: newIsPrettified, }); - updateIsPrettified(newIsPrettified); + setIsPrettified(newIsPrettified); + updateViewHashParams(); break; } case ACTION_NAME.TOGGLE_WORD_WRAP: @@ -271,8 +273,7 @@ const Editor = () => { return; } updateWindowUrlHashParams({logEventNum: newLogEventNum}); - const {updateLogEventNum} = useViewStore.getState(); - updateLogEventNum(newLogEventNum); + updateViewHashParams(); }, []); // Synchronize `beginLineNumToLogEventNumRef` with `beginLineNumToLogEventNum`. diff --git a/src/components/StatusBar/index.tsx b/src/components/StatusBar/index.tsx index 00bd92659..418406e8e 100644 --- a/src/components/StatusBar/index.tsx +++ b/src/components/StatusBar/index.tsx @@ -22,6 +22,7 @@ import { copyPermalinkToClipboard, updateWindowUrlHashParams, } from "../../utils/url"; +import {updateViewHashParams} from "../AppController.tsx"; import LogLevelSelect from "./LogLevelSelect"; import StatusBarToggleButton from "./StatusBarToggleButton"; @@ -45,7 +46,6 @@ const StatusBar = () => { const logEventNum = useViewStore((state) => state.logEventNum); const numEvents = useLogFileStore((state) => state.numEvents); const uiState = useUiStore((state) => state.uiState); - const updateIsPrettified = useViewStore((state) => state.updateIsPrettified); const handleStatusButtonClick = (ev: React.MouseEvent) => { const {actionName} = ev.currentTarget.dataset; @@ -55,7 +55,7 @@ const StatusBar = () => { updateWindowUrlHashParams({ [HASH_PARAM_NAMES.IS_PRETTIFIED]: false === isPrettified, }); - updateIsPrettified(!isPrettified); + updateViewHashParams(); break; default: console.error(`Unexpected action: ${actionName}`); diff --git a/src/stores/logFileStore.ts b/src/stores/logFileStore.ts index a29c873dd..c1bd63d63 100644 --- a/src/stores/logFileStore.ts +++ b/src/stores/logFileStore.ts @@ -116,8 +116,8 @@ const useLogFileStore = create((set) => ({ const {setExportProgress} = useLogExportStore.getState(); setExportProgress(LOG_EXPORT_STORE_DEFAULT.exportProgress); - const {setPageData} = useViewStore.getState(); - setPageData({ + const {updatePageData} = useViewStore.getState(); + updatePageData({ beginLineNumToLogEventNum: VIEW_PAGE_DEFAULT.beginLineNumToLogEventNum, cursorLineNum: 1, logEventNum: VIEW_EVENT_DEFAULT.logEventNum, @@ -148,8 +148,8 @@ const useLogFileStore = create((set) => ({ const {isPrettified} = useViewStore.getState(); const pageData = await logFileManagerProxy.loadPage(cursor, isPrettified); - const {updatePageData} = useViewStore.getState(); updatePageData(pageData); + setUiState(UI_STATE.READY); const {startQuery} = useQueryStore.getState(); startQuery(); diff --git a/src/stores/viewStore/createViewEventSlice.ts b/src/stores/viewStore/createViewEventSlice.ts index fde3d22e3..b7a6bb578 100644 --- a/src/stores/viewStore/createViewEventSlice.ts +++ b/src/stores/viewStore/createViewEventSlice.ts @@ -1,16 +1,5 @@ import {StateCreator} from "zustand"; -import {UI_STATE} from "../../typings/states"; -import { - CURSOR_CODE, - CursorType, -} from "../../typings/worker"; -import {clamp} from "../../utils/math"; -import {updateUrlIfEventOnPage} from "../../utils/url"; -import useLogFileManagerStore from "../logFileManagerProxyStore"; -import useLogFileStore from "../logFileStore"; -import {handleErrorWithNotification} from "../notificationStore"; -import useUiStore from "../uiStore"; import { ViewEventSlice, ViewEventValues, @@ -26,43 +15,14 @@ const VIEW_EVENT_DEFAULT: ViewEventValues = { * Creates a slice for updating log events. * * @param set - * @param get * @return */ const createViewEventSlice: StateCreator< ViewState, [], [], ViewEventSlice -> = (set, get) => ({ +> = (set) => ({ ...VIEW_EVENT_DEFAULT, - updateLogEventNum: (newLogEventNum) => { - const {numEvents} = useLogFileStore.getState(); - if (0 === numEvents) { - return; - } - - const clampedLogEventNum = clamp(newLogEventNum, 1, numEvents); - set({logEventNum: clampedLogEventNum}); - const {beginLineNumToLogEventNum} = get(); - const logEventNumsOnPage: number [] = Array.from(beginLineNumToLogEventNum.values()); - if (updateUrlIfEventOnPage(clampedLogEventNum, logEventNumsOnPage)) { - // No need to request a new page since the log event is on the current page. - return; - } - - // If the log event is not on the current page, request a new page. - const {setUiState} = useUiStore.getState(); - setUiState(UI_STATE.FAST_LOADING); - (async () => { - const {logFileManagerProxy} = useLogFileManagerStore.getState(); - const cursor: CursorType = { - code: CURSOR_CODE.EVENT_NUM, - args: {eventNum: clampedLogEventNum}, - }; - const {isPrettified} = get(); - - const pageData = await logFileManagerProxy.loadPage(cursor, isPrettified); - const {updatePageData} = get(); - updatePageData(pageData); - })().catch(handleErrorWithNotification); + setLogEventNum: (logEventNum: number) => { + set({logEventNum}); }, }); diff --git a/src/stores/viewStore/createViewFilterSlice.ts b/src/stores/viewStore/createViewFilterSlice.ts index bf90ee9d8..700c4fe27 100644 --- a/src/stores/viewStore/createViewFilterSlice.ts +++ b/src/stores/viewStore/createViewFilterSlice.ts @@ -42,6 +42,7 @@ const createViewFilterSlice: StateCreator< const {updatePageData} = get(); updatePageData(pageData); + setUiState(UI_STATE.READY); const {startQuery} = useQueryStore.getState(); startQuery(); diff --git a/src/stores/viewStore/createViewFormattingSlice.ts b/src/stores/viewStore/createViewFormattingSlice.ts index 7f70fc78a..3439f6619 100644 --- a/src/stores/viewStore/createViewFormattingSlice.ts +++ b/src/stores/viewStore/createViewFormattingSlice.ts @@ -1,14 +1,5 @@ import {StateCreator} from "zustand"; -import {UI_STATE} from "../../typings/states"; -import { - CURSOR_CODE, - CursorType, -} from "../../typings/worker"; -import useLogFileManagerStore from "../logFileManagerProxyStore"; -import {handleErrorWithNotification} from "../notificationStore"; -import useUiStore from "../uiStore"; -import {VIEW_EVENT_DEFAULT} from "./createViewEventSlice"; import { ViewFormattingSlice, ViewFormattingValues, @@ -24,40 +15,14 @@ const VIEW_FORMATTING_DEFAULT: ViewFormattingValues = { * Creates a slice for managing log formatting state. * * @param set - * @param get * @return */ const createViewFormattingSlice: StateCreator< ViewState, [], [], ViewFormattingSlice -> = (set, get) => ({ +> = (set) => ({ ...VIEW_FORMATTING_DEFAULT, - updateIsPrettified: (newIsPrettified: boolean) => { - const {isPrettified} = get(); - if (newIsPrettified === isPrettified) { - return; - } - - const {setUiState} = useUiStore.getState(); - setUiState(UI_STATE.FAST_LOADING); - - set({isPrettified: newIsPrettified}); - - const {logEventNum} = get(); - let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; - if (VIEW_EVENT_DEFAULT.logEventNum !== logEventNum) { - cursor = { - code: CURSOR_CODE.EVENT_NUM, - args: {eventNum: logEventNum}, - }; - } - - (async () => { - const {logFileManagerProxy} = useLogFileManagerStore.getState(); - const pageData = await logFileManagerProxy.loadPage(cursor, newIsPrettified); - - const {updatePageData} = get(); - updatePageData(pageData); - })().catch(handleErrorWithNotification); + setIsPrettified: (isPrettified: boolean) => { + set({isPrettified}); }, }); diff --git a/src/stores/viewStore/createViewPageSlice.ts b/src/stores/viewStore/createViewPageSlice.ts index a356cc9e2..31a68bd69 100644 --- a/src/stores/viewStore/createViewPageSlice.ts +++ b/src/stores/viewStore/createViewPageSlice.ts @@ -13,7 +13,7 @@ import { NavigationAction, } from "../../utils/actions"; import {clamp} from "../../utils/math"; -import {updateWindowUrlHashParams} from "../../utils/url"; +import {updateWindowUrlHashParams} from "../../utils/url.ts"; import useLogFileManagerStore from "../logFileManagerProxyStore"; import useLogFileStore from "../logFileStore"; import {handleErrorWithNotification} from "../notificationStore"; @@ -92,23 +92,15 @@ const createViewPageSlice: StateCreator< ViewState, [], [], ViewPageSlice > = (set, get) => ({ ...VIEW_PAGE_DEFAULT, - setPageData: (pageData: PageData) => { + updatePageData: (pageData: PageData) => { set({ + beginLineNumToLogEventNum: pageData.beginLineNumToLogEventNum, logData: pageData.logs, + logEventNum: pageData.logEventNum, numPages: pageData.numPages, pageNum: pageData.pageNum, - beginLineNumToLogEventNum: pageData.beginLineNumToLogEventNum, }); - }, - updatePageData: (pageData: PageData) => { - const {setPageData} = get(); - setPageData(pageData); - const newLogEventNum = pageData.logEventNum; - updateWindowUrlHashParams({logEventNum: newLogEventNum}); - const {updateLogEventNum} = get(); - updateLogEventNum(newLogEventNum); - const {setUiState} = useUiStore.getState(); - setUiState(UI_STATE.READY); + updateWindowUrlHashParams({logEventNum: pageData.logEventNum}); }, loadPageByAction: (navAction: NavigationAction) => { if (navAction.code === ACTION_NAME.RELOAD) { @@ -149,9 +141,9 @@ const createViewPageSlice: StateCreator< const {logFileManagerProxy} = useLogFileManagerStore.getState(); const {isPrettified} = get(); const pageData = await logFileManagerProxy.loadPage(cursor, isPrettified); - const {updatePageData} = get(); updatePageData(pageData); + setUiState(UI_STATE.READY); })().catch(handleErrorWithNotification); }, }); diff --git a/src/stores/viewStore/types.ts b/src/stores/viewStore/types.ts index 428a50d9b..1ade75a28 100644 --- a/src/stores/viewStore/types.ts +++ b/src/stores/viewStore/types.ts @@ -15,7 +15,6 @@ interface ViewPageValues { interface ViewPageActions { loadPageByAction: (navAction: NavigationAction) => void; - setPageData: (pageData: PageData) => void; updatePageData: (pageData: PageData) => void; } @@ -26,7 +25,7 @@ interface ViewEventValues { } interface ViewEventActions { - updateLogEventNum: (newLogEventNum: number) => void; + setLogEventNum: (newLogEventNum: number) => void; } type ViewEventSlice = ViewEventValues & ViewEventActions; @@ -36,7 +35,7 @@ interface ViewFormattingValues { } interface ViewFormattingActions { - updateIsPrettified: (newIsPrettified: boolean) => void; + setIsPrettified: (newIsPrettified: boolean) => void; } type ViewFormattingSlice = ViewFormattingValues & ViewFormattingActions; From 1785afe692976f279bfd857c2d3266d0982aa5cf Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Tue, 22 Jul 2025 18:52:11 -0400 Subject: [PATCH 2/8] refactor `getCursorFromHashParams`, `updateViewHashParams`, and `updateQueryHashParams` to urlHash --- src/components/AppController.tsx | 129 +----------------- src/stores/viewStore/createViewPageSlice.ts | 2 +- src/utils/{url.ts => url/index.ts} | 4 +- src/utils/url/urlHash.ts | 142 ++++++++++++++++++++ 4 files changed, 149 insertions(+), 128 deletions(-) rename src/utils/{url.ts => url/index.ts} (99%) create mode 100644 src/utils/url/urlHash.ts diff --git a/src/components/AppController.tsx b/src/components/AppController.tsx index d03ec48f1..49b35df4c 100644 --- a/src/components/AppController.tsx +++ b/src/components/AppController.tsx @@ -3,146 +3,25 @@ import React, { useRef, } from "react"; -import useLogFileManagerProxyStore from "../stores/logFileManagerProxyStore"; import useLogFileStore from "../stores/logFileStore"; -import {handleErrorWithNotification} from "../stores/notificationStore"; import useQueryStore from "../stores/queryStore"; -import useUiStore from "../stores/uiStore"; -import useViewStore from "../stores/viewStore"; import {Nullable} from "../typings/common"; -import {UI_STATE} from "../typings/states"; import { CURSOR_CODE, CursorType, } from "../typings/worker"; -import {clamp} from "../utils/math"; import { getWindowUrlHashParams, getWindowUrlSearchParams, - updateUrlIfEventOnPage, - updateWindowUrlHashParams, URL_HASH_PARAMS_DEFAULT, URL_SEARCH_PARAMS_DEFAULT, } from "../utils/url"; +import { + updateQueryHashParams, + updateViewHashParams, +} from "../utils/url/urlHash"; -/** - * Determines the cursor for navigating log events based on URL hash parameters. - * - * @param params An object containing the following properties: - * @param params.isPrettified Whether the log view is in prettified mode. - * @param params.logEventNum The log event number from the URL hash. - * @param params.timestamp The timestamp from the URL hash. - * @return `CursorType` object if a navigation action is needed, or `null` if no action is required. - */ -const getCursorFromHashParams = ({isPrettified, logEventNum, timestamp}: { - isPrettified: boolean; logEventNum: number; timestamp: number; -}): Nullable => { - const {numEvents} = useLogFileStore.getState(); - if (0 === numEvents) { - updateWindowUrlHashParams({logEventNum: URL_HASH_PARAMS_DEFAULT.logEventNum}); - - return null; - } - - const { - isPrettified: prevIsPrettified, setIsPrettified, setLogEventNum, - } = useViewStore.getState(); - const clampedLogEventNum = clamp(logEventNum, 1, numEvents); - - if (isPrettified !== prevIsPrettified) { - setIsPrettified(isPrettified); - - return { - code: CURSOR_CODE.EVENT_NUM, - args: {eventNum: clampedLogEventNum}, - }; - } - - if (timestamp !== URL_HASH_PARAMS_DEFAULT.timestamp) { - return { - code: CURSOR_CODE.TIMESTAMP, - args: {timestamp: timestamp}, - }; - } else if (logEventNum !== URL_HASH_PARAMS_DEFAULT.logEventNum) { - setLogEventNum(clampedLogEventNum); - updateWindowUrlHashParams({logEventNum: clampedLogEventNum}); - const {beginLineNumToLogEventNum} = useViewStore.getState(); - const logEventNumsOnPage: number[] = Array.from(beginLineNumToLogEventNum.values()); - if (updateUrlIfEventOnPage(clampedLogEventNum, logEventNumsOnPage)) { - // No need to request a new page since the log event is on the current page. - return null; - } - - return { - code: CURSOR_CODE.EVENT_NUM, - args: {eventNum: clampedLogEventNum}, - }; - } - - // If we reach here, we have no valid cursor. - return null; -}; - -/** - * Updates view-related states from URL hash parameters. - * NOTE: this may modify the URL parameters. - */ -const updateViewHashParams = () => { - const {isPrettified, logEventNum, timestamp} = getWindowUrlHashParams(); - updateWindowUrlHashParams({ - isPrettified: URL_HASH_PARAMS_DEFAULT.isPrettified, - timestamp: URL_HASH_PARAMS_DEFAULT.timestamp, - }); - - const cursor = getCursorFromHashParams({isPrettified, logEventNum, timestamp}); - if (null === cursor) { - // If no cursor was set, we can return early. - return; - } - - (async () => { - const {logFileManagerProxy} = useLogFileManagerProxyStore.getState(); - const {updatePageData} = useViewStore.getState(); - const pageData = await logFileManagerProxy.loadPage(cursor, isPrettified); - updatePageData(pageData); - const {setUiState} = useUiStore.getState(); - setUiState(UI_STATE.READY); - })().catch(handleErrorWithNotification); -}; - -/** - * Updates query-related states from URL hash parameters. - * NOTE: this may modify the URL parameters. - * - * @return Whether any query-related parameters were modified. - */ -const updateQueryHashParams = () => { - const {queryIsCaseSensitive, queryIsRegex, queryString} = getWindowUrlHashParams(); - updateWindowUrlHashParams({queryIsCaseSensitive, queryIsRegex, queryString}); - - const { - queryIsCaseSensitive: currentQueryIsCaseSensitive, - queryIsRegex: currentQueryIsRegex, - queryString: currentQueryString, - setQueryIsCaseSensitive, - setQueryIsRegex, - setQueryString, - } = useQueryStore.getState(); - - let isQueryModified = false; - isQueryModified ||= queryIsCaseSensitive !== currentQueryIsCaseSensitive; - setQueryIsCaseSensitive(queryIsCaseSensitive); - - isQueryModified ||= queryIsRegex !== currentQueryIsRegex; - setQueryIsRegex(queryIsRegex); - - isQueryModified ||= queryString !== currentQueryString; - setQueryString(queryString); - - return isQueryModified; -}; - /** * Handles hash change events by updating the application state based on the URL hash parameters. * diff --git a/src/stores/viewStore/createViewPageSlice.ts b/src/stores/viewStore/createViewPageSlice.ts index 31a68bd69..0007d0525 100644 --- a/src/stores/viewStore/createViewPageSlice.ts +++ b/src/stores/viewStore/createViewPageSlice.ts @@ -13,7 +13,7 @@ import { NavigationAction, } from "../../utils/actions"; import {clamp} from "../../utils/math"; -import {updateWindowUrlHashParams} from "../../utils/url.ts"; +import {updateWindowUrlHashParams} from "../../utils/url"; import useLogFileManagerStore from "../logFileManagerProxyStore"; import useLogFileStore from "../logFileStore"; import {handleErrorWithNotification} from "../notificationStore"; diff --git a/src/utils/url.ts b/src/utils/url/index.ts similarity index 99% rename from src/utils/url.ts rename to src/utils/url/index.ts index b90b13f01..7ff98b35d 100644 --- a/src/utils/url.ts +++ b/src/utils/url/index.ts @@ -6,11 +6,11 @@ import { UrlHashParamUpdatesType, UrlSearchParams, UrlSearchParamUpdatesType, -} from "../typings/url"; +} from "../../typings/url.ts"; import { findNearestLessThanOrEqualElement, isWithinBounds, -} from "../utils/data"; +} from "../data.ts"; /** diff --git a/src/utils/url/urlHash.ts b/src/utils/url/urlHash.ts new file mode 100644 index 000000000..b9a37e17a --- /dev/null +++ b/src/utils/url/urlHash.ts @@ -0,0 +1,142 @@ +import useLogFileManagerProxyStore from "../../stores/logFileManagerProxyStore.ts"; +import useLogFileStore from "../../stores/logFileStore.ts"; +import {handleErrorWithNotification} from "../../stores/notificationStore.ts"; +import useQueryStore from "../../stores/queryStore"; +import useUiStore from "../../stores/uiStore"; +import useViewStore from "../../stores/viewStore"; +import {Nullable} from "../../typings/common"; +import {UI_STATE} from "../../typings/states"; +import { + CURSOR_CODE, + CursorType, +} from "../../typings/worker"; +import {clamp} from "../math"; +import { + getWindowUrlHashParams, + updateUrlIfEventOnPage, + updateWindowUrlHashParams, + URL_HASH_PARAMS_DEFAULT, +} from "./index"; + + +/** + * Determines the cursor for navigating log events based on URL hash parameters. + * + * @param params An object containing the following properties: + * @param params.isPrettified Whether the log view is in prettified mode. + * @param params.logEventNum The log event number from the URL hash. + * @param params.timestamp The timestamp from the URL hash. + * @return `CursorType` object if a navigation action is needed, or `null` if no action is required. + */ +const getCursorFromHashParams = ({isPrettified, logEventNum, timestamp}: { + isPrettified: boolean; logEventNum: number; timestamp: number; +}): Nullable => { + const {numEvents} = useLogFileStore.getState(); + if (0 === numEvents) { + updateWindowUrlHashParams({logEventNum: URL_HASH_PARAMS_DEFAULT.logEventNum}); + + return null; + } + + const { + isPrettified: prevIsPrettified, setIsPrettified, setLogEventNum, + } = useViewStore.getState(); + const clampedLogEventNum = clamp(logEventNum, 1, numEvents); + + if (isPrettified !== prevIsPrettified) { + setIsPrettified(isPrettified); + + return { + code: CURSOR_CODE.EVENT_NUM, + args: {eventNum: clampedLogEventNum}, + }; + } + + if (timestamp !== URL_HASH_PARAMS_DEFAULT.timestamp) { + return { + code: CURSOR_CODE.TIMESTAMP, + args: {timestamp: timestamp}, + }; + } else if (logEventNum !== URL_HASH_PARAMS_DEFAULT.logEventNum) { + setLogEventNum(clampedLogEventNum); + updateWindowUrlHashParams({logEventNum: clampedLogEventNum}); + const {beginLineNumToLogEventNum} = useViewStore.getState(); + const logEventNumsOnPage: number[] = Array.from(beginLineNumToLogEventNum.values()); + if (updateUrlIfEventOnPage(clampedLogEventNum, logEventNumsOnPage)) { + // No need to request a new page since the log event is on the current page. + return null; + } + + return { + code: CURSOR_CODE.EVENT_NUM, + args: {eventNum: clampedLogEventNum}, + }; + } + + // If we reach here, we have no valid cursor. + return null; +}; + +/** + * Updates view-related states from URL hash parameters. + * NOTE: this may modify the URL parameters. + */ +const updateViewHashParams = () => { + const {isPrettified, logEventNum, timestamp} = getWindowUrlHashParams(); + updateWindowUrlHashParams({ + isPrettified: URL_HASH_PARAMS_DEFAULT.isPrettified, + timestamp: URL_HASH_PARAMS_DEFAULT.timestamp, + }); + + const cursor = getCursorFromHashParams({isPrettified, logEventNum, timestamp}); + if (null === cursor) { + // If no cursor was set, we can return early. + return; + } + + (async () => { + const {logFileManagerProxy} = useLogFileManagerProxyStore.getState(); + const {updatePageData} = useViewStore.getState(); + const pageData = await logFileManagerProxy.loadPage(cursor, isPrettified); + updatePageData(pageData); + const {setUiState} = useUiStore.getState(); + setUiState(UI_STATE.READY); + })().catch(handleErrorWithNotification); +}; + +/** + * Updates query-related states from URL hash parameters. + * NOTE: this may modify the URL parameters. + * + * @return Whether any query-related parameters were modified. + */ +const updateQueryHashParams = () => { + const {queryIsCaseSensitive, queryIsRegex, queryString} = getWindowUrlHashParams(); + updateWindowUrlHashParams({queryIsCaseSensitive, queryIsRegex, queryString}); + + const { + queryIsCaseSensitive: currentQueryIsCaseSensitive, + queryIsRegex: currentQueryIsRegex, + queryString: currentQueryString, + setQueryIsCaseSensitive, + setQueryIsRegex, + setQueryString, + } = useQueryStore.getState(); + + let isQueryModified = false; + isQueryModified ||= queryIsCaseSensitive !== currentQueryIsCaseSensitive; + setQueryIsCaseSensitive(queryIsCaseSensitive); + + isQueryModified ||= queryIsRegex !== currentQueryIsRegex; + setQueryIsRegex(queryIsRegex); + + isQueryModified ||= queryString !== currentQueryString; + setQueryString(queryString); + + return isQueryModified; +}; + +export { + updateQueryHashParams, + updateViewHashParams, +}; From 6f65b765fcf4ce3e12a5fce15bbdd1c856b875df Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Tue, 22 Jul 2025 18:59:52 -0400 Subject: [PATCH 3/8] For some reason, ide doesn't refactor these imports --- .../Sidebar/SidebarTabs/SearchTabPanel/Result.tsx | 2 +- src/components/Editor/index.tsx | 2 +- src/components/StatusBar/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx b/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx index 99497c690..7d115ff5f 100644 --- a/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx +++ b/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx @@ -6,7 +6,7 @@ import { } from "@mui/joy"; import {updateWindowUrlHashParams} from "../../../../../utils/url"; -import {updateViewHashParams} from "../../../../AppController.tsx"; +import {updateViewHashParams} from "../../../../../utils/url/urlHash"; import "./Result.css"; diff --git a/src/components/Editor/index.tsx b/src/components/Editor/index.tsx index 6c35ac9fa..8c643a20d 100644 --- a/src/components/Editor/index.tsx +++ b/src/components/Editor/index.tsx @@ -34,7 +34,7 @@ import { getMapValueWithNearestLessThanOrEqualKey, } from "../../utils/data"; import {updateWindowUrlHashParams} from "../../utils/url"; -import {updateViewHashParams} from "../AppController.tsx"; +import {updateViewHashParams} from "../../utils/url/urlHash"; import MonacoInstance from "./MonacoInstance"; import {goToPositionAndCenter} from "./MonacoInstance/utils"; diff --git a/src/components/StatusBar/index.tsx b/src/components/StatusBar/index.tsx index 418406e8e..eccf571e3 100644 --- a/src/components/StatusBar/index.tsx +++ b/src/components/StatusBar/index.tsx @@ -22,7 +22,7 @@ import { copyPermalinkToClipboard, updateWindowUrlHashParams, } from "../../utils/url"; -import {updateViewHashParams} from "../AppController.tsx"; +import {updateViewHashParams} from "../../utils/url/urlHash"; import LogLevelSelect from "./LogLevelSelect"; import StatusBarToggleButton from "./StatusBarToggleButton"; From ed36392d969ece1f3b56162a4d46234a8d250968 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:07:20 -0400 Subject: [PATCH 4/8] fix issue #348 --- src/components/AppController.tsx | 17 ++++++++++------- src/stores/logFileStore.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/AppController.tsx b/src/components/AppController.tsx index 49b35df4c..67b3f8262 100644 --- a/src/components/AppController.tsx +++ b/src/components/AppController.tsx @@ -5,7 +5,7 @@ import React, { import useLogFileStore from "../stores/logFileStore"; import useQueryStore from "../stores/queryStore"; -import {Nullable} from "../typings/common"; +import useViewStore from "../stores/viewStore"; import { CURSOR_CODE, CursorType, @@ -13,6 +13,7 @@ import { import { getWindowUrlHashParams, getWindowUrlSearchParams, + updateWindowUrlHashParams, URL_HASH_PARAMS_DEFAULT, URL_SEARCH_PARAMS_DEFAULT, } from "../utils/url"; @@ -24,13 +25,10 @@ import { /** * Handles hash change events by updating the application state based on the URL hash parameters. - * - * @param [ev] The hash change event, or `null` when called on application initialization. */ -const handleHashChange = (ev: Nullable) => { +const handleHashChange = () => { updateViewHashParams(); - const isTriggeredByHashChange = null !== ev; - if (isTriggeredByHashChange && updateQueryHashParams()) { + if (updateQueryHashParams()) { const {startQuery} = useQueryStore.getState(); startQuery(); } @@ -62,8 +60,11 @@ const AppController = ({children}: AppControllerProps) => { isInitialized.current = true; // Handle initial page load and maintain full URL state - handleHashChange(null); const hashParams = getWindowUrlHashParams(); + updateWindowUrlHashParams({ + isPrettified: URL_HASH_PARAMS_DEFAULT.isPrettified, + timestamp: URL_HASH_PARAMS_DEFAULT.timestamp, + }); const searchParams = getWindowUrlSearchParams(); if (URL_SEARCH_PARAMS_DEFAULT.filePath !== searchParams.filePath) { let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; @@ -74,6 +75,8 @@ const AppController = ({children}: AppControllerProps) => { args: {timestamp: hashParams.timestamp}, }; } else if (URL_HASH_PARAMS_DEFAULT.logEventNum !== hashParams.logEventNum) { + const {setLogEventNum} = useViewStore.getState(); + setLogEventNum(hashParams.logEventNum); cursor = { code: CURSOR_CODE.EVENT_NUM, args: {eventNum: hashParams.logEventNum}, diff --git a/src/stores/logFileStore.ts b/src/stores/logFileStore.ts index c1bd63d63..329f82bea 100644 --- a/src/stores/logFileStore.ts +++ b/src/stores/logFileStore.ts @@ -21,13 +21,13 @@ import { } from "../typings/worker"; import {getConfig} from "../utils/config"; import {updateWindowUrlSearchParams} from "../utils/url"; +import {updateQueryHashParams} from "../utils/url/urlHash"; import useLogExportStore, {LOG_EXPORT_STORE_DEFAULT} from "./logExportStore"; import useLogFileManagerProxyStore from "./logFileManagerProxyStore"; import useNotificationStore, {handleErrorWithNotification} from "./notificationStore"; import useQueryStore from "./queryStore"; import useUiStore from "./uiStore"; import useViewStore from "./viewStore"; -import {VIEW_EVENT_DEFAULT} from "./viewStore/createViewEventSlice"; import {VIEW_PAGE_DEFAULT} from "./viewStore/createViewPageSlice"; @@ -120,7 +120,7 @@ const useLogFileStore = create((set) => ({ updatePageData({ beginLineNumToLogEventNum: VIEW_PAGE_DEFAULT.beginLineNumToLogEventNum, cursorLineNum: 1, - logEventNum: VIEW_EVENT_DEFAULT.logEventNum, + logEventNum: useViewStore.getState().logEventNum, logs: "Loading...", numPages: VIEW_PAGE_DEFAULT.numPages, pageNum: VIEW_PAGE_DEFAULT.pageNum, @@ -151,8 +151,10 @@ const useLogFileStore = create((set) => ({ updatePageData(pageData); setUiState(UI_STATE.READY); - const {startQuery} = useQueryStore.getState(); - startQuery(); + if (updateQueryHashParams()) { + const {startQuery} = useQueryStore.getState(); + startQuery(); + } if (0 === decoderOptions.formatString.length && fileInfo.fileTypeInfo.isStructured) { const {postPopUp} = useNotificationStore.getState(); From d171554bb82f041b882ead133f63da27310e874c Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:44:22 -0400 Subject: [PATCH 5/8] remove .ts in imports --- src/utils/url/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/url/index.ts b/src/utils/url/index.ts index 7ff98b35d..a9398c5ee 100644 --- a/src/utils/url/index.ts +++ b/src/utils/url/index.ts @@ -6,11 +6,11 @@ import { UrlHashParamUpdatesType, UrlSearchParams, UrlSearchParamUpdatesType, -} from "../../typings/url.ts"; +} from "../../typings/url"; import { findNearestLessThanOrEqualElement, isWithinBounds, -} from "../data.ts"; +} from "../data"; /** From 471e1e3f6f340bafc45230a219336affd2206e35 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:26:23 -0400 Subject: [PATCH 6/8] refactor: Remove unused lineNum parameter and related logic from MonacoInstance and logFileManager; fix #346. --- .../Editor/MonacoInstance/index.tsx | 25 ++----------------- src/components/Editor/index.tsx | 5 +--- src/services/LogFileManager/index.ts | 1 - src/stores/logFileStore.ts | 1 - src/typings/worker.ts | 2 -- 5 files changed, 3 insertions(+), 31 deletions(-) diff --git a/src/components/Editor/MonacoInstance/index.tsx b/src/components/Editor/MonacoInstance/index.tsx index ee59b5dfd..7e389ad81 100644 --- a/src/components/Editor/MonacoInstance/index.tsx +++ b/src/components/Editor/MonacoInstance/index.tsx @@ -14,18 +14,14 @@ import { MountCallback, TextUpdateCallback, } from "./typings"; -import { - createMonacoEditor, - goToPositionAndCenter, -} from "./utils"; +import {createMonacoEditor} from "./utils"; -import "./bootstrap.ts"; +import "./bootstrap"; import "./index.css"; interface MonacoEditorProps { actions: EditorAction[]; - lineNum: number; text: string; themeName: "dark" | "light"; @@ -44,7 +40,6 @@ interface MonacoEditorProps { * * @param props * @param props.actions - * @param props.lineNum * @param props.text * @param props.themeName * @param props.beforeMount @@ -57,7 +52,6 @@ interface MonacoEditorProps { */ const MonacoInstance = ({ actions, - lineNum, text, themeName, beforeMount, @@ -69,12 +63,6 @@ const MonacoInstance = ({ }: MonacoEditorProps) => { const editorRef = useRef(null); const editorContainerRef = useRef(null); - const lineNumRef = useRef(lineNum); - - // Synchronize `lineNumRef` with `lineNum`. - useEffect(() => { - lineNumRef.current = lineNum; - }, [lineNum]); useEffect(() => { console.log("Initializing Monaco instance..."); @@ -115,7 +103,6 @@ const MonacoInstance = ({ } beforeTextUpdate?.(editorRef.current); editorRef.current.setValue(text); - goToPositionAndCenter(editorRef.current, {lineNumber: lineNumRef.current, column: 1}); onTextUpdate?.(editorRef.current); }, [ text, @@ -123,14 +110,6 @@ const MonacoInstance = ({ onTextUpdate, ]); - // On `lineNum` update, update the cursor's position in the editor. - useEffect(() => { - if (null === editorRef.current) { - return; - } - goToPositionAndCenter(editorRef.current, {lineNumber: lineNum, column: 1}); - }, [lineNum]); - return (
{ const queryIsCaseSensitive = useQueryStore((state) => state.queryIsCaseSensitive); const queryIsRegex = useQueryStore((state) => state.queryIsRegex); - const [lineNum, setLineNum] = useState(1); const beginLineNumToLogEventNumRef = useRef( beginLineNumToLogEventNum ); @@ -333,7 +331,7 @@ const Editor = () => { return; } - setLineNum(logEventLineNum); + goToPositionAndCenter(editorRef.current, {lineNumber: logEventLineNum, column: 1}); }, [ logEventNum, beginLineNumToLogEventNum, @@ -344,7 +342,6 @@ const Editor = () => { ((set) => ({ const {updatePageData} = useViewStore.getState(); updatePageData({ beginLineNumToLogEventNum: VIEW_PAGE_DEFAULT.beginLineNumToLogEventNum, - cursorLineNum: 1, logEventNum: useViewStore.getState().logEventNum, logs: "Loading...", numPages: VIEW_PAGE_DEFAULT.numPages, diff --git a/src/typings/worker.ts b/src/typings/worker.ts index fa01ecbea..44704d614 100644 --- a/src/typings/worker.ts +++ b/src/typings/worker.ts @@ -80,7 +80,6 @@ type LogFileInfo = { type PageData = { beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap; - cursorLineNum: number; logEventNum: number; logs: string; numPages: number; @@ -92,7 +91,6 @@ type PageData = { */ const EMPTY_PAGE_RESP: PageData = Object.freeze({ beginLineNumToLogEventNum: new Map(), - cursorLineNum: 1, logEventNum: 0, logs: "", numPages: 1, From 89fdbd16f5da41c4858626644ab75def42e2f9a8 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Thu, 24 Jul 2025 22:45:47 -0400 Subject: [PATCH 7/8] Remove redundant export in AppController.tsx; Fix doesn't apply hash parameter isPrettified in the initial page load. --- src/components/AppController.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/AppController.tsx b/src/components/AppController.tsx index 67b3f8262..6dda8fbf8 100644 --- a/src/components/AppController.tsx +++ b/src/components/AppController.tsx @@ -65,6 +65,9 @@ const AppController = ({children}: AppControllerProps) => { isPrettified: URL_HASH_PARAMS_DEFAULT.isPrettified, timestamp: URL_HASH_PARAMS_DEFAULT.timestamp, }); + const {setIsPrettified} = useViewStore.getState(); + setIsPrettified(hashParams.isPrettified); + const searchParams = getWindowUrlSearchParams(); if (URL_SEARCH_PARAMS_DEFAULT.filePath !== searchParams.filePath) { let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; @@ -94,5 +97,4 @@ const AppController = ({children}: AppControllerProps) => { return children; }; -export {updateViewHashParams}; export default AppController; From 2256793699b0d11d0f2dffdc329a9bf5217e1f36 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Thu, 24 Jul 2025 22:56:34 -0400 Subject: [PATCH 8/8] add hot fix for progress bar disappeared when toggle isPrettified button --- src/components/StatusBar/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/StatusBar/index.tsx b/src/components/StatusBar/index.tsx index 8c495dc0c..fb40a90a8 100644 --- a/src/components/StatusBar/index.tsx +++ b/src/components/StatusBar/index.tsx @@ -14,7 +14,10 @@ import AutoFixOffIcon from "@mui/icons-material/AutoFixOff"; import useLogFileStore from "../../stores/logFileStore"; import useUiStore from "../../stores/uiStore"; import useViewStore from "../../stores/viewStore"; -import {UI_ELEMENT} from "../../typings/states"; +import { + UI_ELEMENT, + UI_STATE, +} from "../../typings/states"; import {HASH_PARAM_NAMES} from "../../typings/url"; import {ACTION_NAME} from "../../utils/actions"; import {isDisabled} from "../../utils/states"; @@ -55,6 +58,7 @@ const StatusBar = () => { updateWindowUrlHashParams({ [HASH_PARAM_NAMES.IS_PRETTIFIED]: false === isPrettified, }); + useUiStore.getState().setUiState(UI_STATE.FAST_LOADING); updateViewHashParams(); break; default: