A complete blockchain implementation featuring: - synord: Full node with GHOSTDAG consensus - explorer-web: Modern React blockchain explorer with 3D DAG visualization - CLI wallet and tools - Smart contract SDK and example contracts (DEX, NFT, token) - WASM crypto library for browser/mobile
195 lines
6.7 KiB
TypeScript
195 lines
6.7 KiB
TypeScript
/**
|
|
* Visual mini-DAG diagram showing block relationships.
|
|
* Displays parent blocks → current block → child blocks.
|
|
*/
|
|
|
|
import { Link } from 'react-router-dom';
|
|
import { ArrowDown, Box, Layers } from 'lucide-react';
|
|
import { truncateHash } from '../lib/utils';
|
|
import { cn } from '../lib/utils';
|
|
|
|
interface BlockRelationshipDiagramProps {
|
|
currentHash: string;
|
|
parentHashes: string[];
|
|
childrenHashes: string[];
|
|
isChainBlock?: boolean;
|
|
mergeSetBlues?: string[];
|
|
mergeSetReds?: string[];
|
|
}
|
|
|
|
export default function BlockRelationshipDiagram({
|
|
currentHash,
|
|
parentHashes,
|
|
childrenHashes,
|
|
isChainBlock = true,
|
|
mergeSetBlues = [],
|
|
mergeSetReds = [],
|
|
}: BlockRelationshipDiagramProps) {
|
|
// Determine selected parent (first one by convention)
|
|
const selectedParent = parentHashes[0];
|
|
const otherParents = parentHashes.slice(1);
|
|
|
|
return (
|
|
<div className="relative overflow-hidden rounded-2xl border border-gray-700/50 bg-gray-900/40 backdrop-blur-xl p-6">
|
|
{/* Background gradient */}
|
|
<div className="absolute inset-0 bg-gradient-to-br from-synor-500/5 via-transparent to-violet-500/5" />
|
|
|
|
<div className="relative">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-2 mb-6">
|
|
<Layers size={18} className="text-synor-400" />
|
|
<h3 className="font-semibold">Block Relationships</h3>
|
|
</div>
|
|
|
|
<div className="flex flex-col items-center gap-3">
|
|
{/* Parent Blocks */}
|
|
{parentHashes.length > 0 && (
|
|
<>
|
|
<div className="flex items-center gap-3 flex-wrap justify-center">
|
|
{selectedParent && (
|
|
<BlockNode
|
|
hash={selectedParent}
|
|
type="parent"
|
|
isSelected
|
|
label="Selected Parent"
|
|
/>
|
|
)}
|
|
{otherParents.map((hash) => (
|
|
<BlockNode
|
|
key={hash}
|
|
hash={hash}
|
|
type="parent"
|
|
label="Parent"
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Arrow down */}
|
|
<div className="flex flex-col items-center">
|
|
<div className="w-px h-4 bg-gradient-to-b from-synor-500/50 to-synor-500" />
|
|
<ArrowDown size={16} className="text-synor-400 -mt-1" />
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Current Block - Highlighted */}
|
|
<div className="relative">
|
|
{/* Glow effect */}
|
|
<div className="absolute inset-0 -m-2 rounded-2xl bg-synor-500/20 blur-xl" />
|
|
|
|
<div
|
|
className={cn(
|
|
'relative px-6 py-4 rounded-xl border-2 bg-gray-800/80 backdrop-blur',
|
|
isChainBlock
|
|
? 'border-synor-500 shadow-[0_0_30px_rgba(124,58,237,0.3)]'
|
|
: 'border-blue-500 shadow-[0_0_30px_rgba(59,130,246,0.3)]'
|
|
)}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div
|
|
className={cn(
|
|
'p-2 rounded-lg',
|
|
isChainBlock ? 'bg-synor-500/20' : 'bg-blue-500/20'
|
|
)}
|
|
>
|
|
<Box
|
|
size={24}
|
|
className={isChainBlock ? 'text-synor-400' : 'text-blue-400'}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<div className="text-xs text-gray-400 mb-0.5">Current Block</div>
|
|
<div className="font-mono text-sm font-medium">
|
|
{truncateHash(currentHash, 8, 8)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Chain block indicator */}
|
|
{isChainBlock && (
|
|
<div className="absolute -top-2 -right-2 px-2 py-0.5 bg-synor-500 text-white text-xs font-medium rounded-full">
|
|
Chain
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Arrow down to children */}
|
|
{childrenHashes.length > 0 && (
|
|
<>
|
|
<div className="flex flex-col items-center">
|
|
<ArrowDown size={16} className="text-synor-400" />
|
|
<div className="w-px h-4 bg-gradient-to-b from-synor-500 to-synor-500/50" />
|
|
</div>
|
|
|
|
{/* Child Blocks */}
|
|
<div className="flex items-center gap-3 flex-wrap justify-center">
|
|
{childrenHashes.map((hash) => (
|
|
<BlockNode key={hash} hash={hash} type="child" label="Child" />
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Merge set info */}
|
|
{(mergeSetBlues.length > 0 || mergeSetReds.length > 0) && (
|
|
<div className="mt-4 pt-4 border-t border-gray-700/50 w-full">
|
|
<div className="flex items-center gap-4 justify-center text-sm">
|
|
{mergeSetBlues.length > 0 && (
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-blue-500" />
|
|
<span className="text-gray-400">
|
|
{mergeSetBlues.length} blue merge
|
|
</span>
|
|
</div>
|
|
)}
|
|
{mergeSetReds.length > 0 && (
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-red-500" />
|
|
<span className="text-gray-400">
|
|
{mergeSetReds.length} red merge
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface BlockNodeProps {
|
|
hash: string;
|
|
type: 'parent' | 'child';
|
|
isSelected?: boolean;
|
|
label: string;
|
|
}
|
|
|
|
function BlockNode({ hash, type, isSelected, label }: BlockNodeProps) {
|
|
return (
|
|
<Link
|
|
to={`/block/${hash}`}
|
|
className={cn(
|
|
'group relative px-4 py-2.5 rounded-lg border transition-all duration-200',
|
|
'hover:scale-105 hover:shadow-lg',
|
|
isSelected
|
|
? 'bg-amber-900/30 border-amber-600/50 hover:border-amber-500'
|
|
: type === 'parent'
|
|
? 'bg-gray-800/50 border-gray-600/50 hover:border-gray-500'
|
|
: 'bg-gray-800/50 border-gray-600/50 hover:border-gray-500'
|
|
)}
|
|
>
|
|
<div className="text-xs text-gray-500 mb-0.5">{label}</div>
|
|
<div className="font-mono text-sm text-gray-300 group-hover:text-white transition-colors">
|
|
{truncateHash(hash, 6, 6)}
|
|
</div>
|
|
|
|
{/* Selected indicator */}
|
|
{isSelected && (
|
|
<div className="absolute -top-1.5 -right-1.5 w-3 h-3 rounded-full bg-amber-500 border-2 border-gray-900" />
|
|
)}
|
|
</Link>
|
|
);
|
|
}
|