Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(URL): Share link with search parameters #173

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import {
Tooltip,
} 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 {StateContext} from "../../../../../contexts/StateContextProvider";
import {
copyPermalinkToClipboard,
UrlContext,
} from "../../../../../contexts/UrlContextProvider";
import {
QUERY_PROGRESS_VALUE_MAX,
QueryArgs,
Expand Down Expand Up @@ -62,20 +67,30 @@ const getIsRegex =
*
* @return
*/
// eslint max-lines-per-function ["error", { "max": 140 }]
// eslint-disable-next-line max-lines-per-function
const SearchTabPanel = () => {
const {queryProgress, queryResults, startQuery, uiState} = useContext(StateContext);
const {queryString: urlQueryString} = useContext(UrlContext);
const [isAllExpanded, setIsAllExpanded] = useState<boolean>(true);
const [queryOptions, setQueryOptions] = useState<QUERY_OPTION[]>([]);
const [queryString, setQueryString] = useState<string>("");
const [queryString, setQueryString] = useState<string>(urlQueryString ?? "");

const handleCollapseAllButtonClick = () => {
setIsAllExpanded((v) => !v);
};
const handleShareButtonClick = () => {
copyPermalinkToClipboard({}, {
queryString: queryString,
queryIsCaseSensitive: getIsCaseSensitive(queryOptions),
queryIsRegex: getIsRegex(queryOptions),
});
};

const handleQuerySubmit = (newArgs: Partial<QueryArgs>) => {
startQuery({
isCaseSensitive: getIsCaseSensitive(queryOptions),
isRegex: getIsRegex(queryOptions),
queryIsCaseSensitive: getIsCaseSensitive(queryOptions),
queryIsRegex: getIsRegex(queryOptions),
queryString: queryString,
...newArgs,
});
Expand All @@ -92,8 +107,8 @@ const SearchTabPanel = () => {
) => {
setQueryOptions(newOptions);
handleQuerySubmit({
isCaseSensitive: getIsCaseSensitive(newOptions),
isRegex: getIsRegex(newOptions),
queryIsCaseSensitive: getIsCaseSensitive(newOptions),
queryIsRegex: getIsRegex(newOptions),
});
};

Expand All @@ -104,16 +119,24 @@ const SearchTabPanel = () => {
tabName={TAB_NAME.SEARCH}
title={TAB_DISPLAY_NAMES[TAB_NAME.SEARCH]}
titleButtons={
<PanelTitleButton
title={isAllExpanded ?
"Collapse all" :
"Expand all"}
onClick={handleCollapseAllButtonClick}
>
{isAllExpanded ?
<UnfoldLessIcon/> :
<UnfoldMoreIcon/>}
</PanelTitleButton>
<>
<PanelTitleButton
title={isAllExpanded ?
"Collapse all" :
"Expand all"}
onClick={handleCollapseAllButtonClick}
>
{isAllExpanded ?
<UnfoldLessIcon/> :
<UnfoldMoreIcon/>}
</PanelTitleButton>
<PanelTitleButton
title={"Copy URL with search parameters"}
onClick={handleShareButtonClick}
>
<ShareIcon/>
</PanelTitleButton>
</>
}
>
<Box className={"search-tab-container"}>
Expand All @@ -123,6 +146,7 @@ const SearchTabPanel = () => {
maxRows={7}
placeholder={"Search"}
size={"sm"}
value={queryString}
endDecorator={
<ToggleButtonGroup
disabled={isQueryInputBoxDisabled}
Expand Down
61 changes: 48 additions & 13 deletions src/contexts/StateContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ import {
QueryResults,
} from "../typings/query";
import {UI_STATE} from "../typings/states";
import {SEARCH_PARAM_NAMES} from "../typings/url";
import {
HASH_PARAM_NAMES,
SEARCH_PARAM_NAMES,
} from "../typings/url";
import {
BeginLineNumToLogEventNumMap,
CURSOR_CODE,
Expand Down Expand Up @@ -247,14 +250,20 @@ const updateUrlIfEventOnPage = (
// eslint-disable-next-line max-lines-per-function, max-statements
const StateContextProvider = ({children}: StateContextProviderProps) => {
const {postPopUp} = useContext(NotificationContext);
const {filePath, logEventNum} = useContext(UrlContext);
const {
filePath,
queryIsCaseSensitive,
queryIsRegex,
logEventNum,
queryString,
} = useContext(UrlContext);

// States
const [exportProgress, setExportProgress] =
useState<Nullable<number>>(STATE_DEFAULT.exportProgress);
const [fileName, setFileName] = useState<string>(STATE_DEFAULT.fileName);
const [isSettingsModalOpen, setIsSettingsModalOpen] =
useState<boolean>(STATE_DEFAULT.isSettingsModalOpen);
const [fileName, setFileName] = useState<string>(STATE_DEFAULT.fileName);
const [logData, setLogData] = useState<string>(STATE_DEFAULT.logData);
const [numEvents, setNumEvents] = useState<number>(STATE_DEFAULT.numEvents);
const [numPages, setNumPages] = useState<number>(STATE_DEFAULT.numPages);
Expand All @@ -275,6 +284,16 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
const pageNumRef = useRef<number>(pageNum);
const uiStateRef = useRef<UI_STATE>(uiState);

const startQuery = useCallback((queryArgs: QueryArgs) => {
setQueryResults(STATE_DEFAULT.queryResults);
if (null === mainWorkerRef.current) {
console.error("Unexpected null mainWorkerRef.current");

return;
}
workerPostReq(mainWorkerRef.current, WORKER_REQ_CODE.START_QUERY, queryArgs);
}, []);

const handleMainWorkerResp = useCallback((ev: MessageEvent<MainWorkerRespMessage>) => {
const {code, args} = ev.data;
console.log(`[MainWorker -> Renderer] code=${code}`);
Expand Down Expand Up @@ -367,16 +386,6 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
}
}, [postPopUp]);

const startQuery = useCallback((queryArgs: QueryArgs) => {
setQueryResults(STATE_DEFAULT.queryResults);
if (null === mainWorkerRef.current) {
console.error("Unexpected null mainWorkerRef.current");

return;
}
workerPostReq(mainWorkerRef.current, WORKER_REQ_CODE.START_QUERY, queryArgs);
}, []);

const exportLogs = useCallback(() => {
if (null === mainWorkerRef.current) {
console.error("Unexpected null mainWorkerRef.current");
Expand Down Expand Up @@ -405,6 +414,8 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
setLogData("Loading...");
setOnDiskFileSizeInBytes(STATE_DEFAULT.onDiskFileSizeInBytes);
setExportProgress(STATE_DEFAULT.exportProgress);
setQueryResults(STATE_DEFAULT.queryResults);
setQueryProgress(QUERY_PROGRESS_VALUE_MIN);

if ("string" !== typeof fileSrc) {
updateWindowUrlSearchParams({[SEARCH_PARAM_NAMES.FILE_PATH]: null});
Expand Down Expand Up @@ -479,6 +490,30 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
}
}, [uiState]);

useEffect(() => {
if (uiState === UI_STATE.READY) {
if (
URL_HASH_PARAMS_DEFAULT.queryString !== queryString &&
URL_HASH_PARAMS_DEFAULT.queryIsCaseSensitive !== queryIsCaseSensitive &&
URL_HASH_PARAMS_DEFAULT.queryIsRegex !== queryIsRegex
) {
startQuery({queryString, queryIsCaseSensitive, queryIsRegex});
}
updateWindowUrlHashParams({
[HASH_PARAM_NAMES.QUERY_STRING]: URL_HASH_PARAMS_DEFAULT.queryString,
[HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE]:
URL_HASH_PARAMS_DEFAULT.queryIsCaseSensitive,
[HASH_PARAM_NAMES.QUERY_IS_REGEX]: URL_HASH_PARAMS_DEFAULT.queryIsRegex,
});
}
}, [
queryString,
queryIsCaseSensitive,
queryIsRegex,
startQuery,
uiState,
]);

// On `logEventNum` update, clamp it then switch page if necessary or simply update the URL.
useEffect(() => {
if (null === mainWorkerRef.current) {
Expand Down
15 changes: 15 additions & 0 deletions src/contexts/UrlContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
*/
const URL_HASH_PARAMS_DEFAULT = Object.freeze({
[HASH_PARAM_NAMES.LOG_EVENT_NUM]: null,
[HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE]: null,
[HASH_PARAM_NAMES.QUERY_IS_REGEX]: null,
[HASH_PARAM_NAMES.QUERY_STRING]: null,
});

/**
Expand Down Expand Up @@ -181,6 +184,10 @@
);
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`.
Expand Down Expand Up @@ -211,6 +218,14 @@
null :
parsed;
}
const isCaseSensitive = hashParams.get(HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE);
if (null !== isCaseSensitive) {
urlHashParams[HASH_PARAM_NAMES.QUERY_IS_CASE_SENSITIVE] = "true" === isCaseSensitive.toLowerCase();

Check failure on line 223 in src/contexts/UrlContextProvider.tsx

View workflow job for this annotation

GitHub Actions / lint-check

This line has a length of 107. Maximum allowed is 100
}
const isRegex = hashParams.get(HASH_PARAM_NAMES.QUERY_IS_REGEX);
if (null !== isRegex) {
urlHashParams[HASH_PARAM_NAMES.QUERY_IS_REGEX] = "true" === isRegex.toLowerCase();
}

return urlHashParams;
};
Expand Down
8 changes: 5 additions & 3 deletions src/services/LogFileManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,13 @@
*
* @param queryArgs
* @param queryArgs.queryString
* @param queryArgs.isRegex

Check failure on line 294 in src/services/LogFileManager/index.ts

View workflow job for this annotation

GitHub Actions / lint-check

@param "queryArgs.isRegex" does not exist on queryArgs
* @param queryArgs.isCaseSensitive

Check failure on line 295 in src/services/LogFileManager/index.ts

View workflow job for this annotation

GitHub Actions / lint-check

@param "queryArgs.isCaseSensitive" does not exist on queryArgs
* @param queryArgs.queryIsRegex
* @param queryArgs.queryIsCaseSensitive
* @throws {SyntaxError} if the query regex string is invalid.
*/
startQuery ({queryString, isRegex, isCaseSensitive}: QueryArgs): void {
startQuery ({queryString, queryIsRegex, queryIsCaseSensitive}: QueryArgs): void {
this.#queryId++;
this.#queryCount = 0;

Expand All @@ -309,10 +311,10 @@
}

// Construct query RegExp
const regexPattern = isRegex ?
const regexPattern = queryIsRegex ?
queryString :
queryString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regexFlags = isCaseSensitive ?
const regexFlags = queryIsCaseSensitive ?
"" :
"i";

Expand Down
4 changes: 2 additions & 2 deletions src/typings/query.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
interface QueryArgs {
queryString: string;
isCaseSensitive: boolean;
isRegex: boolean;
queryIsCaseSensitive: boolean;
queryIsRegex: boolean;
}

type TextRange = [number, number];
Expand Down
9 changes: 8 additions & 1 deletion src/typings/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ enum SEARCH_PARAM_NAMES {

enum HASH_PARAM_NAMES {
LOG_EVENT_NUM = "logEventNum",
QUERY_IS_CASE_SENSITIVE = "queryIsCaseSensitive",
QUERY_IS_REGEX = "queryIsRegex",
QUERY_STRING = "queryString",
}

interface UrlSearchParams {
[SEARCH_PARAM_NAMES.FILE_PATH]: string;

}

interface UrlHashParams {
logEventNum: number;
[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 = {
Expand Down
4 changes: 2 additions & 2 deletions src/typings/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ type WorkerReqMap = {
};
[WORKER_REQ_CODE.START_QUERY]: {
queryString: string;
isRegex: boolean;
isCaseSensitive: boolean;
queryIsCaseSensitive: boolean;
queryIsRegex: boolean;
};
};

Expand Down
Loading