feat(desktop): add typed inspector panels
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -45,6 +45,10 @@ import {
|
||||
toBackendErrorNotice,
|
||||
type OperatorNotice,
|
||||
} from "./lib/operator-notices";
|
||||
import {
|
||||
buildStructuredInspectorSections,
|
||||
type StructuredInspectorSection,
|
||||
} from "./lib/structured-inspector";
|
||||
import { cn } from "./lib/utils";
|
||||
|
||||
declare const __APP_VERSION__: string;
|
||||
@@ -439,6 +443,14 @@ function App() {
|
||||
const inspectorText = tauriAvailable
|
||||
? formatInspectorValue(selectedValueRecord, selectedKey, isInspectorLoading)
|
||||
: (selectedKey?.value ?? "");
|
||||
const structuredInspectorSections = tauriAvailable
|
||||
? buildStructuredInspectorSections(selectedValueRecord)
|
||||
: [];
|
||||
const showStructuredInspector =
|
||||
selectedKey !== null &&
|
||||
!isInspectorLoading &&
|
||||
structuredInspectorSections.length > 0 &&
|
||||
!isEditable;
|
||||
|
||||
const supportNote = tauriAvailable
|
||||
? buildLiveSupportNote(selectedValueRecord, selectedKey)
|
||||
@@ -1662,15 +1674,20 @@ function App() {
|
||||
</div>
|
||||
|
||||
{selectedKey ? (
|
||||
<Textarea
|
||||
value={isEditable ? draftValue : inspectorText}
|
||||
onChange={(event) => setDraftValue(event.target.value)}
|
||||
readOnly={!isEditable}
|
||||
className={cn(
|
||||
"mt-3 h-64 resize-none font-mono",
|
||||
!isEditable && "border-[var(--line-soft)] bg-[var(--surface-muted)] text-[var(--ink-soft)]",
|
||||
)}
|
||||
/>
|
||||
showStructuredInspector ? (
|
||||
<StructuredInspectorPanels sections={structuredInspectorSections} />
|
||||
) : (
|
||||
<Textarea
|
||||
value={isEditable ? draftValue : inspectorText}
|
||||
onChange={(event) => setDraftValue(event.target.value)}
|
||||
readOnly={!isEditable}
|
||||
className={cn(
|
||||
"mt-3 h-64 resize-none font-mono",
|
||||
!isEditable &&
|
||||
"border-[var(--line-soft)] bg-[var(--surface-muted)] text-[var(--ink-soft)]",
|
||||
)}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div className="mt-3 flex h-64 items-center justify-center rounded-[22px] border border-dashed border-[var(--line-strong)] bg-[var(--surface-muted)] px-6 text-center text-sm text-[var(--ink-soft)]">
|
||||
No key matches the current pattern in {activeDb}. Clear or adjust the search to restore an inspector target.
|
||||
@@ -1944,6 +1961,85 @@ function InfoCard({
|
||||
);
|
||||
}
|
||||
|
||||
function StructuredInspectorPanels({
|
||||
sections,
|
||||
}: {
|
||||
sections: StructuredInspectorSection[];
|
||||
}) {
|
||||
return (
|
||||
<div className="mt-3 h-64 space-y-3 overflow-auto pr-1">
|
||||
{sections.map((section) => (
|
||||
<section
|
||||
key={section.id}
|
||||
className="rounded-[22px] border border-[var(--line-soft)] bg-[var(--surface-muted)] p-4"
|
||||
>
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--ink-0)]">{section.title}</p>
|
||||
<p className="mt-1 text-sm text-[var(--ink-soft)]">{section.summary}</p>
|
||||
</div>
|
||||
<Badge variant="neutral">{section.rows.length} rows</Badge>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 hidden grid-cols-[minmax(0,0.38fr)_minmax(0,0.62fr)] gap-3 px-1 sm:grid">
|
||||
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-[var(--ink-muted)]">
|
||||
{section.primaryLabel}
|
||||
</p>
|
||||
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-[var(--ink-muted)]">
|
||||
{section.secondaryLabel}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 space-y-2">
|
||||
{section.rows.map((row) => (
|
||||
<div
|
||||
key={row.id}
|
||||
className="grid gap-3 rounded-[18px] border border-[var(--line-soft)] bg-[var(--surface-base)] p-3 sm:grid-cols-[minmax(0,0.38fr)_minmax(0,0.62fr)]"
|
||||
>
|
||||
<div>
|
||||
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-[var(--ink-muted)] sm:hidden">
|
||||
{section.primaryLabel}
|
||||
</p>
|
||||
<p className="mt-1 break-all font-mono text-sm text-[var(--ink-0)] sm:mt-0">
|
||||
{row.primary}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-[var(--ink-muted)] sm:hidden">
|
||||
{section.secondaryLabel}
|
||||
</p>
|
||||
<p className="mt-1 break-all font-mono text-sm text-[var(--ink-0)] sm:mt-0">
|
||||
{row.secondary}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{row.detailLines && row.detailLines.length > 0 ? (
|
||||
<div className="sm:col-span-2">
|
||||
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-[var(--ink-muted)]">
|
||||
Payload
|
||||
</p>
|
||||
<div className="mt-2 space-y-2">
|
||||
{row.detailLines.map((detail) => (
|
||||
<div
|
||||
key={`${row.id}-${detail}`}
|
||||
className="rounded-2xl border border-[var(--line-soft)] bg-[var(--surface-muted)] px-3 py-2 font-mono text-xs text-[var(--ink-soft)]"
|
||||
>
|
||||
{detail}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function isTauriRuntimeAvailable() {
|
||||
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
||||
}
|
||||
@@ -2281,7 +2377,7 @@ function buildLiveSupportNote(record: RedisValueRecord | null, selectedKey: KeyR
|
||||
case "set":
|
||||
case "sorted_set":
|
||||
case "stream":
|
||||
return `${formatRedisKeyTypeLabel(record.metadata.key_type)} values are loaded live and remain explicitly read only in the current desktop contract.`;
|
||||
return `${formatRedisKeyTypeLabel(record.metadata.key_type)} values are loaded live into structured read-only panels in the current desktop contract.`;
|
||||
default:
|
||||
return "The current key is available for live inspection through the desktop backend contract.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user