@gavbarosee/react-kickstart
Version:
A modern CLI tool for creating React applications with various frameworks
175 lines (150 loc) • 4.03 kB
JavaScript
import chalk from "chalk";
import { keyboardNavManager } from "../navigation/navigation-manager.js";
/**
* Base class for all prompt steps
*/
export class BaseStep {
constructor(renderer, navigator) {
this.renderer = renderer;
this.navigator = navigator;
this.stepName = "";
this.stepNumber = 0;
this.totalSteps = 12;
this.title = "";
this.icon = "•";
}
/**
* Sets step configuration
*/
configure(config) {
this.stepName = config.stepName;
this.stepNumber = config.stepNumber;
this.totalSteps = config.totalSteps;
this.title = config.title;
this.icon = config.icon || "•";
}
/**
* Gets the choices for this step
* Must be implemented by subclasses
*/
getChoices(answers) {
throw new Error("getChoices must be implemented by subclass");
}
/**
* Gets the prompt message
* Must be implemented by subclasses
*/
getMessage() {
throw new Error("getMessage must be implemented by subclass");
}
/**
* Gets the default selection
*/
getDefault(answers) {
return 0;
}
/**
* Determines the next step based on current answer
*/
getNextStep(selection, answers) {
return null; // Override in subclasses
}
/**
* Handles the back navigation
*/
handleBackNavigation() {
return "BACK";
}
/**
* Processes the user's selection
*/
processSelection(selection, answers) {
if (selection === "BACK_OPTION") {
return this.handleBackNavigation();
}
// Store the answer (don't record step in history here - that happens in PromptFlow)
answers[this.getAnswerKey()] = selection;
return selection;
}
/**
* Gets the key to store the answer under
* Defaults to stepName, override if needed
*/
getAnswerKey() {
return this.stepName;
}
/**
* Executes the step
*/
async execute(answers) {
// Refresh display
await this.renderer.refreshDisplay(answers);
// Get choices and calculate if we can go back BEFORE showing header
const choices = this.getChoices(answers);
const canGoBack = this.navigator.canGoBack();
// Show step header with navigation instructions if we can go back
this.renderer.showStepHeader(
this.stepNumber,
this.totalSteps,
this.title,
this.icon,
canGoBack,
);
const defaultIndex = this.getDefault(answers);
if (canGoBack) {
choices.push(this.renderer.createSeparator());
choices.push(this.renderer.createBackOption());
}
// Get the message
let message = this.getMessage();
// Prompt user with keyboard navigation support
const selection = await this.promptWithKeyboardNavigation({
message: message,
choices: choices,
default: defaultIndex,
canGoBack: canGoBack,
});
// Process selection
const result = this.processSelection(selection, answers);
return {
selection: result,
nextStep: this.getNextStep(result, answers),
};
}
/**
* Enhanced prompt with keyboard navigation support
*/
async promptWithKeyboardNavigation(config) {
if (!config.canGoBack) {
// Use standard prompt if no back navigation is available
return await this.renderer.promptChoice(config);
}
return new Promise((resolve, reject) => {
let isResolved = false;
// Activate keyboard navigation with our callback
keyboardNavManager.activate(() => {
if (!isResolved) {
isResolved = true;
resolve("BACK_OPTION");
}
});
// Run the standard prompt
this.renderer
.promptChoice(config)
.then((selection) => {
if (!isResolved) {
isResolved = true;
keyboardNavManager.deactivate();
resolve(selection);
}
})
.catch((error) => {
if (!isResolved) {
isResolved = true;
keyboardNavManager.deactivate();
reject(error);
}
});
});
}
}