103 lines
2.6 KiB
TypeScript
103 lines
2.6 KiB
TypeScript
|
|
import type { IGameContext, PromptEvent } from 'boardgame-core';
|
|||
|
|
|
|||
|
|
export interface PromptHandlerOptions {
|
|||
|
|
commands: IGameContext<any>['commands'];
|
|||
|
|
onPrompt: (prompt: PromptEvent) => void;
|
|||
|
|
onCancel: (reason?: string) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export class PromptHandler {
|
|||
|
|
private commands: IGameContext<any>['commands'];
|
|||
|
|
private onPrompt: (prompt: PromptEvent) => void;
|
|||
|
|
private onCancel: (reason?: string) => void;
|
|||
|
|
private activePrompt: PromptEvent | null = null;
|
|||
|
|
private isListening = false;
|
|||
|
|
private pendingInput: string | null = null;
|
|||
|
|
|
|||
|
|
constructor(options: PromptHandlerOptions) {
|
|||
|
|
this.commands = options.commands;
|
|||
|
|
this.onPrompt = options.onPrompt;
|
|||
|
|
this.onCancel = options.onCancel;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
start(): void {
|
|||
|
|
this.isListening = true;
|
|||
|
|
this.listenForPrompt();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private listenForPrompt(): void {
|
|||
|
|
if (!this.isListening) return;
|
|||
|
|
|
|||
|
|
this.commands.promptQueue.pop()
|
|||
|
|
.then((promptEvent) => {
|
|||
|
|
this.activePrompt = promptEvent;
|
|||
|
|
|
|||
|
|
// 如果有等待的输入,自动提交
|
|||
|
|
if (this.pendingInput) {
|
|||
|
|
const input = this.pendingInput;
|
|||
|
|
this.pendingInput = null;
|
|||
|
|
const error = this.activePrompt.tryCommit(input);
|
|||
|
|
if (error === null) {
|
|||
|
|
this.activePrompt = null;
|
|||
|
|
this.listenForPrompt();
|
|||
|
|
} else {
|
|||
|
|
// 提交失败,把 prompt 交给 UI 显示错误
|
|||
|
|
this.onPrompt(promptEvent);
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.onPrompt(promptEvent);
|
|||
|
|
})
|
|||
|
|
.catch((reason) => {
|
|||
|
|
this.activePrompt = null;
|
|||
|
|
this.onCancel(reason?.message || 'Cancelled');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Submit an input string to the current prompt.
|
|||
|
|
* @returns null on success (input accepted), error string on validation failure
|
|||
|
|
*/
|
|||
|
|
submit(input: string): string | null {
|
|||
|
|
if (!this.activePrompt) {
|
|||
|
|
// 没有活跃 prompt,保存为待处理输入
|
|||
|
|
this.pendingInput = input;
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const error = this.activePrompt.tryCommit(input);
|
|||
|
|
if (error === null) {
|
|||
|
|
this.activePrompt = null;
|
|||
|
|
this.listenForPrompt();
|
|||
|
|
}
|
|||
|
|
return error;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cancel(reason?: string): void {
|
|||
|
|
if (this.activePrompt) {
|
|||
|
|
this.activePrompt.cancel(reason);
|
|||
|
|
this.activePrompt = null;
|
|||
|
|
}
|
|||
|
|
this.onCancel(reason);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
stop(): void {
|
|||
|
|
this.isListening = false;
|
|||
|
|
if (this.activePrompt) {
|
|||
|
|
this.activePrompt.cancel('Handler stopped');
|
|||
|
|
this.activePrompt = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
destroy(): void {
|
|||
|
|
this.stop();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function createPromptHandler(
|
|||
|
|
options: PromptHandlerOptions,
|
|||
|
|
): PromptHandler {
|
|||
|
|
return new PromptHandler(options);
|
|||
|
|
}
|