From b8bf425097b718df514ca03183683a0de5ee64ae Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Fri, 5 Jul 2024 23:01:57 -0400 Subject: [PATCH 01/79] add UrlContextProvider --- new-log-viewer/src/App.tsx | 5 +- new-log-viewer/src/components/Layout.tsx | 7 ++ .../src/contexts/UrlContextProvider.tsx | 101 ++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 new-log-viewer/src/contexts/UrlContextProvider.tsx diff --git a/new-log-viewer/src/App.tsx b/new-log-viewer/src/App.tsx index 8d2fc373..cbd705ed 100644 --- a/new-log-viewer/src/App.tsx +++ b/new-log-viewer/src/App.tsx @@ -1,5 +1,6 @@ import Layout from "./components/Layout"; import StateContextProvider from "./contexts/StateContextProvider"; +import UrlContextProvider from "./contexts/UrlContextProvider"; /** @@ -11,7 +12,9 @@ const App = () => { return ( <> - + + + ); diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index 05bd0aa1..20f8eb13 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -7,6 +7,7 @@ import { PAGE_SIZE, StateContext, } from "../contexts/StateContextProvider"; +import {UrlContext} from "../contexts/UrlContextProvider"; /** @@ -21,6 +22,12 @@ const Layout = () => { logEventNum, } = useContext(StateContext); + const { + setSearchParamSet, + setHashParamSet, + copyToClipboard, + } = useContext(UrlContext); + useEffect(() => { const urlSearchParams = new URLSearchParams(window.location.search); const filePath = urlSearchParams.get("filePath"); diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx new file mode 100644 index 00000000..4eba035a --- /dev/null +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -0,0 +1,101 @@ +import React, { + createContext, + useEffect, + useState, +} from "react"; + + +interface UrlContextType { + setSearchParamSet: (searchParamSet: Record) => void; + setHashParamSet: (hashParamSet: Record) => void; + copyToClipboard: (searchParamSet: Record, hashParamSet: Record) => void; +} + +const UrlContext = createContext ({} as UrlContextType); + + +interface UrlContextProviderProps { + children: React.ReactNode +} + +/** + * Provides a context for managing URL parameters and hash values, including utilities for setting search and hash parameters, and copying the current URL with these parameters to the clipboard. + * + * @param children.children + * @param children The child components that will have access to the context. + */ +const UrlContextProvider = ({children}: UrlContextProviderProps) => { + const [hashParam, setHashParam] = useState(window.location.hash.substring(1)); + useEffect(() => { + setHashParam(window.location.hash.substring(1)); + }, [window.location.hash]); + + const setSearchParamSetHelper = (searchParamSet: Record) => { + const newSearchParam = new URLSearchParams(window.location.search.substring(1)); + const {filePath} = searchParamSet; + delete searchParamSet.filePath; + + for (const [key, value] of Object.entries(searchParamSet)) { + if (null === value) { + newSearchParam.delete(key); + } else { + newSearchParam.set(key, value); + } + } + if (filePath) { + newSearchParam.set("filePath", filePath); + } + + return newSearchParam; + }; + + const setSearchParamSet = (searchParamSet: Record) => { + const newUrl = new URL(window.location.href); + newUrl.search = setSearchParamSetHelper(searchParamSet).toString(); + if (!(/%23|%26/).test(newUrl.search)) { + newUrl.search = decodeURIComponent(newUrl.search); + } + window.history.pushState({}, "", newUrl.toString()); + }; + + const setHashParamSetHelper = (hashParamSet: Record) => { + const newHashParam = new URLSearchParams(hashParam); + for (const [key, value] of Object.entries(hashParamSet)) { + if (null === value) { + newHashParam.delete(key); + } else { + newHashParam.set(key, value); + } + } + + return newHashParam; + }; + + const setHashParamSet = (hashParamSet: Record) => { + const newUrl = new URL(window.location.href); + newUrl.hash = setHashParamSetHelper(hashParamSet).toString(); + window.history.pushState({}, "", newUrl.toString()); + }; + + const copyToClipboard = (searchParamSet: Record, hashParamSet: Record) => { + const newUrl = new URL(window.location.href); + newUrl.search = setSearchParamSetHelper(searchParamSet).toString(); + newUrl.hash = setHashParamSetHelper(hashParamSet).toString(); + navigator.clipboard.writeText(newUrl.toString()) + .then(() => { + console.log("URL copied to clipboard."); + }) + .catch((error: unknown) => { + console.error("Failed to copy URL to clipboard:", error); + }); + }; + + return ( + + {children} + + ); +}; + +export default UrlContextProvider; +export {UrlContext}; From de2b31cc3ef4fe9d9159b12461880ec3446109e9 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Fri, 5 Jul 2024 23:17:20 -0400 Subject: [PATCH 02/79] adjust styling --- .../src/contexts/UrlContextProvider.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 4eba035a..86c0d0a3 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -1,4 +1,4 @@ -import React, { +import { createContext, useEffect, useState, @@ -8,7 +8,10 @@ import React, { interface UrlContextType { setSearchParamSet: (searchParamSet: Record) => void; setHashParamSet: (hashParamSet: Record) => void; - copyToClipboard: (searchParamSet: Record, hashParamSet: Record) => void; + copyToClipboard: ( + searchParamSet: Record, + hashParamSet: Record + ) => void; } const UrlContext = createContext ({} as UrlContextType); @@ -19,7 +22,9 @@ interface UrlContextProviderProps { } /** - * Provides a context for managing URL parameters and hash values, including utilities for setting search and hash parameters, and copying the current URL with these parameters to the clipboard. + * Provides a context for managing URL parameters and hash values, + * including utilities for setting search and hash parameters, + * and copying the current URL with these parameters to the clipboard. * * @param children.children * @param children The child components that will have access to the context. @@ -77,7 +82,10 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { window.history.pushState({}, "", newUrl.toString()); }; - const copyToClipboard = (searchParamSet: Record, hashParamSet: Record) => { + const copyToClipboard = ( + searchParamSet: Record, + hashParamSet: Record + ) => { const newUrl = new URL(window.location.href); newUrl.search = setSearchParamSetHelper(searchParamSet).toString(); newUrl.hash = setHashParamSetHelper(hashParamSet).toString(); From 14cc8ecb13521d42fa9fe8ee03a4ce6fb8ccc29a Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:00:13 -0400 Subject: [PATCH 03/79] adjust UrlContextProvider's field location --- new-log-viewer/src/App.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/src/App.tsx b/new-log-viewer/src/App.tsx index cbd705ed..a52aba4e 100644 --- a/new-log-viewer/src/App.tsx +++ b/new-log-viewer/src/App.tsx @@ -11,11 +11,11 @@ import UrlContextProvider from "./contexts/UrlContextProvider"; const App = () => { return ( <> - - + + - - + + ); }; From 0860c4dc3849926815a493bc261d636e9a0cd12c Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Wed, 31 Jul 2024 23:47:40 -0400 Subject: [PATCH 04/79] apply patch from junhao --- new-log-viewer/src/components/Layout.tsx | 21 +- .../src/contexts/StateContextProvider.tsx | 14 + .../src/contexts/UrlContextProvider.tsx | 271 +++++++++++++----- 3 files changed, 208 insertions(+), 98 deletions(-) diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index 20f8eb13..bef2bab2 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -1,13 +1,9 @@ -import { - useContext, - useEffect, -} from "react"; +import {useContext} from "react"; import { PAGE_SIZE, StateContext, } from "../contexts/StateContextProvider"; -import {UrlContext} from "../contexts/UrlContextProvider"; /** @@ -18,24 +14,9 @@ import {UrlContext} from "../contexts/UrlContextProvider"; const Layout = () => { const { logData, - loadFile, logEventNum, } = useContext(StateContext); - const { - setSearchParamSet, - setHashParamSet, - copyToClipboard, - } = useContext(UrlContext); - - useEffect(() => { - const urlSearchParams = new URLSearchParams(window.location.search); - const filePath = urlSearchParams.get("filePath"); - if (null !== filePath) { - loadFile(filePath); - } - }, [loadFile]); - return ( <>
diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 95d934ce..e31eea09 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -1,6 +1,8 @@ import React, { createContext, useCallback, + useContext, + useEffect, useMemo, useRef, useState, @@ -15,6 +17,7 @@ import { WORKER_RESP_CODE, WorkerReq, } from "../typings/worker"; +import {UrlContext} from "./UrlContextProvider"; interface StateContextType { @@ -54,6 +57,8 @@ interface StateContextProviderProps { * @return */ const StateContextProvider = ({children}: StateContextProviderProps) => { + const {filePath} = useContext(UrlContext); + const [beginLineNumToLogEventNum, setBeginLineNumToLogEventNum] = useState(STATE_DEFAULT.beginLineNumToLogEventNum); const [logData, setLogData] = useState(STATE_DEFAULT.logData); @@ -132,6 +137,15 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { mainWorkerPostReq, ]); + useEffect(() => { + if ("undefined" !== typeof filePath) { + loadFile(filePath); + } + }, [ + filePath, + loadFile, + ]); + return ( ) => void; - setHashParamSet: (hashParamSet: Record) => void; - copyToClipboard: ( - searchParamSet: Record, - hashParamSet: Record - ) => void; +enum SEARCH_PARAM_NAME { + FILE_PATH = "filePath" } -const UrlContext = createContext ({} as UrlContextType); +enum HASH_PARAM_NAME { + LOG_EVENT_NUM = "logEventNum" +} + +interface UrlSearchParams { + [SEARCH_PARAM_NAME.FILE_PATH]?: string, +} + +interface UrlHashParams { + logEventNum?: number, +} + +type UrlSearchParamUpdatesType = { + [key in keyof UrlSearchParams]?: UrlSearchParams[key] | null +} +type UrlHashParamUpdatesType = { + [key in keyof UrlHashParams]?: UrlHashParams[key] | null +} +type UrlParamsType = { + [key in keyof UrlSearchParams]?: UrlSearchParams[key]; +} & { + [key in keyof UrlHashParams]?: UrlHashParams[key]; +}; + +const UrlContext = createContext ({} as UrlParamsType); interface UrlContextProviderProps { children: React.ReactNode } /** - * Provides a context for managing URL parameters and hash values, - * including utilities for setting search and hash parameters, - * and copying the current URL with these parameters to the clipboard. + * Computes updated URL search parameters based on the provided key-value pairs. * - * @param children.children - * @param children The child components that will have access to the context. + * @param updates An object containing key-value pairs to update the search parameters. + * If a key's value is `null`, the key will be removed from the search parameters. + * @return - The updated search string as a URLSearchParams object. */ -const UrlContextProvider = ({children}: UrlContextProviderProps) => { - const [hashParam, setHashParam] = useState(window.location.hash.substring(1)); - useEffect(() => { - setHashParam(window.location.hash.substring(1)); - }, [window.location.hash]); - - const setSearchParamSetHelper = (searchParamSet: Record) => { - const newSearchParam = new URLSearchParams(window.location.search.substring(1)); - const {filePath} = searchParamSet; - delete searchParamSet.filePath; - - for (const [key, value] of Object.entries(searchParamSet)) { - if (null === value) { - newSearchParam.delete(key); - } else { - newSearchParam.set(key, value); - } +const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { + const newSearchParams = new URLSearchParams(window.location.search.substring(1)); + const {filePath} = updates; + + for (const [key, value] of Object.entries(updates)) { + if ("filePath" === key) { + continue; } - if (filePath) { - newSearchParam.set("filePath", filePath); + if (null === value) { + newSearchParams.delete(key); + } else { + newSearchParams.set(key, String(value)); } + } + if ("string" === typeof filePath) { + newSearchParams.set("filePath", filePath); + } - return newSearchParam; - }; + return newSearchParams; +}; - const setSearchParamSet = (searchParamSet: Record) => { - const newUrl = new URL(window.location.href); - newUrl.search = setSearchParamSetHelper(searchParamSet).toString(); - if (!(/%23|%26/).test(newUrl.search)) { - newUrl.search = decodeURIComponent(newUrl.search); - } - window.history.pushState({}, "", newUrl.toString()); - }; - - const setHashParamSetHelper = (hashParamSet: Record) => { - const newHashParam = new URLSearchParams(hashParam); - for (const [key, value] of Object.entries(hashParamSet)) { - if (null === value) { - newHashParam.delete(key); - } else { - newHashParam.set(key, value); - } +/** + * Computes updated URL search parameters based on the provided key-value pairs. + * + * @param updates An object containing key-value pairs to update the hash parameters. + * If a key's value is `null`, the key will be removed from the hash parameters. + * @return - The updated search string as a URLSearchParams object. + */ +const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { + const newHashParams = new URLSearchParams(window.location.hash.substring(1)); + for (const [key, value] of Object.entries(updates)) { + if (null === value) { + newHashParams.delete(key); + } else { + newHashParams.set(key, String(value)); } + } + + return newHashParams; +}; + +/** + * Updates search parameters in the current window's URL based on the provided key-value pairs. + * + * @param updates An object containing key-value pairs to update the search parameters. + * If a key's value is `null`, the key will be removed from the search parameters. + */ +const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { + const newUrl = new URL(window.location.href); + newUrl.search = getUpdatedSearchParams(updates).toString(); + if (!(/%23|%26/).test(newUrl.search)) { + newUrl.search = decodeURIComponent(newUrl.search); + } + window.history.pushState({}, "", newUrl); +}; + + +/** + * Updates hash parameters in the current window's URL based on the provided key-value pairs. + * + * @param updates An object containing key-value pairs to update the hash parameters. + * If a key's value is `null`, the key will be removed from the hash parameters. + */ +const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { + const newUrl = new URL(window.location.href); + newUrl.hash = getUpdatedHashParams(updates).toString(); + window.history.pushState({}, "", newUrl); +}; + +/** + * + */ +const getAllWindowSearchParams = () => { + const searchParams = new URLSearchParams(window.location.search.substring(1)); + const urlSearchParams: UrlParamsType = {}; + + // TODO: use Ajv to read and validate + const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); + if (null !== filePath) { + urlSearchParams[SEARCH_PARAM_NAME.FILE_PATH] = filePath; + } + + return urlSearchParams; +}; + +/** + * + */ +const getAllWindowHashParams = () => { + const hashParams = new URLSearchParams(window.location.hash.substring(1)); + const urlHashParams: UrlHashParams = {}; + + // TODO: use Ajv to read and validate + const logEventNum = hashParams.get(HASH_PARAM_NAME.LOG_EVENT_NUM); + if (null !== logEventNum) { + urlHashParams[HASH_PARAM_NAME.LOG_EVENT_NUM] = Number(logEventNum); + } + + return urlHashParams; +}; - return newHashParam; - }; - - const setHashParamSet = (hashParamSet: Record) => { - const newUrl = new URL(window.location.href); - newUrl.hash = setHashParamSetHelper(hashParamSet).toString(); - window.history.pushState({}, "", newUrl.toString()); - }; - - const copyToClipboard = ( - searchParamSet: Record, - hashParamSet: Record - ) => { - const newUrl = new URL(window.location.href); - newUrl.search = setSearchParamSetHelper(searchParamSet).toString(); - newUrl.hash = setHashParamSetHelper(hashParamSet).toString(); - navigator.clipboard.writeText(newUrl.toString()) - .then(() => { - console.log("URL copied to clipboard."); - }) - .catch((error: unknown) => { - console.error("Failed to copy URL to clipboard:", error); - }); - }; + +/** + * Copies the current window's URL to the clipboard. If any `updates` parameters are specified, + * the copied URL will include these modifications, while the original window's URL remains + * unchanged. + * + * @param searchParamUpdates An object containing key-value pairs to update the search parameters. + * If a key's value is `null`, the key will be removed from the search parameters. + * @param hashParamsUpdates An object containing key-value pairs to update the hash parameters. + * If a key's value is `null`, the key will be removed from the hash parameters. + */ +const copyToClipboard = ( + searchParamUpdates: UrlSearchParamUpdatesType, + hashParamsUpdates: UrlHashParamUpdatesType, +) => { + const newUrl = new URL(window.location.href); + newUrl.search = getUpdatedSearchParams(searchParamUpdates).toString(); + newUrl.hash = getUpdatedHashParams(hashParamsUpdates).toString(); + navigator.clipboard.writeText(newUrl.toString()) + .then(() => { + console.log("URL copied to clipboard."); + }) + .catch((error: unknown) => { + console.error("Failed to copy URL to clipboard:", error); + }); +}; + +/** + * Provides a context for managing URL parameters and hash values, + * including utilities for setting search and hash parameters, + * and copying the current URL with these parameters to the clipboard. + * + * @param props + * @param props.children The child components that will have access to the context. + * @return + */ +const UrlContextProvider = ({children}: UrlContextProviderProps) => { + const [urlParams, setUrlParams] = useState({}); + + useEffect(() => { + setUrlParams({ + ...getAllWindowSearchParams(), + ...getAllWindowHashParams(), + }); + + const handleHashChange = () => { + // FIXME: handle removal of hash params + setUrlParams((v) => ({ + ...v, + ...getAllWindowHashParams(), + })); + }; + + window.addEventListener("hashchange", handleHashChange); + + return () => { + window.removeEventListener("hashchange", handleHashChange); + }; + }, []); return ( - + {children} ); }; export default UrlContextProvider; -export {UrlContext}; +export { + copyToClipboard, + updateWindowHashParams, + updateWindowSearchParams, + UrlContext, +}; From 44a36a8b9405901bbc0c70f37fb8040ac5ccf1d6 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:44:37 -0400 Subject: [PATCH 05/79] set searchParams as a global variable --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 25353b47..c2446758 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -40,6 +40,8 @@ interface UrlContextProviderProps { children: React.ReactNode } +let searchParams = new URLSearchParams(window.location.search.substring(1)); + /** * Computes updated URL search parameters based on the provided key-value pairs. * @@ -48,7 +50,7 @@ interface UrlContextProviderProps { * @return - The updated search string as a URLSearchParams object. */ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { - const newSearchParams = new URLSearchParams(window.location.search.substring(1)); + const newSearchParams = searchParams; const {filePath} = updates; for (const [key, value] of Object.entries(updates)) { @@ -96,7 +98,8 @@ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { */ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { const newUrl = new URL(window.location.href); - newUrl.search = getUpdatedSearchParams(updates).toString(); + searchParams = getUpdatedSearchParams(updates); + newUrl.search = searchParams.toString(); if (!(/%23|%26/).test(newUrl.search)) { newUrl.search = decodeURIComponent(newUrl.search); } @@ -120,7 +123,6 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { * */ const getAllWindowSearchParams = () => { - const searchParams = new URLSearchParams(window.location.search.substring(1)); const urlSearchParams: UrlParamsType = {}; // TODO: use Ajv to read and validate @@ -195,10 +197,10 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { const handleHashChange = () => { // FIXME: handle removal of hash params - setUrlParams((v) => ({ - ...v, + setUrlParams({ + ...searchParams, ...getAllWindowHashParams(), - })); + }); }; window.addEventListener("hashchange", handleHashChange); From ee6b9ea27849bb8634a29c4dbab0cdccfdb00e25 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:28:01 -0400 Subject: [PATCH 06/79] add docs & small fixes --- .../src/contexts/UrlContextProvider.tsx | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index c2446758..6bf33f73 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -3,37 +3,17 @@ import React, { useEffect, useState, } from "react"; +import { + SEARCH_PARAM_NAME, + HASH_PARAM_NAME, + UrlSearchParams, + UrlHashParams, + UrlSearchParamUpdatesType, + UrlHashParamUpdatesType, + UrlParamsType +} from "../typings/url"; -enum SEARCH_PARAM_NAME { - FILE_PATH = "filePath" -} - -enum HASH_PARAM_NAME { - LOG_EVENT_NUM = "logEventNum" -} - -interface UrlSearchParams { - [SEARCH_PARAM_NAME.FILE_PATH]?: string, -} - -interface UrlHashParams { - logEventNum?: number, -} - -type UrlSearchParamUpdatesType = { - [key in keyof UrlSearchParams]?: UrlSearchParams[key] | null -} -type UrlHashParamUpdatesType = { - [key in keyof UrlHashParams]?: UrlHashParams[key] | null -} - -type UrlParamsType = { - [key in keyof UrlSearchParams]?: UrlSearchParams[key]; -} & { - [key in keyof UrlHashParams]?: UrlHashParams[key]; -}; - const UrlContext = createContext ({} as UrlParamsType); interface UrlContextProviderProps { @@ -120,10 +100,12 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { }; /** + * Retrieves all search parameters from the current window's URL. * + * @return {UrlSearchParams} An object containing the search parameters. */ const getAllWindowSearchParams = () => { - const urlSearchParams: UrlParamsType = {}; + const urlSearchParams: UrlSearchParams = {}; // TODO: use Ajv to read and validate const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); @@ -135,7 +117,9 @@ const getAllWindowSearchParams = () => { }; /** + * Retrieves all hash parameters from the current window's URL. * + * @return {UrlHashParams} An object containing the hash parameters. */ const getAllWindowHashParams = () => { const hashParams = new URLSearchParams(window.location.hash.substring(1)); @@ -196,7 +180,6 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { }); const handleHashChange = () => { - // FIXME: handle removal of hash params setUrlParams({ ...searchParams, ...getAllWindowHashParams(), From 1c4bf53ba82c0a207fc8d3d3a966bc5ae41f25a2 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:17:46 -0400 Subject: [PATCH 07/79] move typings to url.ts --- new-log-viewer/src/typings/url.ts | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 new-log-viewer/src/typings/url.ts diff --git a/new-log-viewer/src/typings/url.ts b/new-log-viewer/src/typings/url.ts new file mode 100644 index 00000000..7318597e --- /dev/null +++ b/new-log-viewer/src/typings/url.ts @@ -0,0 +1,40 @@ +enum SEARCH_PARAM_NAME { + FILE_PATH = "filePath" +} + +enum HASH_PARAM_NAME { + LOG_EVENT_NUM = "logEventNum" +} + +interface UrlSearchParams { + [SEARCH_PARAM_NAME.FILE_PATH]?: string, +} + +interface UrlHashParams { + logEventNum?: number, +} + +type UrlSearchParamUpdatesType = { + [key in keyof UrlSearchParams]?: UrlSearchParams[key] | null +} +type UrlHashParamUpdatesType = { + [key in keyof UrlHashParams]?: UrlHashParams[key] | null +} + +type UrlParamsType = { + [key in keyof UrlSearchParams]?: UrlSearchParams[key]; +} & { + [key in keyof UrlHashParams]?: UrlHashParams[key]; +}; + +export { + SEARCH_PARAM_NAME, + HASH_PARAM_NAME +}; +export type { + UrlSearchParams, + UrlHashParams, + UrlSearchParamUpdatesType, + UrlHashParamUpdatesType, + UrlParamsType +}; From 128f60b0bd134924617b47913f2e18609f4b8f58 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:21:29 -0400 Subject: [PATCH 08/79] change searchParams type from string pairs to actual type pairs --- new-log-viewer/src/App.tsx | 12 +++++++++++- .../src/contexts/UrlContextProvider.tsx | 16 ++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/new-log-viewer/src/App.tsx b/new-log-viewer/src/App.tsx index a52aba4e..801fb666 100644 --- a/new-log-viewer/src/App.tsx +++ b/new-log-viewer/src/App.tsx @@ -1,6 +1,9 @@ import Layout from "./components/Layout"; import StateContextProvider from "./contexts/StateContextProvider"; -import UrlContextProvider from "./contexts/UrlContextProvider"; +import UrlContextProvider, { + copyToClipboard, + updateWindowHashParams +} from "./contexts/UrlContextProvider"; /** @@ -14,6 +17,13 @@ const App = () => { + diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 6bf33f73..45a70e0c 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -20,8 +20,6 @@ interface UrlContextProviderProps { children: React.ReactNode } -let searchParams = new URLSearchParams(window.location.search.substring(1)); - /** * Computes updated URL search parameters based on the provided key-value pairs. * @@ -30,7 +28,7 @@ let searchParams = new URLSearchParams(window.location.search.substring(1)); * @return - The updated search string as a URLSearchParams object. */ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { - const newSearchParams = searchParams; + const newSearchParams = new URLSearchParams(window.location.search.substring(1)); const {filePath} = updates; for (const [key, value] of Object.entries(updates)) { @@ -78,8 +76,7 @@ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { */ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { const newUrl = new URL(window.location.href); - searchParams = getUpdatedSearchParams(updates); - newUrl.search = searchParams.toString(); + newUrl.search = getUpdatedSearchParams(updates).toString(); if (!(/%23|%26/).test(newUrl.search)) { newUrl.search = decodeURIComponent(newUrl.search); } @@ -106,6 +103,7 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { */ const getAllWindowSearchParams = () => { const urlSearchParams: UrlSearchParams = {}; + const searchParams = new URLSearchParams(window.location.search.substring(1)); // TODO: use Ajv to read and validate const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); @@ -161,6 +159,8 @@ const copyToClipboard = ( }); }; +const searchParams = getAllWindowSearchParams(); + /** * Provides a context for managing URL parameters and hash values, * including utilities for setting search and hash parameters, @@ -175,7 +175,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { useEffect(() => { setUrlParams({ - ...getAllWindowSearchParams(), + ...searchParams, ...getAllWindowHashParams(), }); @@ -184,6 +184,10 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { ...searchParams, ...getAllWindowHashParams(), }); + console.log({ + ...searchParams, + ...getAllWindowHashParams(), + }); }; window.addEventListener("hashchange", handleHashChange); From a3e1054da534b9f6814c111e933c69cf8bd103f2 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 5 Aug 2024 14:51:42 -0400 Subject: [PATCH 09/79] Move "Set logEventNum to 3" button to Layout component. --- new-log-viewer/src/App.tsx | 12 +----------- new-log-viewer/src/components/Layout.tsx | 8 ++++++++ .../src/contexts/UrlContextProvider.tsx | 17 +++++++---------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/new-log-viewer/src/App.tsx b/new-log-viewer/src/App.tsx index 801fb666..a52aba4e 100644 --- a/new-log-viewer/src/App.tsx +++ b/new-log-viewer/src/App.tsx @@ -1,9 +1,6 @@ import Layout from "./components/Layout"; import StateContextProvider from "./contexts/StateContextProvider"; -import UrlContextProvider, { - copyToClipboard, - updateWindowHashParams -} from "./contexts/UrlContextProvider"; +import UrlContextProvider from "./contexts/UrlContextProvider"; /** @@ -17,13 +14,6 @@ const App = () => { - diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index bef2bab2..f4b91f9a 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -4,6 +4,7 @@ import { PAGE_SIZE, StateContext, } from "../contexts/StateContextProvider"; +import {updateWindowHashParams} from "../contexts/UrlContextProvider"; /** @@ -30,6 +31,13 @@ const Layout = () => { {" "} {Math.ceil(logEventNum / PAGE_SIZE)} + {logData.split("\n").map((line, index) => (

{`<${index + 1}>`} diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 45a70e0c..44ff649b 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -3,14 +3,15 @@ import React, { useEffect, useState, } from "react"; + import { - SEARCH_PARAM_NAME, HASH_PARAM_NAME, - UrlSearchParams, + SEARCH_PARAM_NAME, UrlHashParams, - UrlSearchParamUpdatesType, UrlHashParamUpdatesType, - UrlParamsType + UrlParamsType, + UrlSearchParams, + UrlSearchParamUpdatesType, } from "../typings/url"; @@ -99,7 +100,7 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { /** * Retrieves all search parameters from the current window's URL. * - * @return {UrlSearchParams} An object containing the search parameters. + * @return An object containing the search parameters. */ const getAllWindowSearchParams = () => { const urlSearchParams: UrlSearchParams = {}; @@ -117,7 +118,7 @@ const getAllWindowSearchParams = () => { /** * Retrieves all hash parameters from the current window's URL. * - * @return {UrlHashParams} An object containing the hash parameters. + * @return An object containing the hash parameters. */ const getAllWindowHashParams = () => { const hashParams = new URLSearchParams(window.location.hash.substring(1)); @@ -184,10 +185,6 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { ...searchParams, ...getAllWindowHashParams(), }); - console.log({ - ...searchParams, - ...getAllWindowHashParams(), - }); }; window.addEventListener("hashchange", handleHashChange); From fcdde69e0bfdd7e8a426571041691ce5af678a1d Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 5 Aug 2024 14:51:57 -0400 Subject: [PATCH 10/79] Update URL hash parameters conditionally and dispatch event. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 44ff649b..d329ce84 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -92,9 +92,14 @@ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { * If a key's value is `null`, the key will be removed from the hash parameters. */ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { - const newUrl = new URL(window.location.href); - newUrl.hash = getUpdatedHashParams(updates).toString(); - window.history.pushState({}, "", newUrl); + const newHash = getUpdatedHashParams(updates).toString(); + const currHash = window.location.hash.substring(1); + if (newHash !== currHash) { + const newUrl = new URL(window.location.href); + newUrl.hash = newHash; + window.history.pushState({}, "", newUrl); + window.dispatchEvent(new HashChangeEvent("hashchange")); + } }; /** From ea69df2f4213afb7311094d1405083892c69725e Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 5 Aug 2024 15:11:51 -0400 Subject: [PATCH 11/79] Simplify state management by using logEventNum from UrlContext directly. --- new-log-viewer/src/components/Layout.tsx | 32 ++++++++++--------- .../src/contexts/StateContextProvider.tsx | 21 ++++++------ 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index f4b91f9a..b1c143c4 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -1,10 +1,10 @@ -import {useContext} from "react"; +import React, {useContext} from "react"; +import {StateContext} from "../contexts/StateContextProvider"; import { - PAGE_SIZE, - StateContext, -} from "../contexts/StateContextProvider"; -import {updateWindowHashParams} from "../contexts/UrlContextProvider"; + updateWindowHashParams, + UrlContext, +} from "../contexts/UrlContextProvider"; /** @@ -15,8 +15,13 @@ import {updateWindowHashParams} from "../contexts/UrlContextProvider"; const Layout = () => { const { logData, - logEventNum, + pageNum, } = useContext(StateContext); + const {logEventNum} = useContext(UrlContext); + + const handleLogEventNumInputChange = (ev: React.ChangeEvent) => { + updateWindowHashParams({logEventNum: Number(ev.target.value)}); + }; return ( <> @@ -24,20 +29,17 @@ const Layout = () => {

LogEventNum - {" "} - {logEventNum} + {" "} | PageNum - {" "} - {Math.ceil(logEventNum / PAGE_SIZE)} + {pageNum}

- + {logData.split("\n").map((line, index) => (

{`<${index + 1}>`} diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index e31eea09..ce798973 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -17,14 +17,16 @@ import { WORKER_RESP_CODE, WorkerReq, } from "../typings/worker"; -import {UrlContext} from "./UrlContextProvider"; +import { + updateWindowHashParams, + UrlContext, +} from "./UrlContextProvider"; interface StateContextType { beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap, loadFile: (fileSrc: FileSrcType) => void, logData: string, - logEventNum: number, numEvents: number, numPages: number, pageNum: number @@ -38,7 +40,6 @@ const STATE_DEFAULT = Object.freeze({ beginLineNumToLogEventNum: new Map(), loadFile: () => null, logData: "Loading...", - logEventNum: 1, numEvents: 0, numPages: 0, pageNum: 0, @@ -57,19 +58,20 @@ interface StateContextProviderProps { * @return */ const StateContextProvider = ({children}: StateContextProviderProps) => { - const {filePath} = useContext(UrlContext); + const {filePath, logEventNum} = useContext(UrlContext); const [beginLineNumToLogEventNum, setBeginLineNumToLogEventNum] = useState(STATE_DEFAULT.beginLineNumToLogEventNum); const [logData, setLogData] = useState(STATE_DEFAULT.logData); const [numEvents, setNumEvents] = useState(STATE_DEFAULT.numEvents); - const [logEventNum, setLogEventNum] = useState(STATE_DEFAULT.logEventNum); const mainWorkerRef = useRef(null); - const pageNum = useMemo(() => { - return Math.ceil(logEventNum / PAGE_SIZE); - }, [logEventNum]); + const pageNum = useMemo(() => ( + "undefined" === typeof logEventNum ? + 0 : + Math.ceil(logEventNum / PAGE_SIZE) + ), [logEventNum]); const numPages = useMemo(() => { return Math.ceil(numEvents / PAGE_SIZE); @@ -95,7 +97,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { if ("undefined" === typeof lastLogEventNum) { lastLogEventNum = 1; } - setLogEventNum(lastLogEventNum); + updateWindowHashParams({logEventNum: lastLogEventNum}); break; } case WORKER_RESP_CODE.NUM_EVENTS: @@ -152,7 +154,6 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { beginLineNumToLogEventNum, loadFile, logData, - logEventNum, numEvents, numPages, pageNum, From 334ea054dc279beb0029c99f335195e78074b5fd Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 19:21:24 -0400 Subject: [PATCH 12/79] Update functions to return strings instead of URLSearchParams objects. --- .../src/contexts/UrlContextProvider.tsx | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index d329ce84..3e370cf6 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -26,14 +26,14 @@ interface UrlContextProviderProps { * * @param updates An object containing key-value pairs to update the search parameters. * If a key's value is `null`, the key will be removed from the search parameters. - * @return - The updated search string as a URLSearchParams object. + * @return The updated search parameters string. */ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { const newSearchParams = new URLSearchParams(window.location.search.substring(1)); const {filePath} = updates; for (const [key, value] of Object.entries(updates)) { - if ("filePath" === key) { + if (SEARCH_PARAM_NAME.FILE_PATH as string === key) { continue; } if (null === value) { @@ -42,19 +42,26 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { newSearchParams.set(key, String(value)); } } - if ("string" === typeof filePath) { - newSearchParams.set("filePath", filePath); + if (null === filePath) { + newSearchParams.delete(SEARCH_PARAM_NAME.FILE_PATH); + } else if ("string" === typeof filePath) { + newSearchParams.set(SEARCH_PARAM_NAME.FILE_PATH, filePath); } - return newSearchParams; + let searchString = newSearchParams.toString(); + if (!(/%23|%26/).test(searchString)) { + searchString = decodeURIComponent(searchString); + } + + return searchString; }; /** - * Computes updated URL search parameters based on the provided key-value pairs. + * Computes updated URL hash parameters based on the provided key-value pairs. * * @param updates An object containing key-value pairs to update the hash parameters. * If a key's value is `null`, the key will be removed from the hash parameters. - * @return - The updated search string as a URLSearchParams object. + * @return The updated hash parameters string. */ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { const newHashParams = new URLSearchParams(window.location.hash.substring(1)); @@ -66,7 +73,7 @@ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { } } - return newHashParams; + return newHashParams.toString(); }; /** @@ -77,10 +84,7 @@ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { */ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { const newUrl = new URL(window.location.href); - newUrl.search = getUpdatedSearchParams(updates).toString(); - if (!(/%23|%26/).test(newUrl.search)) { - newUrl.search = decodeURIComponent(newUrl.search); - } + newUrl.search = getUpdatedSearchParams(updates); window.history.pushState({}, "", newUrl); }; @@ -92,7 +96,7 @@ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { * If a key's value is `null`, the key will be removed from the hash parameters. */ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { - const newHash = getUpdatedHashParams(updates).toString(); + const newHash = getUpdatedHashParams(updates); const currHash = window.location.hash.substring(1); if (newHash !== currHash) { const newUrl = new URL(window.location.href); @@ -154,8 +158,8 @@ const copyToClipboard = ( hashParamsUpdates: UrlHashParamUpdatesType, ) => { const newUrl = new URL(window.location.href); - newUrl.search = getUpdatedSearchParams(searchParamUpdates).toString(); - newUrl.hash = getUpdatedHashParams(hashParamsUpdates).toString(); + newUrl.search = getUpdatedSearchParams(searchParamUpdates); + newUrl.hash = getUpdatedHashParams(hashParamsUpdates); navigator.clipboard.writeText(newUrl.toString()) .then(() => { console.log("URL copied to clipboard."); From 009905fa334915732f64697478f2d223bc4f68e2 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 19:22:10 -0400 Subject: [PATCH 13/79] Add 'Copy link to last log' button. --- new-log-viewer/src/components/Layout.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index b1c143c4..3ecba25b 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -2,6 +2,7 @@ import React, {useContext} from "react"; import {StateContext} from "../contexts/StateContextProvider"; import { + copyToClipboard, updateWindowHashParams, UrlContext, } from "../contexts/UrlContextProvider"; @@ -16,6 +17,7 @@ const Layout = () => { const { logData, pageNum, + numEvents, } = useContext(StateContext); const {logEventNum} = useContext(UrlContext); @@ -23,6 +25,10 @@ const Layout = () => { updateWindowHashParams({logEventNum: Number(ev.target.value)}); }; + const handleCopyLinkButtonClick = () => { + copyToClipboard({}, {logEventNum: numEvents}); + }; + return ( <>

@@ -30,8 +36,8 @@ const Layout = () => { LogEventNum - {" "} {" "} | @@ -40,6 +46,10 @@ const Layout = () => { {pageNum} + + {logData.split("\n").map((line, index) => (

{`<${index + 1}>`} From 7980ca4e1f9c8c7d00236935683ac02ca10c76b3 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 19:23:34 -0400 Subject: [PATCH 14/79] Fix lint issues. --- new-log-viewer/src/typings/url.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/src/typings/url.ts b/new-log-viewer/src/typings/url.ts index 7318597e..99a0ace0 100644 --- a/new-log-viewer/src/typings/url.ts +++ b/new-log-viewer/src/typings/url.ts @@ -28,13 +28,13 @@ type UrlParamsType = { }; export { + HASH_PARAM_NAME, SEARCH_PARAM_NAME, - HASH_PARAM_NAME }; export type { - UrlSearchParams, UrlHashParams, - UrlSearchParamUpdatesType, UrlHashParamUpdatesType, - UrlParamsType + UrlParamsType, + UrlSearchParams, + UrlSearchParamUpdatesType, }; From f9bdc9d97c7fb82c2287d57b59b681239da52370 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 20:18:26 -0400 Subject: [PATCH 15/79] Fix out-of-bounds error in LogFileManager event indexing. --- new-log-viewer/src/services/LogFileManager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index d823f428..999ff0e4 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -207,6 +207,10 @@ class LogFileManager { default: throw new Error(`Unsupported cursor type: ${code}`); } + if (beginLogEventIdx > this.#numEvents) { + beginLogEventIdx = + (Math.floor((this.#numEvents - 1) / this.#pageSize) * this.#pageSize); + } const beginLogEventNum = beginLogEventIdx + 1; const endLogEventNum = Math.min(this.#numEvents, beginLogEventNum + this.#pageSize - 1); From e5b5d610d430d5e89387008b0f20bc5d7d834eff Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 20:47:25 -0400 Subject: [PATCH 16/79] Support loading with initial logEventNum. --- new-log-viewer/src/components/Layout.tsx | 4 +- .../src/contexts/StateContextProvider.tsx | 56 ++++++++++++------- .../src/contexts/UrlContextProvider.tsx | 42 +++++++++----- new-log-viewer/src/typings/common.ts | 5 ++ new-log-viewer/src/typings/url.ts | 12 ++-- 5 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 new-log-viewer/src/typings/common.ts diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index 3ecba25b..e92c949a 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -36,8 +36,10 @@ const Layout = () => { LogEventNum - {" "} {" "} | diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index ce798973..dcab3cb8 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -11,6 +11,7 @@ import React, { import { BeginLineNumToLogEventNumMap, CURSOR_CODE, + CursorType, FileSrcType, MainWorkerRespMessage, WORKER_REQ_CODE, @@ -25,7 +26,7 @@ import { interface StateContextType { beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap, - loadFile: (fileSrc: FileSrcType) => void, + loadFile: (fileSrc: FileSrcType, cursor: CursorType) => void, logData: string, numEvents: number, numPages: number, @@ -44,6 +45,8 @@ const STATE_DEFAULT = Object.freeze({ numPages: 0, pageNum: 0, }); + +const INVALID_PAGE_NUM = 0; const PAGE_SIZE = 10_000; interface StateContextProviderProps { @@ -64,15 +67,11 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { useState(STATE_DEFAULT.beginLineNumToLogEventNum); const [logData, setLogData] = useState(STATE_DEFAULT.logData); const [numEvents, setNumEvents] = useState(STATE_DEFAULT.numEvents); + const initialLogEventRef = useRef(logEventNum); + const pageNumRef = useRef(INVALID_PAGE_NUM); const mainWorkerRef = useRef(null); - const pageNum = useMemo(() => ( - "undefined" === typeof logEventNum ? - 0 : - Math.ceil(logEventNum / PAGE_SIZE) - ), [logEventNum]); - const numPages = useMemo(() => { return Math.ceil(numEvents / PAGE_SIZE); }, [numEvents]); @@ -91,13 +90,20 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { case WORKER_RESP_CODE.PAGE_DATA: { setLogData(args.logs); setBeginLineNumToLogEventNum(args.beginLineNumToLogEventNum); - const logEventNums = Array.from(args.beginLineNumToLogEventNum.values()); - let lastLogEventNum = logEventNums.at(-1); + // Correct logEventNum if it is out of valid range + const allLogEventNums = Array.from(args.beginLineNumToLogEventNum.values()); + let lastLogEventNum = allLogEventNums.at(-1); if ("undefined" === typeof lastLogEventNum) { lastLogEventNum = 1; } - updateWindowHashParams({logEventNum: lastLogEventNum}); + const newLogEventNum = (null === initialLogEventRef.current) ? + lastLogEventNum : + Math.min(initialLogEventRef.current, lastLogEventNum); + + updateWindowHashParams({ + logEventNum: newLogEventNum, + }); break; } case WORKER_RESP_CODE.NUM_EVENTS: @@ -114,7 +120,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { } }, []); - const loadFile = useCallback((fileSrc: FileSrcType) => { + const loadFile = useCallback((fileSrc: FileSrcType, cursor: CursorType) => { if (null !== mainWorkerRef.current) { mainWorkerRef.current.terminate(); } @@ -125,7 +131,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { mainWorkerPostReq(WORKER_REQ_CODE.LOAD_FILE, { fileSrc: fileSrc, pageSize: PAGE_SIZE, - cursor: {code: CURSOR_CODE.LAST_EVENT, args: null}, + cursor: cursor, decoderOptions: { // TODO: these shall come from config provider formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + @@ -140,8 +146,18 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { ]); useEffect(() => { - if ("undefined" !== typeof filePath) { - loadFile(filePath); + pageNumRef.current = (null === logEventNum) ? + INVALID_PAGE_NUM : + Math.ceil(logEventNum / PAGE_SIZE); + }, [logEventNum]); + + useEffect(() => { + if (null !== filePath) { + const cursor: CursorType = (INVALID_PAGE_NUM === pageNumRef.current) ? + {code: CURSOR_CODE.LAST_EVENT, args: null} : + {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}; + + loadFile(filePath, cursor); } }, [ filePath, @@ -151,12 +167,12 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { return ( {children} diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 3e370cf6..486b53f0 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -4,6 +4,7 @@ import React, { useState, } from "react"; +import {Nullable} from "../typings/common"; import { HASH_PARAM_NAME, SEARCH_PARAM_NAME, @@ -17,9 +18,19 @@ import { const UrlContext = createContext ({} as UrlParamsType); -interface UrlContextProviderProps { - children: React.ReactNode -} +/** + * Default values of the search parameters. + */ +const URL_SEARCH_PARAMS_DEFAULT = Object.freeze({ + [SEARCH_PARAM_NAME.FILE_PATH]: null, +}); + +/** + * Default values of the hash parameters. + */ +const URL_HASH_PARAMS_DEFAULT = Object.freeze({ + [HASH_PARAM_NAME.LOG_EVENT_NUM]: null, +}); /** * Computes updated URL search parameters based on the provided key-value pairs. @@ -88,7 +99,6 @@ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { window.history.pushState({}, "", newUrl); }; - /** * Updates hash parameters in the current window's URL based on the provided key-value pairs. * @@ -112,10 +122,9 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { * @return An object containing the search parameters. */ const getAllWindowSearchParams = () => { - const urlSearchParams: UrlSearchParams = {}; + const urlSearchParams: Nullable = structuredClone(URL_SEARCH_PARAMS_DEFAULT); const searchParams = new URLSearchParams(window.location.search.substring(1)); - // TODO: use Ajv to read and validate const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); if (null !== filePath) { urlSearchParams[SEARCH_PARAM_NAME.FILE_PATH] = filePath; @@ -131,9 +140,8 @@ const getAllWindowSearchParams = () => { */ const getAllWindowHashParams = () => { const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const urlHashParams: UrlHashParams = {}; + const urlHashParams: Nullable = structuredClone(URL_HASH_PARAMS_DEFAULT); - // TODO: use Ajv to read and validate const logEventNum = hashParams.get(HASH_PARAM_NAME.LOG_EVENT_NUM); if (null !== logEventNum) { urlHashParams[HASH_PARAM_NAME.LOG_EVENT_NUM] = Number(logEventNum); @@ -171,6 +179,10 @@ const copyToClipboard = ( const searchParams = getAllWindowSearchParams(); +interface UrlContextProviderProps { + children: React.ReactNode +} + /** * Provides a context for managing URL parameters and hash values, * including utilities for setting search and hash parameters, @@ -181,16 +193,18 @@ const searchParams = getAllWindowSearchParams(); * @return */ const UrlContextProvider = ({children}: UrlContextProviderProps) => { - const [urlParams, setUrlParams] = useState({}); + const [urlParams, setUrlParams] = useState({ + ...URL_SEARCH_PARAMS_DEFAULT, + ...URL_HASH_PARAMS_DEFAULT, + ...searchParams, + ...getAllWindowHashParams(), + }); useEffect(() => { - setUrlParams({ - ...searchParams, - ...getAllWindowHashParams(), - }); - const handleHashChange = () => { setUrlParams({ + ...URL_SEARCH_PARAMS_DEFAULT, + ...URL_HASH_PARAMS_DEFAULT, ...searchParams, ...getAllWindowHashParams(), }); diff --git a/new-log-viewer/src/typings/common.ts b/new-log-viewer/src/typings/common.ts new file mode 100644 index 00000000..7f649522 --- /dev/null +++ b/new-log-viewer/src/typings/common.ts @@ -0,0 +1,5 @@ +type Nullable = { + [P in keyof T]: T[P] | null; +}; + +export type {Nullable}; diff --git a/new-log-viewer/src/typings/url.ts b/new-log-viewer/src/typings/url.ts index 99a0ace0..9c241d25 100644 --- a/new-log-viewer/src/typings/url.ts +++ b/new-log-viewer/src/typings/url.ts @@ -7,24 +7,24 @@ enum HASH_PARAM_NAME { } interface UrlSearchParams { - [SEARCH_PARAM_NAME.FILE_PATH]?: string, + [SEARCH_PARAM_NAME.FILE_PATH]: string, } interface UrlHashParams { - logEventNum?: number, + logEventNum: number, } type UrlSearchParamUpdatesType = { - [key in keyof UrlSearchParams]?: UrlSearchParams[key] | null + [T in keyof UrlSearchParams]?: UrlSearchParams[T] | null } type UrlHashParamUpdatesType = { - [key in keyof UrlHashParams]?: UrlHashParams[key] | null + [T in keyof UrlHashParams]?: UrlHashParams[T] | null } type UrlParamsType = { - [key in keyof UrlSearchParams]?: UrlSearchParams[key]; + [T in keyof UrlSearchParams]: UrlSearchParams[T] | null } & { - [key in keyof UrlHashParams]?: UrlHashParams[key]; + [T in keyof UrlHashParams]: UrlHashParams[T] | null }; export { From d1b4979f9c5a5301785292f2dd75500c3f7a1fd0 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 21:15:53 -0400 Subject: [PATCH 17/79] Support page switching via logEventNum in the URL. --- .../src/contexts/StateContextProvider.tsx | 31 ++++++++++++++++--- new-log-viewer/src/services/MainWorker.ts | 12 +++++++ new-log-viewer/src/typings/worker.ts | 7 ++++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index dcab3cb8..16ff424f 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -67,7 +67,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { useState(STATE_DEFAULT.beginLineNumToLogEventNum); const [logData, setLogData] = useState(STATE_DEFAULT.logData); const [numEvents, setNumEvents] = useState(STATE_DEFAULT.numEvents); - const initialLogEventRef = useRef(logEventNum); + const logEventNumRef = useRef(logEventNum); const pageNumRef = useRef(INVALID_PAGE_NUM); const mainWorkerRef = useRef(null); @@ -97,9 +97,9 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { if ("undefined" === typeof lastLogEventNum) { lastLogEventNum = 1; } - const newLogEventNum = (null === initialLogEventRef.current) ? + const newLogEventNum = (null === logEventNumRef.current) ? lastLogEventNum : - Math.min(initialLogEventRef.current, lastLogEventNum); + Math.min(logEventNumRef.current, lastLogEventNum); updateWindowHashParams({ logEventNum: newLogEventNum, @@ -146,10 +146,31 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { ]); useEffect(() => { - pageNumRef.current = (null === logEventNum) ? + logEventNumRef.current = logEventNum; + }, [logEventNum]); + + useEffect(() => { + const newPage = (null === logEventNum) ? INVALID_PAGE_NUM : Math.ceil(logEventNum / PAGE_SIZE); - }, [logEventNum]); + + if (newPage !== pageNumRef.current) { + pageNumRef.current = newPage; + mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { + cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}, + decoderOptions: { + // TODO: these shall come from config provider + formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + + " %message%n", + logLevelKey: "log.level", + timestampKey: "@timestamp", + }, + }); + } + }, [ + logEventNum, + mainWorkerPostReq, + ]); useEffect(() => { if (null !== filePath) { diff --git a/new-log-viewer/src/services/MainWorker.ts b/new-log-viewer/src/services/MainWorker.ts index bfd3f73a..36ce9e18 100644 --- a/new-log-viewer/src/services/MainWorker.ts +++ b/new-log-viewer/src/services/MainWorker.ts @@ -58,6 +58,18 @@ onmessage = async (ev: MessageEvent) => { ); break; } + case WORKER_REQ_CODE.LOAD_PAGE: + if (null === LOG_FILE_MANAGER) { + throw new Error("Log file manager is not initialized"); + } + if ("undefined" !== typeof args.decoderOptions) { + LOG_FILE_MANAGER.setDecoderOptions(args.decoderOptions); + } + postResp( + WORKER_RESP_CODE.PAGE_DATA, + LOG_FILE_MANAGER.loadPage(args.cursor) + ); + break; default: console.error(`Unexpected ev.data: ${JSON.stringify(ev.data)}`); break; diff --git a/new-log-viewer/src/typings/worker.ts b/new-log-viewer/src/typings/worker.ts index bb8602dc..d9450567 100644 --- a/new-log-viewer/src/typings/worker.ts +++ b/new-log-viewer/src/typings/worker.ts @@ -39,6 +39,7 @@ type BeginLineNumToLogEventNumMap = Map; */ enum WORKER_REQ_CODE { LOAD_FILE = "loadFile", + LOAD_PAGE = "loadPage", } enum WORKER_RESP_CODE { @@ -53,7 +54,11 @@ type WorkerReqMap = { pageSize: number, cursor: CursorType, decoderOptions: DecoderOptionsType - }; + }, + [WORKER_REQ_CODE.LOAD_PAGE]: { + cursor: CursorType, + decoderOptions?: DecoderOptionsType + }, }; type WorkerRespMap = { From f02b094e68d3f1ca3a904170eefc18ba229c77a4 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 21:40:56 -0400 Subject: [PATCH 18/79] Clamp logEventNum; refactor code. --- .../src/contexts/StateContextProvider.tsx | 55 ++++++++++++------- .../src/contexts/UrlContextProvider.tsx | 6 +- new-log-viewer/src/typings/common.ts | 11 +++- new-log-viewer/src/typings/worker.ts | 1 + new-log-viewer/src/utils/math.ts | 13 +++++ 5 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 new-log-viewer/src/utils/math.ts diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 16ff424f..6a83877a 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -8,6 +8,7 @@ import React, { useState, } from "react"; +import {Nullable} from "../typings/common"; import { BeginLineNumToLogEventNumMap, CURSOR_CODE, @@ -17,7 +18,9 @@ import { WORKER_REQ_CODE, WORKER_RESP_CODE, WorkerReq, + WorkerRespMap, } from "../typings/worker"; +import {clamp} from "../utils/math"; import { updateWindowHashParams, UrlContext, @@ -30,7 +33,7 @@ interface StateContextType { logData: string, numEvents: number, numPages: number, - pageNum: number + pageNum: Nullable } const StateContext = createContext({} as StateContextType); @@ -46,13 +49,36 @@ const STATE_DEFAULT = Object.freeze({ pageNum: 0, }); -const INVALID_PAGE_NUM = 0; const PAGE_SIZE = 10_000; interface StateContextProviderProps { children: React.ReactNode } +/** + * Update the log event number and modify the window hash params. + * + * @param args The arguments containing the log event numbers. + * @param currentLogEventNum The current log event number. + */ +const updateLogEventNum = ( + args: WorkerRespMap[WORKER_RESP_CODE.PAGE_DATA], + currentLogEventNum: Nullable +) => { + const allLogEventNums = Array.from(args.beginLineNumToLogEventNum.values()); + let lastLogEventNum = allLogEventNums.at(-1); + if ("undefined" === typeof lastLogEventNum) { + lastLogEventNum = 1; + } + const newLogEventNum = (null === currentLogEventNum) ? + lastLogEventNum : + clamp(currentLogEventNum, 1, lastLogEventNum); + + updateWindowHashParams({ + logEventNum: newLogEventNum, + }); +}; + /** * Provides state management for the application. * @@ -68,7 +94,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const [logData, setLogData] = useState(STATE_DEFAULT.logData); const [numEvents, setNumEvents] = useState(STATE_DEFAULT.numEvents); const logEventNumRef = useRef(logEventNum); - const pageNumRef = useRef(INVALID_PAGE_NUM); + const pageNumRef = useRef>(null); const mainWorkerRef = useRef(null); @@ -90,20 +116,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { case WORKER_RESP_CODE.PAGE_DATA: { setLogData(args.logs); setBeginLineNumToLogEventNum(args.beginLineNumToLogEventNum); - - // Correct logEventNum if it is out of valid range - const allLogEventNums = Array.from(args.beginLineNumToLogEventNum.values()); - let lastLogEventNum = allLogEventNums.at(-1); - if ("undefined" === typeof lastLogEventNum) { - lastLogEventNum = 1; - } - const newLogEventNum = (null === logEventNumRef.current) ? - lastLogEventNum : - Math.min(logEventNumRef.current, lastLogEventNum); - - updateWindowHashParams({ - logEventNum: newLogEventNum, - }); + updateLogEventNum(args, logEventNumRef.current); break; } case WORKER_RESP_CODE.NUM_EVENTS: @@ -150,9 +163,9 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }, [logEventNum]); useEffect(() => { - const newPage = (null === logEventNum) ? - INVALID_PAGE_NUM : - Math.ceil(logEventNum / PAGE_SIZE); + const newPage = (null === logEventNum || 0 >= logEventNum) ? + 1 : + Math.max(Math.ceil(logEventNum / PAGE_SIZE)); if (newPage !== pageNumRef.current) { pageNumRef.current = newPage; @@ -174,7 +187,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { useEffect(() => { if (null !== filePath) { - const cursor: CursorType = (INVALID_PAGE_NUM === pageNumRef.current) ? + const cursor: CursorType = (null === pageNumRef.current) ? {code: CURSOR_CODE.LAST_EVENT, args: null} : {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}; diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 486b53f0..20812a6b 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -4,7 +4,7 @@ import React, { useState, } from "react"; -import {Nullable} from "../typings/common"; +import {NullableProperties} from "../typings/common"; import { HASH_PARAM_NAME, SEARCH_PARAM_NAME, @@ -122,7 +122,7 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { * @return An object containing the search parameters. */ const getAllWindowSearchParams = () => { - const urlSearchParams: Nullable = structuredClone(URL_SEARCH_PARAMS_DEFAULT); + const urlSearchParams: NullableProperties = structuredClone(URL_SEARCH_PARAMS_DEFAULT); const searchParams = new URLSearchParams(window.location.search.substring(1)); const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); @@ -140,7 +140,7 @@ const getAllWindowSearchParams = () => { */ const getAllWindowHashParams = () => { const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const urlHashParams: Nullable = structuredClone(URL_HASH_PARAMS_DEFAULT); + const urlHashParams: NullableProperties = structuredClone(URL_HASH_PARAMS_DEFAULT); const logEventNum = hashParams.get(HASH_PARAM_NAME.LOG_EVENT_NUM); if (null !== logEventNum) { diff --git a/new-log-viewer/src/typings/common.ts b/new-log-viewer/src/typings/common.ts index 7f649522..01000ce1 100644 --- a/new-log-viewer/src/typings/common.ts +++ b/new-log-viewer/src/typings/common.ts @@ -1,5 +1,10 @@ -type Nullable = { - [P in keyof T]: T[P] | null; +type Nullable = T | null; + +type NullableProperties = { + [P in keyof T]: Nullable; }; -export type {Nullable}; +export type { + Nullable, + NullableProperties, +}; diff --git a/new-log-viewer/src/typings/worker.ts b/new-log-viewer/src/typings/worker.ts index d9450567..dc62bc97 100644 --- a/new-log-viewer/src/typings/worker.ts +++ b/new-log-viewer/src/typings/worker.ts @@ -105,4 +105,5 @@ export type { MainWorkerRespMessage, WorkerReq, WorkerResp, + WorkerRespMap, }; diff --git a/new-log-viewer/src/utils/math.ts b/new-log-viewer/src/utils/math.ts new file mode 100644 index 00000000..30c4f480 --- /dev/null +++ b/new-log-viewer/src/utils/math.ts @@ -0,0 +1,13 @@ +/** + * Returns a number whose value is limited to the given range. + * + * @param num The number to be clamped. + * @param min The lower boundary of the output range. + * @param max The upper boundary of the output range. + * @return A number in the range [min, max]. + */ +const clamp = function (num: number, min: number, max: number) { + return Math.min(Math.max(num, min), max); +}; + +export {clamp}; From 19031b47ab1670409a600e9ccbd23211d299bbf5 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 22:05:22 -0400 Subject: [PATCH 19/79] Refactor log event number handling and clean up worker typings. --- .../src/contexts/StateContextProvider.tsx | 52 ++++++++++++------- .../src/contexts/UrlContextProvider.tsx | 13 +++-- new-log-viewer/src/typings/worker.ts | 1 - 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 6a83877a..37e1c8f0 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -18,7 +18,6 @@ import { WORKER_REQ_CODE, WORKER_RESP_CODE, WorkerReq, - WorkerRespMap, } from "../typings/worker"; import {clamp} from "../utils/math"; import { @@ -56,29 +55,40 @@ interface StateContextProviderProps { } /** - * Update the log event number and modify the window hash params. + * Updates a user-input log event number value for the window hash parameters. * - * @param args The arguments containing the log event numbers. - * @param currentLogEventNum The current log event number. + * @param lastLogEventNum The last log event number value. + * @param inputLogEventNum The current log event number value. */ const updateLogEventNum = ( - args: WorkerRespMap[WORKER_RESP_CODE.PAGE_DATA], - currentLogEventNum: Nullable + lastLogEventNum: number, + inputLogEventNum: Nullable ) => { - const allLogEventNums = Array.from(args.beginLineNumToLogEventNum.values()); - let lastLogEventNum = allLogEventNums.at(-1); - if ("undefined" === typeof lastLogEventNum) { - lastLogEventNum = 1; - } - const newLogEventNum = (null === currentLogEventNum) ? + const newLogEventNum = (null === inputLogEventNum) ? lastLogEventNum : - clamp(currentLogEventNum, 1, lastLogEventNum); + clamp(inputLogEventNum, 1, lastLogEventNum); updateWindowHashParams({ logEventNum: newLogEventNum, }); }; +/** + * Gets the last log event number from a map of begin line numbers to log event numbers. + * + * @param beginLineNumToLogEventNum + * @return The last log event number. + */ +const getLastLogEventNum = (beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap) => { + const allLogEventNums = Array.from(beginLineNumToLogEventNum.values()); + let lastLogEventNum = allLogEventNums.at(-1); + if ("undefined" === typeof lastLogEventNum) { + lastLogEventNum = 1; + } + + return lastLogEventNum; +}; + /** * Provides state management for the application. * @@ -116,7 +126,8 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { case WORKER_RESP_CODE.PAGE_DATA: { setLogData(args.logs); setBeginLineNumToLogEventNum(args.beginLineNumToLogEventNum); - updateLogEventNum(args, logEventNumRef.current); + const lastLogEventNum = getLastLogEventNum(args.beginLineNumToLogEventNum); + updateLogEventNum(lastLogEventNum, logEventNumRef.current); break; } case WORKER_RESP_CODE.NUM_EVENTS: @@ -165,23 +176,28 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { useEffect(() => { const newPage = (null === logEventNum || 0 >= logEventNum) ? 1 : - Math.max(Math.ceil(logEventNum / PAGE_SIZE)); + Math.max(1, numPages); - if (newPage !== pageNumRef.current) { + if (newPage === pageNumRef.current) { + const lastLogEventNum = getLastLogEventNum(beginLineNumToLogEventNum); + updateLogEventNum(lastLogEventNum, logEventNumRef.current); + } else { pageNumRef.current = newPage; mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}, decoderOptions: { - // TODO: these shall come from config provider + // TODO: these shall come from config provider formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + - " %message%n", + " %message%n", logLevelKey: "log.level", timestampKey: "@timestamp", }, }); } }, [ + beginLineNumToLogEventNum, logEventNum, + numPages, mainWorkerPostReq, ]); diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 20812a6b..32022fd7 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -122,7 +122,9 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { * @return An object containing the search parameters. */ const getAllWindowSearchParams = () => { - const urlSearchParams: NullableProperties = structuredClone(URL_SEARCH_PARAMS_DEFAULT); + const urlSearchParams: NullableProperties = structuredClone( + URL_SEARCH_PARAMS_DEFAULT + ); const searchParams = new URLSearchParams(window.location.search.substring(1)); const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); @@ -140,11 +142,16 @@ const getAllWindowSearchParams = () => { */ const getAllWindowHashParams = () => { const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const urlHashParams: NullableProperties = structuredClone(URL_HASH_PARAMS_DEFAULT); + const urlHashParams: NullableProperties = structuredClone( + URL_HASH_PARAMS_DEFAULT + ); const logEventNum = hashParams.get(HASH_PARAM_NAME.LOG_EVENT_NUM); if (null !== logEventNum) { - urlHashParams[HASH_PARAM_NAME.LOG_EVENT_NUM] = Number(logEventNum); + const parsed = Number(logEventNum); + urlHashParams[HASH_PARAM_NAME.LOG_EVENT_NUM] = isNaN(parsed) ? + null : + parsed; } return urlHashParams; diff --git a/new-log-viewer/src/typings/worker.ts b/new-log-viewer/src/typings/worker.ts index dc62bc97..d9450567 100644 --- a/new-log-viewer/src/typings/worker.ts +++ b/new-log-viewer/src/typings/worker.ts @@ -105,5 +105,4 @@ export type { MainWorkerRespMessage, WorkerReq, WorkerResp, - WorkerRespMap, }; From c61e766d10a1b8d4a2d40345ecef81826fe0d420 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 22:13:20 -0400 Subject: [PATCH 20/79] Refactor code. --- new-log-viewer/src/services/LogFileManager.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index 999ff0e4..c670b820 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -194,24 +194,17 @@ class LogFileManager { }; } - let beginLogEventIdx: number; + let beginLogEventIdx: number = 1; const {code, args} = cursor; - switch (code) { - case CURSOR_CODE.LAST_EVENT: - beginLogEventIdx = - (Math.floor((this.#numEvents - 1) / this.#pageSize) * this.#pageSize); - break; - case CURSOR_CODE.PAGE_NUM: - beginLogEventIdx = ((args.pageNum - 1) * this.#pageSize); - break; - default: - throw new Error(`Unsupported cursor type: ${code}`); + if (CURSOR_CODE.PAGE_NUM === code) { + beginLogEventIdx = ((args.pageNum - 1) * this.#pageSize); } - if (beginLogEventIdx > this.#numEvents) { + if (CURSOR_CODE.LAST_EVENT === code || beginLogEventIdx > this.#numEvents) { beginLogEventIdx = (Math.floor((this.#numEvents - 1) / this.#pageSize) * this.#pageSize); + } else if (CURSOR_CODE.TIMESTAMP === code) { + throw new Error(`Unsupported cursor type: ${code}`); } - const beginLogEventNum = beginLogEventIdx + 1; const endLogEventNum = Math.min(this.#numEvents, beginLogEventNum + this.#pageSize - 1); return {beginLogEventNum, endLogEventNum}; From 6988f298178b12efda5f849f1395e40313597b26 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 22:16:57 -0400 Subject: [PATCH 21/79] Add missing throws in docstring. --- new-log-viewer/src/services/LogFileManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index c670b820..3b2d9267 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -113,6 +113,7 @@ class LogFileManager { * @param cursor The cursor indicating the page to load. See {@link CursorType}. * @return An object containing the logs as a string, a map of line numbers to log event * numbers, and the line number of the first line in the cursor identified event. + * @throws Error if any error occurs during decode. */ loadPage (cursor: CursorType): { logs: string, From 084d1a04e1cda7d3e5f2b63a6167ca947b119f51 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 22:19:05 -0400 Subject: [PATCH 22/79] Refactor type usage to employ Nullable utility type. --- .../src/services/decoders/JsonlDecoder.ts | 5 +++-- new-log-viewer/src/typings/decoders.ts | 7 +++++-- new-log-viewer/src/typings/url.ts | 15 +++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/new-log-viewer/src/services/decoders/JsonlDecoder.ts b/new-log-viewer/src/services/decoders/JsonlDecoder.ts index 05cb7cba..d1ca1469 100644 --- a/new-log-viewer/src/services/decoders/JsonlDecoder.ts +++ b/new-log-viewer/src/services/decoders/JsonlDecoder.ts @@ -1,3 +1,4 @@ +import {Nullable} from "../../typings/common"; import { Decoder, DecodeResultType, @@ -51,7 +52,7 @@ class JsonlDecoder implements Decoder { return this.#logEvents.length; } - buildIdx (beginIdx: number, endIdx: number): LogEventCount | null { + buildIdx (beginIdx: number, endIdx: number): Nullable { // This method is a dummy implementation since the actual deserialization is done in the // constructor. @@ -76,7 +77,7 @@ class JsonlDecoder implements Decoder { return true; } - decode (beginIdx: number, endIdx: number): DecodeResultType[] | null { + decode (beginIdx: number, endIdx: number): Nullable { if (0 > beginIdx || this.#logEvents.length < endIdx) { return null; } diff --git a/new-log-viewer/src/typings/decoders.ts b/new-log-viewer/src/typings/decoders.ts index 1ebaee7f..c30a3700 100644 --- a/new-log-viewer/src/typings/decoders.ts +++ b/new-log-viewer/src/typings/decoders.ts @@ -1,3 +1,6 @@ +import {Nullable} from "./common"; + + interface LogEventCount { numValidEvents: number, numInvalidEvents: number, @@ -47,7 +50,7 @@ interface Decoder { * un-deserializable ("invalid") log events within the range; or null if any log event in the * range doesn't exist (e.g., the range exceeds the number of log events in the file). */ - buildIdx(beginIdx: number, endIdx: number): LogEventCount | null; + buildIdx(beginIdx: number, endIdx: number): Nullable; /** * Sets options for the decoder. @@ -65,7 +68,7 @@ interface Decoder { * @return The decoded log events on success or null if any log event in the range doesn't exist * (e.g., the range exceeds the number of log events in the file). */ - decode(beginIdx: number, endIdx: number): DecodeResultType[] | null; + decode(beginIdx: number, endIdx: number): Nullable; } diff --git a/new-log-viewer/src/typings/url.ts b/new-log-viewer/src/typings/url.ts index 9c241d25..2481cc85 100644 --- a/new-log-viewer/src/typings/url.ts +++ b/new-log-viewer/src/typings/url.ts @@ -1,9 +1,12 @@ +import {Nullable} from "./common"; + + enum SEARCH_PARAM_NAME { - FILE_PATH = "filePath" + FILE_PATH = "filePath", } enum HASH_PARAM_NAME { - LOG_EVENT_NUM = "logEventNum" + LOG_EVENT_NUM = "logEventNum", } interface UrlSearchParams { @@ -15,16 +18,16 @@ interface UrlHashParams { } type UrlSearchParamUpdatesType = { - [T in keyof UrlSearchParams]?: UrlSearchParams[T] | null + [T in keyof UrlSearchParams]?: Nullable; } type UrlHashParamUpdatesType = { - [T in keyof UrlHashParams]?: UrlHashParams[T] | null + [T in keyof UrlHashParams]?: Nullable; } type UrlParamsType = { - [T in keyof UrlSearchParams]: UrlSearchParams[T] | null + [T in keyof UrlSearchParams]: Nullable; } & { - [T in keyof UrlHashParams]: UrlHashParams[T] | null + [T in keyof UrlHashParams]: Nullable; }; export { From 97cfc503928c5b27dfbd696777d71e7e00813a09 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 6 Aug 2024 22:50:32 -0400 Subject: [PATCH 23/79] Add getAbsoluteUrl utility function to resolve relative `filePath`s. --- .../src/contexts/UrlContextProvider.tsx | 3 ++- new-log-viewer/src/utils/url.ts | 26 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 32022fd7..a90635dc 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -14,6 +14,7 @@ import { UrlSearchParams, UrlSearchParamUpdatesType, } from "../typings/url"; +import {getAbsoluteUrl} from "../utils/url"; const UrlContext = createContext ({} as UrlParamsType); @@ -129,7 +130,7 @@ const getAllWindowSearchParams = () => { const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); if (null !== filePath) { - urlSearchParams[SEARCH_PARAM_NAME.FILE_PATH] = filePath; + urlSearchParams[SEARCH_PARAM_NAME.FILE_PATH] = getAbsoluteUrl(filePath); } return urlSearchParams; diff --git a/new-log-viewer/src/utils/url.ts b/new-log-viewer/src/utils/url.ts index 531dc78d..5b69c27d 100644 --- a/new-log-viewer/src/utils/url.ts +++ b/new-log-viewer/src/utils/url.ts @@ -1,3 +1,23 @@ +/** + * Gets an absolute URL composed of a given path relative to the + * window.location, if the given path is a relative reference; otherwise + * the given path is returned verbatim. + * + * @param path The path to be resolved. + * @return The absolute URL of the given path. + * @throws {Error} if the given `path` is a relative reference but invalid. + */ +const getAbsoluteUrl = (path: string) => { + try { + // eslint-disable-next-line no-new + new URL(path); + } catch (e) { + path = new URL(path, window.location.origin).href; + } + + return path; +}; + /** * Extracts the basename (filename) from a given string containing a URL. * @@ -24,4 +44,8 @@ const getBasenameFromUrlOrDefault = ( return basename; }; -export {getBasenameFromUrlOrDefault}; + +export { + getAbsoluteUrl, + getBasenameFromUrlOrDefault, +}; From 559dd0e7dfd085886d7358a5566f890c809f171a Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 10:21:23 -0400 Subject: [PATCH 24/79] Change the conditional check from string to undefined for filePath for explicitness. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index a90635dc..8b4c5611 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -56,7 +56,7 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { } if (null === filePath) { newSearchParams.delete(SEARCH_PARAM_NAME.FILE_PATH); - } else if ("string" === typeof filePath) { + } else if ("undefined" !== typeof filePath) { newSearchParams.set(SEARCH_PARAM_NAME.FILE_PATH, filePath); } From a0f65a8dceeab30c658e366bf4ec8803f2a4196e Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 10:41:13 -0400 Subject: [PATCH 25/79] Docs - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/utils/math.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/new-log-viewer/src/utils/math.ts b/new-log-viewer/src/utils/math.ts index 30c4f480..38041daa 100644 --- a/new-log-viewer/src/utils/math.ts +++ b/new-log-viewer/src/utils/math.ts @@ -1,10 +1,11 @@ /** - * Returns a number whose value is limited to the given range. + * Clamps a number to the given range. E.g. If the number is greater than the range's upper bound, + * the range's upper bound is returned. * * @param num The number to be clamped. * @param min The lower boundary of the output range. * @param max The upper boundary of the output range. - * @return A number in the range [min, max]. + * @return The clamped number. */ const clamp = function (num: number, min: number, max: number) { return Math.min(Math.max(num, min), max); From f6fc9245963db7d91897be95734ec86983e45b9b Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 10:42:13 -0400 Subject: [PATCH 26/79] Change `clamp` into an arrow function. --- new-log-viewer/src/utils/math.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/new-log-viewer/src/utils/math.ts b/new-log-viewer/src/utils/math.ts index 38041daa..4054aad2 100644 --- a/new-log-viewer/src/utils/math.ts +++ b/new-log-viewer/src/utils/math.ts @@ -7,8 +7,6 @@ * @param max The upper boundary of the output range. * @return The clamped number. */ -const clamp = function (num: number, min: number, max: number) { - return Math.min(Math.max(num, min), max); -}; +const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, min), max); export {clamp}; From aa9bbc2b6abc0074f84455c8253de156a8b6395e Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 10:52:01 -0400 Subject: [PATCH 27/79] Set default log event number to 1 in debug input. --- new-log-viewer/src/components/Layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index e92c949a..a8ba4740 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -38,7 +38,7 @@ const Layout = () => { {" "} From 55423ba6a838196771c1643df047fcdea340696c Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 10:54:45 -0400 Subject: [PATCH 28/79] Docs - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/contexts/StateContextProvider.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 37e1c8f0..c74dbc39 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -55,10 +55,12 @@ interface StateContextProviderProps { } /** - * Updates a user-input log event number value for the window hash parameters. - * - * @param lastLogEventNum The last log event number value. - * @param inputLogEventNum The current log event number value. +Updates the log event number in the current window's URL hash parameters. + +@param lastLogEventNum The last log event number value. +@param inputLogEventNum The log event number to set. If `null`, the hash parameter log event +number will be set to `lastLogEventNum`. If it's outside the range `[1, lastLogEventNum]`, the +hash parameter log event number will be clamped to that range. */ const updateLogEventNum = ( lastLogEventNum: number, From 6dab65c47ac8711864971c6a73e1cf42a54b55ac Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 10:55:48 -0400 Subject: [PATCH 29/79] Rename updateLogEventNum -> updateLogEventNumInUrl. --- .../src/contexts/StateContextProvider.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index c74dbc39..6e5e8b1d 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -55,14 +55,14 @@ interface StateContextProviderProps { } /** -Updates the log event number in the current window's URL hash parameters. - -@param lastLogEventNum The last log event number value. -@param inputLogEventNum The log event number to set. If `null`, the hash parameter log event -number will be set to `lastLogEventNum`. If it's outside the range `[1, lastLogEventNum]`, the -hash parameter log event number will be clamped to that range. + * Updates the log event number in the current window's URL hash parameters. + * + * @param lastLogEventNum The last log event number value. + * @param inputLogEventNum The log event number to set. If `null`, the hash parameter log event + * number will be set to `lastLogEventNum`. If it's outside the range `[1, lastLogEventNum]`, the + * hash parameter log event number will be clamped to that range. */ -const updateLogEventNum = ( +const updateLogEventNumInUrl = ( lastLogEventNum: number, inputLogEventNum: Nullable ) => { @@ -129,7 +129,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { setLogData(args.logs); setBeginLineNumToLogEventNum(args.beginLineNumToLogEventNum); const lastLogEventNum = getLastLogEventNum(args.beginLineNumToLogEventNum); - updateLogEventNum(lastLogEventNum, logEventNumRef.current); + updateLogEventNumInUrl(lastLogEventNum, logEventNumRef.current); break; } case WORKER_RESP_CODE.NUM_EVENTS: @@ -182,7 +182,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { if (newPage === pageNumRef.current) { const lastLogEventNum = getLastLogEventNum(beginLineNumToLogEventNum); - updateLogEventNum(lastLogEventNum, logEventNumRef.current); + updateLogEventNumInUrl(lastLogEventNum, logEventNumRef.current); } else { pageNumRef.current = newPage; mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { From 77b69950f54b33ae6b9e33a9a8fbb5eccc6daec8 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:26:21 -0400 Subject: [PATCH 30/79] Refactor page calculation logic; addd `getPageNumFromLogEventNum` utility function. --- .../src/contexts/StateContextProvider.tsx | 11 +++++++---- new-log-viewer/src/services/LogFileManager.ts | 3 ++- new-log-viewer/src/utils/math.ts | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 6e5e8b1d..547f41c1 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -19,7 +19,10 @@ import { WORKER_RESP_CODE, WorkerReq, } from "../typings/worker"; -import {clamp} from "../utils/math"; +import { + clamp, + getPageNumFromLogEventNum, +} from "../utils/math"; import { updateWindowHashParams, UrlContext, @@ -111,7 +114,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const mainWorkerRef = useRef(null); const numPages = useMemo(() => { - return Math.ceil(numEvents / PAGE_SIZE); + return getPageNumFromLogEventNum(numEvents, PAGE_SIZE); }, [numEvents]); const mainWorkerPostReq = useCallback(( @@ -176,9 +179,9 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }, [logEventNum]); useEffect(() => { - const newPage = (null === logEventNum || 0 >= logEventNum) ? + const newPage = (null === logEventNum) ? 1 : - Math.max(1, numPages); + clamp(getPageNumFromLogEventNum(logEventNum, PAGE_SIZE), 1, numPages); if (newPage === pageNumRef.current) { const lastLogEventNum = getLastLogEventNum(beginLineNumToLogEventNum); diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index 3b2d9267..ac2666d5 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -10,6 +10,7 @@ import { FileSrcType, } from "../typings/worker"; import {getUint8ArrayFrom} from "../utils/http"; +import {getPageNumFromLogEventNum} from "../utils/math"; import {formatSizeInBytes} from "../utils/units"; import {getBasenameFromUrlOrDefault} from "../utils/url"; import JsonlDecoder from "./decoders/JsonlDecoder"; @@ -202,7 +203,7 @@ class LogFileManager { } if (CURSOR_CODE.LAST_EVENT === code || beginLogEventIdx > this.#numEvents) { beginLogEventIdx = - (Math.floor((this.#numEvents - 1) / this.#pageSize) * this.#pageSize); + getPageNumFromLogEventNum(this.#numEvents, this.#pageSize) * this.#pageSize; } else if (CURSOR_CODE.TIMESTAMP === code) { throw new Error(`Unsupported cursor type: ${code}`); } diff --git a/new-log-viewer/src/utils/math.ts b/new-log-viewer/src/utils/math.ts index 4054aad2..e5a95c9f 100644 --- a/new-log-viewer/src/utils/math.ts +++ b/new-log-viewer/src/utils/math.ts @@ -9,4 +9,17 @@ */ const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, min), max); -export {clamp}; +/** + * Returns the page number based on the log event number and page size. + * + * @param logEventNum The log event number. + * @param pageSize The number of log events in each page. + * @return The calculated page number. + */ +const getPageNumFromLogEventNum = + (logEventNum: number, pageSize: number) => Math.ceil(logEventNum / pageSize); + +export { + clamp, + getPageNumFromLogEventNum, +}; From e8e5b0b6a38829e2a07002ec49d0ab4f28ff8645 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:36:47 -0400 Subject: [PATCH 31/79] Docs - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- .../src/contexts/UrlContextProvider.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 8b4c5611..2d9d8df1 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -36,8 +36,8 @@ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ /** * Computes updated URL search parameters based on the provided key-value pairs. * - * @param updates An object containing key-value pairs to update the search parameters. - * If a key's value is `null`, the key will be removed from the search parameters. + * @param updates An object containing key-value pairs to update the search parameters. If a value + * is `null`, the corresponding kv-pair will be removed from the updated search parameters. * @return The updated search parameters string. */ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { @@ -71,8 +71,8 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { /** * Computes updated URL hash parameters based on the provided key-value pairs. * - * @param updates An object containing key-value pairs to update the hash parameters. - * If a key's value is `null`, the key will be removed from the hash parameters. + * @param updates An object containing key-value pairs to update the hash parameters. If a key's + * value is `null`, the key will be removed from the updated hash parameters. * @return The updated hash parameters string. */ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { @@ -89,10 +89,10 @@ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { }; /** - * Updates search parameters in the current window's URL based on the provided key-value pairs. + * Updates search parameters in the current window's URL with the given key-value pairs. * - * @param updates An object containing key-value pairs to update the search parameters. - * If a key's value is `null`, the key will be removed from the search parameters. + * @param updates An object containing key-value pairs to update the search parameters. If a value + * is `null`, the corresponding kv-pair will be removed from the URL's search parameters. */ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { const newUrl = new URL(window.location.href); @@ -101,10 +101,10 @@ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { }; /** - * Updates hash parameters in the current window's URL based on the provided key-value pairs. + * Updates hash parameters in the current window's URL with the given key-value pairs. * - * @param updates An object containing key-value pairs to update the hash parameters. - * If a key's value is `null`, the key will be removed from the hash parameters. + * @param updates An object containing key-value pairs to update the hash parameters. If a value is + * `null`, the corresponding kv-pair will be removed from the URL's hash parameters. */ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { const newHash = getUpdatedHashParams(updates); From 28717404189c62937876c3733e49844bcdd0f3d2 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:37:42 -0400 Subject: [PATCH 32/79] Add docs - why we need to dispatch "hashchange" event manually. Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 2d9d8df1..a05804ba 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -113,6 +113,7 @@ const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { const newUrl = new URL(window.location.href); newUrl.hash = newHash; window.history.pushState({}, "", newUrl); + // `history.pushState` doesn't trigger a `hashchange`, so we need to dispatch one manually. window.dispatchEvent(new HashChangeEvent("hashchange")); } }; From 0dd4a46718c55e096793a5fcd695d0d277b3a486 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:39:44 -0400 Subject: [PATCH 33/79] Early return in `updateWindowHashParams` if no change is required. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index a05804ba..d5b9649c 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -109,13 +109,15 @@ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { const newHash = getUpdatedHashParams(updates); const currHash = window.location.hash.substring(1); - if (newHash !== currHash) { - const newUrl = new URL(window.location.href); - newUrl.hash = newHash; - window.history.pushState({}, "", newUrl); - // `history.pushState` doesn't trigger a `hashchange`, so we need to dispatch one manually. - window.dispatchEvent(new HashChangeEvent("hashchange")); + if (newHash === currHash) { + return; } + + const newUrl = new URL(window.location.href); + newUrl.hash = newHash; + window.history.pushState({}, "", newUrl); + // `history.pushState` doesn't trigger a `hashchange`, so we need to dispatch one manually. + window.dispatchEvent(new HashChangeEvent("hashchange")); }; /** From 9d2a65c51007cc81e867e5a772f51f728c589220 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:40:37 -0400 Subject: [PATCH 34/79] Rename `updateWindowSearchParams` -> `updateWindowUrlSearchParams`, `updateWindowHashParams` -> `updateWindowUrlHashParams`. --- new-log-viewer/src/components/Layout.tsx | 4 ++-- new-log-viewer/src/contexts/StateContextProvider.tsx | 4 ++-- new-log-viewer/src/contexts/UrlContextProvider.tsx | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index a8ba4740..9060dd9b 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -3,7 +3,7 @@ import React, {useContext} from "react"; import {StateContext} from "../contexts/StateContextProvider"; import { copyToClipboard, - updateWindowHashParams, + updateWindowUrlHashParams, UrlContext, } from "../contexts/UrlContextProvider"; @@ -22,7 +22,7 @@ const Layout = () => { const {logEventNum} = useContext(UrlContext); const handleLogEventNumInputChange = (ev: React.ChangeEvent) => { - updateWindowHashParams({logEventNum: Number(ev.target.value)}); + updateWindowUrlHashParams({logEventNum: Number(ev.target.value)}); }; const handleCopyLinkButtonClick = () => { diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 547f41c1..5abd9c33 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -24,7 +24,7 @@ import { getPageNumFromLogEventNum, } from "../utils/math"; import { - updateWindowHashParams, + updateWindowUrlHashParams, UrlContext, } from "./UrlContextProvider"; @@ -73,7 +73,7 @@ const updateLogEventNumInUrl = ( lastLogEventNum : clamp(inputLogEventNum, 1, lastLogEventNum); - updateWindowHashParams({ + updateWindowUrlHashParams({ logEventNum: newLogEventNum, }); }; diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index d5b9649c..5b8950e3 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -94,7 +94,7 @@ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { * @param updates An object containing key-value pairs to update the search parameters. If a value * is `null`, the corresponding kv-pair will be removed from the URL's search parameters. */ -const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { +const updateWindowUrlSearchParams = (updates: UrlSearchParamUpdatesType) => { const newUrl = new URL(window.location.href); newUrl.search = getUpdatedSearchParams(updates); window.history.pushState({}, "", newUrl); @@ -106,7 +106,7 @@ const updateWindowSearchParams = (updates: UrlSearchParamUpdatesType) => { * @param updates An object containing key-value pairs to update the hash parameters. If a value is * `null`, the corresponding kv-pair will be removed from the URL's hash parameters. */ -const updateWindowHashParams = (updates: UrlHashParamUpdatesType) => { +const updateWindowUrlHashParams = (updates: UrlHashParamUpdatesType) => { const newHash = getUpdatedHashParams(updates); const currHash = window.location.hash.substring(1); if (newHash === currHash) { @@ -238,7 +238,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { export default UrlContextProvider; export { copyToClipboard, - updateWindowHashParams, - updateWindowSearchParams, + updateWindowUrlHashParams, + updateWindowUrlSearchParams, UrlContext, }; From 8ba9c7a57854afab6803fdf518c140a25873d6b0 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:42:00 -0400 Subject: [PATCH 35/79] Lint. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 5b8950e3..1fe5ab18 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -72,7 +72,7 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { * Computes updated URL hash parameters based on the provided key-value pairs. * * @param updates An object containing key-value pairs to update the hash parameters. If a key's - * value is `null`, the key will be removed from the updated hash parameters. + * value is `null`, the key will be removed from the updated hash parameters. * @return The updated hash parameters string. */ const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { @@ -116,6 +116,7 @@ const updateWindowUrlHashParams = (updates: UrlHashParamUpdatesType) => { const newUrl = new URL(window.location.href); newUrl.hash = newHash; window.history.pushState({}, "", newUrl); + // `history.pushState` doesn't trigger a `hashchange`, so we need to dispatch one manually. window.dispatchEvent(new HashChangeEvent("hashchange")); }; From 318607efdbd9eb8543383285fa1e219ed31ee272 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:45:28 -0400 Subject: [PATCH 36/79] Rename `getAllWindowSearchParams` -> `getWindowSearchParams`, `getAllWindowHashParams` -> `getWindowHashParams`; rename variables & swap statements orders in these functions for consistency. --- .../src/contexts/UrlContextProvider.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 1fe5ab18..98ddf50c 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -126,18 +126,18 @@ const updateWindowUrlHashParams = (updates: UrlHashParamUpdatesType) => { * * @return An object containing the search parameters. */ -const getAllWindowSearchParams = () => { - const urlSearchParams: NullableProperties = structuredClone( +const getWindowSearchParams = () => { + const searchParams : NullableProperties = structuredClone( URL_SEARCH_PARAMS_DEFAULT ); - const searchParams = new URLSearchParams(window.location.search.substring(1)); + const urlSearchParams = new URLSearchParams(window.location.search.substring(1)); - const filePath = searchParams.get(SEARCH_PARAM_NAME.FILE_PATH); + const filePath = urlSearchParams.get(SEARCH_PARAM_NAME.FILE_PATH); if (null !== filePath) { - urlSearchParams[SEARCH_PARAM_NAME.FILE_PATH] = getAbsoluteUrl(filePath); + searchParams[SEARCH_PARAM_NAME.FILE_PATH] = getAbsoluteUrl(filePath); } - return urlSearchParams; + return searchParams; }; /** @@ -145,11 +145,11 @@ const getAllWindowSearchParams = () => { * * @return An object containing the hash parameters. */ -const getAllWindowHashParams = () => { - const hashParams = new URLSearchParams(window.location.hash.substring(1)); +const getWindowHashParams = () => { const urlHashParams: NullableProperties = structuredClone( URL_HASH_PARAMS_DEFAULT ); + const hashParams = new URLSearchParams(window.location.hash.substring(1)); const logEventNum = hashParams.get(HASH_PARAM_NAME.LOG_EVENT_NUM); if (null !== logEventNum) { @@ -189,7 +189,7 @@ const copyToClipboard = ( }); }; -const searchParams = getAllWindowSearchParams(); +const searchParams = getWindowSearchParams(); interface UrlContextProviderProps { children: React.ReactNode @@ -209,7 +209,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { ...URL_SEARCH_PARAMS_DEFAULT, ...URL_HASH_PARAMS_DEFAULT, ...searchParams, - ...getAllWindowHashParams(), + ...getWindowHashParams(), }); useEffect(() => { @@ -218,7 +218,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { ...URL_SEARCH_PARAMS_DEFAULT, ...URL_HASH_PARAMS_DEFAULT, ...searchParams, - ...getAllWindowHashParams(), + ...getWindowHashParams(), }); }; From b04f179a1c35179f7270fe5fff107e8bc8ec3a83 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:47:30 -0400 Subject: [PATCH 37/79] Use `Number.isNaN()` instead of `isNaN()` - "Because coercion inside the isNaN() function can be surprising, you may prefer to use Number.isNaN()". --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 98ddf50c..38751e23 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -154,7 +154,7 @@ const getWindowHashParams = () => { const logEventNum = hashParams.get(HASH_PARAM_NAME.LOG_EVENT_NUM); if (null !== logEventNum) { const parsed = Number(logEventNum); - urlHashParams[HASH_PARAM_NAME.LOG_EVENT_NUM] = isNaN(parsed) ? + urlHashParams[HASH_PARAM_NAME.LOG_EVENT_NUM] = Number.isNaN(parsed) ? null : parsed; } From f5705bc4597003e9831b6443b18f9fb1b55aec97 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:48:22 -0400 Subject: [PATCH 38/79] Docs - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 38751e23..c0134ae8 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -165,13 +165,13 @@ const getWindowHashParams = () => { /** * Copies the current window's URL to the clipboard. If any `updates` parameters are specified, - * the copied URL will include these modifications, while the original window's URL remains - * unchanged. + * the copied URL will include these modifications, but the original window's URL will not be + * changed. * * @param searchParamUpdates An object containing key-value pairs to update the search parameters. - * If a key's value is `null`, the key will be removed from the search parameters. + * If a value is `null`, the corresponding kv-pair will be removed from the URL's search parameters. * @param hashParamsUpdates An object containing key-value pairs to update the hash parameters. - * If a key's value is `null`, the key will be removed from the hash parameters. + * If a value is `null`, the corresponding kv-pair will be removed from the URL's hash parameters. */ const copyToClipboard = ( searchParamUpdates: UrlSearchParamUpdatesType, From ec6d3463461fc45fb7b7e53ecade1edd3c59c919 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:50:15 -0400 Subject: [PATCH 39/79] Move `copyUrlToClipboard` to two methods above; rename the function as `copyWindowUrlToClipboard`. --- new-log-viewer/src/components/Layout.tsx | 4 +- .../src/contexts/UrlContextProvider.tsx | 55 +++++++++---------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index 9060dd9b..2fd82198 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -2,7 +2,7 @@ import React, {useContext} from "react"; import {StateContext} from "../contexts/StateContextProvider"; import { - copyToClipboard, + copyWindowUrlToClipboard, updateWindowUrlHashParams, UrlContext, } from "../contexts/UrlContextProvider"; @@ -26,7 +26,7 @@ const Layout = () => { }; const handleCopyLinkButtonClick = () => { - copyToClipboard({}, {logEventNum: numEvents}); + copyWindowUrlToClipboard({}, {logEventNum: numEvents}); }; return ( diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index c0134ae8..4659c204 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -121,6 +121,32 @@ const updateWindowUrlHashParams = (updates: UrlHashParamUpdatesType) => { window.dispatchEvent(new HashChangeEvent("hashchange")); }; +/** + * Copies the current window's URL to the clipboard. If any `updates` parameters are specified, + * the copied URL will include these modifications, but the original window's URL will not be + * changed. + * + * @param searchParamUpdates An object containing key-value pairs to update the search parameters. + * If a value is `null`, the corresponding kv-pair will be removed from the URL's search parameters. + * @param hashParamsUpdates An object containing key-value pairs to update the hash parameters. + * If a value is `null`, the corresponding kv-pair will be removed from the URL's hash parameters. + */ +const copyWindowUrlToClipboard = ( + searchParamUpdates: UrlSearchParamUpdatesType, + hashParamsUpdates: UrlHashParamUpdatesType, +) => { + const newUrl = new URL(window.location.href); + newUrl.search = getUpdatedSearchParams(searchParamUpdates); + newUrl.hash = getUpdatedHashParams(hashParamsUpdates); + navigator.clipboard.writeText(newUrl.toString()) + .then(() => { + console.log("URL copied to clipboard."); + }) + .catch((error: unknown) => { + console.error("Failed to copy URL to clipboard:", error); + }); +}; + /** * Retrieves all search parameters from the current window's URL. * @@ -162,33 +188,6 @@ const getWindowHashParams = () => { return urlHashParams; }; - -/** - * Copies the current window's URL to the clipboard. If any `updates` parameters are specified, - * the copied URL will include these modifications, but the original window's URL will not be - * changed. - * - * @param searchParamUpdates An object containing key-value pairs to update the search parameters. - * If a value is `null`, the corresponding kv-pair will be removed from the URL's search parameters. - * @param hashParamsUpdates An object containing key-value pairs to update the hash parameters. - * If a value is `null`, the corresponding kv-pair will be removed from the URL's hash parameters. - */ -const copyToClipboard = ( - searchParamUpdates: UrlSearchParamUpdatesType, - hashParamsUpdates: UrlHashParamUpdatesType, -) => { - const newUrl = new URL(window.location.href); - newUrl.search = getUpdatedSearchParams(searchParamUpdates); - newUrl.hash = getUpdatedHashParams(hashParamsUpdates); - navigator.clipboard.writeText(newUrl.toString()) - .then(() => { - console.log("URL copied to clipboard."); - }) - .catch((error: unknown) => { - console.error("Failed to copy URL to clipboard:", error); - }); -}; - const searchParams = getWindowSearchParams(); interface UrlContextProviderProps { @@ -238,7 +237,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { export default UrlContextProvider; export { - copyToClipboard, + copyWindowUrlToClipboard, updateWindowUrlHashParams, updateWindowUrlSearchParams, UrlContext, From 92655f239298dfa54d2090aa7f3786f29a6d1a7e Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:52:26 -0400 Subject: [PATCH 40/79] Docs - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 4659c204..db8b624b 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -195,9 +195,8 @@ interface UrlContextProviderProps { } /** - * Provides a context for managing URL parameters and hash values, - * including utilities for setting search and hash parameters, - * and copying the current URL with these parameters to the clipboard. + * Provides a context for managing URL search and hash parameters including utilities for setting + * each, and copying the current URL with these parameters to the clipboard. * * @param props * @param props.children The child components that will have access to the context. From 41e0a100201c13f8a726068567d093fe1ceb56c2 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 12:58:54 -0400 Subject: [PATCH 41/79] Change the @throws tag to include curly braces around {Error} in LogFileManager.loadPage method JSDoc. --- new-log-viewer/src/services/LogFileManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index ac2666d5..154dfb40 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -114,7 +114,7 @@ class LogFileManager { * @param cursor The cursor indicating the page to load. See {@link CursorType}. * @return An object containing the logs as a string, a map of line numbers to log event * numbers, and the line number of the first line in the cursor identified event. - * @throws Error if any error occurs during decode. + * @throws {Error} if any error occurs during decode. */ loadPage (cursor: CursorType): { logs: string, From 4ada12a4aa2f07624e8ab26c6dcf31e6f0d91221 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 13:26:45 -0400 Subject: [PATCH 42/79] Rename SEARCH_PARAM_NAME and HASH_PARAM_NAME enums to SEARCH_PARAM_NAMES and HASH_PARAM_NAMES. --- .../src/contexts/UrlContextProvider.tsx | 22 +++++++++---------- new-log-viewer/src/typings/url.ts | 10 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index db8b624b..c526a8b1 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -6,8 +6,8 @@ import React, { import {NullableProperties} from "../typings/common"; import { - HASH_PARAM_NAME, - SEARCH_PARAM_NAME, + HASH_PARAM_NAMES, + SEARCH_PARAM_NAMES, UrlHashParams, UrlHashParamUpdatesType, UrlParamsType, @@ -23,14 +23,14 @@ const UrlContext = createContext ({} as UrlParamsType); * Default values of the search parameters. */ const URL_SEARCH_PARAMS_DEFAULT = Object.freeze({ - [SEARCH_PARAM_NAME.FILE_PATH]: null, + [SEARCH_PARAM_NAMES.FILE_PATH]: null, }); /** * Default values of the hash parameters. */ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ - [HASH_PARAM_NAME.LOG_EVENT_NUM]: null, + [HASH_PARAM_NAMES.LOG_EVENT_NUM]: null, }); /** @@ -45,7 +45,7 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { const {filePath} = updates; for (const [key, value] of Object.entries(updates)) { - if (SEARCH_PARAM_NAME.FILE_PATH as string === key) { + if (SEARCH_PARAM_NAMES.FILE_PATH as string === key) { continue; } if (null === value) { @@ -55,9 +55,9 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { } } if (null === filePath) { - newSearchParams.delete(SEARCH_PARAM_NAME.FILE_PATH); + newSearchParams.delete(SEARCH_PARAM_NAMES.FILE_PATH); } else if ("undefined" !== typeof filePath) { - newSearchParams.set(SEARCH_PARAM_NAME.FILE_PATH, filePath); + newSearchParams.set(SEARCH_PARAM_NAMES.FILE_PATH, filePath); } let searchString = newSearchParams.toString(); @@ -158,9 +158,9 @@ const getWindowSearchParams = () => { ); const urlSearchParams = new URLSearchParams(window.location.search.substring(1)); - const filePath = urlSearchParams.get(SEARCH_PARAM_NAME.FILE_PATH); + const filePath = urlSearchParams.get(SEARCH_PARAM_NAMES.FILE_PATH); if (null !== filePath) { - searchParams[SEARCH_PARAM_NAME.FILE_PATH] = getAbsoluteUrl(filePath); + searchParams[SEARCH_PARAM_NAMES.FILE_PATH] = getAbsoluteUrl(filePath); } return searchParams; @@ -177,10 +177,10 @@ const getWindowHashParams = () => { ); const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const logEventNum = hashParams.get(HASH_PARAM_NAME.LOG_EVENT_NUM); + const logEventNum = hashParams.get(HASH_PARAM_NAMES.LOG_EVENT_NUM); if (null !== logEventNum) { const parsed = Number(logEventNum); - urlHashParams[HASH_PARAM_NAME.LOG_EVENT_NUM] = Number.isNaN(parsed) ? + urlHashParams[HASH_PARAM_NAMES.LOG_EVENT_NUM] = Number.isNaN(parsed) ? null : parsed; } diff --git a/new-log-viewer/src/typings/url.ts b/new-log-viewer/src/typings/url.ts index 2481cc85..0aabb157 100644 --- a/new-log-viewer/src/typings/url.ts +++ b/new-log-viewer/src/typings/url.ts @@ -1,16 +1,16 @@ import {Nullable} from "./common"; -enum SEARCH_PARAM_NAME { +enum SEARCH_PARAM_NAMES { FILE_PATH = "filePath", } -enum HASH_PARAM_NAME { +enum HASH_PARAM_NAMES { LOG_EVENT_NUM = "logEventNum", } interface UrlSearchParams { - [SEARCH_PARAM_NAME.FILE_PATH]: string, + [SEARCH_PARAM_NAMES.FILE_PATH]: string, } interface UrlHashParams { @@ -31,8 +31,8 @@ type UrlParamsType = { }; export { - HASH_PARAM_NAME, - SEARCH_PARAM_NAME, + HASH_PARAM_NAMES, + SEARCH_PARAM_NAMES, }; export type { UrlHashParams, From 1536bd914e89a74b71a75100f2ac8490a74a8272 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 13:27:41 -0400 Subject: [PATCH 43/79] Docs & Prompts - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/services/LogFileManager.ts | 1 + new-log-viewer/src/services/MainWorker.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index 154dfb40..67523b72 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -202,6 +202,7 @@ class LogFileManager { beginLogEventIdx = ((args.pageNum - 1) * this.#pageSize); } if (CURSOR_CODE.LAST_EVENT === code || beginLogEventIdx > this.#numEvents) { + // Set to the first event of the last page beginLogEventIdx = getPageNumFromLogEventNum(this.#numEvents, this.#pageSize) * this.#pageSize; } else if (CURSOR_CODE.TIMESTAMP === code) { diff --git a/new-log-viewer/src/services/MainWorker.ts b/new-log-viewer/src/services/MainWorker.ts index 36ce9e18..25034326 100644 --- a/new-log-viewer/src/services/MainWorker.ts +++ b/new-log-viewer/src/services/MainWorker.ts @@ -60,7 +60,7 @@ onmessage = async (ev: MessageEvent) => { } case WORKER_REQ_CODE.LOAD_PAGE: if (null === LOG_FILE_MANAGER) { - throw new Error("Log file manager is not initialized"); + throw new Error("Log file manager hasn't been initialized"); } if ("undefined" !== typeof args.decoderOptions) { LOG_FILE_MANAGER.setDecoderOptions(args.decoderOptions); From f06c3ce79ab97a16b96d7bc331f11980c437e7cb Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 13:28:34 -0400 Subject: [PATCH 44/79] Correct initial beginLogEventIdx `1` -> `0` - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/services/LogFileManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index 67523b72..3917cf04 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -196,7 +196,7 @@ class LogFileManager { }; } - let beginLogEventIdx: number = 1; + let beginLogEventIdx: number = 0; const {code, args} = cursor; if (CURSOR_CODE.PAGE_NUM === code) { beginLogEventIdx = ((args.pageNum - 1) * this.#pageSize); From 11ddbd90f87c66f33cefd1fa9734c59537c96701 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 14:20:36 -0400 Subject: [PATCH 45/79] Add docs for non-trivial useEffect() hooks. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 5abd9c33..44f76cc7 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -174,10 +174,12 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { mainWorkerPostReq, ]); + // Synchronize `logEventNumRef` with `logEventNum`. useEffect(() => { logEventNumRef.current = logEventNum; }, [logEventNum]); + // On `logEventNum` updates, clamp the number or switch page when needed. useEffect(() => { const newPage = (null === logEventNum) ? 1 : @@ -206,6 +208,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { mainWorkerPostReq, ]); + // On `filePath` updates, load file. useEffect(() => { if (null !== filePath) { const cursor: CursorType = (null === pageNumRef.current) ? From 683ac297ade57f0c5385decbd72da3a88c5b4a90 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 17:03:41 -0400 Subject: [PATCH 46/79] Rename `copyWindowUrlToClipboard` -> `copyPermalinkToClipboard`. --- new-log-viewer/src/components/Layout.tsx | 4 ++-- new-log-viewer/src/contexts/UrlContextProvider.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/src/components/Layout.tsx b/new-log-viewer/src/components/Layout.tsx index 2fd82198..db16ce85 100644 --- a/new-log-viewer/src/components/Layout.tsx +++ b/new-log-viewer/src/components/Layout.tsx @@ -2,7 +2,7 @@ import React, {useContext} from "react"; import {StateContext} from "../contexts/StateContextProvider"; import { - copyWindowUrlToClipboard, + copyPermalinkToClipboard, updateWindowUrlHashParams, UrlContext, } from "../contexts/UrlContextProvider"; @@ -26,7 +26,7 @@ const Layout = () => { }; const handleCopyLinkButtonClick = () => { - copyWindowUrlToClipboard({}, {logEventNum: numEvents}); + copyPermalinkToClipboard({}, {logEventNum: numEvents}); }; return ( diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index c526a8b1..dd941c4e 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -131,7 +131,7 @@ const updateWindowUrlHashParams = (updates: UrlHashParamUpdatesType) => { * @param hashParamsUpdates An object containing key-value pairs to update the hash parameters. * If a value is `null`, the corresponding kv-pair will be removed from the URL's hash parameters. */ -const copyWindowUrlToClipboard = ( +const copyPermalinkToClipboard = ( searchParamUpdates: UrlSearchParamUpdatesType, hashParamsUpdates: UrlHashParamUpdatesType, ) => { @@ -236,7 +236,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { export default UrlContextProvider; export { - copyWindowUrlToClipboard, + copyPermalinkToClipboard, updateWindowUrlHashParams, updateWindowUrlSearchParams, UrlContext, From 013566da174cfa07cdd181c33f0885bb3d1a6826 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 17:14:49 -0400 Subject: [PATCH 47/79] Update StateContextProvider doc for UrlContextProvider dependency. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 44f76cc7..800bdf4a 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -96,6 +96,7 @@ const getLastLogEventNum = (beginLineNumToLogEventNum: BeginLineNumToLogEventNum /** * Provides state management for the application. + * This provider must be wrapped by UrlContextProvider to function correctly. * * @param props * @param props.children From 8a08cffd6c5ad13329fe2ecc576427da53cbcd29 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 21:54:18 -0400 Subject: [PATCH 48/79] Ensure `filePath` is updated last to maintain order. --- .../src/contexts/UrlContextProvider.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index dd941c4e..41abb410 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -42,10 +42,11 @@ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ */ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { const newSearchParams = new URLSearchParams(window.location.search.substring(1)); - const {filePath} = updates; + const {filePath: newFilePath} = updates; for (const [key, value] of Object.entries(updates)) { if (SEARCH_PARAM_NAMES.FILE_PATH as string === key) { + // Updates to `filePath` should be handled last. continue; } if (null === value) { @@ -54,10 +55,16 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { newSearchParams.set(key, String(value)); } } - if (null === filePath) { - newSearchParams.delete(SEARCH_PARAM_NAMES.FILE_PATH); - } else if ("undefined" !== typeof filePath) { - newSearchParams.set(SEARCH_PARAM_NAMES.FILE_PATH, filePath); + + // `filePath` should always be the last search parameter + const originalFilePath = newSearchParams.get(SEARCH_PARAM_NAMES.FILE_PATH); + newSearchParams.delete(SEARCH_PARAM_NAMES.FILE_PATH); + if ("undefined" === typeof newFilePath && null !== originalFilePath) { + // If no change in `filePath` is specified, put the original `filePath` back. + newSearchParams.set(SEARCH_PARAM_NAMES.FILE_PATH, originalFilePath); + } else if ("undefined" !== typeof newFilePath && null !== newFilePath) { + // If the new `filePath` has a non-null value, set the value. + newSearchParams.set(SEARCH_PARAM_NAMES.FILE_PATH, newFilePath); } let searchString = newSearchParams.toString(); From c3c730e0463544a2089c3cf8d3cfab696eec3cbd Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 22:17:29 -0400 Subject: [PATCH 49/79] Assume `filePath` to be the last search parameter in URL search parameters parsing. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 41abb410..eb7e6861 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -165,9 +165,12 @@ const getWindowSearchParams = () => { ); const urlSearchParams = new URLSearchParams(window.location.search.substring(1)); - const filePath = urlSearchParams.get(SEARCH_PARAM_NAMES.FILE_PATH); - if (null !== filePath) { - searchParams[SEARCH_PARAM_NAMES.FILE_PATH] = getAbsoluteUrl(filePath); + if (urlSearchParams.has(SEARCH_PARAM_NAMES.FILE_PATH)) { + let [, filePath] = window.location.search.split("filePath="); + if ("undefined" !== typeof filePath && 0 !== filePath.length) { + filePath = filePath.substring(filePath.indexOf("#")); + searchParams[SEARCH_PARAM_NAMES.FILE_PATH] = getAbsoluteUrl(filePath); + } } return searchParams; From 913b2bf7da9dd5e8e6db0e9bf3b589c17ca11401 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Thu, 8 Aug 2024 22:21:20 -0400 Subject: [PATCH 50/79] Assume `filePath` to be the last search parameter in URL search parameters parsing. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index eb7e6861..5c3fbb60 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -166,6 +166,8 @@ const getWindowSearchParams = () => { const urlSearchParams = new URLSearchParams(window.location.search.substring(1)); if (urlSearchParams.has(SEARCH_PARAM_NAMES.FILE_PATH)) { + // Split the search string and take everything after as `filePath` value. + // This ensures any parameters following `filePath=` are incorporated into the `filePath`. let [, filePath] = window.location.search.split("filePath="); if ("undefined" !== typeof filePath && 0 !== filePath.length) { filePath = filePath.substring(filePath.indexOf("#")); From 4b95e439b1aee815ad8a305a9365d924b3f3be20 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 12:03:43 -0400 Subject: [PATCH 51/79] Remove redundant line in `filePath` handling. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 5c3fbb60..383a7702 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -168,9 +168,8 @@ const getWindowSearchParams = () => { if (urlSearchParams.has(SEARCH_PARAM_NAMES.FILE_PATH)) { // Split the search string and take everything after as `filePath` value. // This ensures any parameters following `filePath=` are incorporated into the `filePath`. - let [, filePath] = window.location.search.split("filePath="); + const [, filePath] = window.location.search.split("filePath="); if ("undefined" !== typeof filePath && 0 !== filePath.length) { - filePath = filePath.substring(filePath.indexOf("#")); searchParams[SEARCH_PARAM_NAMES.FILE_PATH] = getAbsoluteUrl(filePath); } } From 2936d4eb3bb15e24d595b352d2facc364fdbc38b Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:19:06 -0400 Subject: [PATCH 52/79] add config utilities --- .../src/contexts/StateContextProvider.tsx | 18 +--- new-log-viewer/src/typings/config.ts | 34 ++++++++ new-log-viewer/src/utils/config.ts | 85 +++++++++++++++++++ 3 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 new-log-viewer/src/typings/config.ts create mode 100644 new-log-viewer/src/utils/config.ts diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 800bdf4a..7ae883c8 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -9,6 +9,7 @@ import React, { } from "react"; import {Nullable} from "../typings/common"; +import {CONFIG_NAME} from "../typings/config"; import { BeginLineNumToLogEventNumMap, CURSOR_CODE, @@ -19,6 +20,7 @@ import { WORKER_RESP_CODE, WorkerReq, } from "../typings/worker"; +import {getConfig} from "../utils/config"; import { clamp, getPageNumFromLogEventNum, @@ -162,13 +164,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { fileSrc: fileSrc, pageSize: PAGE_SIZE, cursor: cursor, - decoderOptions: { - // TODO: these shall come from config provider - formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + - " %message%n", - logLevelKey: "log.level", - timestampKey: "@timestamp", - }, + decoderOptions: getConfig(CONFIG_NAME.DECODER_OPTIONS), }); }, [ handleMainWorkerResp, @@ -193,13 +189,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { pageNumRef.current = newPage; mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}, - decoderOptions: { - // TODO: these shall come from config provider - formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + - " %message%n", - logLevelKey: "log.level", - timestampKey: "@timestamp", - }, + decoderOptions: getConfig(CONFIG_NAME.DECODER_OPTIONS), }); } }, [ diff --git a/new-log-viewer/src/typings/config.ts b/new-log-viewer/src/typings/config.ts new file mode 100644 index 00000000..6b9ce9c3 --- /dev/null +++ b/new-log-viewer/src/typings/config.ts @@ -0,0 +1,34 @@ +enum CONFIG_NAME { + DECODER_OPTIONS = "decoderOptions", + THEME = "theme", + PAGE_SIZE = "pageSize", +} + +enum LOCAL_STORAGE_KEY { + DECODER_OPTIONS_FORMAT_STRING = `${CONFIG_NAME.DECODER_OPTIONS}/formatString`, + DECODER_OPTIONS_LOG_LEVEL_KEY = `${CONFIG_NAME.DECODER_OPTIONS}/logLevelKey`, + DECODER_OPTIONS_TIMESTAMP_KEY = `${CONFIG_NAME.DECODER_OPTIONS}/timestampKey`, + THEME = CONFIG_NAME.THEME, + PAGE_SIZE = CONFIG_NAME.PAGE_SIZE, +} + +type ConfigMap = { + [CONFIG_NAME.DECODER_OPTIONS]: { + formatString: string, + logLevelKey: string, + timestampKey: string, + }, + [CONFIG_NAME.THEME]: string, + [CONFIG_NAME.PAGE_SIZE]: number, +} + +type ConfigUpdate = { + [T in keyof ConfigMap]: { code: T, value: ConfigMap[T] }; +}[keyof ConfigMap]; + +export { + CONFIG_NAME, LOCAL_STORAGE_KEY, +}; +export type { + ConfigMap, ConfigUpdate, +}; diff --git a/new-log-viewer/src/utils/config.ts b/new-log-viewer/src/utils/config.ts new file mode 100644 index 00000000..694c019d --- /dev/null +++ b/new-log-viewer/src/utils/config.ts @@ -0,0 +1,85 @@ +import * as assert from "node:assert"; + +import { + CONFIG_NAME, + ConfigMap, + ConfigUpdate, + LOCAL_STORAGE_KEY, +} from "../typings/config"; + + +const DECODER_DEFAULT = { + formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + " %message%n", + logLevelKey: "log.level", + timestampKey: "@timestamp", +}; + + +const THEME_DEFAULT = "light"; + +/** + * + * @param code + */ +const getConfig = (code: T) => { + const result: Partial = {}; + switch (code) { + case CONFIG_NAME.DECODER_OPTIONS: + result[CONFIG_NAME.DECODER_OPTIONS] = { + formatString: window.localStorage.getItem( + LOCAL_STORAGE_KEY.DECODER_OPTIONS_FORMAT_STRING + ) || DECODER_DEFAULT.formatString, + logLevelKey: window.localStorage.getItem( + LOCAL_STORAGE_KEY.DECODER_OPTIONS_LOG_LEVEL_KEY + ) || DECODER_DEFAULT.logLevelKey, + timestampKey: window.localStorage.getItem( + LOCAL_STORAGE_KEY.DECODER_OPTIONS_TIMESTAMP_KEY + ) || DECODER_DEFAULT.timestampKey, + }; + break; + case CONFIG_NAME.THEME: + result[CONFIG_NAME.THEME] = window.localStorage.getItem(LOCAL_STORAGE_KEY.THEME) || THEME_DEFAULT; + break; + case CONFIG_NAME.PAGE_SIZE: + result[CONFIG_NAME.PAGE_SIZE] = Number(window.localStorage.getItem(LOCAL_STORAGE_KEY.PAGE_SIZE)); + break; + default: + console.error(`Unexpected code: ${code}`); + break; + } + + return result[code] as ConfigMap[T]; +}; + +/** + * + * @param configUpdates + */ +const setConfig = (configUpdates: ConfigUpdate) => { + const {code, value} = configUpdates; + switch (code) { + case CONFIG_NAME.DECODER_OPTIONS: + window.localStorage.setItem( + LOCAL_STORAGE_KEY.DECODER_OPTIONS_FORMAT_STRING, + value.formatString + ); + window.localStorage.setItem( + LOCAL_STORAGE_KEY.DECODER_OPTIONS_LOG_LEVEL_KEY, + value.logLevelKey + ); + window.localStorage.setItem( + LOCAL_STORAGE_KEY.DECODER_OPTIONS_TIMESTAMP_KEY, + value.timestampKey + ); + break; + case CONFIG_NAME.THEME: + window.localStorage.setItem(LOCAL_STORAGE_KEY.THEME, value); + break; + case CONFIG_NAME.PAGE_SIZE: + window.localStorage.setItem(LOCAL_STORAGE_KEY.PAGE_SIZE, value.toString()); + break; + default: break; + } +}; + +export {getConfig}; From e1e3315840a492c83f13c6e4c5d792634766bcd8 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:22:08 -0400 Subject: [PATCH 53/79] Revert "Remove redundant line in `filePath` handling." This reverts commit 4b95e439b1aee815ad8a305a9365d924b3f3be20. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 383a7702..5c3fbb60 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -168,8 +168,9 @@ const getWindowSearchParams = () => { if (urlSearchParams.has(SEARCH_PARAM_NAMES.FILE_PATH)) { // Split the search string and take everything after as `filePath` value. // This ensures any parameters following `filePath=` are incorporated into the `filePath`. - const [, filePath] = window.location.search.split("filePath="); + let [, filePath] = window.location.search.split("filePath="); if ("undefined" !== typeof filePath && 0 !== filePath.length) { + filePath = filePath.substring(filePath.indexOf("#")); searchParams[SEARCH_PARAM_NAMES.FILE_PATH] = getAbsoluteUrl(filePath); } } From bed1917833273a9d543106993e845bfa8c52a8d6 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:28:35 -0400 Subject: [PATCH 54/79] Remove config utilities --- new-log-viewer/src/typings/config.ts | 34 ----------- new-log-viewer/src/utils/config.ts | 85 ---------------------------- 2 files changed, 119 deletions(-) delete mode 100644 new-log-viewer/src/typings/config.ts delete mode 100644 new-log-viewer/src/utils/config.ts diff --git a/new-log-viewer/src/typings/config.ts b/new-log-viewer/src/typings/config.ts deleted file mode 100644 index 6b9ce9c3..00000000 --- a/new-log-viewer/src/typings/config.ts +++ /dev/null @@ -1,34 +0,0 @@ -enum CONFIG_NAME { - DECODER_OPTIONS = "decoderOptions", - THEME = "theme", - PAGE_SIZE = "pageSize", -} - -enum LOCAL_STORAGE_KEY { - DECODER_OPTIONS_FORMAT_STRING = `${CONFIG_NAME.DECODER_OPTIONS}/formatString`, - DECODER_OPTIONS_LOG_LEVEL_KEY = `${CONFIG_NAME.DECODER_OPTIONS}/logLevelKey`, - DECODER_OPTIONS_TIMESTAMP_KEY = `${CONFIG_NAME.DECODER_OPTIONS}/timestampKey`, - THEME = CONFIG_NAME.THEME, - PAGE_SIZE = CONFIG_NAME.PAGE_SIZE, -} - -type ConfigMap = { - [CONFIG_NAME.DECODER_OPTIONS]: { - formatString: string, - logLevelKey: string, - timestampKey: string, - }, - [CONFIG_NAME.THEME]: string, - [CONFIG_NAME.PAGE_SIZE]: number, -} - -type ConfigUpdate = { - [T in keyof ConfigMap]: { code: T, value: ConfigMap[T] }; -}[keyof ConfigMap]; - -export { - CONFIG_NAME, LOCAL_STORAGE_KEY, -}; -export type { - ConfigMap, ConfigUpdate, -}; diff --git a/new-log-viewer/src/utils/config.ts b/new-log-viewer/src/utils/config.ts deleted file mode 100644 index 694c019d..00000000 --- a/new-log-viewer/src/utils/config.ts +++ /dev/null @@ -1,85 +0,0 @@ -import * as assert from "node:assert"; - -import { - CONFIG_NAME, - ConfigMap, - ConfigUpdate, - LOCAL_STORAGE_KEY, -} from "../typings/config"; - - -const DECODER_DEFAULT = { - formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + " %message%n", - logLevelKey: "log.level", - timestampKey: "@timestamp", -}; - - -const THEME_DEFAULT = "light"; - -/** - * - * @param code - */ -const getConfig = (code: T) => { - const result: Partial = {}; - switch (code) { - case CONFIG_NAME.DECODER_OPTIONS: - result[CONFIG_NAME.DECODER_OPTIONS] = { - formatString: window.localStorage.getItem( - LOCAL_STORAGE_KEY.DECODER_OPTIONS_FORMAT_STRING - ) || DECODER_DEFAULT.formatString, - logLevelKey: window.localStorage.getItem( - LOCAL_STORAGE_KEY.DECODER_OPTIONS_LOG_LEVEL_KEY - ) || DECODER_DEFAULT.logLevelKey, - timestampKey: window.localStorage.getItem( - LOCAL_STORAGE_KEY.DECODER_OPTIONS_TIMESTAMP_KEY - ) || DECODER_DEFAULT.timestampKey, - }; - break; - case CONFIG_NAME.THEME: - result[CONFIG_NAME.THEME] = window.localStorage.getItem(LOCAL_STORAGE_KEY.THEME) || THEME_DEFAULT; - break; - case CONFIG_NAME.PAGE_SIZE: - result[CONFIG_NAME.PAGE_SIZE] = Number(window.localStorage.getItem(LOCAL_STORAGE_KEY.PAGE_SIZE)); - break; - default: - console.error(`Unexpected code: ${code}`); - break; - } - - return result[code] as ConfigMap[T]; -}; - -/** - * - * @param configUpdates - */ -const setConfig = (configUpdates: ConfigUpdate) => { - const {code, value} = configUpdates; - switch (code) { - case CONFIG_NAME.DECODER_OPTIONS: - window.localStorage.setItem( - LOCAL_STORAGE_KEY.DECODER_OPTIONS_FORMAT_STRING, - value.formatString - ); - window.localStorage.setItem( - LOCAL_STORAGE_KEY.DECODER_OPTIONS_LOG_LEVEL_KEY, - value.logLevelKey - ); - window.localStorage.setItem( - LOCAL_STORAGE_KEY.DECODER_OPTIONS_TIMESTAMP_KEY, - value.timestampKey - ); - break; - case CONFIG_NAME.THEME: - window.localStorage.setItem(LOCAL_STORAGE_KEY.THEME, value); - break; - case CONFIG_NAME.PAGE_SIZE: - window.localStorage.setItem(LOCAL_STORAGE_KEY.PAGE_SIZE, value.toString()); - break; - default: break; - } -}; - -export {getConfig}; From 5265f537577413f01f1006dbab6a50f0bf002c7d Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:48:21 -0400 Subject: [PATCH 55/79] Reapply "Remove redundant line in `filePath` handling." This reverts commit e1e3315840a492c83f13c6e4c5d792634766bcd8. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 5c3fbb60..383a7702 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -168,9 +168,8 @@ const getWindowSearchParams = () => { if (urlSearchParams.has(SEARCH_PARAM_NAMES.FILE_PATH)) { // Split the search string and take everything after as `filePath` value. // This ensures any parameters following `filePath=` are incorporated into the `filePath`. - let [, filePath] = window.location.search.split("filePath="); + const [, filePath] = window.location.search.split("filePath="); if ("undefined" !== typeof filePath && 0 !== filePath.length) { - filePath = filePath.substring(filePath.indexOf("#")); searchParams[SEARCH_PARAM_NAMES.FILE_PATH] = getAbsoluteUrl(filePath); } } From 8f55ee5c73ec45d041d3e7ccc1b5e08608174236 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:49:15 -0400 Subject: [PATCH 56/79] Revert "add config utilities" This reverts commit 2936d4eb3bb15e24d595b352d2facc364fdbc38b. --- .../src/contexts/StateContextProvider.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 7ae883c8..800bdf4a 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -9,7 +9,6 @@ import React, { } from "react"; import {Nullable} from "../typings/common"; -import {CONFIG_NAME} from "../typings/config"; import { BeginLineNumToLogEventNumMap, CURSOR_CODE, @@ -20,7 +19,6 @@ import { WORKER_RESP_CODE, WorkerReq, } from "../typings/worker"; -import {getConfig} from "../utils/config"; import { clamp, getPageNumFromLogEventNum, @@ -164,7 +162,13 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { fileSrc: fileSrc, pageSize: PAGE_SIZE, cursor: cursor, - decoderOptions: getConfig(CONFIG_NAME.DECODER_OPTIONS), + decoderOptions: { + // TODO: these shall come from config provider + formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + + " %message%n", + logLevelKey: "log.level", + timestampKey: "@timestamp", + }, }); }, [ handleMainWorkerResp, @@ -189,7 +193,13 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { pageNumRef.current = newPage; mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}, - decoderOptions: getConfig(CONFIG_NAME.DECODER_OPTIONS), + decoderOptions: { + // TODO: these shall come from config provider + formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + + " %message%n", + logLevelKey: "log.level", + timestampKey: "@timestamp", + }, }); } }, [ From 97a2615f945bbd9334cef775c92ec71eb012faa6 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:29:17 -0400 Subject: [PATCH 57/79] Docs - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/contexts/StateContextProvider.tsx | 6 +++--- new-log-viewer/src/contexts/UrlContextProvider.tsx | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 800bdf4a..1f2b77ee 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -95,8 +95,8 @@ const getLastLogEventNum = (beginLineNumToLogEventNum: BeginLineNumToLogEventNum }; /** - * Provides state management for the application. - * This provider must be wrapped by UrlContextProvider to function correctly. + * Provides state management for the application. This provider must be wrapped by + * UrlContextProvider to function correctly. * * @param props * @param props.children @@ -180,7 +180,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { logEventNumRef.current = logEventNum; }, [logEventNum]); - // On `logEventNum` updates, clamp the number or switch page when needed. + // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { const newPage = (null === logEventNum) ? 1 : diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 383a7702..8279be41 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -56,7 +56,13 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { } } - // `filePath` should always be the last search parameter + // `filePath` should always be the last search parameter so that: + // 1. Users can specify a remote filePath (a URL) that itself contains URL parameters without + // encoding them. E.g. "/?filePath=https://example.com/log/?p1=v1&p2=v2" + // 2. Users can easily modify it in the URL + // + // NOTE: We're relying on URLSearchParams.set() and URLSearchParams.toString() to store and + // serialize the parameters in the order that they were set. const originalFilePath = newSearchParams.get(SEARCH_PARAM_NAMES.FILE_PATH); newSearchParams.delete(SEARCH_PARAM_NAMES.FILE_PATH); if ("undefined" === typeof newFilePath && null !== originalFilePath) { @@ -68,7 +74,7 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { } let searchString = newSearchParams.toString(); - if (!(/%23|%26/).test(searchString)) { + if (false === (/%23|%26/).test(searchString)) { searchString = decodeURIComponent(searchString); } From 6d94ca650fcab76aad9a5e567aa95a8aff901f80 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:30:01 -0400 Subject: [PATCH 58/79] Docs - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/contexts/StateContextProvider.tsx | 2 +- new-log-viewer/src/contexts/UrlContextProvider.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 1f2b77ee..1e44c859 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -209,7 +209,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { mainWorkerPostReq, ]); - // On `filePath` updates, load file. + // On `filePath` update, load file. useEffect(() => { if (null !== filePath) { const cursor: CursorType = (null === pageNumRef.current) ? diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 8279be41..f006e38a 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -73,6 +73,10 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { newSearchParams.set(SEARCH_PARAM_NAMES.FILE_PATH, newFilePath); } + // If the stringified search params doesn't contain characters that would make the URL ambiguous + // to parse, URL-decode it so that the `filePath` remains human-readable. E.g. + // "filePath=https://example.com/log/?s1=1&s2=2#h1=0" would make the final URL ambiguous to + // parse since `filePath` itself contains URL parameters. let searchString = newSearchParams.toString(); if (false === (/%23|%26/).test(searchString)) { searchString = decodeURIComponent(searchString); From a84099989a6f6219cee19c1949e746c48542c054 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:31:00 -0400 Subject: [PATCH 59/79] Rename `getPageNumFromLogEventNum` -> `getChunkNum` - Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- new-log-viewer/src/utils/math.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/new-log-viewer/src/utils/math.ts b/new-log-viewer/src/utils/math.ts index e5a95c9f..c772432f 100644 --- a/new-log-viewer/src/utils/math.ts +++ b/new-log-viewer/src/utils/math.ts @@ -10,14 +10,15 @@ const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, min), max); /** - * Returns the page number based on the log event number and page size. + * Gets the chunk number that contains an item (assuming that item is in a collection and that + * collection is divided into chunks of a given size). * - * @param logEventNum The log event number. - * @param pageSize The number of log events in each page. - * @return The calculated page number. + * @param itemNum + * @param chunkSize + * @return The chunk number. */ -const getPageNumFromLogEventNum = - (logEventNum: number, pageSize: number) => Math.ceil(logEventNum / pageSize); +const getChunkNum = + (itemNum: number, chunkSize: number) => Math.ceil(itemNum / chunkSize); export { clamp, From 86de1096094227f2759a2de59932d40cbb6d100f Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:32:19 -0400 Subject: [PATCH 60/79] Fix references - rename `getPageNumFromLogEventNum` -> `getChunkNum`. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 6 +++--- new-log-viewer/src/services/LogFileManager.ts | 4 ++-- new-log-viewer/src/utils/math.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 1e44c859..a26cbb12 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -21,7 +21,7 @@ import { } from "../typings/worker"; import { clamp, - getPageNumFromLogEventNum, + getChunkNum, } from "../utils/math"; import { updateWindowUrlHashParams, @@ -115,7 +115,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const mainWorkerRef = useRef(null); const numPages = useMemo(() => { - return getPageNumFromLogEventNum(numEvents, PAGE_SIZE); + return getChunkNum(numEvents, PAGE_SIZE); }, [numEvents]); const mainWorkerPostReq = useCallback(( @@ -184,7 +184,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { useEffect(() => { const newPage = (null === logEventNum) ? 1 : - clamp(getPageNumFromLogEventNum(logEventNum, PAGE_SIZE), 1, numPages); + clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPages); if (newPage === pageNumRef.current) { const lastLogEventNum = getLastLogEventNum(beginLineNumToLogEventNum); diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index 3917cf04..d4eb9329 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -10,7 +10,7 @@ import { FileSrcType, } from "../typings/worker"; import {getUint8ArrayFrom} from "../utils/http"; -import {getPageNumFromLogEventNum} from "../utils/math"; +import {getChunkNum} from "../utils/math"; import {formatSizeInBytes} from "../utils/units"; import {getBasenameFromUrlOrDefault} from "../utils/url"; import JsonlDecoder from "./decoders/JsonlDecoder"; @@ -204,7 +204,7 @@ class LogFileManager { if (CURSOR_CODE.LAST_EVENT === code || beginLogEventIdx > this.#numEvents) { // Set to the first event of the last page beginLogEventIdx = - getPageNumFromLogEventNum(this.#numEvents, this.#pageSize) * this.#pageSize; + getChunkNum(this.#numEvents, this.#pageSize) * this.#pageSize; } else if (CURSOR_CODE.TIMESTAMP === code) { throw new Error(`Unsupported cursor type: ${code}`); } diff --git a/new-log-viewer/src/utils/math.ts b/new-log-viewer/src/utils/math.ts index c772432f..df1877dd 100644 --- a/new-log-viewer/src/utils/math.ts +++ b/new-log-viewer/src/utils/math.ts @@ -22,5 +22,5 @@ const getChunkNum = export { clamp, - getPageNumFromLogEventNum, + getChunkNum, }; From d3cbae96add8b0a2082d082144039eb8a9d02744 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:33:01 -0400 Subject: [PATCH 61/79] Rename `numPage` -> `newPageNum`. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index a26cbb12..595e3f86 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -182,15 +182,15 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { - const newPage = (null === logEventNum) ? + const newPageNum = (null === logEventNum) ? 1 : clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPages); - if (newPage === pageNumRef.current) { + if (newPageNum === pageNumRef.current) { const lastLogEventNum = getLastLogEventNum(beginLineNumToLogEventNum); updateLogEventNumInUrl(lastLogEventNum, logEventNumRef.current); } else { - pageNumRef.current = newPage; + pageNumRef.current = newPageNum; mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}, decoderOptions: { From bd25e09a239490cabe22e03991d2529698741e34 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:35:19 -0400 Subject: [PATCH 62/79] Fix getChunkNum to ensure minimum of 1. --- new-log-viewer/src/utils/math.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/utils/math.ts b/new-log-viewer/src/utils/math.ts index df1877dd..61f4bedc 100644 --- a/new-log-viewer/src/utils/math.ts +++ b/new-log-viewer/src/utils/math.ts @@ -18,7 +18,7 @@ const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, * @return The chunk number. */ const getChunkNum = - (itemNum: number, chunkSize: number) => Math.ceil(itemNum / chunkSize); + (itemNum: number, chunkSize: number) => Math.max(1, Math.ceil(itemNum / chunkSize)); export { clamp, From 84d95094267e164f3dd1ca403f5928d2bbee5cec Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:36:01 -0400 Subject: [PATCH 63/79] Use concise arrow function for useMemo callback. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 595e3f86..e9357694 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -114,9 +114,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const mainWorkerRef = useRef(null); - const numPages = useMemo(() => { - return getChunkNum(numEvents, PAGE_SIZE); - }, [numEvents]); + const numPages = useMemo(() => getChunkNum(numEvents, PAGE_SIZE), [numEvents]); const mainWorkerPostReq = useCallback(( code: T, From 3431616ce45dadd7d8cc505d48dfc42a84215510 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:43:27 -0400 Subject: [PATCH 64/79] Refactor ambiguous URL characters check in UrlContextProvider. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index f006e38a..d8afcefd 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -33,6 +33,12 @@ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ [HASH_PARAM_NAMES.LOG_EVENT_NUM]: null, }); +/** + * Regular expression pattern for identifying ambiguous characters in URL. + */ +const AMBIGUOUS_URL_CHARS_REGEX = + new RegExp(`${encodeURIComponent("#")}|${encodeURIComponent("&")}`); + /** * Computes updated URL search parameters based on the provided key-value pairs. * @@ -78,7 +84,7 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { // "filePath=https://example.com/log/?s1=1&s2=2#h1=0" would make the final URL ambiguous to // parse since `filePath` itself contains URL parameters. let searchString = newSearchParams.toString(); - if (false === (/%23|%26/).test(searchString)) { + if (false === AMBIGUOUS_URL_CHARS_REGEX.test(searchString)) { searchString = decodeURIComponent(searchString); } From dfb4d169033ff89bdfee740c91940df8d2c4292c Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 16:44:49 -0400 Subject: [PATCH 65/79] Rename `getWindowSearchParams` & `getWindowSearchParams` -> `getWindowUrlSearchParams` & `getWindowUrlSearchParams` for consistency. --- new-log-viewer/src/contexts/UrlContextProvider.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index d8afcefd..48e3e2c2 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -175,7 +175,7 @@ const copyPermalinkToClipboard = ( * * @return An object containing the search parameters. */ -const getWindowSearchParams = () => { +const getWindowUrlSearchParams = () => { const searchParams : NullableProperties = structuredClone( URL_SEARCH_PARAMS_DEFAULT ); @@ -198,7 +198,7 @@ const getWindowSearchParams = () => { * * @return An object containing the hash parameters. */ -const getWindowHashParams = () => { +const getWindowUrlHashParams = () => { const urlHashParams: NullableProperties = structuredClone( URL_HASH_PARAMS_DEFAULT ); @@ -215,7 +215,7 @@ const getWindowHashParams = () => { return urlHashParams; }; -const searchParams = getWindowSearchParams(); +const searchParams = getWindowUrlSearchParams(); interface UrlContextProviderProps { children: React.ReactNode @@ -234,7 +234,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { ...URL_SEARCH_PARAMS_DEFAULT, ...URL_HASH_PARAMS_DEFAULT, ...searchParams, - ...getWindowHashParams(), + ...getWindowUrlHashParams(), }); useEffect(() => { @@ -243,7 +243,7 @@ const UrlContextProvider = ({children}: UrlContextProviderProps) => { ...URL_SEARCH_PARAMS_DEFAULT, ...URL_HASH_PARAMS_DEFAULT, ...searchParams, - ...getWindowHashParams(), + ...getWindowUrlHashParams(), }); }; From 1a52be6da4eae291bf9dbd1ac4b2c64033cc57b4 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 17:57:42 -0400 Subject: [PATCH 66/79] Fix beginLogEventIdx calculation off by 1. --- new-log-viewer/src/services/LogFileManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index d4eb9329..d49f1af2 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -204,7 +204,7 @@ class LogFileManager { if (CURSOR_CODE.LAST_EVENT === code || beginLogEventIdx > this.#numEvents) { // Set to the first event of the last page beginLogEventIdx = - getChunkNum(this.#numEvents, this.#pageSize) * this.#pageSize; + (getChunkNum(this.#numEvents, this.#pageSize) - 1) * this.#pageSize; } else if (CURSOR_CODE.TIMESTAMP === code) { throw new Error(`Unsupported cursor type: ${code}`); } From a047b1a67833576f2ec7780f8468613d56730ac2 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 17:59:21 -0400 Subject: [PATCH 67/79] Simplify the dependency array of the page-switching useEffect hook to avoid unnecessary invocations. --- .../src/contexts/StateContextProvider.tsx | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index e9357694..e8018536 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -3,7 +3,6 @@ import React, { useCallback, useContext, useEffect, - useMemo, useRef, useState, } from "react"; @@ -43,12 +42,12 @@ const StateContext = createContext({} as StateContextType); * Default values of the state object. */ const STATE_DEFAULT = Object.freeze({ - beginLineNumToLogEventNum: new Map(), + beginLineNumToLogEventNum: new Map(), loadFile: () => null, logData: "Loading...", numEvents: 0, numPages: 0, - pageNum: 0, + pageNum: null, }); const PAGE_SIZE = 10_000; @@ -73,6 +72,7 @@ const updateLogEventNumInUrl = ( lastLogEventNum : clamp(inputLogEventNum, 1, lastLogEventNum); + console.log(newLogEventNum); updateWindowUrlHashParams({ logEventNum: newLogEventNum, }); @@ -105,17 +105,17 @@ const getLastLogEventNum = (beginLineNumToLogEventNum: BeginLineNumToLogEventNum const StateContextProvider = ({children}: StateContextProviderProps) => { const {filePath, logEventNum} = useContext(UrlContext); - const [beginLineNumToLogEventNum, setBeginLineNumToLogEventNum] = - useState(STATE_DEFAULT.beginLineNumToLogEventNum); const [logData, setLogData] = useState(STATE_DEFAULT.logData); const [numEvents, setNumEvents] = useState(STATE_DEFAULT.numEvents); + const beginLineNumToLogEventNumRef = + useRef(STATE_DEFAULT.beginLineNumToLogEventNum); const logEventNumRef = useRef(logEventNum); - const pageNumRef = useRef>(null); + const numEventsRef = useRef(STATE_DEFAULT.numEvents); + const numPagesRef = useRef(STATE_DEFAULT.numPages); + const pageNumRef = useRef>(STATE_DEFAULT.pageNum); const mainWorkerRef = useRef(null); - const numPages = useMemo(() => getChunkNum(numEvents, PAGE_SIZE), [numEvents]); - const mainWorkerPostReq = useCallback(( code: T, args: WorkerReq @@ -129,7 +129,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { switch (code) { case WORKER_RESP_CODE.PAGE_DATA: { setLogData(args.logs); - setBeginLineNumToLogEventNum(args.beginLineNumToLogEventNum); + beginLineNumToLogEventNumRef.current = args.beginLineNumToLogEventNum; const lastLogEventNum = getLastLogEventNum(args.beginLineNumToLogEventNum); updateLogEventNumInUrl(lastLogEventNum, logEventNumRef.current); break; @@ -178,19 +178,24 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { logEventNumRef.current = logEventNum; }, [logEventNum]); + // On `numEvents` update, synchronize `numEventsRef` and update `numPagesRef`. + useEffect(() => { + numEventsRef.current = numEvents; + numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); + }, [numEvents]); + // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { - const newPageNum = (null === logEventNum) ? - 1 : - clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPages); + if (null === logEventNum) { + return; + } + const newPageNum = clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPagesRef.current); if (newPageNum === pageNumRef.current) { - const lastLogEventNum = getLastLogEventNum(beginLineNumToLogEventNum); - updateLogEventNumInUrl(lastLogEventNum, logEventNumRef.current); - } else { - pageNumRef.current = newPageNum; + updateLogEventNumInUrl(numEventsRef.current, logEventNumRef.current); + } else if (null !== pageNumRef.current) { mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { - cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}, + cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}}, decoderOptions: { // TODO: these shall come from config provider formatString: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%process.thread.name] %log.level" + @@ -200,10 +205,10 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }, }); } + + pageNumRef.current = newPageNum; }, [ - beginLineNumToLogEventNum, logEventNum, - numPages, mainWorkerPostReq, ]); @@ -224,11 +229,11 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { return ( From 11f38765bbcc2a6ea4d27a53383ef3003ff1b88c Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 18:02:17 -0400 Subject: [PATCH 68/79] Remove debug console.log. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index e8018536..d7ecd3aa 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -72,7 +72,6 @@ const updateLogEventNumInUrl = ( lastLogEventNum : clamp(inputLogEventNum, 1, lastLogEventNum); - console.log(newLogEventNum); updateWindowUrlHashParams({ logEventNum: newLogEventNum, }); From a7cb2e890c4f8bd9593b95668ed749207d017a8b Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 18:38:02 -0400 Subject: [PATCH 69/79] Refine state synchronization logic. --- .../src/contexts/StateContextProvider.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index d7ecd3aa..453563a9 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -109,7 +109,6 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const beginLineNumToLogEventNumRef = useRef(STATE_DEFAULT.beginLineNumToLogEventNum); const logEventNumRef = useRef(logEventNum); - const numEventsRef = useRef(STATE_DEFAULT.numEvents); const numPagesRef = useRef(STATE_DEFAULT.numPages); const pageNumRef = useRef>(STATE_DEFAULT.pageNum); @@ -177,21 +176,18 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { logEventNumRef.current = logEventNum; }, [logEventNum]); - // On `numEvents` update, synchronize `numEventsRef` and update `numPagesRef`. - useEffect(() => { - numEventsRef.current = numEvents; - numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); - }, [numEvents]); - + // On `numEvents` update, re-calculate `numPagesRef`. // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { - if (null === logEventNum) { + if (null === logEventNum || 0 === numEvents) { return; + } else if (0 === numPagesRef.current) { + numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); } const newPageNum = clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPagesRef.current); if (newPageNum === pageNumRef.current) { - updateLogEventNumInUrl(numEventsRef.current, logEventNumRef.current); + updateLogEventNumInUrl(numEvents, logEventNumRef.current); } else if (null !== pageNumRef.current) { mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}}, @@ -207,19 +203,22 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { pageNumRef.current = newPageNum; }, [ + numEvents, logEventNum, mainWorkerPostReq, ]); // On `filePath` update, load file. useEffect(() => { - if (null !== filePath) { - const cursor: CursorType = (null === pageNumRef.current) ? - {code: CURSOR_CODE.LAST_EVENT, args: null} : - {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: pageNumRef.current}}; - - loadFile(filePath, cursor); + if (null === filePath) { + return; + } + let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; + if (null !== logEventNumRef.current) { + const newPageNum = Math.max(getChunkNum(logEventNumRef.current, PAGE_SIZE), 1); + cursor = {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}}; } + loadFile(filePath, cursor); }, [ filePath, loadFile, From 79e896a6a458f71b58fa2d1bbaff802f002fb6d4 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Fri, 9 Aug 2024 18:39:32 -0400 Subject: [PATCH 70/79] Remove redundant `else` after early return. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 453563a9..481ecbe9 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -181,10 +181,10 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { useEffect(() => { if (null === logEventNum || 0 === numEvents) { return; - } else if (0 === numPagesRef.current) { + } + if (0 === numPagesRef.current) { numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); } - const newPageNum = clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPagesRef.current); if (newPageNum === pageNumRef.current) { updateLogEventNumInUrl(numEvents, logEventNumRef.current); From 4022cf2bc2a0d8add74aa36ec9412d94312fb15e Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Sun, 11 Aug 2024 16:37:29 -0400 Subject: [PATCH 71/79] Replace literals with constants and update docs. --- .../src/contexts/StateContextProvider.tsx | 22 ++++++++++++++----- .../src/contexts/UrlContextProvider.tsx | 2 ++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 481ecbe9..62adb5f1 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -24,6 +24,8 @@ import { } from "../utils/math"; import { updateWindowUrlHashParams, + URL_HASH_PARAMS_DEFAULT, + URL_SEARCH_PARAMS_DEFAULT, UrlContext, } from "./UrlContextProvider"; @@ -179,16 +181,23 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { // On `numEvents` update, re-calculate `numPagesRef`. // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { - if (null === logEventNum || 0 === numEvents) { + if (URL_HASH_PARAMS_DEFAULT.logEventNum === logEventNum || + STATE_DEFAULT.numEvents === numEvents) { return; } - if (0 === numPagesRef.current) { + if (STATE_DEFAULT.numPages === numPagesRef.current) { numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); } const newPageNum = clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPagesRef.current); if (newPageNum === pageNumRef.current) { + // If no page switching is needed, update `logEventNum` in the URL. updateLogEventNumInUrl(numEvents, logEventNumRef.current); - } else if (null !== pageNumRef.current) { + } else if (STATE_DEFAULT.pageNum !== pageNumRef.current) { + // On non-initial page load, when `pageNum` changes due to a `logEventNum` update, + // request page switching. + // Note `updateLogEventNumInUrl()` is called in the handling of any received + // `WORKER_RESP_CODE.PAGE_DATA`, which the response code of + // any `WORKER_REQ_CODE.LOAD_PAGE` requests. mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}}, decoderOptions: { @@ -210,11 +219,14 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { // On `filePath` update, load file. useEffect(() => { - if (null === filePath) { + if (URL_SEARCH_PARAMS_DEFAULT.filePath === filePath) { return; } + let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; - if (null !== logEventNumRef.current) { + if (URL_HASH_PARAMS_DEFAULT.logEventNum !== logEventNumRef.current) { + // If user provides an initial `logEventNum`, calculate `pageNum` so that the initially + // loaded page will contain the desired log event. const newPageNum = Math.max(getChunkNum(logEventNumRef.current, PAGE_SIZE), 1); cursor = {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}}; } diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 48e3e2c2..333a945f 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -266,5 +266,7 @@ export { copyPermalinkToClipboard, updateWindowUrlHashParams, updateWindowUrlSearchParams, + URL_HASH_PARAMS_DEFAULT, + URL_SEARCH_PARAMS_DEFAULT, UrlContext, }; From 72178a5811d38801cbaf91baff6805602def2e59 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Sun, 11 Aug 2024 16:38:24 -0400 Subject: [PATCH 72/79] Update docs. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 62adb5f1..235ff28f 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -195,7 +195,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { } else if (STATE_DEFAULT.pageNum !== pageNumRef.current) { // On non-initial page load, when `pageNum` changes due to a `logEventNum` update, // request page switching. - // Note `updateLogEventNumInUrl()` is called in the handling of any received + // Note `updateLogEventNumInUrl()` is also called in the handling of any received // `WORKER_RESP_CODE.PAGE_DATA`, which the response code of // any `WORKER_REQ_CODE.LOAD_PAGE` requests. mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { From 97c8f359ded5466059e7b5e3bc4b0ec63731a6a9 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Sun, 11 Aug 2024 17:38:26 -0400 Subject: [PATCH 73/79] Add documentation on using state references in React hooks. --- new-log-viewer/docs/dev-guide/state-and-stateRef.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 new-log-viewer/docs/dev-guide/state-and-stateRef.md diff --git a/new-log-viewer/docs/dev-guide/state-and-stateRef.md b/new-log-viewer/docs/dev-guide/state-and-stateRef.md new file mode 100644 index 00000000..1a7bd36c --- /dev/null +++ b/new-log-viewer/docs/dev-guide/state-and-stateRef.md @@ -0,0 +1,5 @@ +# Using State References to Maintain Current Values in React Hooks + +Whenever we encounter a stateRef reference alongside a state variable (e.g., logEventNumRef vs. logEventNum), the +reference is guaranteed to hold the current value of the state variable. We create these references (mirrors) to use the +current state values in React hooks without including the state variables in the dependency arrays. From 39008d5f572ca9268f3b3a202055c44559e36427 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Sun, 11 Aug 2024 17:38:50 -0400 Subject: [PATCH 74/79] Update docs. --- new-log-viewer/docs/dev-guide/state-and-stateRef.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/new-log-viewer/docs/dev-guide/state-and-stateRef.md b/new-log-viewer/docs/dev-guide/state-and-stateRef.md index 1a7bd36c..48eb9ea0 100644 --- a/new-log-viewer/docs/dev-guide/state-and-stateRef.md +++ b/new-log-viewer/docs/dev-guide/state-and-stateRef.md @@ -1,5 +1,5 @@ # Using State References to Maintain Current Values in React Hooks -Whenever we encounter a stateRef reference alongside a state variable (e.g., logEventNumRef vs. logEventNum), the -reference is guaranteed to hold the current value of the state variable. We create these references (mirrors) to use the +Whenever we encounter a `stateRef` reference alongside a `state` variable (e.g., `logEventNumRef` vs. `logEventNum`), +the reference is guaranteed to hold the current value of the state variable. We create these references (mirrors) to use the current state values in React hooks without including the state variables in the dependency arrays. From 89a4bbf20677c779facec80b6953ec59ffec5e59 Mon Sep 17 00:00:00 2001 From: Kirk Rodrigues <2454684+kirkrodrigues@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:17:14 -0400 Subject: [PATCH 75/79] Refactor docs. --- new-log-viewer/docs/dev-guide/state-and-stateRef.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/new-log-viewer/docs/dev-guide/state-and-stateRef.md b/new-log-viewer/docs/dev-guide/state-and-stateRef.md index 48eb9ea0..6072ba23 100644 --- a/new-log-viewer/docs/dev-guide/state-and-stateRef.md +++ b/new-log-viewer/docs/dev-guide/state-and-stateRef.md @@ -1,5 +1,6 @@ -# Using State References to Maintain Current Values in React Hooks +# Using state references to maintain current values in React Hooks -Whenever we encounter a `stateRef` reference alongside a `state` variable (e.g., `logEventNumRef` vs. `logEventNum`), -the reference is guaranteed to hold the current value of the state variable. We create these references (mirrors) to use the -current state values in React hooks without including the state variables in the dependency arrays. +Whenever we encounter a `stateRef` reference alongside a `state` variable (e.g., `logEventNumRef` +and `logEventNum` in `StateContextProvider`), the reference is meant to hold the current value of +the state variable. We create these references (mirrors) to use the current state values in React +hooks without including the state variables in the dependency arrays. From 12ad8837e55d7ad80d9d438551846e05f28e6d99 Mon Sep 17 00:00:00 2001 From: Kirk Rodrigues <2454684+kirkrodrigues@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:20:37 -0400 Subject: [PATCH 76/79] Unwrap line. --- new-log-viewer/src/services/LogFileManager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/new-log-viewer/src/services/LogFileManager.ts b/new-log-viewer/src/services/LogFileManager.ts index d49f1af2..2dcfbf62 100644 --- a/new-log-viewer/src/services/LogFileManager.ts +++ b/new-log-viewer/src/services/LogFileManager.ts @@ -203,8 +203,7 @@ class LogFileManager { } if (CURSOR_CODE.LAST_EVENT === code || beginLogEventIdx > this.#numEvents) { // Set to the first event of the last page - beginLogEventIdx = - (getChunkNum(this.#numEvents, this.#pageSize) - 1) * this.#pageSize; + beginLogEventIdx = (getChunkNum(this.#numEvents, this.#pageSize) - 1) * this.#pageSize; } else if (CURSOR_CODE.TIMESTAMP === code) { throw new Error(`Unsupported cursor type: ${code}`); } From 51d68f7670c6b43b209c3fd91394b9537f124a10 Mon Sep 17 00:00:00 2001 From: Kirk Rodrigues <2454684+kirkrodrigues@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:21:29 -0400 Subject: [PATCH 77/79] Refactor some comments and add a newline. --- .../src/contexts/StateContextProvider.tsx | 19 ++++++++++--------- .../src/contexts/UrlContextProvider.tsx | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 235ff28f..02a5dd5a 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -178,26 +178,26 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { logEventNumRef.current = logEventNum; }, [logEventNum]); - // On `numEvents` update, re-calculate `numPagesRef`. + // On `numEvents` update, recalculate `numPagesRef`. // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { if (URL_HASH_PARAMS_DEFAULT.logEventNum === logEventNum || STATE_DEFAULT.numEvents === numEvents) { return; } + if (STATE_DEFAULT.numPages === numPagesRef.current) { numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); } const newPageNum = clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPagesRef.current); if (newPageNum === pageNumRef.current) { - // If no page switching is needed, update `logEventNum` in the URL. + // Don't need to switch pages so just update `logEventNum` in the URL. updateLogEventNumInUrl(numEvents, logEventNumRef.current); } else if (STATE_DEFAULT.pageNum !== pageNumRef.current) { - // On non-initial page load, when `pageNum` changes due to a `logEventNum` update, - // request page switching. - // Note `updateLogEventNumInUrl()` is also called in the handling of any received - // `WORKER_RESP_CODE.PAGE_DATA`, which the response code of - // any `WORKER_REQ_CODE.LOAD_PAGE` requests. + // This is not the initial page load, so request a page switch. + // NOTE: We don't need to call `updateLogEventNumInUrl()` since it's called when + // handling the `WORKER_RESP_CODE.PAGE_DATA` response (the response to + // `WORKER_REQ_CODE.LOAD_PAGE` requests) . mainWorkerPostReq(WORKER_REQ_CODE.LOAD_PAGE, { cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}}, decoderOptions: { @@ -225,8 +225,9 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; if (URL_HASH_PARAMS_DEFAULT.logEventNum !== logEventNumRef.current) { - // If user provides an initial `logEventNum`, calculate `pageNum` so that the initially - // loaded page will contain the desired log event. + // Set which page to load since the user specified a specific `logEventNum`. + // NOTE: Since we don't know how many pages the log file contains, we only clamp the + // minimum of the page number. const newPageNum = Math.max(getChunkNum(logEventNumRef.current, PAGE_SIZE), 1); cursor = {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}}; } diff --git a/new-log-viewer/src/contexts/UrlContextProvider.tsx b/new-log-viewer/src/contexts/UrlContextProvider.tsx index 333a945f..5862aa9d 100644 --- a/new-log-viewer/src/contexts/UrlContextProvider.tsx +++ b/new-log-viewer/src/contexts/UrlContextProvider.tsx @@ -34,7 +34,7 @@ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ }); /** - * Regular expression pattern for identifying ambiguous characters in URL. + * Regular expression pattern for identifying ambiguous characters in a URL. */ const AMBIGUOUS_URL_CHARS_REGEX = new RegExp(`${encodeURIComponent("#")}|${encodeURIComponent("&")}`); From e50379ea791384025120552c8309258593b7271f Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 12 Aug 2024 17:46:52 -0400 Subject: [PATCH 78/79] Split `numPagesRef` updates from the `logEventNum` update useEffect hook. --- .../src/contexts/StateContextProvider.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index 02a5dd5a..a5e82ed4 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -52,7 +52,7 @@ const STATE_DEFAULT = Object.freeze({ pageNum: null, }); -const PAGE_SIZE = 10_000; +const PAGE_SIZE = 1_000; interface StateContextProviderProps { children: React.ReactNode @@ -179,16 +179,20 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }, [logEventNum]); // On `numEvents` update, recalculate `numPagesRef`. - // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { - if (URL_HASH_PARAMS_DEFAULT.logEventNum === logEventNum || - STATE_DEFAULT.numEvents === numEvents) { + if (STATE_DEFAULT.numEvents === numEvents) { return; } - if (STATE_DEFAULT.numPages === numPagesRef.current) { - numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); + numPagesRef.current = getChunkNum(numEvents, PAGE_SIZE); + }, [numEvents]); + + // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. + useEffect(() => { + if (URL_HASH_PARAMS_DEFAULT.logEventNum === logEventNum) { + return; } + const newPageNum = clamp(getChunkNum(logEventNum, PAGE_SIZE), 1, numPagesRef.current); if (newPageNum === pageNumRef.current) { // Don't need to switch pages so just update `logEventNum` in the URL. From ce42600dde3444ab5e3007d379f17b15b58813d7 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 12 Aug 2024 17:48:14 -0400 Subject: [PATCH 79/79] Restore PAGE_SIZE to 10_000. --- new-log-viewer/src/contexts/StateContextProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index a5e82ed4..6fa65aad 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -52,7 +52,7 @@ const STATE_DEFAULT = Object.freeze({ pageNum: null, }); -const PAGE_SIZE = 1_000; +const PAGE_SIZE = 10_000; interface StateContextProviderProps { children: React.ReactNode