Files
ttrpg-tools/src/components/journal/VariableView.tsx
T
hyper 0b029b325e docs(declare): document the YAML declare format
Update the journal-set doc entry, the CLI diagnostic table, and the
authoring skill, along with the in-app empty-state hint and the var
message comment.
2026-09-12 22:51:12 +08:00

142 lines
4.4 KiB
TypeScript

/**
* VariableView — flat list of all variable key-value pairs.
*
* Hovering a value shows a popup with:
* - Declaration expression (if any), styled distinctly
* - Active tag modifiers: #tag +N (from $source)
*/
import { Component, For, createMemo, Show, createSignal } from "solid-js";
import { useJournalStream } from "../stores/journalStream";
import { useJournalCompletions } from "./completions";
import { getMods, getDeclExpr } from "./var-reactivity";
export const VariableView: Component = () => {
const stream = useJournalStream();
const comp = useJournalCompletions();
const values = createMemo(() => stream.variables);
/** Runtime engine diagnostics (bad declarations / modifiers). */
const diagnostics = createMemo(() => comp.diagnostics());
/** Flat list of all variables */
const items = createMemo(() => {
const vars = values();
return Object.entries(vars).map(([key, value]) => ({
key,
value,
isTag: value.startsWith("#"),
}));
});
const hasAnyData = () =>
comp.data.declarations.length > 0 || Object.keys(values()).length > 0;
return (
<div class="h-full overflow-y-auto bg-gray-50">
<Show when={diagnostics().length > 0}>
<div class="border-b border-amber-200 bg-amber-50 px-3 py-2">
<div class="text-xs font-semibold text-amber-800 mb-1">
变量求值有 {diagnostics().length} 个问题
</div>
<ul class="space-y-1">
<For each={diagnostics()}>
{(d) => (
<li class="text-xs text-amber-700 font-mono break-all">{d.message}</li>
)}
</For>
</ul>
</div>
</Show>
<Show
when={hasAnyData()}
fallback={
<p class="text-center text-gray-400 text-xs py-8">
暂无变量定义。在文档中使用 ```yaml role=declare 代码块定义变量。
</p>
}
>
<div class="divide-y divide-gray-100">
<For each={items()}>
{(item) => (
<VariableRow
key={item.key}
value={item.value}
isTag={item.isTag}
/>
)}
</For>
</div>
</Show>
</div>
);
};
// ---------------------------------------------------------------------------
// VariableRow — single row with hover popup
// ---------------------------------------------------------------------------
const VariableRow: Component<{
key: string;
value: string;
isTag: boolean;
}> = (props) => {
const [hovered, setHovered] = createSignal(false);
const declExpr = createMemo(() => getDeclExpr(props.key));
const mods = createMemo(() => getMods(props.key));
const hasPopup = () => !!declExpr() || mods().length > 0;
return (
<div
class="relative flex items-center px-3 py-1.5 hover:bg-white/50 transition-colors"
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<span class="flex-1 text-sm font-mono text-gray-800">{props.key}</span>
<span
class={props.isTag
? "text-sm font-mono text-purple-700 font-semibold"
: "text-sm font-mono text-gray-700 font-semibold"
}
>
{props.value}
</span>
{/* Hover popup */}
<Show when={hasPopup() && hovered()}>
<div class="absolute right-0 top-full mt-1 z-50 bg-white border border-gray-200 rounded-md shadow-lg p-2 min-w-45 text-xs">
{/* Declaration expression */}
<Show when={declExpr()}>
<div class="mb-1">
<span class="text-gray-400">expr: </span>
<span class="font-mono text-gray-500 bg-gray-50 px-1 rounded">
{declExpr()}
</span>
</div>
</Show>
{/* Tag modifiers */}
<Show when={mods().length > 0}>
<div class={declExpr() ? "border-t border-gray-100 pt-1 mt-1" : ""}>
<For each={mods()}>
{(mod) => (
<div class="flex items-center gap-1 text-gray-500">
<span class="font-mono text-purple-600">{mod.tag}</span>
<span>+{mod.value}</span>
<span class="text-gray-400">(from {mod.source})</span>
</div>
)}
</For>
</div>
</Show>
</div>
</Show>
</div>
);
};
export default VariableView;