Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/core/src/molecule/predefined.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ export const Bool: Codec<boolean> = Codec.from({
export const BoolOpt = option(Bool);
export const BoolVec = vector(Bool);

export const Byte: Codec<HexLike, Hex> = Codec.from({
byteLength: 1,
encode: (value) => bytesFrom(value),
decode: (buffer) => hexFrom(buffer),
});
export const ByteOpt = option(Byte);

export const Byte4: Codec<HexLike, Hex> = Codec.from({
byteLength: 4,
encode: (value) => bytesFrom(value),
Expand Down
1 change: 1 addition & 0 deletions packages/demo/src/app/connected/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const TABS: [ReactNode, string, keyof typeof icons, string][] = [
["Hash", "/utils/Hash", "Barcode", "text-violet-500"],
["Mnemonic", "/utils/Mnemonic", "SquareAsterisk", "text-fuchsia-500"],
["Keystore", "/utils/Keystore", "Notebook", "text-rose-500"],
["Molecule", "/utils/Molecule", "Hash", "text-emerald-500"],
];
/* eslint-enable react/jsx-key */

Expand Down
133 changes: 133 additions & 0 deletions packages/demo/src/app/utils/(tools)/Molecule/DataInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { useEffect, useState } from "react";
import { ccc } from "@ckb-ccc/connector-react";
import JsonView from "@uiw/react-json-view";
import { useApp } from "@/src/context";
import { Button } from "@/src/components/Button";
import { Textarea } from "@/src/components/Textarea";
import { darkTheme } from "@uiw/react-json-view/dark";
import { Dropdown } from "@/src/components/Dropdown";
export type UnpackType =
| string
| number
| undefined
| { [property: string]: UnpackType }
| UnpackType[];

type Props = {
codec: ccc.mol.Codec<any, any> | undefined;
mode: "decode" | "encode";
};

const formatInput = (input: string): string => {
if (!input.startsWith("0x")) {
return `0x${input}`;
}
return input;
};

const isBlank = (data: UnpackType): boolean => {
if (!data) {
return true;
}
return false;
};

export const DataInput: React.FC<Props> = ({ codec, mode }) => {
const [inputData, setInputData] = useState<string>("");
const [decodeResult, setDecodeResult] = useState<UnpackType>(undefined);
const [encodeResult, setEncodeResult] = useState<ccc.Hex | undefined>(
undefined,
);
const { createSender } = useApp();
const { log, error } = createSender("Molecule");

const handleDecode = () => {
if (!codec) {
error("please select codec");
return;
}
try {
const result = codec.decode(formatInput(inputData));
log("Successfully decoded data");
setDecodeResult(result);
} catch (e: unknown) {
setDecodeResult(undefined);
error((e as Error).message);
}
};

const handleEncode = () => {
if (!codec) {
error("please select codec");
return;
}
try {
const inputObject = JSON.parse(inputData);
const result = codec.encode(inputObject);
log("Successfully encoded data");
setEncodeResult(ccc.hexFrom(result));
} catch (e: unknown) {
setEncodeResult(undefined);
error((e as Error).message);
}
};

// If mode changes, clear the input data
useEffect(() => {
setInputData("");
setDecodeResult(undefined);
setEncodeResult(undefined);
}, [mode]);

return (
<div>
<div style={{ marginBottom: 16 }}>
<label htmlFor="input-data">Input data</label>
<div>
<Textarea
id="input-data"
state={[inputData, setInputData]}
placeholder={
mode === "decode" ? "0x..." : "Please input data in JSON Object"
}
/>
</div>
</div>

{mode === "decode" && (
<div style={{ marginBottom: 16 }}>
<Button type="button" onClick={handleDecode}>
Decode
</Button>
</div>
)}

{mode === "encode" && (
<div style={{ marginBottom: 16 }}>
<Button type="button" onClick={handleEncode}>
Encode
</Button>
</div>
)}

{!isBlank(decodeResult) && (
<div>
<JsonView
value={
typeof decodeResult === "object"
? decodeResult
: { value: decodeResult }
}
style={darkTheme}
/>
</div>
)}

{encodeResult && (
<div>
<JsonView value={{ value: encodeResult }} style={darkTheme} />
</div>
)}
</div>
);
};
73 changes: 73 additions & 0 deletions packages/demo/src/app/utils/(tools)/Molecule/Molecule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useCallback, useState } from "react";
import {
blockchainSchema,
builtinCodecs,
mergeBuiltinCodecs,
} from "./constants";
import { ccc } from "@ckb-ccc/connector-react";
import { useApp } from "@/src/context";
import { Textarea } from "@/src/components/Textarea";
import { Button } from "@/src/components/Button";

type Props = {
updateCodecMap: (token: any) => void;
};

export const MoleculeParser: React.FC<Props> = ({ updateCodecMap }) => {
const [inputMol, setInputMol] = useState(() => {
if (typeof window !== "undefined") {
return localStorage.getItem("cachedMol") || "";
}
return "";
});
const [parseSuccess, setParseSuccess] = useState(false);
const { createSender } = useApp();
const { log, error } = createSender("Molecule");

const handleConfirm = useCallback(() => {
try {
// get user input schema, and append with primitive schema
const userCodecMap = ccc.molecule.getCodecMapFromMol(
inputMol + blockchainSchema,
{
refs: builtinCodecs,
},
);
const codecMap = mergeBuiltinCodecs(userCodecMap);
setParseSuccess(true);
updateCodecMap(codecMap);
log("Successfully parsed schema");
if (typeof window !== "undefined") {
localStorage.setItem("cachedMol", inputMol);
}
} catch (error: any) {
setParseSuccess(false);
updateCodecMap({});
error(error.message);
}
}, [inputMol, log, updateCodecMap]);

return (
<div style={{ marginBottom: 16 }}>
<div style={{ marginBottom: 16 }}>
<label htmlFor="input-schema">
Input schema(mol)
<label title="Uint8/16/.../512, Byte32, BytesVec, Bytes, BytesVec, BytesOpt are used as primitive schemas, please do not override." />
</label>
<div>
<Textarea
id="input-schema"
state={[inputMol, setInputMol]}
placeholder="e.g. vector OutPointVec <OutPoint>;"
/>
</div>
</div>

<div>
<Button type="button" onClick={handleConfirm}>
Parse
</Button>
</div>
</div>
);
};
64 changes: 64 additions & 0 deletions packages/demo/src/app/utils/(tools)/Molecule/SchemaSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from "react";
import { ccc } from "@ckb-ccc/connector-react";
import { Dropdown } from "@/src/components/Dropdown";

type Props = {
codecMap: ccc.molecule.CodecMap;
selectedCodecName: string;
onSelectCodec: (name: string) => void;
mode: "decode" | "encode";
onSelectMode: (mode: "decode" | "encode") => void;
};

const createCodecOptionsFromMap = (
codecMap: ccc.molecule.CodecMap,
): string[] => {
return Object.keys(codecMap);
};

export const SchemaSelect: React.FC<Props> = ({
onSelectCodec,
selectedCodecName,
codecMap,
mode,
onSelectMode,
}) => {
const handleChange = (newValue: string | null) => {
onSelectCodec(newValue as string);
};
const schemaOptions = createCodecOptionsFromMap(codecMap).map((schema) => ({
name: schema,
displayName: schema,
iconName: "Hash" as const,
}));

return (
<div className="flex flex-row items-center gap-4">
<label className="min-w-32 shrink-0">Select schema(mol)</label>
<Dropdown
options={schemaOptions}
selected={selectedCodecName}
onSelect={(value: string | null) => handleChange(value)}
className="flex-1"
/>
<label htmlFor="mode">Mode</label>
<Dropdown
options={[
{
name: "decode",
displayName: "Decode",
iconName: "ArrowRight",
},
{
name: "encode",
displayName: "Encode",
iconName: "ArrowLeft",
},
]}
selected={mode}
onSelect={(value) => onSelectMode(value as "decode" | "encode")}
className="flex-2"
/>
</div>
);
};
Loading