130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
import { ExpandBtn } from "@/components/Expand"
|
|
import { ViewStruct } from "@codec-components"
|
|
import { Var } from "@polkadot-api/metadata-builders"
|
|
import { useStateObservable } from "@react-rxjs/core"
|
|
import React, { useContext, useState } from "react"
|
|
import { twMerge } from "tailwind-merge"
|
|
import { Marker } from "../common/Markers"
|
|
import {
|
|
isActive$,
|
|
isCollapsed$,
|
|
setHovered,
|
|
toggleCollapsed,
|
|
} from "../common/paths.state"
|
|
import { useSubtreeFocus } from "../common/SubtreeFocus"
|
|
import { ItemMarker } from "../EditCodec/Tree/codec-components"
|
|
import { CopyChildBinary, useReportBinary } from "./CopyBinary"
|
|
import { ChildProvider, TitleContext } from "./TitleContext"
|
|
import { isComplexNested } from "./utils"
|
|
import { Dot } from "lucide-react"
|
|
|
|
const StructItem: React.FC<{
|
|
name: string
|
|
children: React.ReactNode
|
|
path: string[]
|
|
field: Var
|
|
value: unknown
|
|
}> = ({ name, children, path, field, value }) => {
|
|
const pathStr = path.join(".")
|
|
const isActive = useStateObservable(isActive$(pathStr))
|
|
const isExpanded = !useStateObservable(isCollapsed$(pathStr))
|
|
const hasParentTitle = !!useContext(TitleContext)
|
|
const [titleElement, setTitleElement] = useState<HTMLElement | null>(null)
|
|
|
|
const isComplexShape = isComplexNested(field, value)
|
|
|
|
const title = isComplexShape ? (
|
|
<span
|
|
onClick={() => toggleCollapsed(pathStr)}
|
|
className="cursor-pointer flex select-none items-center py-1 gap-1"
|
|
>
|
|
{hasParentTitle && <ItemMarker />}
|
|
<ExpandBtn expanded={isExpanded} />
|
|
<span className="flex items-center gap-1" ref={setTitleElement}>
|
|
<span className="opacity-75">{name}</span>
|
|
</span>
|
|
<div className="flex-1 text-right">
|
|
<CopyChildBinary visible={isActive} />
|
|
</div>
|
|
</span>
|
|
) : (
|
|
<span className="flex items-center py-1 gap-1">
|
|
{hasParentTitle && <ItemMarker />}
|
|
<Dot size={16} />
|
|
<span className="select-none flex items-center gap-1">
|
|
<span className="opacity-75">{name}</span>
|
|
<span ref={setTitleElement} />
|
|
</span>
|
|
<div>{children}</div>
|
|
<div className="flex-1 text-right">
|
|
<CopyChildBinary visible={isActive} />
|
|
</div>
|
|
</span>
|
|
)
|
|
|
|
return (
|
|
<li
|
|
className={twMerge(
|
|
"flex flex-col transition-all duration-300",
|
|
isActive && "bg-secondary/80",
|
|
)}
|
|
onMouseEnter={() => setHovered({ id: pathStr, hover: true })}
|
|
onMouseLeave={() => setHovered({ id: pathStr, hover: false })}
|
|
>
|
|
<ChildProvider titleElement={titleElement}>
|
|
<Marker id={path} />
|
|
{title}
|
|
{isComplexShape && (
|
|
<div
|
|
className={twMerge(
|
|
"flex flex-row pb-2",
|
|
hasParentTitle ? "pl-4" : "pl-2",
|
|
isExpanded ? "" : "hidden",
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
)}
|
|
</ChildProvider>
|
|
</li>
|
|
)
|
|
}
|
|
|
|
export const CStruct: ViewStruct = ({
|
|
innerComponents,
|
|
path,
|
|
shape,
|
|
encodedValue,
|
|
value,
|
|
}) => {
|
|
useReportBinary(encodedValue)
|
|
const focus = useSubtreeFocus()
|
|
const hasParentTitle = !!useContext(TitleContext)
|
|
const sub = focus.getNextPath(path)
|
|
if (sub) {
|
|
const field = Object.entries(innerComponents).find(([key]) => key === sub)
|
|
return field?.[1]
|
|
}
|
|
|
|
return (
|
|
<ul
|
|
className={twMerge(
|
|
"flex flex-col w-full",
|
|
hasParentTitle && "border-l border-primary",
|
|
)}
|
|
>
|
|
{Object.entries(innerComponents).map(([name, jsx]) => (
|
|
<StructItem
|
|
name={name}
|
|
key={name}
|
|
path={[...path, name]}
|
|
field={shape.value[name]}
|
|
value={value[name]}
|
|
>
|
|
{jsx}
|
|
</StructItem>
|
|
))}
|
|
</ul>
|
|
)
|
|
}
|