Skip to content

Commit

Permalink
[Nu-1780] add ui
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotr Rudnicki committed Sep 6, 2024
1 parent 3f3cd29 commit d984547
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 13 deletions.
1 change: 1 addition & 0 deletions designer/client/src/assets/img/advanced-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions designer/client/src/components/table/SearchFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { css, cx } from "@emotion/css";
import React from "react";
import SearchSvg from "../../assets/img/search.svg";
import AdvancedSearchSvg from "../../assets/img/advanced-search.svg";
import DeleteSvg from "../../assets/img/toolbarButtons/delete.svg";
import { useTheme } from "@mui/material";

Expand All @@ -9,6 +10,32 @@ const flex = css({
flex: 1,
});

export function AdvancedOptionsIcon(props: {
isActive?: boolean;
collapseHandler: React.Dispatch<React.SetStateAction<boolean>>;
}): JSX.Element {
const theme = useTheme();

const toggleCollapseHandler = () => {
props.collapseHandler((p) => !p);
};

return (
<AdvancedSearchSvg
onClick={toggleCollapseHandler}
className={cx(
flex,
css({
".icon-fill": {
fill: "none",
stroke: !props.isActive ? theme.palette.text.secondary : theme.palette.primary.main,
},
}),
)}
/>
);
}

