Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
be6e9e0
Migrate isPrettified
zzxthehappiest May 21, 2025
1f0aeda
Remove UrlContextProvider
zzxthehappiest May 22, 2025
81585cb
Remove hashchange event when updateHashUrlParams
zzxthehappiest May 24, 2025
0ae420c
Add logEventNum to viewStore and remove from Url
zzxthehappiest May 22, 2025
cd36fb8
Add fileSrc to logFileStore and remove from UrlContext
zzxthehappiest May 22, 2025
0dfe7b8
Fix lint
zzxthehappiest May 25, 2025
5852960
Merge branch 'main' into zzx/feat-269
zzxthehappiest May 25, 2025
c808cf2
Fix coderabit comments
zzxthehappiest May 25, 2025
00cadb9
Fix coderabbit review
zzxthehappiest May 25, 2025
6a873d4
Add junhaoliao@b972113
zzxthehappiest May 25, 2025
6620583
Fix lint
zzxthehappiest May 26, 2025
66abf15
Fix coderabbit comments
zzxthehappiest May 26, 2025
cf10ebe
Fix comments
zzxthehappiest May 27, 2025
0910692
Fix comments
zzxthehappiest May 27, 2025
6ebcf09
Merge branch 'main' into zzx/feat-269
zzxthehappiest May 28, 2025
f4cc127
refactor: Rename uiStoreState type to UiStoreState to follow PascalCa…
Henry8192 May 26, 2025
9879d0a
docs: Add deployment instructions (resolves #217). (#236)
junhaoliao May 27, 2025
7159bf2
Fix comments
zzxthehappiest May 28, 2025
e9b2cbb
Merge branch 'zzx/feat-269' of github.com:zzxthehappiest/yscope-log-v…
zzxthehappiest May 28, 2025
4139d0e
Merge branch 'main' into zzx/feat-269
zzxthehappiest May 29, 2025
23aa8b3
Merge queryStore to zustandard (not test yet)
zzxthehappiest May 29, 2025
d204c9b
Fix lint
zzxthehappiest May 29, 2025
a464dc9
Finish the rest
zzxthehappiest May 30, 2025
0dabdfc
Fix #306
zzxthehappiest Jun 1, 2025
de74c4f
Merge branch 'main' into zzx/feat-269
zzxthehappiest Jun 1, 2025
3add41d
Fix coderabbit comments
zzxthehappiest Jun 1, 2025
7166d2b
Fix comments except: Remove null in URL params
zzxthehappiest Jun 4, 2025
def27ea
Final fix
zzxthehappiest Jun 5, 2025
3369449
Minor fix
zzxthehappiest Jun 5, 2025
3c9bd0d
Merge branch 'main' into zzx/feat-269
zzxthehappiest Jun 5, 2025
9669cd5
Fix latest comments
zzxthehappiest Jun 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import AppController from "./components/AppController";
import Layout from "./components/Layout";
import UrlContextProvider from "./contexts/UrlContextProvider";


/**
Expand All @@ -10,11 +9,9 @@ import UrlContextProvider from "./contexts/UrlContextProvider";
*/
const App = () => {
return (
<UrlContextProvider>
<AppController>
<Layout/>
</AppController>
</UrlContextProvider>
<AppController>
<Layout/>
</AppController>
);
};

Expand Down
217 changes: 116 additions & 101 deletions src/components/AppController.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import React, {
useContext,
useEffect,
useRef,
} from "react";

import {
updateWindowUrlHashParams,
URL_HASH_PARAMS_DEFAULT,
URL_SEARCH_PARAMS_DEFAULT,
UrlContext,
} from "../contexts/UrlContextProvider";
import useContextStore from "../stores/contextStore";
import useLogFileManagerStore 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 {UrlHashParams} from "../typings/url";
import {
CURSOR_CODE,
CursorType,
Expand All @@ -27,6 +21,13 @@ import {
isWithinBounds,
} from "../utils/data";
import {clamp} from "../utils/math";
import {
getWindowUrlHashParams,
getWindowUrlSearchParams,
updateWindowUrlHashParams,
URL_HASH_PARAMS_DEFAULT,
URL_SEARCH_PARAMS_DEFAULT,
} from "../utils/url";


/**
Expand Down Expand Up @@ -67,6 +68,73 @@ const updateUrlIfEventOnPage = (
return true;
};

/**
* Updates view-related parameters from URL hash.
*
* @param hashParams
*/
const updateViewHashParams = (hashParams: UrlHashParams): void => {
const {isPrettified, logEventNum} = hashParams;
const {updateIsPrettified, setLogEventNum} = useViewStore.getState();

updateIsPrettified(isPrettified);
setLogEventNum(logEventNum);
};

/**
* Updates query-related parameters from URL hash.
*
* @param hashParams
* @return Whether any query parameters were modified.
*/
const updateQueryHashParams = (hashParams: UrlHashParams): boolean => {
const {queryIsCaseSensitive, queryIsRegex, queryString} = hashParams;
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.
*
* @param [ev] The hash change event, or `null` when called on application initialization.
* @return The parsed URL hash parameters.
*/
const handleHashChange = (ev: Nullable<HashChangeEvent>): UrlHashParams => {
const hashParams = getWindowUrlHashParams();
updateViewHashParams(hashParams);
const isQueryModified = updateQueryHashParams(hashParams);
const isTriggeredByHashChange = null !== ev;
if (isTriggeredByHashChange && isQueryModified) {
const {startQuery} = useQueryStore.getState();
startQuery();
}

// Remove empty or falsy parameters.
updateWindowUrlHashParams({
...hashParams,
});

return hashParams;
};

interface AppControllerProps {
children: React.ReactNode;
}
Expand All @@ -79,127 +147,74 @@ interface AppControllerProps {
* @return
*/
const AppController = ({children}: AppControllerProps) => {
const {
filePath, isPrettified, logEventNum, queryString, queryIsRegex, queryIsCaseSensitive,
} = useContext(UrlContext);

// States
const setLogEventNum = useContextStore((state) => state.setLogEventNum);
const logFileManagerProxy = useLogFileManagerStore((state) => state.logFileManagerProxy);
const loadFile = useLogFileStore((state) => state.loadFile);
const numEvents = useLogFileStore((state) => state.numEvents);
const beginLineNumToLogEventNum = useViewStore((state) => state.beginLineNumToLogEventNum);
const setIsPrettified = useViewStore((state) => state.updateIsPrettified);
const updatePageData = useViewStore((state) => state.updatePageData);
const uiState = useUiStore((state) => state.uiState);
const setUiState = useUiStore((state) => state.setUiState);
const logEventNum = useViewStore((state) => state.logEventNum);

// Refs
const isPrettifiedRef = useRef<boolean>(isPrettified ?? false);
const logEventNumRef = useRef(logEventNum);
const isInitialized = useRef<boolean>(false);

// Synchronize `logEventNumRef` with `logEventNum`.
// On app init, register hash change handler, and handle hash and search parameters.
useEffect(() => {
if (null !== logEventNum) {
logEventNumRef.current = logEventNum;
setLogEventNum(logEventNum);
window.addEventListener("hashchange", handleHashChange);

// Prevent re-initialization on re-renders.
if (isInitialized.current) {
return () => null;
}
isInitialized.current = true;

// Handle initial page load and maintain full URL state
const hashParams = handleHashChange(null);
const searchParams = getWindowUrlSearchParams();
if (URL_SEARCH_PARAMS_DEFAULT.filePath !== searchParams.filePath) {
let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null};

if (URL_HASH_PARAMS_DEFAULT.logEventNum !== hashParams.logEventNum) {
cursor = {
code: CURSOR_CODE.EVENT_NUM,
args: {eventNum: hashParams.logEventNum},
};
}
const {loadFile} = useLogFileStore.getState();
loadFile(searchParams.filePath, cursor);
}
}, [
logEventNum,
setLogEventNum,
]);

// Synchronize `isPrettifiedRef` with `isPrettified`.
useEffect(() => {
isPrettifiedRef.current = isPrettified ?? false;
setIsPrettified(isPrettifiedRef.current);
}, [
isPrettified,
setIsPrettified,
]);
return () => {
window.removeEventListener("hashchange", handleHashChange);
};
}, []);

// On `logEventNum` update, clamp it then switch page if necessary or simply update the URL.
useEffect(() => {
const {numEvents} = useLogFileStore.getState();
if (0 === numEvents || URL_HASH_PARAMS_DEFAULT.logEventNum === logEventNum) {
return;
}

const clampedLogEventNum = clamp(logEventNum, 1, numEvents);
const logEventNumsOnPage: number [] =
Array.from(beginLineNumToLogEventNum.values());

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;
}

// 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 pageData = await logFileManagerProxy.loadPage(cursor, isPrettifiedRef.current);
const {isPrettified} = useViewStore.getState();

const pageData = await logFileManagerProxy.loadPage(cursor, isPrettified);
const {updatePageData} = useViewStore.getState();
updatePageData(pageData);
})().catch(handleErrorWithNotification);
}, [
beginLineNumToLogEventNum,
logEventNum,
logFileManagerProxy,
numEvents,
setUiState,
updatePageData,
]);

