增加转格式

This commit is contained in:
yinsx
2025-12-20 11:51:35 +08:00
parent 5315a97613
commit d9abc57b0b
32 changed files with 4339 additions and 229 deletions

124
lib/utils/stepui.js Normal file
View File

@ -0,0 +1,124 @@
import color from "picocolors";
import { initKeypress, onKey } from "../keyboard.js";
export function createStepUI(options) {
const { title, getSteps } = options;
let steps = [];
let results = [];
let completed = new Set();
let currentStep = 0;
let currentOption = 0;
function initResults() {
steps = typeof getSteps === "function" ? getSteps() : getSteps;
results = steps.map(s => s.type === "multiselect" ? [...(s.default || [])] : s.default);
completed = new Set();
currentStep = 0;
currentOption = 0;
}
function renderNav() {
const nav = steps.map((step, i) => {
if (completed.has(i)) return color.green("☑ " + step.name);
if (i === currentStep) return color.bgCyan(color.black(" " + step.name + " "));
return color.dim("□ " + step.name);
});
return "← " + nav.join(" ") + " " + color.green("✓Submit") + " →";
}
function renderOptions() {
const step = steps[currentStep];
const lines = [color.cyan(step.message), ""];
if (!step.options || !step.options.length) {
lines.push(color.dim(step.emptyMessage || "无可用选项"));
return lines.join("\n");
}
step.options.forEach((opt, i) => {
const isCurrent = i === currentOption;
const isSelected = step.type === "multiselect"
? results[currentStep]?.includes(opt.value)
: results[currentStep] === opt.value;
const prefix = step.type === "multiselect"
? (isSelected ? color.green("◉ ") : "○ ")
: (isSelected ? color.green("● ") : "○ ");
const cursor = isCurrent ? color.cyan(" ") : " ";
const label = isCurrent ? color.cyan(opt.label) : opt.label;
const check = isSelected ? color.green(" ✓") : "";
lines.push(cursor + prefix + label + check);
if (opt.hint) lines.push(" " + color.dim(opt.hint));
});
return lines.join("\n");
}
function render() {
console.clear();
console.log(color.bgCyan(color.black(` ${title} `)));
console.log("\n" + renderNav());
console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择 | Space 选中 | Tab 提交 | Esc 返回\n"));
console.log(renderOptions());
}
function runInteractive() {
initResults();
initKeypress();
return new Promise(resolve => {
render();
onKey((str, key) => {
if (!key) return;
const step = steps[currentStep];
const optCount = step.options?.length || 0;
if (key.name === "left") {
if (currentStep > 0) { currentStep--; currentOption = 0; }
render();
} else if (key.name === "right") {
if (currentStep < steps.length - 1) { currentStep++; currentOption = 0; }
render();
} else if (key.name === "up") {
if (!optCount) return;
currentOption = (currentOption - 1 + optCount) % optCount;
render();
} else if (key.name === "down") {
if (!optCount) return;
currentOption = (currentOption + 1) % optCount;
render();
} else if (key.name === "space") {
if (!optCount) return;
const opt = step.options[currentOption];
if (step.type === "multiselect") {
const list = results[currentStep];
const idx = list.indexOf(opt.value);
if (idx >= 0) list.splice(idx, 1);
else list.push(opt.value);
} else {
results[currentStep] = opt.value;
}
completed.add(currentStep);
render();
} else if (key.name === "return") {
if (!optCount) return;
if (step.type === "select") {
results[currentStep] = step.options[currentOption].value;
}
completed.add(currentStep);
if (currentStep < steps.length - 1) { currentStep++; currentOption = 0; }
render();
} else if (key.name === "tab") {
resolve({ steps, results });
} else if (key.name === "escape" || (key.ctrl && key.name === "c")) {
resolve(null);
}
});
});
}
function showSummary(lines) {
console.clear();
console.log(color.bgCyan(color.black(` ${title} `)));
console.log("\n" + color.green("配置完成!当前设置:"));
lines.forEach(line => console.log(" " + line));
console.log();
}
return { runInteractive, showSummary, initResults };
}