ograph/packages/engine/ui/src/components/ProjectionDefs.tsx
小糯 🐱 f950654827 feat(ui): visual refresh — custom theme, refined layout, fixed bugs
- Custom color system (surface-0..4, accent, mint) replacing raw gray-xxx
- Inter + JetBrains Mono fonts via Google Fonts
- Refined sidebar: compact logo, geometric icons, subtle active states
- Fixed bg-gray-850 bug (invalid Tailwind class) in 9 components
- Polished login page with centered card + gradient logo
- Unified card/table/button/input styling across all components
- Subtle grain texture overlay for depth
- Smoother animations (fade-in, slide-up)
2026-04-13 17:56:39 +08:00

131 lines
5.5 KiB
TypeScript

import { useState, useEffect } from 'react'
import { getProjectionDefs } from '../api'
import { Spinner, EmptyState, HashBadge } from './Common'
export default function ProjectionDefs() {
const [data, setData] = useState<any[]>([])
const [expanded, setExpanded] = useState<Set<string>>(new Set())
const [error, setError] = useState('')
const [loading, setLoading] = useState(true)
useEffect(() => {
getProjectionDefs()
.then((res) => setData(res.projection_defs))
.catch((e) => setError(e.message))
.finally(() => setLoading(false))
}, [])
const toggleExpand = (hash: string) => {
setExpanded((prev) => {
const next = new Set(prev)
if (next.has(hash)) {
next.delete(hash)
} else {
next.add(hash)
}
return next
})
}
if (loading) return <Spinner />
if (error) return <div className="text-red-500 text-center p-8">Error: {error}</div>
return (
<div className="max-w-5xl mx-auto">
<h2 className="text-lg font-semibold text-white tracking-tight mb-5">Projection Definitions</h2>
{data.length === 0 ? (
<EmptyState message="No projection definitions found" />
) : (
<div className="space-y-4">
{data.map((def, i) => (
<div
key={i}
className="bg-surface-1 rounded-xl p-5 border border-white/[0.06] hover:border-white/[0.1] transition-colors"
>
<div className="flex items-start justify-between mb-3">
<div>
<h3 className="font-mono text-lg text-gray-100">{def.name}</h3>
<div className="mt-1">
<HashBadge hash={def.hash || 'unknown'} short={false} />
</div>
</div>
<button
onClick={() => toggleExpand(def.hash || i)}
className="text-blue-400 hover:text-blue-300 text-sm font-medium transition-colors"
>
{expanded.has(def.hash || i) ? 'Hide Details' : 'Show Details'}
</button>
</div>
{expanded.has(def.hash || i) && (
<div className="mt-4 space-y-3 text-sm bg-surface-3/30 rounded-lg p-4 border border-gray-700/50">
{def.sources && (
<div>
<span className="text-gray-400 font-medium">Sources:</span>
<div className="mt-2 space-y-3">
{def.sources.map((s: any, i: number) => (
<div key={i} className="pl-3 border-l-2 border-gray-600">
<div className="text-gray-200 font-mono text-xs">{s.event_def_hash}</div>
<div className="mt-1">
<span className="text-gray-500 text-xs">bindings:</span>{' '}
<span className="text-yellow-400 text-xs">{JSON.stringify(s.bindings)}</span>
</div>
<div className="mt-0.5">
<span className="text-gray-500 text-xs">expression:</span>{' '}
<code className="text-green-400 text-xs">{s.expression}</code>
</div>
</div>
))}
</div>
</div>
)}
{def.params && (
<div>
<span className="text-gray-400 font-medium">Params:</span>
<pre className="mt-1.5 p-3 bg-gray-950 rounded-lg text-xs overflow-x-auto border border-white/[0.06]">
{JSON.stringify(def.params, null, 2)
.split('\n')
.map((line, i) => {
if (line.includes(':')) {
const [key, ...rest] = line.split(':')
return (
<div key={i}>
<span className="text-blue-400">{key}:</span>
<span className="text-green-300">{rest.join(':')}</span>
</div>
)
}
return (
<div key={i} className="text-gray-300">
{line}
</div>
)
})}
</pre>
</div>
)}
{def.value_schema && (
<div>
<span className="text-gray-400 font-medium">Value Schema:</span>
<pre className="mt-1.5 p-3 bg-gray-950 rounded-lg text-xs overflow-x-auto border border-white/[0.06] text-purple-300">
{JSON.stringify(def.value_schema, null, 2)}
</pre>
</div>
)}
{def.initial_value !== undefined && (
<div>
<span className="text-gray-400 font-medium">Initial Value:</span>
<pre className="mt-1.5 p-3 bg-gray-950 rounded-lg text-xs overflow-x-auto border border-white/[0.06] text-blue-300">
{JSON.stringify(def.initial_value, null, 2)}
</pre>
</div>
)}
</div>
)}
</div>
))}
</div>
)}
</div>
)
}