// On `filePath` update, load file.
useEffect(() => {
if (URL_SEARCH_PARAMS_DEFAULT.filePath === filePath) {
return;
}

let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null};
if (URL_HASH_PARAMS_DEFAULT.logEventNum !== logEventNumRef.current) {
cursor = {
code: CURSOR_CODE.EVENT_NUM,
args: {eventNum: logEventNumRef.current},
};
}
loadFile(filePath, cursor);
}, [
filePath,
loadFile,
]);

// Synchronize `queryIsCaseSensitive` with the Zustand QueryStore.
useEffect(() => {
if (null !== queryIsCaseSensitive) {
const {setQueryIsCaseSensitive} = useQueryStore.getState();
setQueryIsCaseSensitive(queryIsCaseSensitive);
}
}, [queryIsCaseSensitive]);

// Synchronize `queryIsRegex` with the Zustand QueryStore.
useEffect(() => {
if (null !== queryIsRegex) {
const {setQueryIsRegex} = useQueryStore.getState();
setQueryIsRegex(queryIsRegex);
}
}, [queryIsRegex]);

useEffect(() => {
if (null !== queryString) {
const {setQueryString} = useQueryStore.getState();
setQueryString(queryString);
}
if (UI_STATE.READY === uiState) {
const {startQuery} = useQueryStore.getState();
startQuery();
}
}, [
uiState,
queryString,
]);
}, [logEventNum]);

