Skip to content

Commit b0336c1

Browse files
committed
feat: molecule demo
1 parent 1dd0da8 commit b0336c1

File tree

8 files changed

+482
-1
lines changed

8 files changed

+482
-1
lines changed

packages/demo/src/app/connected/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const TABS: [ReactNode, string, keyof typeof icons, string][] = [
5151
["Hash", "/utils/Hash", "Barcode", "text-violet-500"],
5252
["Mnemonic", "/utils/Mnemonic", "SquareAsterisk", "text-fuchsia-500"],
5353
["Keystore", "/utils/Keystore", "Notebook", "text-rose-500"],
54+
["Molecule", "/utils/Molecule", "Hash", "text-emerald-500"],
5455
];
5556
/* eslint-enable react/jsx-key */
5657

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React, { useEffect, useState } from "react";
2+
import { ccc } from "@ckb-ccc/connector-react";
3+
import JsonView from "@uiw/react-json-view";
4+
import { useApp } from "@/src/context";
5+
import { Button } from "@/src/components/Button";
6+
import { Textarea } from "@/src/components/Textarea";
7+
import { darkTheme } from "@uiw/react-json-view/dark";
8+
import { Dropdown } from "@/src/components/Dropdown";
9+
export type UnpackType =
10+
| string
11+
| number
12+
| undefined
13+
| { [property: string]: UnpackType }
14+
| UnpackType[];
15+
16+
type Props = {
17+
codec: ccc.mol.Codec<any, any> | undefined;
18+
mode: "decode" | "encode";
19+
};
20+
21+
const formatInput = (input: string): string => {
22+
if (!input.startsWith("0x")) {
23+
return `0x${input}`;
24+
}
25+
return input;
26+
};
27+
28+
const isBlank = (data: UnpackType): boolean => {
29+
if (!data) {
30+
return true;
31+
}
32+
return false;
33+
};
34+
35+
export const DataInput: React.FC<Props> = ({ codec, mode }) => {
36+
const [inputData, setInputData] = useState<string>("");
37+
const [decodeResult, setDecodeResult] = useState<UnpackType>(undefined);
38+
const [encodeResult, setEncodeResult] = useState<ccc.Hex | undefined>(
39+
undefined,
40+
);
41+
const { createSender } = useApp();
42+
const { log, error } = createSender("Molecule");
43+
44+
const handleDecode = () => {
45+
if (!codec) {
46+
error("please select codec");
47+
return;
48+
}
49+
try {
50+
const result = codec.decode(formatInput(inputData));
51+
log("Successfully decoded data");
52+
setDecodeResult(result);
53+
} catch (e: unknown) {
54+
setDecodeResult(undefined);
55+
error((e as Error).message);
56+
}
57+
};
58+
59+
const handleEncode = () => {
60+
if (!codec) {
61+
error("please select codec");
62+
return;
63+
}
64+
try {
65+
const inputObject = JSON.parse(inputData);
66+
const result = codec.encode(inputObject);
67+
log("Successfully encoded data");
68+
setEncodeResult(ccc.hexFrom(result));
69+
} catch (e: unknown) {
70+
setEncodeResult(undefined);
71+
error((e as Error).message);
72+
}
73+
};
74+
75+
// If mode changes, clear the input data
76+
useEffect(() => {
77+
setInputData("");
78+
setDecodeResult(undefined);
79+
setEncodeResult(undefined);
80+
}, [mode]);
81+
82+
return (
83+
<div>
84+
<div style={{ marginBottom: 16 }}>
85+
<label htmlFor="input-data">Input data</label>
86+
<div>
87+
<Textarea
88+
id="input-data"
89+
state={[inputData, setInputData]}
90+
placeholder={
91+
mode === "decode" ? "0x..." : "Please input data in JSON Object"
92+
}
93+
/>
94+
</div>
95+
</div>
96+
97+
{mode === "decode" && (
98+
<div style={{ marginBottom: 16 }}>
99+
<Button type="button" onClick={handleDecode}>
100+
Decode
101+
</Button>
102+
</div>
103+
)}
104+
105+
{mode === "encode" && (
106+
<div style={{ marginBottom: 16 }}>
107+
<Button type="button" onClick={handleEncode}>
108+
Encode
109+
</Button>
110+
</div>
111+
)}
112+
113+
{!isBlank(decodeResult) && (
114+
<div>
115+
<JsonView
116+
value={
117+
typeof decodeResult === "object"
118+
? decodeResult
119+
: { value: decodeResult }
120+
}
121+
style={darkTheme}
122+
/>
123+
</div>
124+
)}
125+
126+
{encodeResult && (
127+
<div>
128+
<JsonView value={{ value: encodeResult }} style={darkTheme} />
129+
</div>
130+
)}
131+
</div>
132+
);
133+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React, { useCallback, useState } from "react";
2+
import {
3+
blockchainSchema,
4+
builtinCodecs,
5+
mergeBuiltinCodecs,
6+
} from "./constants";
7+
import { ccc } from "@ckb-ccc/connector-react";
8+
import { useApp } from "@/src/context";
9+
import { Textarea } from "@/src/components/Textarea";
10+
import { Button } from "@/src/components/Button";
11+
12+
type Props = {
13+
updateCodecMap: (token: any) => void;
14+
};
15+
16+
export const MoleculeParser: React.FC<Props> = ({ updateCodecMap }) => {
17+
const [inputMol, setInputMol] = useState(() => {
18+
if (typeof window !== "undefined") {
19+
return localStorage.getItem("cachedMol") || "";
20+
}
21+
return "";
22+
});
23+
const [parseSuccess, setParseSuccess] = useState(false);
24+
const { createSender } = useApp();
25+
const { log, error } = createSender("Molecule");
26+
27+
const handleConfirm = useCallback(() => {
28+
try {
29+
// get user input schema, and append with primitive schema
30+
const userCodecMap = ccc.molecule.getCodecMapFromMol(
31+
inputMol + blockchainSchema,
32+
{
33+
refs: builtinCodecs,
34+
},
35+
);
36+
const codecMap = mergeBuiltinCodecs(userCodecMap);
37+
setParseSuccess(true);
38+
updateCodecMap(codecMap);
39+
log("Successfully parsed schema");
40+
if (typeof window !== "undefined") {
41+
localStorage.setItem("cachedMol", inputMol);
42+
}
43+
} catch (error: any) {
44+
setParseSuccess(false);
45+
updateCodecMap({});
46+
error(error.message);
47+
}
48+
}, [inputMol, log, updateCodecMap]);
49+
50+
return (
51+
<div style={{ marginBottom: 16 }}>
52+
<div style={{ marginBottom: 16 }}>
53+
<label htmlFor="input-schema">
54+
Input schema(mol)
55+
<label title="Uint8/16/.../512, Byte32, BytesVec, Bytes, BytesVec, BytesOpt are used as primitive schemas, please do not override." />
56+
</label>
57+
<div>
58+
<Textarea
59+
id="input-schema"
60+
state={[inputMol, setInputMol]}
61+
placeholder="e.g. vector OutPointVec <OutPoint>;"
62+
/>
63+
</div>
64+
</div>
65+
66+
<div>
67+
<Button type="button" onClick={handleConfirm}>
68+
Parse
69+
</Button>
70+
</div>
71+
</div>
72+
);
73+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from "react";
2+
import { ccc } from "@ckb-ccc/connector-react";
3+
import { Dropdown } from "@/src/components/Dropdown";
4+
5+
type Props = {
6+
codecMap: ccc.molecule.CodecMap;
7+
selectedCodecName: string;
8+
onSelectCodec: (name: string) => void;
9+
mode: "decode" | "encode";
10+
onSelectMode: (mode: "decode" | "encode") => void;
11+
};
12+
13+
const createCodecOptionsFromMap = (
14+
codecMap: ccc.molecule.CodecMap,
15+
): string[] => {
16+
return Object.keys(codecMap);
17+
};
18+
19+
export const SchemaSelect: React.FC<Props> = ({
20+
onSelectCodec,
21+
selectedCodecName,
22+
codecMap,
23+
mode,
24+
onSelectMode,
25+
}) => {
26+
const handleChange = (newValue: string | null) => {
27+
onSelectCodec(newValue as string);
28+
};
29+
const schemaOptions = createCodecOptionsFromMap(codecMap).map((schema) => ({
30+
name: schema,
31+
displayName: schema,
32+
iconName: "Hash" as const,
33+
}));
34+
35+
return (
36+
<div className="flex flex-row items-center gap-4">
37+
<label className="min-w-32 shrink-0">Select schema(mol)</label>
38+
<Dropdown
39+
options={schemaOptions}
40+
selected={selectedCodecName}
41+
onSelect={(value: string | null) => handleChange(value)}
42+
className="flex-1"
43+
/>
44+
<label htmlFor="mode">Mode</label>
45+
<Dropdown
46+
options={[
47+
{
48+
name: "decode",
49+
displayName: "Decode",
50+
iconName: "ArrowRight",
51+
},
52+
{
53+
name: "encode",
54+
displayName: "Encode",
55+
iconName: "ArrowLeft",
56+
},
57+
]}
58+
selected={mode}
59+
onSelect={(value) => onSelectMode(value as "decode" | "encode")}
60+
className="flex-2"
61+
/>
62+
</div>
63+
);
64+
};

0 commit comments

Comments
 (0)