feat: implement shared dispatch error signal
Introduce a shared `dispatchError` signal in the command dispatcher to unify error reporting across different components. This allows `CommandLinkManager` to surface errors through the `JournalInput` UI, ensuring that errors from `:cmd[]` directive clicks are visible to the user.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { sendMessage } from "../stores/journalStream";
|
||||
import { createSignal } from "solid-js";
|
||||
import { parseInput } from "./command-parser";
|
||||
import { resolveRollPayload } from "./types/roll";
|
||||
import { resolveSparkPayload } from "./types/spark";
|
||||
@@ -26,6 +27,14 @@ export type DispatchResult =
|
||||
| { ok: true }
|
||||
| { ok: false; error: string };
|
||||
|
||||
/**
|
||||
* Shared signal for dispatch errors from any source (typed or cmd-link clicks).
|
||||
* Components that show errors (JournalInput) read from here; callers that
|
||||
* want errors surfaced (CommandLinkManager) write to it.
|
||||
*/
|
||||
export const [dispatchError, setDispatchError] =
|
||||
createSignal<string | null>(null);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main dispatch
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -60,25 +69,25 @@ export async function dispatchCommand(
|
||||
// Observers can only send chat
|
||||
if (ctx.role === "observer") {
|
||||
const result = sendMessage("chat", { text: raw });
|
||||
return unwrap(result);
|
||||
return finish(unwrap(result));
|
||||
}
|
||||
|
||||
const prefixed = raw.startsWith("/") ? raw : "/" + raw;
|
||||
const parsed = parseInput(prefixed);
|
||||
if (parsed.error) return { ok: false, error: parsed.error };
|
||||
if (parsed.error) return finish({ ok: false, error: parsed.error });
|
||||
|
||||
// Players: chat + stat commands only
|
||||
if (ctx.role === "player") {
|
||||
if (parsed.type === "chat") {
|
||||
const result = sendMessage("chat", { text: raw });
|
||||
return unwrap(result);
|
||||
return finish(unwrap(result));
|
||||
}
|
||||
|
||||
if (parsed.type === "stat") {
|
||||
return dispatchStat(parsed.payload as Record<string, unknown>, ctx);
|
||||
return finish(dispatchStat(parsed.payload as Record<string, unknown>, ctx));
|
||||
}
|
||||
|
||||
return { ok: false, error: "玩家只能发送聊天消息或使用 /stat 命令" };
|
||||
return finish({ ok: false, error: "玩家只能发送聊天消息或使用 /stat 命令" });
|
||||
}
|
||||
|
||||
// GM: all commands
|
||||
@@ -87,7 +96,7 @@ export async function dispatchCommand(
|
||||
parsed.payload as { notation: string; label?: string },
|
||||
);
|
||||
const result = sendMessage("roll", p);
|
||||
return unwrap(result);
|
||||
return finish(unwrap(result));
|
||||
}
|
||||
|
||||
if (parsed.type === "spark") {
|
||||
@@ -98,21 +107,27 @@ export async function dispatchCommand(
|
||||
const remix = match?.remix ?? false;
|
||||
const p = await resolveSparkPayload({ key, csvPath, remix });
|
||||
const result = sendMessage("spark", p);
|
||||
return unwrap(result);
|
||||
return finish(unwrap(result));
|
||||
} catch (e) {
|
||||
return {
|
||||
return finish({
|
||||
ok: false,
|
||||
error: e instanceof Error ? e.message : "种子表掷骰失败",
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed.type === "stat") {
|
||||
return dispatchStat(parsed.payload as Record<string, unknown>, ctx);
|
||||
return finish(dispatchStat(parsed.payload as Record<string, unknown>, ctx));
|
||||
}
|
||||
|
||||
const result = sendMessage(parsed.type, parsed.payload);
|
||||
return unwrap(result);
|
||||
return finish(unwrap(result));
|
||||
}
|
||||
|
||||
/** Update the shared error signal and return the result (pass-through). */
|
||||
function finish(r: DispatchResult): DispatchResult {
|
||||
setDispatchError(r.ok ? null : r.error);
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user