第一版
This commit is contained in:
@ -2,7 +2,13 @@
|
|||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(cat:*)",
|
"Bash(cat:*)",
|
||||||
"Bash(npm install:*)"
|
"Bash(npm install:*)",
|
||||||
|
"Bash(node test-poem.js:*)",
|
||||||
|
"Bash(printf:*)",
|
||||||
|
"Bash(timeout 2 node:*)",
|
||||||
|
"Bash(npm ls:*)",
|
||||||
|
"Bash(timeout 1 node:*)",
|
||||||
|
"Bash(node -e:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
cli.js
50
cli.js
@ -1,35 +1,35 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import color from "picocolors";
|
import color from "picocolors";
|
||||||
import { checkToktx, scanImages, compressAll } from "./lib/compressor.js";
|
import { showMainMenu } from "./lib/menu.js";
|
||||||
import { runInteractive, showSummary } from "./lib/ui.js";
|
|
||||||
|
|
||||||
// 检查 toktx
|
// 主循环
|
||||||
checkToktx();
|
while (true) {
|
||||||
|
const selected = await showMainMenu();
|
||||||
|
|
||||||
// 运行交互界面
|
console.clear();
|
||||||
const [exts, quality, encoding, mipmap, outputOpts] = await runInteractive();
|
console.log(color.cyan(`\n正在启动: ${selected.name}...\n`));
|
||||||
|
|
||||||
const config = { exts, quality, encoding, mipmap, outputOpts };
|
try {
|
||||||
|
const tool = await import(selected.module);
|
||||||
|
const result = await tool.run();
|
||||||
|
|
||||||
// 显示配置摘要
|
// 返回主菜单
|
||||||
showSummary(config);
|
if (result === "back") continue;
|
||||||
|
|
||||||
// 扫描文件
|
// 工具完成后退出
|
||||||
const { images, cwd } = scanImages(exts);
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(color.yellow(`\n⚠️ ${selected.name} 模块尚未实现`));
|
||||||
|
console.log(color.dim(err.message));
|
||||||
|
console.log(color.dim("\n按任意键返回主菜单..."));
|
||||||
|
|
||||||
if (images.length === 0) {
|
// 等待按键
|
||||||
console.log(color.yellow("当前目录没有匹配的图片"));
|
await new Promise(resolve => {
|
||||||
process.exit(0);
|
process.stdin.setRawMode(true);
|
||||||
|
process.stdin.once("data", () => {
|
||||||
|
process.stdin.setRawMode(false);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`📁 找到 ${color.cyan(images.length)} 个待转换文件\n`);
|
|
||||||
|
|
||||||
// 执行压缩
|
|
||||||
const { total, failed } = await compressAll(images, config, cwd);
|
|
||||||
|
|
||||||
// 显示结果
|
|
||||||
if (failed > 0) {
|
|
||||||
console.log(color.yellow(`\n⚠️ 完成,但有 ${failed} 个文件失败`));
|
|
||||||
} else {
|
|
||||||
console.log(color.green("\n🎉 全部文件压缩完成!"));
|
|
||||||
}
|
}
|
||||||
|
|||||||
46
lib/bigtext.js
Normal file
46
lib/bigtext.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import color from "picocolors";
|
||||||
|
|
||||||
|
// 计算字符串显示宽度(中文2,英文1)
|
||||||
|
function strWidth(str) {
|
||||||
|
let width = 0;
|
||||||
|
for (const char of str) {
|
||||||
|
width += char.charCodeAt(0) > 127 ? 2 : 1;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大字效果框
|
||||||
|
export function bigText(text, options = {}) {
|
||||||
|
const {
|
||||||
|
style = "double",
|
||||||
|
textColor = "yellow",
|
||||||
|
padding = 2,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const chars = {
|
||||||
|
block: { tl: "█", tr: "█", bl: "█", br: "█", h: "█", v: "█" },
|
||||||
|
double: { tl: "╔", tr: "╗", bl: "╚", br: "╝", h: "═", v: "║" },
|
||||||
|
simple: { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const c = chars[style] || chars.double;
|
||||||
|
const lines = text.split("\n");
|
||||||
|
const maxWidth = Math.max(...lines.map(strWidth));
|
||||||
|
const boxWidth = maxWidth + padding * 2;
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
result.push(c.tl + c.h.repeat(boxWidth) + c.tr);
|
||||||
|
result.push(c.v + " ".repeat(boxWidth) + c.v);
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const lineWidth = strWidth(line);
|
||||||
|
const rightPad = maxWidth - lineWidth;
|
||||||
|
result.push(c.v + " ".repeat(padding) + line + " ".repeat(rightPad + padding) + c.v);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(c.v + " ".repeat(boxWidth) + c.v);
|
||||||
|
result.push(c.bl + c.h.repeat(boxWidth) + c.br);
|
||||||
|
|
||||||
|
const box = result.join("\n");
|
||||||
|
return color[textColor] ? color[textColor](box) : box;
|
||||||
|
}
|
||||||
119
lib/grid.js
Normal file
119
lib/grid.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import color from "picocolors";
|
||||||
|
import { initKeypress, onKey } from "./keyboard.js";
|
||||||
|
|
||||||
|
// 计算字符串显示宽度
|
||||||
|
function strWidth(str) {
|
||||||
|
let width = 0;
|
||||||
|
for (const char of str) {
|
||||||
|
width += char.charCodeAt(0) > 127 ? 2 : 1;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右填充到指定宽度
|
||||||
|
function padEnd(str, width) {
|
||||||
|
const diff = width - strWidth(str);
|
||||||
|
return str + " ".repeat(Math.max(0, diff));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网格选择菜单
|
||||||
|
*/
|
||||||
|
export async function gridSelect(options) {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
cols = 3,
|
||||||
|
colWidth = 24,
|
||||||
|
title = "",
|
||||||
|
renderHeader = null
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let current = 0;
|
||||||
|
const rows = Math.ceil(items.length / cols);
|
||||||
|
const termWidth = process.stdout.columns || 80;
|
||||||
|
const totalWidth = cols * colWidth;
|
||||||
|
const pad = " ".repeat(Math.max(0, Math.floor((termWidth - totalWidth) / 2)));
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
console.clear();
|
||||||
|
|
||||||
|
if (renderHeader) {
|
||||||
|
console.log(renderHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
const titlePad = " ".repeat(Math.max(0, Math.floor((termWidth - title.length - 4) / 2)));
|
||||||
|
console.log(titlePad + color.bgMagenta(color.white(` ${title} `)));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log(pad + color.dim("↑ ↓ ← → 选择 | Enter 确认 | Esc 退出"));
|
||||||
|
console.log("");
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
let line = "";
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const idx = row * cols + col;
|
||||||
|
if (idx < items.length) {
|
||||||
|
const item = items[idx];
|
||||||
|
if (idx === current) {
|
||||||
|
line += color.cyan("[" + item.name + "]");
|
||||||
|
line += " ".repeat(colWidth - strWidth(item.name) - 2);
|
||||||
|
} else {
|
||||||
|
line += " " + item.name + " ";
|
||||||
|
line += " ".repeat(colWidth - strWidth(item.name) - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(pad + line);
|
||||||
|
|
||||||
|
let descLine = "";
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const idx = row * cols + col;
|
||||||
|
if (idx < items.length) {
|
||||||
|
descLine += padEnd(items[idx].desc || "", colWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(pad + color.dim(descLine));
|
||||||
|
console.log("");
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initKeypress();
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
onKey((str, key) => {
|
||||||
|
if (!key) return;
|
||||||
|
|
||||||
|
const row = Math.floor(current / cols);
|
||||||
|
const col = current % cols;
|
||||||
|
|
||||||
|
if (key.name === "up" && row > 0) {
|
||||||
|
current -= cols;
|
||||||
|
render();
|
||||||
|
} else if (key.name === "down" && row < rows - 1 && current + cols < items.length) {
|
||||||
|
current += cols;
|
||||||
|
render();
|
||||||
|
} else if (key.name === "left" && col > 0) {
|
||||||
|
current--;
|
||||||
|
render();
|
||||||
|
} else if (key.name === "right" && col < cols - 1 && current < items.length - 1) {
|
||||||
|
current++;
|
||||||
|
render();
|
||||||
|
} else if (key.name === "return") {
|
||||||
|
setImmediate(() => resolve(items[current]));
|
||||||
|
} else if (key.name === "escape" || (key.ctrl && key.name === "c")) {
|
||||||
|
process.stdin.setRawMode(false);
|
||||||
|
console.clear();
|
||||||
|
console.log(color.yellow("👋 再见!"));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
18
lib/keyboard.js
Normal file
18
lib/keyboard.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import readline from "readline";
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
export function initKeypress() {
|
||||||
|
if (!initialized) {
|
||||||
|
readline.emitKeypressEvents(process.stdin);
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
process.stdin.removeAllListeners("keypress");
|
||||||
|
if (process.stdin.isTTY) {
|
||||||
|
process.stdin.setRawMode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onKey(handler) {
|
||||||
|
process.stdin.on("keypress", handler);
|
||||||
|
}
|
||||||
115
lib/ktx2/compressor.js
Normal file
115
lib/ktx2/compressor.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { spawn } from "child_process";
|
||||||
|
import color from "picocolors";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const toktx = path.join(__dirname, "..", "..", "bin", "toktx.exe");
|
||||||
|
|
||||||
|
// 检查 toktx 是否存在
|
||||||
|
export function checkToktx() {
|
||||||
|
if (!fs.existsSync(toktx)) {
|
||||||
|
console.error("❌ 找不到 toktx.exe");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描图片文件
|
||||||
|
export function scanImages(exts) {
|
||||||
|
console.log("🔍 扫描目标文件中...");
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const images = fs.readdirSync(cwd).filter(f =>
|
||||||
|
exts.some(ext => f.toLowerCase().endsWith("." + ext))
|
||||||
|
);
|
||||||
|
return { images, cwd };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建压缩参数
|
||||||
|
export function buildArgs(input, output, config) {
|
||||||
|
const args = ["--t2"];
|
||||||
|
|
||||||
|
if (config.encoding === "uastc") {
|
||||||
|
args.push("--encode", "uastc");
|
||||||
|
const zcmpLevel = { none: "0", standard: "10", high: "18", extreme: "22" };
|
||||||
|
args.push("--zcmp", zcmpLevel[config.quality] || "10");
|
||||||
|
} else if (config.encoding === "etc1s") {
|
||||||
|
args.push("--encode", "etc1s");
|
||||||
|
} else if (config.encoding === "astc") {
|
||||||
|
args.push("--encode", "astc");
|
||||||
|
const blkSize = { none: "8x8", standard: "6x6", high: "5x5", extreme: "4x4" };
|
||||||
|
args.push("--astc_blk_d", blkSize[config.quality] || "6x6");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.mipmap === "auto") {
|
||||||
|
args.push("--genmipmap");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(output, input);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 压缩单个文件
|
||||||
|
export function compressFile(img, config, cwd, progress) {
|
||||||
|
const baseName = img.replace(/\.[^.]+$/, "");
|
||||||
|
const out = baseName + ".ktx2";
|
||||||
|
|
||||||
|
// 点动画
|
||||||
|
let dots = 0;
|
||||||
|
const dotAnim = setInterval(() => {
|
||||||
|
const dotStr = ".".repeat(dots);
|
||||||
|
process.stdout.write(`\r${progress} ${img} 正在转换中${dotStr} `);
|
||||||
|
dots = dots >= 3 ? 0 : dots + 1;
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
process.stdout.write(`${progress} ${img} 正在转换中.`);
|
||||||
|
|
||||||
|
const args = buildArgs(img, out, config);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const proc = spawn(toktx, args, { cwd });
|
||||||
|
|
||||||
|
let stderr = "";
|
||||||
|
proc.stderr?.on("data", data => {
|
||||||
|
stderr += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
proc.on("close", code => {
|
||||||
|
clearInterval(dotAnim);
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`\r${progress} ${color.green("✓")} ${out} `);
|
||||||
|
resolve({ success: true });
|
||||||
|
} else {
|
||||||
|
console.log(`\r${progress} ${color.red("✗")} ${img} 失败 `);
|
||||||
|
if (stderr) {
|
||||||
|
console.log(color.dim(` 错误: ${stderr.trim()}`));
|
||||||
|
}
|
||||||
|
resolve({ success: false, error: stderr });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
proc.on("error", err => {
|
||||||
|
clearInterval(dotAnim);
|
||||||
|
console.log(`\r${progress} ${color.red("✗")} ${img} 失败 `);
|
||||||
|
console.log(color.dim(` 错误: ${err.message}`));
|
||||||
|
resolve({ success: false, error: err.message });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量压缩
|
||||||
|
export async function compressAll(images, config, cwd) {
|
||||||
|
const total = images.length;
|
||||||
|
let finished = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
for (const img of images) {
|
||||||
|
finished++;
|
||||||
|
const progress = `(${finished}/${total})`;
|
||||||
|
const result = await compressFile(img, config, cwd, progress);
|
||||||
|
if (!result.success) failed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { total, failed };
|
||||||
|
}
|
||||||
62
lib/ktx2/config.js
Normal file
62
lib/ktx2/config.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 步骤配置
|
||||||
|
export const steps = [
|
||||||
|
{
|
||||||
|
name: "文件格式",
|
||||||
|
type: "multiselect",
|
||||||
|
message: "请选择要压缩的图片类型",
|
||||||
|
options: [
|
||||||
|
{ value: "png", label: "PNG (.png)(无损格式,适合图标和透明图)" },
|
||||||
|
{ value: "jpg", label: "JPG (.jpg)(有损格式,适合照片和复杂图像)" },
|
||||||
|
{ value: "jpeg", label: "JPEG (.jpeg)(同JPG,仅扩展名不同)" },
|
||||||
|
{ value: "webp", label: "WebP (.webp)(新一代格式,体积更小)" },
|
||||||
|
{ value: "tga", label: "TGA (.tga)(游戏纹理常用格式)" }
|
||||||
|
],
|
||||||
|
default: ["png", "jpg"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "压缩程度",
|
||||||
|
type: "select",
|
||||||
|
message: "请选择压缩级别",
|
||||||
|
options: [
|
||||||
|
{ value: "none", label: "无压缩(原始质量)", hint: "保持原始文件大小,图片和内容无损" },
|
||||||
|
{ value: "standard", label: "标准压缩(推荐)", hint: "平衡文件大小与质量,压缩率约40%" },
|
||||||
|
{ value: "high", label: "高度压缩(最小体积)", hint: "最大程度减小文件体积,可能轻微影响清晰度" },
|
||||||
|
{ value: "extreme", label: "极限压缩(极致压缩)", hint: "牺牲部分质量换取最小体积,适合网络传输" }
|
||||||
|
],
|
||||||
|
default: "standard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "编码格式",
|
||||||
|
type: "select",
|
||||||
|
message: "请选择编码格式",
|
||||||
|
options: [
|
||||||
|
{ value: "uastc", label: "UASTC(通用超压缩纹理)", hint: "高质量GPU纹理,解码快,适合实时渲染" },
|
||||||
|
{ value: "etc1s", label: "ETC1S(基础压缩纹理)", hint: "文件体积最小,兼容性好,适合移动端" },
|
||||||
|
{ value: "astc", label: "ASTC(自适应纹理压缩)", hint: "灵活块大小,质量与体积可调,适合高端设备" }
|
||||||
|
],
|
||||||
|
default: "uastc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mipmap",
|
||||||
|
type: "select",
|
||||||
|
message: "请选择Mipmap生成方式",
|
||||||
|
options: [
|
||||||
|
{ value: "auto", label: "自动生成(推荐)", hint: "根据图片尺寸自动生成多级纹理,优化远距离渲染" },
|
||||||
|
{ value: "none", label: "不生成Mipmap", hint: "仅保留原始尺寸,文件更小但可能出现锯齿" },
|
||||||
|
{ value: "custom", label: "自定义层级", hint: "手动指定Mipmap层数,精细控制纹理细节" }
|
||||||
|
],
|
||||||
|
default: "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "输出选项",
|
||||||
|
type: "multiselect",
|
||||||
|
message: "请选择输出选项",
|
||||||
|
options: [
|
||||||
|
{ value: "overwrite", label: "覆盖已存在文件(自动替换同名文件)" },
|
||||||
|
{ value: "keepOriginal", label: "保留原文件(压缩后不删除源文件)" },
|
||||||
|
{ value: "report", label: "生成压缩报告(输出详细的压缩统计信息)" },
|
||||||
|
{ value: "silent", label: "静默模式(减少控制台输出信息)" }
|
||||||
|
],
|
||||||
|
default: ["overwrite", "keepOriginal"]
|
||||||
|
}
|
||||||
|
];
|
||||||
40
lib/ktx2/index.js
Normal file
40
lib/ktx2/index.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import color from "picocolors";
|
||||||
|
import { checkToktx, scanImages, compressAll } from "./compressor.js";
|
||||||
|
import { runInteractive, showSummary } from "./ui.js";
|
||||||
|
|
||||||
|
export async function run() {
|
||||||
|
// 检查 toktx
|
||||||
|
checkToktx();
|
||||||
|
|
||||||
|
// 运行交互界面
|
||||||
|
const result = await runInteractive();
|
||||||
|
|
||||||
|
// ESC 返回主菜单
|
||||||
|
if (!result) return "back";
|
||||||
|
|
||||||
|
const [exts, quality, encoding, mipmap, outputOpts] = result;
|
||||||
|
const config = { exts, quality, encoding, mipmap, outputOpts };
|
||||||
|
|
||||||
|
// 显示配置摘要
|
||||||
|
showSummary(config);
|
||||||
|
|
||||||
|
// 扫描文件
|
||||||
|
const { images, cwd } = scanImages(exts);
|
||||||
|
|
||||||
|
if (images.length === 0) {
|
||||||
|
console.log(color.yellow("当前目录没有匹配的图片"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📁 找到 ${color.cyan(images.length)} 个待转换文件\n`);
|
||||||
|
|
||||||
|
// 执行压缩
|
||||||
|
const { total, failed } = await compressAll(images, config, cwd);
|
||||||
|
|
||||||
|
// 显示结果
|
||||||
|
if (failed > 0) {
|
||||||
|
console.log(color.yellow(`\n⚠️ 完成,但有 ${failed} 个文件失败`));
|
||||||
|
} else {
|
||||||
|
console.log(color.green("\n🎉 全部文件压缩完成!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
151
lib/ktx2/ui.js
Normal file
151
lib/ktx2/ui.js
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import color from "picocolors";
|
||||||
|
import { steps } from "./config.js";
|
||||||
|
import { initKeypress, onKey } from "../keyboard.js";
|
||||||
|
|
||||||
|
// 存储结果和状态
|
||||||
|
let results = [];
|
||||||
|
let completed = new Set();
|
||||||
|
let currentStep = 0;
|
||||||
|
let currentOption = 0;
|
||||||
|
|
||||||
|
// 初始化结果
|
||||||
|
export function initResults() {
|
||||||
|
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}`);
|
||||||
|
} else if (i === currentStep) {
|
||||||
|
return color.bgCyan(color.black(` ${step.name} `));
|
||||||
|
} else {
|
||||||
|
return color.dim(`□ ${step.name}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return `← ${nav.join(" ")} ${color.green("✓Submit")} →`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染选项列表
|
||||||
|
function renderOptions() {
|
||||||
|
const step = steps[currentStep];
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
lines.push(color.cyan(step.message));
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
step.options.forEach((opt, i) => {
|
||||||
|
const isCurrent = i === currentOption;
|
||||||
|
const isSelected = step.type === "multiselect"
|
||||||
|
? results[currentStep].includes(opt.value)
|
||||||
|
: results[currentStep] === opt.value;
|
||||||
|
|
||||||
|
let prefix;
|
||||||
|
if (step.type === "multiselect") {
|
||||||
|
prefix = isSelected ? color.green("◉ ") : "○ ";
|
||||||
|
} else {
|
||||||
|
prefix = 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(" KTX2 纹理压缩工具 ")));
|
||||||
|
console.log("\n" + renderNav());
|
||||||
|
console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择 | Space 选中 | Tab 提交 | Esc 返回\n"));
|
||||||
|
console.log(renderOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主交互循环
|
||||||
|
export async function runInteractive() {
|
||||||
|
initResults();
|
||||||
|
initKeypress();
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
onKey((str, key) => {
|
||||||
|
if (!key) return;
|
||||||
|
|
||||||
|
const step = steps[currentStep];
|
||||||
|
const optCount = step.options.length;
|
||||||
|
|
||||||
|
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") {
|
||||||
|
currentOption = (currentOption - 1 + optCount) % optCount;
|
||||||
|
render();
|
||||||
|
} else if (key.name === "down") {
|
||||||
|
currentOption = (currentOption + 1) % optCount;
|
||||||
|
render();
|
||||||
|
} else if (key.name === "space") {
|
||||||
|
const opt = step.options[currentOption];
|
||||||
|
if (step.type === "multiselect") {
|
||||||
|
const idx = results[currentStep].indexOf(opt.value);
|
||||||
|
if (idx >= 0) {
|
||||||
|
results[currentStep].splice(idx, 1);
|
||||||
|
} else {
|
||||||
|
results[currentStep].push(opt.value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results[currentStep] = opt.value;
|
||||||
|
}
|
||||||
|
completed.add(currentStep);
|
||||||
|
render();
|
||||||
|
} else if (key.name === "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(results);
|
||||||
|
} else if (key.name === "escape" || (key.ctrl && key.name === "c")) {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示配置摘要
|
||||||
|
export function showSummary(config) {
|
||||||
|
console.clear();
|
||||||
|
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
|
||||||
|
console.log("\n" + color.green("配置完成!当前设置:"));
|
||||||
|
console.log(` 文件格式: ${config.exts.join(", ")}`);
|
||||||
|
console.log(` 压缩程度: ${config.quality}`);
|
||||||
|
console.log(` 编码格式: ${config.encoding}`);
|
||||||
|
console.log(` Mipmap: ${config.mipmap}`);
|
||||||
|
console.log(` 输出选项: ${config.outputOpts.join(", ")}\n`);
|
||||||
|
}
|
||||||
90
lib/menu.js
Normal file
90
lib/menu.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import color from "picocolors";
|
||||||
|
import boxen from "boxen";
|
||||||
|
import figlet from "figlet";
|
||||||
|
import { gridSelect } from "./grid.js";
|
||||||
|
|
||||||
|
// 古诗配置
|
||||||
|
let poemConfig = {
|
||||||
|
lines: ["你我皆牛马", "生在人世间", "终日奔波苦", "一刻不得闲"],
|
||||||
|
perLine: 2,
|
||||||
|
padding: { top: 2, bottom: 2, left: 6, right: 6 },
|
||||||
|
borderStyle: "double",
|
||||||
|
borderColor: "cyan",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 标题配置
|
||||||
|
let titleConfig = {
|
||||||
|
text: "Zguiy Tool Box",
|
||||||
|
font: "Standard",
|
||||||
|
color: "magenta",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具列表
|
||||||
|
const tools = [
|
||||||
|
{ name: "KTX2 纹理压缩", desc: "图片转KTX2格式", module: "./lib/ktx2/index.js" },
|
||||||
|
{ name: "模型压缩", desc: "压缩glTF/GLB模型", module: "./lib/model/index.js" },
|
||||||
|
{ name: "图片批量处理", desc: "裁剪/缩放/转换", module: "./lib/image/index.js" },
|
||||||
|
{ name: "Sprite图集", desc: "合并精灵图集", module: "./lib/sprite/index.js" },
|
||||||
|
{ name: "LOD生成器", desc: "生成多级细节", module: "./lib/lod/index.js" },
|
||||||
|
{ name: "音频压缩", desc: "压缩音频文件", module: "./lib/audio/index.js" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 设置古诗
|
||||||
|
export function setPoem(lines, perLine = 2) {
|
||||||
|
poemConfig.lines = lines;
|
||||||
|
poemConfig.perLine = perLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置古诗框样式
|
||||||
|
export function setPoemStyle(style) {
|
||||||
|
Object.assign(poemConfig, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置标题
|
||||||
|
export function setTitle(text, font = "Standard", titleColor = "magenta") {
|
||||||
|
titleConfig.text = text;
|
||||||
|
titleConfig.font = font;
|
||||||
|
titleConfig.color = titleColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染古诗
|
||||||
|
function renderPoem() {
|
||||||
|
const merged = [];
|
||||||
|
for (let i = 0; i < poemConfig.lines.length; i += poemConfig.perLine) {
|
||||||
|
merged.push(poemConfig.lines.slice(i, i + poemConfig.perLine).join(","));
|
||||||
|
}
|
||||||
|
return boxen(color.yellow(merged.join("\n")), {
|
||||||
|
padding: poemConfig.padding,
|
||||||
|
borderStyle: poemConfig.borderStyle,
|
||||||
|
borderColor: poemConfig.borderColor,
|
||||||
|
textAlignment: "center",
|
||||||
|
float: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染标题
|
||||||
|
function renderTitle() {
|
||||||
|
const art = figlet.textSync(titleConfig.text, { font: titleConfig.font });
|
||||||
|
const termWidth = process.stdout.columns || 80;
|
||||||
|
return art.split("\n").map(line => {
|
||||||
|
const pad = Math.max(0, Math.floor((termWidth - line.length) / 2));
|
||||||
|
return " ".repeat(pad) + color[titleConfig.color](line);
|
||||||
|
}).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染头部
|
||||||
|
function renderHeader() {
|
||||||
|
return renderPoem() + "\n\n" + renderTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主菜单
|
||||||
|
export async function showMainMenu() {
|
||||||
|
return gridSelect({
|
||||||
|
items: tools,
|
||||||
|
cols: 3,
|
||||||
|
colWidth: 24,
|
||||||
|
renderHeader: renderHeader,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { tools, poemConfig, titleConfig };
|
||||||
31
lib/poem.js
Normal file
31
lib/poem.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import boxen from "boxen";
|
||||||
|
import color from "picocolors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示带边框的古诗(固定在顶部居中)
|
||||||
|
* @param {string[]} lines - 诗句数组
|
||||||
|
* @param {number} perLine - 每行显示几句,默认2
|
||||||
|
*/
|
||||||
|
export function showPoem(lines, perLine = 2) {
|
||||||
|
// 合并诗句
|
||||||
|
const merged = [];
|
||||||
|
for (let i = 0; i < lines.length; i += perLine) {
|
||||||
|
merged.push(lines.slice(i, i + perLine).join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = merged.join("\n");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
boxen(color.yellow(content), {
|
||||||
|
padding: { top: 1, bottom: 1, left: 4, right: 4 },
|
||||||
|
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
||||||
|
borderStyle: "double",
|
||||||
|
borderColor: "cyan",
|
||||||
|
textAlignment: "center",
|
||||||
|
float: "center",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认古诗
|
||||||
|
export const defaultPoem = ["你我皆牛马", "生在人世间", "终日奔波苦", "一刻不得闲"];
|
||||||
28
lib/ui.js
28
lib/ui.js
@ -1,5 +1,6 @@
|
|||||||
import color from "picocolors";
|
import color from "picocolors";
|
||||||
import readline from "readline";
|
import readline from "readline";
|
||||||
|
import boxen from "boxen";
|
||||||
import { steps } from "./config.js";
|
import { steps } from "./config.js";
|
||||||
|
|
||||||
// 存储结果和状态
|
// 存储结果和状态
|
||||||
@ -8,6 +9,31 @@ let completed = new Set();
|
|||||||
let currentStep = 0;
|
let currentStep = 0;
|
||||||
let currentOption = 0;
|
let currentOption = 0;
|
||||||
|
|
||||||
|
// 古诗配置
|
||||||
|
let poemLines = ["你我皆牛马", "生在人世间", "终日奔波苦", "一刻不得闲"];
|
||||||
|
let poemPerLine = 2;
|
||||||
|
|
||||||
|
// 设置古诗
|
||||||
|
export function setPoem(lines, perLine = 2) {
|
||||||
|
poemLines = lines;
|
||||||
|
poemPerLine = perLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染古诗
|
||||||
|
function renderPoem() {
|
||||||
|
const merged = [];
|
||||||
|
for (let i = 0; i < poemLines.length; i += poemPerLine) {
|
||||||
|
merged.push(poemLines.slice(i, i + poemPerLine).join(","));
|
||||||
|
}
|
||||||
|
return boxen(color.yellow(merged.join("\n")), {
|
||||||
|
padding: { top: 1, bottom: 1, left: 4, right: 4 },
|
||||||
|
borderStyle: "double",
|
||||||
|
borderColor: "cyan",
|
||||||
|
textAlignment: "center",
|
||||||
|
float: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化结果
|
// 初始化结果
|
||||||
export function initResults() {
|
export function initResults() {
|
||||||
results = steps.map(s => s.type === "multiselect" ? [...s.default] : s.default);
|
results = steps.map(s => s.type === "multiselect" ? [...s.default] : s.default);
|
||||||
@ -67,6 +93,7 @@ function renderOptions() {
|
|||||||
// 渲染整个界面
|
// 渲染整个界面
|
||||||
function render() {
|
function render() {
|
||||||
console.clear();
|
console.clear();
|
||||||
|
console.log(renderPoem());
|
||||||
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
|
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
|
||||||
console.log("\n" + renderNav());
|
console.log("\n" + renderNav());
|
||||||
console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择选项 | Space 选中 | Enter 确认 | Tab 提交\n"));
|
console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择选项 | Space 选中 | Enter 确认 | Tab 提交\n"));
|
||||||
@ -152,6 +179,7 @@ export async function runInteractive() {
|
|||||||
// 显示配置摘要
|
// 显示配置摘要
|
||||||
export function showSummary(config) {
|
export function showSummary(config) {
|
||||||
console.clear();
|
console.clear();
|
||||||
|
console.log(renderPoem());
|
||||||
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
|
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
|
||||||
console.log("\n" + color.green("配置完成!当前设置:"));
|
console.log("\n" + color.green("配置完成!当前设置:"));
|
||||||
console.log(` 文件格式: ${config.exts.join(", ")}`);
|
console.log(` 文件格式: ${config.exts.join(", ")}`);
|
||||||
|
|||||||
261
package-lock.json
generated
261
package-lock.json
generated
@ -9,6 +9,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.11.0",
|
"@clack/prompts": "^0.11.0",
|
||||||
|
"boxen": "^8.0.1",
|
||||||
|
"figlet": "^1.9.4",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"prompts": "^2.4.2"
|
"prompts": "^2.4.2"
|
||||||
},
|
},
|
||||||
@ -37,6 +39,189 @@
|
|||||||
"sisteransi": "^1.0.5"
|
"sisteransi": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ansi-align": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-align/node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-align/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ansi-align/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-align/node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/boxen": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-align": "^3.0.1",
|
||||||
|
"camelcase": "^8.0.0",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"cli-boxes": "^3.0.0",
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"type-fest": "^4.21.0",
|
||||||
|
"widest-line": "^5.0.0",
|
||||||
|
"wrap-ansi": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/camelcase": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cli-boxes": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "14.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
|
||||||
|
"integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/figlet": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-uN6QE+TrzTAHC1IWTyrc4FfGo2KH/82J8Jl1tyKB7+z5DBit/m3D++Iu5lg91qJMnQQ3vpJrj5gxcK/pk4R9tQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^14.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"figlet": "bin/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-east-asian-width": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/kleur": {
|
"node_modules/kleur": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
@ -70,6 +255,82 @@
|
|||||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-fest": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/widest-line": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^6.2.1",
|
||||||
|
"string-width": "^7.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.11.0",
|
"@clack/prompts": "^0.11.0",
|
||||||
|
"boxen": "^8.0.1",
|
||||||
|
"figlet": "^1.9.4",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"prompts": "^2.4.2"
|
"prompts": "^2.4.2"
|
||||||
}
|
}
|
||||||
|
|||||||
7
test-poem.js
Normal file
7
test-poem.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { showPoem, defaultPoem } from "./lib/poem.js";
|
||||||
|
|
||||||
|
// 使用默认古诗
|
||||||
|
showPoem(defaultPoem);
|
||||||
|
|
||||||
|
// 自定义古诗
|
||||||
|
// showPoem(["床前明月光", "疑是地上霜", "举头望明月", "低头思故乡"]);
|
||||||
Reference in New Issue
Block a user