diff --git a/src/components/AppController.tsx b/src/components/AppController.tsx index 6ec75747b..0edc16ef0 100644 --- a/src/components/AppController.tsx +++ b/src/components/AppController.tsx @@ -14,6 +14,7 @@ import useContextStore from "../stores/contextStore"; import useLogFileManagerStore from "../stores/logFileManagerProxyStore"; import useLogFileStore from "../stores/logFileStore"; import {handleErrorWithNotification} from "../stores/notificationStore"; +import useQueryStore from "../stores/queryStore"; import useUiStore from "../stores/uiStore"; import useViewStore from "../stores/viewStore"; import {UI_STATE} from "../typings/states"; @@ -78,7 +79,9 @@ interface AppControllerProps { * @return */ const AppController = ({children}: AppControllerProps) => { - const {filePath, isPrettified, logEventNum} = useContext(UrlContext); + const { + filePath, isPrettified, logEventNum, queryString, queryIsRegex, queryIsCaseSensitive, + } = useContext(UrlContext); // States const setLogEventNum = useContextStore((state) => state.setLogEventNum); @@ -88,6 +91,7 @@ const AppController = ({children}: AppControllerProps) => { const beginLineNumToLogEventNum = useViewStore((state) => state.beginLineNumToLogEventNum); const setIsPrettified = useViewStore((state) => state.updateIsPrettified); const updatePageData = useViewStore((state) => state.updatePageData); + const uiState = useUiStore((state) => state.uiState); const setUiState = useUiStore((state) => state.setUiState); // Refs @@ -167,6 +171,36 @@ const AppController = ({children}: AppControllerProps) => { loadFile, ]); + // Synchronize `queryIsCaseSensitive` with the Zustand QueryStore. + useEffect(() => { + if (null !== queryIsCaseSensitive) { + const {setQueryIsCaseSensitive} = useQueryStore.getState(); + setQueryIsCaseSensitive(queryIsCaseSensitive); + } + }, [queryIsCaseSensitive]); + + // Synchronize `queryIsRegex` with the Zustand QueryStore. + useEffect(() => { + if (null !== queryIsRegex) { + const {setQueryIsRegex} = useQueryStore.getState(); + setQueryIsRegex(queryIsRegex); + } + }, [queryIsRegex]); + + useEffect(() => { + if (null !== queryString) { + const {setQueryString} = useQueryStore.getState(); + setQueryString(queryString); + } + if (UI_STATE.READY === uiState) { + const {startQuery} = useQueryStore.getState(); + startQuery(); + } + }, [ + uiState, + queryString, + ]); + return children; }; diff --git a/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx b/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx index 6ae403e02..58aeff83b 100644 --- a/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx +++ b/src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx @@ -1,13 +1,18 @@ -import {useState} from "react"; +import { + useCallback, + useState, +} from "react"; import { AccordionGroup, Box, } from "@mui/joy"; +import ShareIcon from "@mui/icons-material/Share"; import UnfoldLessIcon from "@mui/icons-material/UnfoldLess"; import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore"; +import {copyPermalinkToClipboard} from "../../../../../contexts/UrlContextProvider"; import useQueryStore from "../../../../../stores/queryStore"; import { TAB_DISPLAY_NAMES, @@ -27,29 +32,55 @@ import "./index.css"; * @return */ const SearchTabPanel = () => { + const queryIsCaseSensitive = useQueryStore((state) => state.queryIsCaseSensitive); + const queryIsRegex = useQueryStore((state) => state.queryIsRegex); + const queryString = useQueryStore((state) => state.queryString); const queryResults = useQueryStore((state) => state.queryResults); const [isAllExpanded, setIsAllExpanded] = useState(true); - const handleCollapseAllButtonClick = () => { + const handleCollapseAllButtonClick = useCallback(() => { setIsAllExpanded((v) => !v); - }; + }, []); + + const handleShareButtonClick = useCallback(() => { + copyPermalinkToClipboard({}, { + logEventNum: null, + queryString: "" === queryString ? + null : + queryString, + queryIsCaseSensitive: queryIsCaseSensitive, + queryIsRegex: queryIsRegex, + }); + }, [ + queryIsCaseSensitive, + queryIsRegex, + queryString, + ]); return ( - {isAllExpanded ? - : - } - + <> + + {isAllExpanded ? + : + } + + + + + } > diff --git a/src/contexts/UrlContextProvider.tsx b/src/contexts/UrlContextProvider.tsx index b03978691..f7fe7998d 100644 --- a/src/contexts/UrlContextProvider.tsx +++ b/src/contexts/UrlContextProvider.tsx @@ -1,3 +1,4 @@ +/* eslint max-lines: ["error", 350] */ import React, { createContext, useEffect, @@ -32,6 +33,9 @@ const URL_SEARCH_PARAMS_DEFAULT = Object.freeze({ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ [HASH_PARAM_NAMES.IS_PRETTIFIED]: false, [HASH_PARAM_NAMES.LOG_EVENT_NUM]: null, + [HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE]: false, + [HASH_PARAM_NAMES.QUERY_IS_REGEX]: false, + [HASH_PARAM_NAMES.QUERY_STRING]: null, }); /** @@ -102,7 +106,7 @@ const getUpdatedSearchParams = (updates: UrlSearchParamUpdatesType) => { const getUpdatedHashParams = (updates: UrlHashParamUpdatesType) => { const newHashParams = new URLSearchParams(window.location.hash.substring(1)); for (const [key, value] of Object.entries(updates)) { - if (null === value) { + if (null === value || false === value) { newHashParams.delete(key); } else { newHashParams.set(key, String(value)); @@ -182,6 +186,10 @@ const getWindowUrlSearchParams = () => { ); const urlSearchParams = new URLSearchParams(window.location.search.substring(1)); + urlSearchParams.forEach((value, key) => { + searchParams[key as keyof UrlSearchParams] = value; + }); + 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`. @@ -218,6 +226,22 @@ const getWindowUrlHashParams = () => { parsed; } + const queryString = hashParams.get(HASH_PARAM_NAMES.QUERY_STRING); + if (null !== queryString) { + urlHashParams[HASH_PARAM_NAMES.QUERY_STRING] = queryString; + } + + const queryIsCaseSensitive = hashParams.get(HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE); + if (null !== queryIsCaseSensitive) { + urlHashParams[HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE] = + "true" === queryIsCaseSensitive; + } + + const queryIsRegex = hashParams.get(HASH_PARAM_NAMES.QUERY_IS_REGEX); + if (null !== queryIsRegex) { + urlHashParams[HASH_PARAM_NAMES.QUERY_IS_REGEX] = "true" === queryIsRegex; + } + const isPrettified = hashParams.get(HASH_PARAM_NAMES.IS_PRETTIFIED); if (null !== isPrettified) { urlHashParams[HASH_PARAM_NAMES.IS_PRETTIFIED] = "true" === isPrettified; diff --git a/src/services/LogFileManagerProxy.ts b/src/services/LogFileManagerProxy.ts index 6410bd376..7be97d0f7 100644 --- a/src/services/LogFileManagerProxy.ts +++ b/src/services/LogFileManagerProxy.ts @@ -75,7 +75,7 @@ class LogFileManagerProxy { */ #getLogFileManager (): LogFileManager { if (null === this.logFileManager) { - throw new Error("LogFileManager hasn't initialized"); + throw new Error("LogFileManager hasn't been initialized"); } return this.logFileManager; diff --git a/src/typings/url.ts b/src/typings/url.ts index b9e331228..210b23fe3 100644 --- a/src/typings/url.ts +++ b/src/typings/url.ts @@ -8,6 +8,9 @@ enum SEARCH_PARAM_NAMES { enum HASH_PARAM_NAMES { LOG_EVENT_NUM = "logEventNum", IS_PRETTIFIED = "isPrettified", + QUERY_IS_CASE_SENSITIVE = "queryIsCaseSensitive", + QUERY_IS_REGEX = "queryIsRegex", + QUERY_STRING = "queryString", } interface UrlSearchParams { @@ -17,6 +20,9 @@ interface UrlSearchParams { interface UrlHashParams { [HASH_PARAM_NAMES.IS_PRETTIFIED]: boolean; [HASH_PARAM_NAMES.LOG_EVENT_NUM]: number; + [HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE]: boolean; + [HASH_PARAM_NAMES.QUERY_IS_REGEX]: boolean; + [HASH_PARAM_NAMES.QUERY_STRING]: string; } type UrlSearchParamUpdatesType = {