return children;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, {useCallback} from "react";

import {
LinearProgress,
Expand All @@ -11,6 +11,7 @@ import useUiStore from "../../../../../stores/uiStore";
import {QUERY_PROGRESS_VALUE_MAX} from "../../../../../typings/query";
import {UI_ELEMENT} from "../../../../../typings/states";
import {isDisabled} from "../../../../../utils/states";
import {updateWindowUrlHashParams} from "../../../../../utils/url";
import ToggleIconButton from "./ToggleIconButton";

import "./QueryInputBox.css";
Expand All @@ -25,27 +26,32 @@ const QueryInputBox = () => {
const isCaseSensitive = useQueryStore((state) => state.queryIsCaseSensitive);
const isRegex = useQueryStore((state) => state.queryIsRegex);
const querystring = useQueryStore((state) => state.queryString);
const setQueryIsCaseSensitive = useQueryStore((state) => state.setQueryIsCaseSensitive);
const setQueryIsRegex = useQueryStore((state) => state.setQueryIsRegex);
const setQueryString = useQueryStore((state) => state.setQueryString);
const queryProgress = useQueryStore((state) => state.queryProgress);
const startQuery = useQueryStore((state) => state.startQuery);
const uiState = useUiStore((state) => state.uiState);

const handleQueryInputChange = (ev: React.ChangeEvent<HTMLTextAreaElement>) => {
setQueryString(ev.target.value);
const handleQueryInputChange = useCallback((ev: React.ChangeEvent<HTMLTextAreaElement>) => {
const newQueryString = ev.target.value;
updateWindowUrlHashParams({queryString: newQueryString});
const {setQueryString, startQuery} = useQueryStore.getState();
setQueryString(newQueryString);
startQuery();
};
}, []);

const handleCaseSensitivityButtonClick = () => {
setQueryIsCaseSensitive(!isCaseSensitive);
const handleCaseSensitivityButtonClick = useCallback(() => {
const newQueryIsSensitive = !isCaseSensitive;
updateWindowUrlHashParams({queryIsCaseSensitive: newQueryIsSensitive});
const {setQueryIsCaseSensitive, startQuery} = useQueryStore.getState();
setQueryIsCaseSensitive(newQueryIsSensitive);
startQuery();
};
}, [isCaseSensitive]);

const handleRegexButtonClick = () => {
setQueryIsRegex(!isRegex);
const handleRegexButtonClick = useCallback(() => {
const newQueryIsRegex = !isRegex;
updateWindowUrlHashParams({queryIsRegex: newQueryIsRegex});
const {setQueryIsRegex, startQuery} = useQueryStore.getState();
setQueryIsRegex(newQueryIsRegex);
startQuery();
};
}, [isRegex]);

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {useCallback} from "react";

import {
ListItemButton,
Typography,
} from "@mui/joy";

import {updateWindowUrlHashParams} from "../../../../../contexts/UrlContextProvider";
import useViewStore from "../../../../../stores/viewStore";
import {updateWindowUrlHashParams} from "../../../../../utils/url";

import "./Result.css";

Expand Down Expand Up @@ -36,9 +39,12 @@ const Result = ({logEventNum, message, matchRange}: ResultProps) => {
message.slice(...matchRange),
message.slice(matchRange[1]),
];
const handleResultButtonClick = () => {

const handleResultButtonClick = useCallback(() => {
updateWindowUrlHashParams({logEventNum});
};
const {setLogEventNum} = useViewStore.getState();
setLogEventNum(logEventNum);
}, [logEventNum]);

return (
<ListItemButton
Expand Down
Loading
Loading