export function SearchIcon(props: { isEmpty?: boolean }): JSX.Element {
const theme = useTheme();
return (
Expand Down
19 changes: 12 additions & 7 deletions designer/client/src/components/themed/InputWithIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { css, cx } from "@emotion/css";
import { styled, useTheme } from "@mui/material";
import React, { forwardRef, PropsWithChildren, ReactElement, useCallback, useImperativeHandle, useRef } from "react";
import { Box, styled, useTheme } from "@mui/material";
import React, { forwardRef, PropsWithChildren, ReactElement, ReactNode, useCallback, useImperativeHandle, useRef } from "react";
import { ClearIcon } from "../table/SearchFilter";
import { InputProps, ThemedInput } from "./ThemedInput";

type Props = PropsWithChildren<InputProps> & {
onClear?: () => void;
onAddonClick?: () => void;
additionalIcon?: ReactNode;
};

export type Focusable = {
Expand All @@ -23,6 +24,9 @@ export const InputWithIcon = forwardRef<Focusable, Props>(function InputWithIcon

const wrapperWithAddonStyles = css({
position: "relative",
display: "flex",
flexDirection: "row",
alignItems: "center",
});

const addonWrapperStyles = css({
Expand Down Expand Up @@ -59,18 +63,19 @@ export const InputWithIcon = forwardRef<Focusable, Props>(function InputWithIcon

return (
<div className={cx(children && wrapperWithAddonStyles)}>
{children && (
<div className={addonStyles} onClick={onAddonClick ?? (() => focus())}>
{children}
</div>
)}
<ThemedInput ref={ref} {...props} />
<div className={addonWrapperStyles}>
{!!props.value && onClear && (
<div className={addonStyles} onClick={onClear}>
<ClearIcon />
</div>
)}
{children && (
<div className={addonStyles} onClick={onAddonClick ?? (() => focus())}>
{children}
</div>
)}
{props.additionalIcon && <div className={addonStyles}>{props.additionalIcon}</div>}
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion designer/client/src/components/themed/SearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { InputWithIcon } from "./InputWithIcon";

export const SearchInputWithIcon = styled(InputWithIcon)(({ theme }) => ({
...theme.typography.body2,
width: "100%",
width: "75%",
borderRadius: 0,
height: "36px !important",
color: theme.palette.text.secondary,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from "react";
import { TextField, Button, Box, Typography } from "@mui/material";

const transformInput = (input: string, fieldName: string) => {
return input === "" ? "" : `${fieldName}:(${input})`;
};

export function AdvancedSearchOptions({
setFilter,
setCollapsedHandler,
}: {
setFilter: React.Dispatch<React.SetStateAction<string>>;
setCollapsedHandler: React.Dispatch<React.SetStateAction<boolean>>;
}) {
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

const formData = new FormData(event.currentTarget);
const idInput = (formData.get("id") as string) || "";
const descriptionInput = (formData.get("description") as string) || "";
const typeInput = (formData.get("type") as string) || "";
const paramNameInput = (formData.get("paramName") as string) || "";
const paramValueInput = (formData.get("paramValue") as string) || "";
const outputValueInput = (formData.get("outputValue") as string) || "";
const edgeExpressionInput = (formData.get("edgeExpression") as string) || "";

const transformedIdInput = transformInput(idInput, "id");
const transformedDescriptionInput = transformInput(descriptionInput, "description");
const transformedTypeInput = transformInput(typeInput, "type");
const transformedParamNameInput = transformInput(paramNameInput, "paramName");
const transformedParamValueInput = transformInput(paramValueInput, "paramValue");
const transformedOutputValueInput = transformInput(outputValueInput, "outputValue");
const transformedEdgeExpressionInput = transformInput(edgeExpressionInput, "edgeExpression");

const transformedInputs = [
transformedIdInput,
transformedDescriptionInput,
transformedTypeInput,
transformedParamNameInput,
transformedParamValueInput,
transformedOutputValueInput,
transformedEdgeExpressionInput,
];

const finalText = transformedInputs.join(" ").trimEnd();

setFilter(finalText);
};

const handleCancel = () => {
setCollapsedHandler(false);
};

return (
<Box
component="form"
onSubmit={handleSubmit}
sx={{
display: "flex",
flexDirection: "column",
maxWidth: "300px",
justifyContent: "center",
alignItems: "center",
}}
>
<Typography fontWeight="bold" sx={{ m: 1 }}>
Advanced Search
</Typography>
<TextField sx={{ m: 1 }} size="small" label="id" name="id" />
<TextField size="small" sx={{ m: 1 }} label="paramValue" name="paramValue" />
<TextField size="small" sx={{ m: 1 }} label="paramName" name="paramName" />
<TextField size="small" sx={{ m: 1 }} label="outputValue" name="outputValue" />
<TextField size="small" sx={{ m: 1 }} label="description" name="description" />
<TextField size="small" sx={{ m: 1 }} label="type" name="type" />
<TextField size="small" sx={{ m: 1 }} label="edgeExpression" name="edgeExpression" />
<Box sx={{ display: "flex", flexDirection: "row", justifyContent: "space-between", width: "85%", mt: 1, mb: 1 }}>
<Button sx={{ width: "45%" }} size="small" variant="outlined" onClick={handleCancel}>
Cancel
</Button>
<Button variant="contained" size="small" sx={{ width: "45%" }} type="submit">
Submit
</Button>
</Box>
</Box>
);
}
15 changes: 13 additions & 2 deletions designer/client/src/components/toolbars/search/SearchPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
import { isEmpty } from "lodash";
import React, { ReactElement, useCallback, useRef, useState } from "react";
import React, { ReactElement, useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { SearchIcon } from "../../table/SearchFilter";
import { AdvancedOptionsIcon, SearchIcon } from "../../table/SearchFilter";
import { Focusable } from "../../themed/InputWithIcon";
import { ToolbarPanelProps } from "../../toolbarComponents/DefaultToolbarPanel";
import { ToolbarWrapper } from "../../toolbarComponents/toolbarWrapper/ToolbarWrapper";
import { SearchResults } from "./SearchResults";
import { SearchInputWithIcon } from "../../themed/SearchInput";
import { EventTrackingSelector, getEventTrackingProps } from "../../../containers/event-tracking";
import { Collapse } from "@mui/material";
import { AdvancedSearchOptions } from "./AdvancedSearchOptions";

export function SearchPanel(props: ToolbarPanelProps): ReactElement {
const { t } = useTranslation();
const [filter, setFilter] = useState<string>("");
const clearFilter = useCallback(() => setFilter(""), []);
const [advancedOptionsCollapsed, setAdvancedOptionsCollapsed] = useState(true);

const searchRef = useRef<Focusable>();

useEffect(() => {
setAdvancedOptionsCollapsed(false);
}, [filter]);

return (
<ToolbarWrapper {...props} title={t("panels.search.title", "Search")} onExpand={() => searchRef.current?.focus()}>
<SearchInputWithIcon
ref={searchRef}
onChange={setFilter}
additionalIcon={<AdvancedOptionsIcon isActive={advancedOptionsCollapsed} collapseHandler={setAdvancedOptionsCollapsed} />}
onClear={clearFilter}
value={filter}
placeholder={t("panels.search.filter.placeholder", "type here to search nodes...")}
{...getEventTrackingProps({ selector: EventTrackingSelector.NodesInScenario })}
>
<SearchIcon isEmpty={isEmpty(filter)} />
</SearchInputWithIcon>
<Collapse in={advancedOptionsCollapsed} timeout="auto" unmountOnExit={false}>
<AdvancedSearchOptions setFilter={setFilter} setCollapsedHandler={setAdvancedOptionsCollapsed} />
</Collapse>
<SearchResults filterRawText={filter} />
</ToolbarWrapper>
);
Expand Down
14 changes: 11 additions & 3 deletions designer/client/src/components/toolbars/search/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,25 @@ export function resolveSearchOption(filterRawText: string): SearchOption {
}
}

function splitString(input: string): string[] {
//split string by comma respecting quoted elements
//"a,b,c" -> ["a", "b", "c"]
//"a,\"b,c\",d" -> ["a", "b,c", "d"]
return input.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
}

function parseRawTextToAdvancedSearch(text: string): AdvancedSearch {
const result: AdvancedSearch = { searchType: SearchType.ADVANCED };
const regex = /(\w+):\(([^)]*)\)/g;
let match: RegExpExecArray | null;

while ((match = regex.exec(text)) !== null) {
const key = match[1] as keyof Exclude<keyof AdvancedSearch, "searchType">;
console.log(key);
const values = match[2].split(",").map((value) => value.trim().replace(/"/g, ""));
console.log(match[2]);
const values = splitString(match[2]).map((value) => value.trim().replace(/"/g, ""));
//const values = match[2].split(",").map((value) => value.trim().replace(/"/g, ""));

result[key] = values.length > 0 ? values : undefined;
result[key] = values.length > 0 ? values : "";
}

return result;
Expand Down

0 comments on commit d984547

Please sign in to comment.