Skip to content

Commit a5b995a

Browse files
committed
feat(web-devtools): inputable-dropdown-for-arbitrables-with-storage-saving
1 parent 28e2411 commit a5b995a

File tree

4 files changed

+111
-15
lines changed

4 files changed

+111
-15
lines changed

web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx

+66-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import React from "react";
1+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
import styled from "styled-components";
33

4-
import { Field } from "@kleros/ui-components-library";
4+
import { Address } from "viem";
5+
6+
import { DropdownSelect, Field } from "@kleros/ui-components-library";
57

68
import { useRulerContext } from "context/RulerContext";
9+
import { shortenAddress } from "utils/shortenAddress";
710

811
const Arbitrables = styled.div`
912
width: 100%;
@@ -20,18 +23,70 @@ const Arbitrables = styled.div`
2023
`;
2124
const StyledLabel = styled.label``;
2225

26+
const SelectContainer = styled.div`
27+
position: relative;
28+
`;
29+
const StyledDropdown = styled(DropdownSelect)`
30+
position: absolute;
31+
z-index: 0;
32+
top: 40px;
33+
left: 0;
34+
width: 100%;
35+
> button {
36+
display: none;
37+
}
38+
> div {
39+
z-index: 1;
40+
width: 100%;
41+
> div {
42+
width: 100%;
43+
}
44+
}
45+
.simplebar-content {
46+
> div {
47+
width: 100%;
48+
}
49+
}
50+
`;
51+
2352
const SelectArbitrable: React.FC = () => {
24-
const { arbitrable, setArbitrable } = useRulerContext();
53+
const { arbitrable, setArbitrable, knownArbitrables } = useRulerContext();
54+
const ref = useRef<HTMLDivElement>(null);
55+
const [isClient, setIsClient] = useState(false);
56+
57+
// hydration workaround, local storage is inevitably going to be different, so knownArbitrables will be different
58+
// server and client side
59+
useEffect(() => {
60+
setIsClient(true);
61+
}, []);
62+
63+
const items = useMemo(
64+
() =>
65+
!isClient ? [] : knownArbitrables.map((arbitrable) => ({ text: shortenAddress(arbitrable), value: arbitrable })),
66+
[isClient, knownArbitrables]
67+
);
68+
69+
const openDropdown = useCallback(() => {
70+
if (!ref.current || knownArbitrables.length === 0) return;
71+
72+
const child = ref.current.firstElementChild?.firstChild as HTMLButtonElement;
73+
child.click();
74+
}, [knownArbitrables, ref]);
75+
2576
return (
26-
<Arbitrables>
77+
<Arbitrables suppressHydrationWarning={true}>
2778
<StyledLabel>Arbitrable:</StyledLabel>
28-
<Field
29-
value={arbitrable}
30-
placeholder="Enter Arbitrable"
31-
onChange={(e) => {
32-
setArbitrable(e.target.value);
33-
}}
34-
/>
79+
<SelectContainer ref={ref}>
80+
<StyledDropdown defaultValue={arbitrable} items={items} callback={(val) => setArbitrable(val as Address)} />
81+
<Field
82+
value={arbitrable}
83+
placeholder="Enter Arbitrable"
84+
onChange={(e) => {
85+
setArbitrable(e.target.value as Address);
86+
}}
87+
onClick={openDropdown}
88+
/>
89+
</SelectContainer>
3590
</Arbitrables>
3691
);
3792
};

web-devtools/src/context/RulerContext.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
22

33
import { RULING_MODE } from "consts";
4-
import { Address } from "viem";
5-
import { useAccount } from "wagmi";
4+
import { Address, isAddress } from "viem";
65

76
import { useReadKlerosCoreRulerRulers, useReadKlerosCoreRulerSettings } from "hooks/contracts/generated";
7+
import { useLocalStorage } from "hooks/useLocalStorage";
88
import { isUndefined } from "utils/isUndefined";
99

1010
const REFETCH_INTERVAL = 5000;
@@ -22,13 +22,14 @@ interface IRulerContext {
2222
arbitrableSettings?: ArbitrableSettings;
2323
currentDeveloper?: Address;
2424
refetchData: () => void;
25+
knownArbitrables: string[];
2526
}
2627
const RulerContext = createContext<IRulerContext | undefined>(undefined);
2728

2829
const RulerContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
2930
const [arbitrable, setArbitrable] = useState<Address>();
3031
const [arbitrableSettings, setArbitrableSettings] = useState<ArbitrableSettings>();
31-
const { address } = useAccount();
32+
const [knownArbitrables, setKnownArbitrables] = useLocalStorage<string[]>("knownArbitrables", []);
3233

3334
const { data: currentDeveloper, refetch: refetchDeveloper } = useReadKlerosCoreRulerRulers({
3435
query: {
@@ -56,6 +57,13 @@ const RulerContextProvider: React.FC<{ children: React.ReactNode }> = ({ childre
5657
});
5758
}, [arbitrableSettingsData]);
5859

60+
useEffect(() => {
61+
if (isUndefined(arbitrable) || !isAddress(arbitrable) || knownArbitrables.includes(arbitrable?.toLowerCase()))
62+
return;
63+
64+
setKnownArbitrables([...knownArbitrables, arbitrable?.toLowerCase()]);
65+
}, [arbitrable, knownArbitrables, setKnownArbitrables]);
66+
5967
const refetchData = useCallback(() => {
6068
refetchArbitrableSettings();
6169
refetchDeveloper();
@@ -70,8 +78,9 @@ const RulerContextProvider: React.FC<{ children: React.ReactNode }> = ({ childre
7078
arbitrableSettings,
7179
currentDeveloper,
7280
refetchData,
81+
knownArbitrables,
7382
}),
74-
[arbitrable, setArbitrable, arbitrableSettings, currentDeveloper, refetchData]
83+
[arbitrable, setArbitrable, arbitrableSettings, currentDeveloper, refetchData, knownArbitrables]
7584
)}
7685
>
7786
{children}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useState } from "react";
2+
3+
export function useLocalStorage<T>(keyName: string, defaultValue: T) {
4+
const [storedValue, setStoredValue] = useState(() => {
5+
try {
6+
const value = window.localStorage.getItem(keyName);
7+
return value ? JSON.parse(value) : defaultValue;
8+
} catch (err) {
9+
return defaultValue;
10+
}
11+
});
12+
13+
const setValue = (newValue: T) => {
14+
try {
15+
window.localStorage.setItem(keyName, JSON.stringify(newValue));
16+
} finally {
17+
setStoredValue(newValue);
18+
}
19+
};
20+
21+
return [storedValue, setValue];
22+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { getAddress } from "viem";
2+
3+
export function shortenAddress(address: string): string {
4+
try {
5+
const formattedAddress = getAddress(address);
6+
return formattedAddress.substring(0, 6) + "..." + formattedAddress.substring(formattedAddress.length - 4);
7+
} catch {
8+
throw new TypeError("Invalid input, address can't be parsed");
9+
}
10+
}

0 commit comments

Comments
 (0)