refactor: replace stat system with variable system
Replaces the specialized `stat` system with a more general-purpose
variable system. This includes:
- Renaming `ReactiveStatManager` to `ReactiveVariableManager`
- Changing template syntax from `${key}` to `{{$key}}`
- Replacing `StatsView` with `VariableView` to show declared, plain,
and tag-typed variables
- Updating command `/stat` to `/set`
- Refactoring the reactivity engine to support variable declarations
and tag modifiers via `csv role=declare` blocks
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* VariableView — table view of all variable key-value pairs and active tags.
|
||||
*
|
||||
* Shows variables grouped by type (declared, plain, tag-typed) and an
|
||||
* active tags section showing which variable activates each tag and
|
||||
* what modifier effects are in play.
|
||||
*/
|
||||
|
||||
import { Component, For, createMemo, Show } from "solid-js";
|
||||
import { useJournalStream } from "../stores/journalStream";
|
||||
import { useJournalCompletions } from "./completions";
|
||||
import { computeActiveTags } from "./var-reactivity";
|
||||
import { extractDependencies } from "./var-reactivity";
|
||||
import type { VarDeclaration } from "./declare-parser";
|
||||
|
||||
export const VariableView: Component = () => {
|
||||
const stream = useJournalStream();
|
||||
const comp = useJournalCompletions();
|
||||
|
||||
const declarations = createMemo(() => comp.data.declarations);
|
||||
const tagModifiers = createMemo(() => comp.data.tagModifiers);
|
||||
const values = createMemo(() => stream.variables);
|
||||
|
||||
/** Build a map: $key → VarDeclaration */
|
||||
const declMap = createMemo(() => {
|
||||
const map = new Map<string, VarDeclaration>();
|
||||
for (const d of declarations()) {
|
||||
map.set(d.key, d);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
/** Active tags */
|
||||
const activeTags = createMemo(() => computeActiveTags(values()));
|
||||
|
||||
/** Tag modifier index: #tag → [{target, expression}] */
|
||||
const tagModMap = createMemo(() => {
|
||||
const map = new Map<string, Array<{ target: string; expression: string }>>();
|
||||
for (const tm of tagModifiers()) {
|
||||
let list = map.get(tm.tag);
|
||||
if (!list) {
|
||||
list = [];
|
||||
map.set(tm.tag, list);
|
||||
}
|
||||
list.push({ target: tm.target, expression: tm.expression });
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
/** Which variable activates each tag */
|
||||
const tagActivators = createMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
const vars = values();
|
||||
for (const [key, val] of Object.entries(vars)) {
|
||||
if (val.startsWith("#")) {
|
||||
if (!map.has(val)) map.set(val, key);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
/** Group variables: declared, plain (set directly), tag-typed */
|
||||
const groups = createMemo(() => {
|
||||
const vars = values();
|
||||
const declKeys = new Set(declarations().map((d) => d.key));
|
||||
|
||||
const declared: Array<{ key: string; value: string; expr: string; deps: string[] }> = [];
|
||||
const plain: Array<{ key: string; value: string }> = [];
|
||||
const tagVars: Array<{ key: string; value: string }> = [];
|
||||
|
||||
for (const [key, val] of Object.entries(vars)) {
|
||||
if (val.startsWith("#")) {
|
||||
tagVars.push({ key, value: val });
|
||||
} else if (declKeys.has(key)) {
|
||||
const decl = declMap().get(key)!;
|
||||
declared.push({
|
||||
key,
|
||||
value: val,
|
||||
expr: decl.expression,
|
||||
deps: extractDependencies(decl.expression),
|
||||
});
|
||||
} else {
|
||||
plain.push({ key, value: val });
|
||||
}
|
||||
}
|
||||
|
||||
return { declared, plain, tagVars };
|
||||
});
|
||||
|
||||
const hasAnyData = () =>
|
||||
declarations().length > 0 ||
|
||||
Object.keys(values()).length > 0 ||
|
||||
activeTags().length > 0;
|
||||
|
||||
return (
|
||||
<div class="h-full overflow-y-auto bg-gray-50">
|
||||
<Show
|
||||
when={hasAnyData()}
|
||||
fallback={
|
||||
<p class="text-center text-gray-400 text-xs py-8">
|
||||
暂无变量定义。在文档中使用 ```csv role=declare 代码块定义变量。
|
||||
</p>
|
||||
}
|
||||
>
|
||||
{/* Active Tags */}
|
||||
<Show when={activeTags().length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="sticky top-0 bg-gray-100 px-3 py-1.5 border-b border-gray-200">
|
||||
<span class="text-xs font-semibold text-gray-600">激活标签</span>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<For each={activeTags()}>
|
||||
{(tag) => {
|
||||
const activator = () => tagActivators().get(tag);
|
||||
const mods = () => tagModMap().get(tag) ?? [];
|
||||
return (
|
||||
<div class="px-3 py-1.5">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-sm font-mono text-purple-700 font-semibold">
|
||||
{tag}
|
||||
</span>
|
||||
<Show when={activator()}>
|
||||
<span class="text-[10px] text-gray-400">
|
||||
← {activator()}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={mods().length > 0}>
|
||||
<div class="mt-1 space-y-0.5">
|
||||
<For each={mods()}>
|
||||
{(mod) => {
|
||||
const currentVal = () => values()[mod.target];
|
||||
return (
|
||||
<div class="text-[10px] text-gray-500 ml-2 flex items-center gap-1">
|
||||
<span class="font-mono">{mod.target}</span>
|
||||
<span>+{mod.expression}</span>
|
||||
<Show when={currentVal() !== undefined}>
|
||||
<span class="text-gray-400">
|
||||
= {currentVal()}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Declared Variables */}
|
||||
<Show when={groups().declared.length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="sticky top-0 bg-gray-100 px-3 py-1.5 border-b border-gray-200">
|
||||
<span class="text-xs font-semibold text-gray-600">
|
||||
声明变量
|
||||
</span>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<For each={groups().declared}>
|
||||
{(item) => (
|
||||
<div class="flex items-center px-3 py-1.5 hover:bg-white/50 transition-colors">
|
||||
<div class="flex-1 min-w-0 flex items-center gap-1.5">
|
||||
<span class="text-sm font-mono text-gray-800">
|
||||
{item.key}
|
||||
</span>
|
||||
<span class="text-[10px] text-gray-400 bg-gray-100 px-1 rounded shrink-0">
|
||||
{item.expr}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<span class="text-sm font-mono text-gray-700 font-semibold">
|
||||
{item.value}
|
||||
</span>
|
||||
<Show when={item.deps.length > 0}>
|
||||
<span class="text-[10px] text-gray-400">
|
||||
← {item.deps.join(", ")}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Plain Variables */}
|
||||
<Show when={groups().plain.length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="sticky top-0 bg-gray-100 px-3 py-1.5 border-b border-gray-200">
|
||||
<span class="text-xs font-semibold text-gray-600">
|
||||
直接设置
|
||||
</span>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<For each={groups().plain}>
|
||||
{(item) => (
|
||||
<div class="flex items-center px-3 py-1.5 hover:bg-white/50 transition-colors">
|
||||
<span class="flex-1 text-sm font-mono text-gray-800">
|
||||
{item.key}
|
||||
</span>
|
||||
<span class="text-sm font-mono text-gray-700 font-semibold">
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Tag Variables */}
|
||||
<Show when={groups().tagVars.length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="sticky top-0 bg-gray-100 px-3 py-1.5 border-b border-gray-200">
|
||||
<span class="text-xs font-semibold text-gray-600">标签变量</span>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<For each={groups().tagVars}>
|
||||
{(item) => (
|
||||
<div class="flex items-center px-3 py-1.5 hover:bg-white/50 transition-colors">
|
||||
<span class="flex-1 text-sm font-mono text-gray-800">
|
||||
{item.key}
|
||||
</span>
|
||||
<span class="text-sm font-mono text-purple-700 font-semibold">
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user