diff --git a/apps/mesh-cloud/.eslintrc.cjs b/apps/mesh-cloud/.eslintrc.cjs index 4482f0b..d567c05 100644 --- a/apps/mesh-cloud/.eslintrc.cjs +++ b/apps/mesh-cloud/.eslintrc.cjs @@ -47,6 +47,7 @@ const config = { "@typescript-eslint/prefer-optional-chain": "off", "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-redundant-type-constituents": "off", + "@typescript-eslint/prefer-nullish-coalescing": "off", }, }; module.exports = config; diff --git a/apps/mesh-cloud/package.json b/apps/mesh-cloud/package.json index 7953012..3ca39a3 100644 --- a/apps/mesh-cloud/package.json +++ b/apps/mesh-cloud/package.json @@ -15,30 +15,38 @@ "dev": "next dev", "postinstall": "prisma generate", "lint": "next lint", - "start": "next start" + "start": "next start", + "clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm package-lock.json" }, "dependencies": { "@auth/prisma-adapter": "^1.6.0", + "@cardananium/cquisitor-lib": "^0.1.0-beta.12", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", "@heroicons/react": "^2.1.5", - "@meshsdk/core": "1.8.2", - "@meshsdk/react": "1.8.2", + "@meshsdk/core": "1.9.0-beta.34", + "@meshsdk/react": "1.9.0-beta.34", + "@mui/material": "^7.0.2", "@prisma/client": "^5.14.0", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-checkbox": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-menubar": "^1.1.7", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", + "@textea/json-viewer": "^4.0.1", "@trpc/client": "^11.0.0-rc.446", "@trpc/next": "^11.0.0-rc.446", "@trpc/react-query": "^11.0.0-rc.446", "@trpc/server": "^11.0.0-rc.446", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "copy-to-clipboard": "^3.3.3", "geist": "^1.3.0", "lucide-react": "^0.436.0", @@ -72,6 +80,5 @@ }, "ct3aMetadata": { "initVersion": "7.37.0" - }, - "packageManager": "npm@10.7.0" -} \ No newline at end of file + } +} diff --git a/apps/mesh-cloud/src/apps/cquisitor/command.tsx b/apps/mesh-cloud/src/apps/cquisitor/command.tsx new file mode 100644 index 0000000..a9bd69c --- /dev/null +++ b/apps/mesh-cloud/src/apps/cquisitor/command.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; + +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "@/components/ui/command"; +import { Button } from "@/components/ui/button"; +import * as cq from "@cardananium/cquisitor-lib"; +import { nonCSLDecodeOption } from "./utils"; + +export function CquisitorCommand({ + currentType, + setItem, +}: { + currentType: string; + setItem: (item: string) => void; +}) { + const [open, setOpen] = useState(false); + const [suggestions, setSuggestions] = useState([]); + const [allTypes, setAllTypes] = useState([]); + + // Command + J to open panel + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "j" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + const updateTypeCount = useCallback((type: string) => { + const typeCounts = JSON.parse(localStorage.getItem("typeCounts") || "{}"); + typeCounts[type] = (typeCounts[type] || 0) + 1; + localStorage.setItem("typeCounts", JSON.stringify(typeCounts)); + }, []); + + const getAllOptions = () => { + const allCslTypes = cq.get_decodable_types(); + const allNonCslTypes = Object.keys(nonCSLDecodeOption); + return [...allCslTypes, ...allNonCslTypes]; + }; + + const updateSuggestions = useCallback(() => { + const typeCounts: Record = JSON.parse( + localStorage.getItem("typeCounts") || "{}", + ); + + // Convert the typeCounts object into an array of [type, count] pairs + const sortedTypes = Object.entries(typeCounts) + .sort(([, countA], [, countB]) => countB - countA) // Sort by count in descending order + .slice(0, 3) // Take the top 3 items + .map(([type]) => type); // Extract only the type names + + setSuggestions(sortedTypes); + + const allTypes = getAllOptions(); + const filteredTypes = allTypes.filter( + (type) => !sortedTypes.includes(type), + ); + setAllTypes(filteredTypes); + }, []); + + useEffect(() => { + updateSuggestions(); + }, [updateSuggestions]); + + const onSelect = (item: string) => { + updateTypeCount(item); + updateSuggestions(); + setItem(item); + setOpen(false); + }; + + return ( + <> + + + + + No results found. + + {suggestions.map((item) => ( + { + onSelect(item); + }} + > + {item} + + ))} + + + + {allTypes.map((item) => ( + { + onSelect(item); + }} + > + {item} + + ))} + + + + + ); +} diff --git a/apps/mesh-cloud/src/apps/cquisitor/cquisitor.tsx b/apps/mesh-cloud/src/apps/cquisitor/cquisitor.tsx new file mode 100644 index 0000000..f567672 --- /dev/null +++ b/apps/mesh-cloud/src/apps/cquisitor/cquisitor.tsx @@ -0,0 +1,146 @@ +import CardSection from "@/components/card-section"; +import CquisitorLayout from "./layout"; +import { useEffect, useState } from "react"; +import Metatags from "@/components/site/metatags"; +import { Card } from "@/components/ui/card"; + +import { JsonViewer } from "@textea/json-viewer"; +import { TopBar } from "./top-bar"; +import { CquisitorCommand } from "./command"; + +import { Textarea } from "@/components/ui/textarea"; +import { + decode, + getPositionDataType, + getTxAddressDataType, + getTxIdDataType, + nonCSLDecodeOption, + type DecodeType, +} from "./utils"; +import * as cq from "@cardananium/cquisitor-lib"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; + +export default function Cquisitor() { + const [apiKey, setApiKey] = useState(""); + const [network, setNetwork] = useState("mainnet"); + const [service, setService] = useState<"blockfrost" | "maestro">( + "blockfrost", + ); + const [loading, setLoading] = useState(false); + + const [cborHex, setCborHex] = useState(""); + const [decodeType, setDecodeType] = useState("decode-by-csl"); + const [currentType, setCurrentType] = useState("Transaction"); + const [currentData, setCurrentData] = useState(""); + + // display + const [cborPosition, setCborPosition] = useState([0, 0]); + const [possibleTypes, setPossibleTypes] = useState([]); + + const setItem = (item: string) => { + setCurrentType(item); + if (nonCSLDecodeOption[item]) { + setDecodeType(nonCSLDecodeOption[item]); + } else { + setDecodeType("decode-by-csl"); + } + }; + + useEffect(() => { + const decoded = decode(cborHex, decodeType, currentType); + setCurrentData(decoded); + const possibleTypes = cq.get_possible_types_for_input(cborHex); + setPossibleTypes(possibleTypes); + console.log("possibleTypes", possibleTypes); + }, [currentType, cborHex, decodeType]); + + let showAsJson = true; + if (typeof currentData === "string" || currentData instanceof String) { + console.log(currentData); + showAsJson = false; + } + + useEffect(() => { + const apiKey = localStorage.getItem("apiKey"); + if (apiKey) setApiKey(apiKey); + const network = localStorage.getItem("network"); + if (network) setNetwork(network); + }, []); + + return ( + + + + <> + + <> +
+
+