Skip to content

Commit 8a194ae

Browse files
authored
feat(filter): Implement filter feature (#19)
1 parent e9052a6 commit 8a194ae

File tree

14 files changed

+244
-49
lines changed

14 files changed

+244
-49
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"packageManager": "yarn@3.1.1",
55
"scripts": {
66
"dev": "next dev",
7-
"build": "BASE_PATH=/next next build && next export -o dist/next && rsync -a template/ dist",
7+
"build": "BASE_PATH=/next next build && next export -o dist/next && rsync -a template/ dist && rsync -a public/ dist",
88
"zip": "cd dist && zip -r ../dist.zip ."
99
},
1010
"dependencies": {

public/largeIcons.svg

Lines changed: 2 additions & 0 deletions
Loading

src/components/Checkbox/index.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@
33
align-items: center;
44
line-height: normal;
55
column-gap: 4px;
6+
7+
> input {
8+
width: auto;
9+
}
610
}

src/components/Checkbox/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface CheckboxProps {
55
isChecked: boolean;
66
onChange(value: boolean): void;
77
children?: React.ReactNode;
8+
className?: string;
89
}
910
const Checkbox: React.FC<CheckboxProps> = ({
1011
isChecked,
@@ -13,7 +14,7 @@ const Checkbox: React.FC<CheckboxProps> = ({
1314
...props
1415
}) => {
1516
return (
16-
<label className={styles.checkbox} {...props}>
17+
<label {...props} className={`${styles.checkbox} ${props.className ?? ""}`}>
1718
<input
1819
type="checkbox"
1920
checked={isChecked}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.icon-button {
2+
flex-shrink: 0;
3+
line-height: 0;
4+
border: none;
5+
background: none;
6+
cursor: pointer;
7+
8+
.icon {
9+
display: inline-block;
10+
width: 28px;
11+
height: 24px;
12+
-webkit-mask-image: url(/largeIcons.svg);
13+
background-color: var(--pb-fg);
14+
15+
&:hover {
16+
background-color: var(--pb-fg-hover);
17+
}
18+
}
19+
20+
.filter {
21+
-webkit-mask-position: -56px 120px;
22+
23+
&.active {
24+
background-color: var(--oc-red-3);
25+
}
26+
}
27+
28+
.search {
29+
-webkit-mask-position: -196px 96px;
30+
}
31+
32+
.clear {
33+
-webkit-mask-position: 0px 144px;
34+
}
35+
}

src/components/IconButton/index.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import styles from "./index.module.scss";
2+
3+
type Icon = "filter" | "search" | "clear";
4+
5+
export interface IconButtonProps
6+
extends Omit<React.HTMLAttributes<HTMLButtonElement>, "children"> {
7+
icon: Icon;
8+
isActive?: boolean;
9+
}
10+
11+
const IconButton: React.FC<IconButtonProps> = ({
12+
icon,
13+
isActive,
14+
...props
15+
}) => {
16+
return (
17+
<button
18+
{...props}
19+
className={`${styles["icon-button"]} ${props.className ?? ""}`}
20+
>
21+
<span
22+
className={`${styles.icon} ${styles[icon]} ${
23+
isActive ? styles.active : ""
24+
}`}
25+
></span>
26+
</button>
27+
);
28+
};
29+
30+
export default IconButton;

src/pages/_app.scss

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
:root {
2+
height: 100%;
3+
overflow: hidden;
4+
25
--pb-bg: var(--oc-white);
36
--pb-fg: var(--oc-black);
47
--pb-sep: var(--oc-gray-6);
@@ -26,3 +29,35 @@ body {
2629
color: var(--pb-fg);
2730
max-height: 100vh;
2831
}
32+
33+
button {
34+
padding: 0;
35+
}
36+
37+
input {
38+
border: 0;
39+
color: var(--oc-gray-5);
40+
background-color: var(--oc-gray-9);
41+
padding: 0.25em;
42+
width: 100%;
43+
}
44+
45+
// For compatibility with the Chromium devTools environment.
46+
body {
47+
height: 100%;
48+
width: 100%;
49+
position: relative;
50+
overflow: hidden;
51+
margin: 0;
52+
cursor: default;
53+
font-family: system-ui, sans-serif;
54+
font-size: 12px;
55+
tab-size: 4;
56+
user-select: none;
57+
}
58+
59+
* {
60+
box-sizing: border-box;
61+
min-width: 0;
62+
min-height: 0;
63+
}

src/pages/index.page.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ import RequestDetail from "./index/RequestDetail";
1818
import RequestList from "./index/RequestList";
1919
import { preserveLogAtom } from "./index/atoms/setting";
2020
import Settings from "./index/Settings";
21+
import Experimental from "./index/Experimental";
2122

2223
const Page: NextPage = () => {
2324
useDevtoolsCommunicationLogic();
2425
return (
25-
<div className={style.page}>
26-
<div className={style.sidebar}>
27-
<Settings />
28-
<RequestList />
26+
<>
27+
<div className={style.page}>
28+
<div className={style.sidebar}>
29+
<Settings />
30+
{process.env.NODE_ENV === "development" && <Experimental />}
31+
<RequestList />
32+
</div>
33+
<RequestDetail />
2934
</div>
30-
<RequestDetail />
31-
</div>
35+
</>
3236
);
3337
};
3438

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.settings {
2+
padding: 4px;
3+
display: flex;
4+
flex-direction: column;
5+
6+
border-bottom: 1px solid var(--pb-sep);
7+
}
8+
9+
.search-section {
10+
display: flex;
11+
align-items: center;
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from "react";
2+
import Button from "../../../components/Button";
3+
import styles from "./index.module.scss";
4+
import { useAddMockRequests } from "../mocks/requests";
5+
6+
interface ExperimentalProps {}
7+
const Experimental: React.FC<ExperimentalProps> = () => {
8+
const addMockRequests = useAddMockRequests();
9+
return (
10+
<div className={styles.settings}>
11+
<Button onClick={() => addMockRequests()}>Add mock reqs</Button>
12+
</div>
13+
);
14+
};
15+
16+
export default React.memo(Experimental);

src/pages/index/RequestList/index.tsx

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { memo } from "react";
1+
import { useMemo, memo } from "react";
22
import { atom, useAtom } from "jotai";
33
import { requestsAtom } from "../atoms/request";
4+
import { filterAtom, searchValueAtom } from "../atoms/setting";
45
import { selectedRequestKeyAtom } from "../atoms/ui";
56
import Button from "../../../components/Button";
67
import style from "./index.module.scss";
@@ -11,30 +12,40 @@ const RequestList: React.FC<RequestListProps> = () => {
1112
const [selectedRequestKey, setSelectedRequestKey] = useAtom(
1213
selectedRequestKeyAtom
1314
);
15+
const [isFilterActive] = useAtom(filterAtom);
16+
const [searchValue] = useAtom(searchValueAtom);
17+
const memoizedRequestList = useMemo(() => {
18+
if (searchValue.length === 0 || !isFilterActive) return requestList;
19+
return requestList.filter(({ servicePath, rpcName }) => {
20+
return servicePath.includes(searchValue) || rpcName.includes(searchValue);
21+
});
22+
}, [isFilterActive, requestList, searchValue]);
1423
return (
1524
<div className={style["request-list"]}>
16-
{requestList.map(
17-
({ key, servicePath, rpcName, responsePayloads, responseError }) => (
18-
<Button
19-
key={key}
20-
className={style["request-list-item"]}
21-
data-selected={key === selectedRequestKey}
22-
onClick={() => setSelectedRequestKey(key)}
23-
>
24-
<div className={style["list-main"]}>
25-
<div className={style["service-path"]}>{servicePath}</div>
26-
<div className={style["rpc-name"]}>{rpcName}</div>
27-
</div>
28-
<div className={style["list-status"]}>
29-
{responsePayloads.length > 0 && (
30-
<div className={style["payload-circle"]}>
31-
{responsePayloads.length}
32-
</div>
33-
)}
34-
{responseError && <div className={style["error-circle"]} />}
35-
</div>
36-
</Button>
37-
)
25+
{memoizedRequestList.map(
26+
({ key, servicePath, rpcName, responsePayloads, responseError }) => {
27+
return (
28+
<Button
29+
key={key}
30+
className={style["request-list-item"]}
31+
data-selected={key === selectedRequestKey}
32+
onClick={() => setSelectedRequestKey(key)}
33+
>
34+
<div className={style["list-main"]}>
35+
<div className={style["service-path"]}>{servicePath}</div>
36+
<div className={style["rpc-name"]}>{rpcName}</div>
37+
</div>
38+
<div className={style["list-status"]}>
39+
{responsePayloads.length > 0 && (
40+
<div className={style["payload-circle"]}>
41+
{responsePayloads.length}
42+
</div>
43+
)}
44+
{responseError && <div className={style["error-circle"]} />}
45+
</div>
46+
</Button>
47+
);
48+
}
3849
)}
3950
</div>
4051
);
@@ -47,7 +58,12 @@ const requestListAtom = atom((get) => {
4758
const { servicePath, rpcName, responsePayloadsAtom, responseError } = get(
4859
requests[key]
4960
);
50-
const responsePayloads = get(responsePayloadsAtom);
51-
return { key, servicePath, rpcName, responsePayloads, responseError };
61+
return {
62+
key,
63+
servicePath,
64+
rpcName,
65+
responsePayloads: get(responsePayloadsAtom),
66+
responseError,
67+
};
5268
});
5369
});

src/pages/index/Settings/index.module.scss

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,33 @@
22
padding: 4px;
33
display: flex;
44
flex-direction: column;
5+
6+
border-bottom: 1px solid var(--pb-sep);
7+
8+
> * {
9+
padding: 4px 0;
10+
11+
&:first-child {
12+
padding-top: 0;
13+
}
14+
15+
&:not(:first-child) {
16+
border-top: 1px solid var(--pb-sep);
17+
}
18+
19+
&:last-child {
20+
padding-bottom: 0;
21+
}
22+
}
23+
}
24+
25+
.search-section {
26+
display: flex;
27+
align-items: center;
28+
}
29+
30+
.vertical-sep {
31+
height: 100%;
32+
border-right: 1px solid var(--pb-sep);
33+
margin-right: 0.625em;
534
}

src/pages/index/Settings/index.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
11
import React from "react";
22
import { useAtom } from "jotai";
33
import { useUpdateAtom } from "jotai/utils";
4-
import Button from "../../../components/Button";
54
import Checkbox from "../../../components/Checkbox";
5+
import IconButton from "../../../components/IconButton";
66
import { resetRequestsAtom } from "../atoms/ui";
7-
import { preserveLogAtom } from "../atoms/setting";
7+
import { filterAtom, preserveLogAtom, searchValueAtom } from "../atoms/setting";
88
import styles from "./index.module.scss";
9-
import { useAddMockRequests } from "../mocks/requests";
109

1110
interface SettingsProps {}
1211
const Settings: React.FC<SettingsProps> = () => {
1312
const resetRequests = useUpdateAtom(resetRequestsAtom);
14-
const addMockRequests = useAddMockRequests();
1513
const [preserveLog, setPreserveLog] = useAtom(preserveLogAtom);
14+
const [searchValue, setSearchValue] = useAtom(searchValueAtom);
15+
const [isFilterActive, setFilterActive] = useAtom(filterAtom);
1616
return (
1717
<div className={styles.settings}>
18-
<Button onClick={resetRequests}>Remove cache</Button>
19-
{process.env.NODE_ENV === "development" && (
20-
<Button onClick={() => addMockRequests()}>Add mock reqs</Button>
21-
)}
22-
<Checkbox
23-
isChecked={preserveLog}
24-
onChange={(v) => {
25-
setPreserveLog(v);
26-
}}
27-
>
28-
Preserve log
29-
</Checkbox>
18+
<div className={styles["search-section"]}>
19+
<IconButton icon="clear" onClick={resetRequests} />
20+
<div className={styles["vertical-sep"]} />
21+
<Checkbox isChecked={preserveLog} onChange={setPreserveLog}>
22+
Preserve log
23+
</Checkbox>
24+
</div>
25+
<div className={styles["search-section"]}>
26+
<IconButton
27+
icon="filter"
28+
isActive={isFilterActive}
29+
onClick={() => setFilterActive((prev) => !prev)}
30+
/>
31+
<input
32+
placeholder="Filter (rpc, service path)"
33+
value={searchValue}
34+
onChange={(e) => setSearchValue(e.target.value)}
35+
/>
36+
</div>
3037
</div>
3138
);
3239
};

src/pages/index/atoms/setting.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
import { atom } from "jotai";
22

33
export const preserveLogAtom = atom<boolean>(false);
4+
5+
export const searchValueAtom = atom<string>("");
6+
7+
export const filterAtom = atom<boolean>(true);

0 commit comments

Comments
 (0)