ansimax 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +274 -0
- package/LICENSE +201 -21
- package/README.es.md +752 -628
- package/README.md +493 -369
- package/dist/index.d.mts +1256 -336
- package/dist/index.d.ts +1256 -336
- package/dist/index.js +4417 -683
- package/dist/index.mjs +4319 -682
- package/examples/01-cli-installer.ts +96 -0
- package/examples/01-quick-smoke.ts +121 -0
- package/examples/02-colors-gradients.ts +108 -0
- package/examples/02-live-dashboard.ts +152 -0
- package/examples/03-ascii-banners.ts +81 -0
- package/examples/03-pixel-art-game.ts +170 -0
- package/examples/04-interactive-deploy.ts +163 -0
- package/examples/04-trees.ts +117 -0
- package/examples/05-components.ts +96 -0
- package/examples/05-tree-visualizations.ts +183 -0
- package/examples/06-everything-together.ts +466 -0
- package/examples/06-pixel-art.ts +116 -0
- package/examples/07-animations.ts +68 -0
- package/examples/08-loaders.ts +98 -0
- package/examples/09-themes.ts +90 -0
- package/examples/10-everything.ts +133 -0
- package/examples/all-in-one.cjs +210 -0
- package/examples/all-in-one.mjs +203 -0
- package/examples/animations.ts +172 -0
- package/examples/demo.js +213 -0
- package/examples/demo.ts +212 -0
- package/examples/loaders.ts +254 -0
- package/examples/showcase.ts +174 -0
- package/examples/trees-basic.ts +99 -0
- package/examples/tsconfig.json +18 -0
- package/examples/tsconfig.vscode.json +25 -0
- package/package.json +19 -7
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────
|
|
2
|
+
// EXAMPLE 1 — CLI installer with hierarchical tasks
|
|
3
|
+
//
|
|
4
|
+
// Demonstrates a realistic project setup flow:
|
|
5
|
+
// - Banner intro with theme gradient
|
|
6
|
+
// - Hierarchical tasks (parent + subtasks with rollup)
|
|
7
|
+
// - Status indicators with custom icons
|
|
8
|
+
// - Box for final summary
|
|
9
|
+
//
|
|
10
|
+
// Run:
|
|
11
|
+
// npx ts-node examples/01-cli-installer.ts
|
|
12
|
+
// ─────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
ascii,
|
|
16
|
+
themes,
|
|
17
|
+
loader,
|
|
18
|
+
components,
|
|
19
|
+
configure,
|
|
20
|
+
sleep,
|
|
21
|
+
} from '../dist/index.js';
|
|
22
|
+
|
|
23
|
+
// Configure global defaults
|
|
24
|
+
configure({
|
|
25
|
+
theme: 'dracula',
|
|
26
|
+
animationSpeed: 'fast',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Simulated work
|
|
30
|
+
const work = (ms: number, fail = false) => async (): Promise<string> => {
|
|
31
|
+
await sleep(ms);
|
|
32
|
+
if (fail) throw new Error('Simulated failure');
|
|
33
|
+
return 'ok';
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const main = async (): Promise<void> => {
|
|
37
|
+
// ── Intro banner ──────────────────────────────
|
|
38
|
+
console.log();
|
|
39
|
+
console.log(themes.banner('CREATE-APP', { font: 'small' }));
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(components.section(themes.primary('Initializing project'), { width: 60 }));
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
// ── Hierarchical task list ────────────────────
|
|
45
|
+
const results = await loader.tasks([
|
|
46
|
+
{
|
|
47
|
+
text: 'Setup environment',
|
|
48
|
+
fn: work(300),
|
|
49
|
+
subtasks: [
|
|
50
|
+
{ text: 'Detect Node version', fn: work(150) },
|
|
51
|
+
{ text: 'Verify npm registry', fn: work(200) },
|
|
52
|
+
{ text: 'Create project dir', fn: work(100) },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
text: 'Install dependencies',
|
|
57
|
+
fn: work(400),
|
|
58
|
+
subtasks: [
|
|
59
|
+
{ text: 'Resolve package tree', fn: work(250) },
|
|
60
|
+
{ text: 'Download tarballs', fn: work(500) },
|
|
61
|
+
{ text: 'Build native modules', fn: work(300) },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
text: 'Run post-install hooks',
|
|
66
|
+
fn: work(200),
|
|
67
|
+
subtasks: [
|
|
68
|
+
{ text: 'Generate types', fn: work(150) },
|
|
69
|
+
{ text: 'Lint workspace', fn: work(180) },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
// ── Final summary box ─────────────────────────
|
|
75
|
+
console.log();
|
|
76
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
77
|
+
const failed = results.length - succeeded;
|
|
78
|
+
|
|
79
|
+
const summary = [
|
|
80
|
+
components.status('success', `${succeeded} steps completed`),
|
|
81
|
+
failed > 0
|
|
82
|
+
? components.status('error', `${failed} steps failed`)
|
|
83
|
+
: components.status('info', 'No errors'),
|
|
84
|
+
'',
|
|
85
|
+
themes.muted('Next steps:'),
|
|
86
|
+
' ' + themes.accent('cd my-app && npm run dev'),
|
|
87
|
+
].join('\n');
|
|
88
|
+
|
|
89
|
+
console.log(components.box(summary, { borderStyle: 'rounded', padding: 1 }));
|
|
90
|
+
console.log();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
main().catch((err) => {
|
|
94
|
+
console.error(themes.error('✗ ' + err.message));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 01 — Quick Smoke Test
|
|
3
|
+
*
|
|
4
|
+
* Verifies all major named exports from `ansimax` are importable and callable.
|
|
5
|
+
* This is the FIRST test to run — if it fails, the package is broken.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx tsx examples/01-quick-smoke.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
color,
|
|
12
|
+
gradient,
|
|
13
|
+
rainbow,
|
|
14
|
+
ascii,
|
|
15
|
+
loader,
|
|
16
|
+
animate,
|
|
17
|
+
frames,
|
|
18
|
+
components,
|
|
19
|
+
themes,
|
|
20
|
+
images,
|
|
21
|
+
trees,
|
|
22
|
+
tree,
|
|
23
|
+
configure,
|
|
24
|
+
getConfig,
|
|
25
|
+
// Utils
|
|
26
|
+
stripAnsi,
|
|
27
|
+
visibleLen,
|
|
28
|
+
hexToRgb,
|
|
29
|
+
rgbToHex,
|
|
30
|
+
clamp,
|
|
31
|
+
// ANSI primitives
|
|
32
|
+
cursor,
|
|
33
|
+
screen,
|
|
34
|
+
supportsColor,
|
|
35
|
+
} from '../src/index.js';
|
|
36
|
+
|
|
37
|
+
const pass = (msg: string): void => console.log(color.green('✓'), msg);
|
|
38
|
+
const info = (label: string, value: unknown): void =>
|
|
39
|
+
console.log(' ' + color.dim(label.padEnd(20)), value);
|
|
40
|
+
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(color.bold(color.cyan('━━━ Ansimax v1.1.0 Smoke Test ━━━')));
|
|
43
|
+
console.log();
|
|
44
|
+
|
|
45
|
+
// 1. Colors
|
|
46
|
+
pass('color.red / color.green / color.blue');
|
|
47
|
+
info('color.red:', color.red('red text'));
|
|
48
|
+
info('color.green:', color.green('green text'));
|
|
49
|
+
|
|
50
|
+
// 2. Gradient
|
|
51
|
+
pass('gradient with multi-stop');
|
|
52
|
+
info('gradient:', gradient('rainbow text', ['#ff0000', '#00ff00', '#0000ff']));
|
|
53
|
+
|
|
54
|
+
// 3. Rainbow preset
|
|
55
|
+
pass('rainbow preset');
|
|
56
|
+
info('rainbow:', rainbow('hello rainbow!'));
|
|
57
|
+
|
|
58
|
+
// 4. Style modifiers
|
|
59
|
+
pass('color.bold / italic / underline');
|
|
60
|
+
info('bold:', color.bold('bold text'));
|
|
61
|
+
info('italic:', color.italic('italic text'));
|
|
62
|
+
info('underline:', color.underline('underlined text'));
|
|
63
|
+
|
|
64
|
+
// 5. Hex & RGB
|
|
65
|
+
pass('color.hex / color.rgb');
|
|
66
|
+
info('color.hex:', color.hex('#ff79c6')('Dracula pink'));
|
|
67
|
+
info('color.rgb:', color.rgb(189, 147, 249)('Dracula purple'));
|
|
68
|
+
|
|
69
|
+
// 6. ANSI utils
|
|
70
|
+
pass('stripAnsi / visibleLen');
|
|
71
|
+
const colored = color.red('hello');
|
|
72
|
+
info('stripAnsi:', JSON.stringify(stripAnsi(colored)));
|
|
73
|
+
info('visibleLen:', visibleLen(colored));
|
|
74
|
+
|
|
75
|
+
// 7. hexToRgb / rgbToHex
|
|
76
|
+
pass('hexToRgb / rgbToHex');
|
|
77
|
+
info('hexToRgb #ff79c6:', JSON.stringify(hexToRgb('#ff79c6')));
|
|
78
|
+
info('rgbToHex 189,147,249:', rgbToHex(189, 147, 249));
|
|
79
|
+
|
|
80
|
+
// 8. clamp
|
|
81
|
+
pass('clamp');
|
|
82
|
+
info('clamp(15, 0, 10):', clamp(15, 0, 10));
|
|
83
|
+
|
|
84
|
+
// 9. Capability detection
|
|
85
|
+
pass('supportsColor');
|
|
86
|
+
info('supportsColor():', supportsColor());
|
|
87
|
+
|
|
88
|
+
// 10. Module namespaces
|
|
89
|
+
pass('All namespaces accessible');
|
|
90
|
+
info('color:', typeof color);
|
|
91
|
+
info('ascii:', typeof ascii);
|
|
92
|
+
info('loader:', typeof loader);
|
|
93
|
+
info('animate:', typeof animate);
|
|
94
|
+
info('frames:', typeof frames);
|
|
95
|
+
info('components:', typeof components);
|
|
96
|
+
info('themes:', typeof themes);
|
|
97
|
+
info('images:', typeof images);
|
|
98
|
+
info('trees:', typeof trees);
|
|
99
|
+
|
|
100
|
+
// 11. tree() builder
|
|
101
|
+
pass('tree() builder');
|
|
102
|
+
const t = tree({ label: 'root', icon: '📦' });
|
|
103
|
+
t.addLeaf({ label: 'child1', icon: '📄' });
|
|
104
|
+
info('tree.render():', '\n' + t.render());
|
|
105
|
+
|
|
106
|
+
// 12. configure
|
|
107
|
+
pass('configure / getConfig');
|
|
108
|
+
const cfg = getConfig();
|
|
109
|
+
info('theme:', cfg.theme);
|
|
110
|
+
info('colorMode:', cfg.colorMode);
|
|
111
|
+
|
|
112
|
+
// 13. cursor / screen primitives
|
|
113
|
+
pass('cursor / screen primitives');
|
|
114
|
+
info('cursor.hide():', JSON.stringify(cursor.hide()));
|
|
115
|
+
info('cursor.show():', JSON.stringify(cursor.show()));
|
|
116
|
+
info('screen.clearLine():', JSON.stringify(screen.clearLine()));
|
|
117
|
+
|
|
118
|
+
console.log();
|
|
119
|
+
console.log(color.bold(color.green('✅ All smoke tests passed!')));
|
|
120
|
+
console.log(color.dim('You have a working ansimax installation.'));
|
|
121
|
+
console.log();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 02 — Colors & Gradients
|
|
3
|
+
* Tests verified against actual ansimax@1.1.0 exports.
|
|
4
|
+
*
|
|
5
|
+
* Run: npx tsx examples/02-colors-gradients.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
color,
|
|
10
|
+
gradient,
|
|
11
|
+
rainbow,
|
|
12
|
+
colorPresets,
|
|
13
|
+
presetNames,
|
|
14
|
+
compose,
|
|
15
|
+
chain,
|
|
16
|
+
registerPreset,
|
|
17
|
+
listPresets,
|
|
18
|
+
} from '../src/index.js';
|
|
19
|
+
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(color.bold('━━━ Basic Colors ━━━'));
|
|
22
|
+
console.log();
|
|
23
|
+
|
|
24
|
+
const basic = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'] as const;
|
|
25
|
+
console.log('Basic 8 colors:');
|
|
26
|
+
for (const c of basic) process.stdout.write(color[c](c.padEnd(8)) + ' ');
|
|
27
|
+
console.log('\n');
|
|
28
|
+
|
|
29
|
+
const bright = ['brightRed', 'brightGreen', 'brightYellow', 'brightBlue', 'brightMagenta', 'brightCyan'] as const;
|
|
30
|
+
console.log('Bright variants:');
|
|
31
|
+
for (const c of bright) process.stdout.write(color[c](c.padEnd(15)) + ' ');
|
|
32
|
+
console.log('\n');
|
|
33
|
+
|
|
34
|
+
const bg = ['bgRed', 'bgGreen', 'bgBlue', 'bgYellow', 'bgMagenta', 'bgCyan'] as const;
|
|
35
|
+
console.log('Backgrounds:');
|
|
36
|
+
for (const c of bg) process.stdout.write(color[c](` ${c} `) + ' ');
|
|
37
|
+
console.log('\n');
|
|
38
|
+
|
|
39
|
+
console.log('Modifiers:');
|
|
40
|
+
console.log(' ',
|
|
41
|
+
color.bold('bold'),
|
|
42
|
+
color.italic('italic'),
|
|
43
|
+
color.underline('underline'),
|
|
44
|
+
color.dim('dim'),
|
|
45
|
+
color.inverse('inverse'),
|
|
46
|
+
color.strikethrough('strikethrough'),
|
|
47
|
+
);
|
|
48
|
+
console.log();
|
|
49
|
+
|
|
50
|
+
console.log(color.bold('━━━ Hex / RGB / 256 ━━━'));
|
|
51
|
+
console.log();
|
|
52
|
+
|
|
53
|
+
const hexes = ['#ff79c6', '#bd93f9', '#8be9fd', '#50fa7b', '#ffb86c', '#ff5555'];
|
|
54
|
+
console.log('Hex colors:');
|
|
55
|
+
for (const hex of hexes) process.stdout.write(color.hex(hex)(hex) + ' ');
|
|
56
|
+
console.log('\n');
|
|
57
|
+
|
|
58
|
+
console.log('RGB:');
|
|
59
|
+
console.log(' ', color.rgb(255, 121, 198)('rgb(255, 121, 198)'));
|
|
60
|
+
console.log(' ', color.rgb(189, 147, 249)('rgb(189, 147, 249)'));
|
|
61
|
+
console.log();
|
|
62
|
+
|
|
63
|
+
console.log('256-color (palette indices):');
|
|
64
|
+
for (const n of [196, 208, 226, 46, 51, 21, 201]) {
|
|
65
|
+
process.stdout.write(color.color256(n)(`[${n}]`) + ' ');
|
|
66
|
+
}
|
|
67
|
+
console.log('\n');
|
|
68
|
+
|
|
69
|
+
console.log(color.bold('━━━ Gradients ━━━'));
|
|
70
|
+
console.log();
|
|
71
|
+
|
|
72
|
+
console.log('Multi-stop gradients:');
|
|
73
|
+
console.log(' ', gradient('Hello gradient world!', ['#ff0000', '#00ff00', '#0000ff']));
|
|
74
|
+
console.log(' ', gradient('Fire to ice', ['#ff6b6b', '#feca57', '#48dbfb']));
|
|
75
|
+
console.log(' ', gradient('Dracula theme', ['#ff79c6', '#bd93f9', '#8be9fd']));
|
|
76
|
+
console.log();
|
|
77
|
+
|
|
78
|
+
console.log(color.bold('━━━ Built-in colorPresets ━━━'));
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(' Available:', presetNames.join(', '));
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(' rainbow: ', rainbow('rainbow text!'));
|
|
83
|
+
console.log(' sunset: ', colorPresets.sunset!('sunset preset'));
|
|
84
|
+
console.log(' ocean: ', colorPresets.ocean!('ocean preset'));
|
|
85
|
+
console.log(' fire: ', colorPresets.fire!('fire preset'));
|
|
86
|
+
console.log(' neon: ', colorPresets.neon!('neon preset'));
|
|
87
|
+
console.log(' forest: ', colorPresets.forest!('forest preset'));
|
|
88
|
+
console.log(' aurora: ', colorPresets.aurora!('aurora preset'));
|
|
89
|
+
console.log(' candy: ', colorPresets.candy!('candy preset'));
|
|
90
|
+
console.log(' gold: ', colorPresets.gold!('gold preset'));
|
|
91
|
+
console.log();
|
|
92
|
+
|
|
93
|
+
console.log(color.bold('━━━ Custom presets ━━━'));
|
|
94
|
+
console.log();
|
|
95
|
+
registerPreset('my-pink', ['#ff6b6b', '#ff79c6', '#ffb6c1']);
|
|
96
|
+
console.log(' Registered. All presets now:', listPresets().join(', '));
|
|
97
|
+
console.log(' my-pink: ', colorPresets['my-pink']!('My custom pink preset!'));
|
|
98
|
+
console.log();
|
|
99
|
+
|
|
100
|
+
console.log(color.bold('━━━ Compose & Chain ━━━'));
|
|
101
|
+
console.log();
|
|
102
|
+
const fancy = compose(color.red, color.bold, color.underline);
|
|
103
|
+
console.log(' compose: ', fancy('red + bold + underline'));
|
|
104
|
+
console.log(' chain: ', chain().magenta().italic().underline().apply('chained styles'));
|
|
105
|
+
console.log();
|
|
106
|
+
|
|
107
|
+
console.log(color.bold(color.green('✓ Colors & gradients test complete')));
|
|
108
|
+
console.log();
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────
|
|
2
|
+
// EXAMPLE 2 — Real-time dashboard with live updates
|
|
3
|
+
//
|
|
4
|
+
// Demonstrates:
|
|
5
|
+
// - frames.live() for sticky bottom-of-screen UI
|
|
6
|
+
// - components.progressBar with gradient
|
|
7
|
+
// - components.table for data display
|
|
8
|
+
// - onResize listener for responsive layout
|
|
9
|
+
// - throttle for rate-limited updates
|
|
10
|
+
// - AbortSignal cleanup
|
|
11
|
+
//
|
|
12
|
+
// Run:
|
|
13
|
+
// npx ts-node examples/02-live-dashboard.ts
|
|
14
|
+
// ─────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
themes,
|
|
18
|
+
components,
|
|
19
|
+
frames,
|
|
20
|
+
onResize,
|
|
21
|
+
throttle,
|
|
22
|
+
termSize,
|
|
23
|
+
sleep,
|
|
24
|
+
cursor,
|
|
25
|
+
screen,
|
|
26
|
+
write,
|
|
27
|
+
} from '../dist/index.js';
|
|
28
|
+
|
|
29
|
+
interface Stat {
|
|
30
|
+
service: string;
|
|
31
|
+
status: 'up' | 'down' | 'pending';
|
|
32
|
+
latency: number;
|
|
33
|
+
load: number; // 0..100
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Simulated metrics that drift over time
|
|
37
|
+
const stats: Stat[] = [
|
|
38
|
+
{ service: 'api-gateway', status: 'up', latency: 14, load: 32 },
|
|
39
|
+
{ service: 'auth-service', status: 'up', latency: 22, load: 58 },
|
|
40
|
+
{ service: 'database', status: 'pending', latency: 0, load: 0 },
|
|
41
|
+
{ service: 'cdn-edge', status: 'up', latency: 8, load: 18 },
|
|
42
|
+
{ service: 'cache-layer', status: 'up', latency: 3, load: 71 },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const drift = (): void => {
|
|
46
|
+
for (const s of stats) {
|
|
47
|
+
if (s.status === 'pending') {
|
|
48
|
+
// Pending services come online randomly
|
|
49
|
+
if (Math.random() < 0.1) {
|
|
50
|
+
s.status = 'up';
|
|
51
|
+
s.latency = 10 + Math.floor(Math.random() * 30);
|
|
52
|
+
}
|
|
53
|
+
} else if (s.status === 'up') {
|
|
54
|
+
// Slight load + latency wander
|
|
55
|
+
s.load = Math.max(0, Math.min(100, s.load + (Math.random() - 0.5) * 8));
|
|
56
|
+
s.latency = Math.max(1, s.latency + (Math.random() - 0.5) * 3);
|
|
57
|
+
// Tiny chance of going down
|
|
58
|
+
if (Math.random() < 0.005) s.status = 'down';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const renderFrame = (): string => {
|
|
64
|
+
const { cols } = termSize();
|
|
65
|
+
const innerWidth = Math.max(40, Math.min(cols - 4, 80));
|
|
66
|
+
|
|
67
|
+
// ── Header with theme gradient ──
|
|
68
|
+
const title = themes.banner('STATUS', { font: 'small', perCharColor: false });
|
|
69
|
+
|
|
70
|
+
// ── Service rows ──
|
|
71
|
+
const rows: string[][] = [
|
|
72
|
+
['Service', 'Status', 'Latency', 'Load'],
|
|
73
|
+
...stats.map((s) => {
|
|
74
|
+
const statusIcon =
|
|
75
|
+
s.status === 'up' ? themes.accent('● up')
|
|
76
|
+
: s.status === 'down' ? themes.error('● down')
|
|
77
|
+
: themes.warning('● pending');
|
|
78
|
+
|
|
79
|
+
const latencyStr = s.status === 'up'
|
|
80
|
+
? (s.latency < 20 ? themes.accent : s.latency < 50 ? themes.warning : themes.error)
|
|
81
|
+
(`${s.latency.toFixed(0)}ms`)
|
|
82
|
+
: themes.muted('—');
|
|
83
|
+
|
|
84
|
+
const loadBar = s.status === 'up'
|
|
85
|
+
? components.progressBar(s.load, {
|
|
86
|
+
width: 20,
|
|
87
|
+
gradient: ['#00ff88', '#fdcb6e', '#ff6b6b'],
|
|
88
|
+
showPercentage: true,
|
|
89
|
+
})
|
|
90
|
+
: themes.muted('—');
|
|
91
|
+
|
|
92
|
+
return [s.service, statusIcon, latencyStr, loadBar];
|
|
93
|
+
}),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const table = components.table(rows, { borderStyle: 'rounded', maxColWidth: innerWidth / 4 });
|
|
97
|
+
|
|
98
|
+
// ── Footer ──
|
|
99
|
+
const upCount = stats.filter((s) => s.status === 'up').length;
|
|
100
|
+
const downCount = stats.filter((s) => s.status === 'down').length;
|
|
101
|
+
const footer = themes.muted(
|
|
102
|
+
`${upCount} up · ${downCount} down · ${stats.length} total · ` +
|
|
103
|
+
`${cols}×${termSize().rows} terminal · Ctrl+C to exit`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return [title, '', table, '', footer].join('\n');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const main = async (): Promise<void> => {
|
|
110
|
+
// Hide cursor while live UI runs
|
|
111
|
+
write(cursor.hide());
|
|
112
|
+
|
|
113
|
+
// Throttled resize redraw — coalesce rapid resize events into one render
|
|
114
|
+
const onAnyResize = throttle(() => {
|
|
115
|
+
// Frame engine will pick up the new size on its next tick
|
|
116
|
+
}, 100);
|
|
117
|
+
const offResize = onResize(onAnyResize);
|
|
118
|
+
|
|
119
|
+
// Start live engine at 4 fps (smooth but not CPU-hungry)
|
|
120
|
+
const live = frames.live({ fps: 4, autoStart: true });
|
|
121
|
+
|
|
122
|
+
// Drift loop
|
|
123
|
+
const ctrl = new AbortController();
|
|
124
|
+
process.on('SIGINT', () => {
|
|
125
|
+
ctrl.abort();
|
|
126
|
+
live.stop({ clear: false });
|
|
127
|
+
offResize();
|
|
128
|
+
write(cursor.show());
|
|
129
|
+
write('\n');
|
|
130
|
+
console.log(themes.muted('Dashboard stopped.'));
|
|
131
|
+
process.exit(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
while (!ctrl.signal.aborted) {
|
|
136
|
+
drift();
|
|
137
|
+
live.update(renderFrame());
|
|
138
|
+
await sleep(250, { signal: ctrl.signal });
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// Aborted — fall through to cleanup
|
|
142
|
+
} finally {
|
|
143
|
+
live.stop({ clear: false });
|
|
144
|
+
offResize();
|
|
145
|
+
write(cursor.show());
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
main().catch((err) => {
|
|
150
|
+
console.error(themes.error('✗ ' + (err as Error).message));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 03 — ASCII Banners & Boxes
|
|
3
|
+
* Real box styles: single, double, rounded, heavy, dashed, ascii
|
|
4
|
+
*
|
|
5
|
+
* Run: npx tsx examples/03-ascii-banners.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ascii, gradient, color, listFonts, hasFont } from '../src/index.js';
|
|
9
|
+
|
|
10
|
+
console.log();
|
|
11
|
+
console.log(color.bold('━━━ ASCII Banners ━━━'));
|
|
12
|
+
console.log();
|
|
13
|
+
|
|
14
|
+
console.log(color.dim('// Big font (default)'));
|
|
15
|
+
console.log(ascii.banner('HELLO'));
|
|
16
|
+
|
|
17
|
+
console.log(color.dim('// Small font'));
|
|
18
|
+
console.log(ascii.banner('HELLO', { font: 'small' }));
|
|
19
|
+
|
|
20
|
+
console.log(color.dim('// With gradient'));
|
|
21
|
+
console.log(ascii.banner('ANSIMAX', {
|
|
22
|
+
font: 'big',
|
|
23
|
+
colorFn: (t) => gradient(t, ['#ff79c6', '#bd93f9', '#8be9fd']),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
console.log(color.dim('// Aligned center'));
|
|
27
|
+
console.log(ascii.banner('CENTER', { align: 'center' }));
|
|
28
|
+
|
|
29
|
+
console.log(color.dim('// Available fonts:'));
|
|
30
|
+
console.log(' fonts:', listFonts().join(', '));
|
|
31
|
+
console.log(' has "big":', hasFont('big'));
|
|
32
|
+
console.log(' has "small":', hasFont('small'));
|
|
33
|
+
console.log(' has "unicorn":', hasFont('unicorn'));
|
|
34
|
+
console.log();
|
|
35
|
+
|
|
36
|
+
console.log(color.bold('━━━ Boxes (6 real styles) ━━━'));
|
|
37
|
+
console.log();
|
|
38
|
+
|
|
39
|
+
// REAL box styles: single, double, rounded, heavy, dashed, ascii
|
|
40
|
+
const boxStyles = ['single', 'double', 'rounded', 'heavy', 'dashed', 'ascii'] as const;
|
|
41
|
+
for (const style of boxStyles) {
|
|
42
|
+
console.log(color.dim(`// borderStyle: ${style}`));
|
|
43
|
+
console.log(ascii.box(`This is a ${style} box`, {
|
|
44
|
+
padding: 1,
|
|
45
|
+
borderStyle: style,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(color.dim('// Multi-line box'));
|
|
50
|
+
console.log(ascii.box(
|
|
51
|
+
'Line 1\nLine 2 with a bit longer text\nLine 3',
|
|
52
|
+
{ padding: 1, borderStyle: 'rounded' },
|
|
53
|
+
));
|
|
54
|
+
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(color.bold('━━━ Dividers ━━━'));
|
|
57
|
+
console.log();
|
|
58
|
+
|
|
59
|
+
console.log(ascii.divider({ char: '─', width: 50 }));
|
|
60
|
+
console.log(ascii.divider({ char: '═', width: 50 }));
|
|
61
|
+
console.log(ascii.divider({ char: '━', width: 50, label: 'Section' }));
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
console.log(color.bold('━━━ Logo composer ━━━'));
|
|
65
|
+
console.log();
|
|
66
|
+
|
|
67
|
+
console.log(ascii.logo('LOGO', {
|
|
68
|
+
font: 'big',
|
|
69
|
+
colorFn: (t) => gradient(t, ['#ff6b6b', '#feca57', '#48dbfb']),
|
|
70
|
+
borderStyle: 'rounded',
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(color.bold('━━━ ascii.measure ━━━'));
|
|
75
|
+
console.log();
|
|
76
|
+
const dim = ascii.measure('Hello World', { font: 'big' });
|
|
77
|
+
console.log(` Banner dimensions: ${dim.width} × ${dim.height}`);
|
|
78
|
+
console.log();
|
|
79
|
+
|
|
80
|
+
console.log(color.bold(color.green('✓ ASCII test complete')));
|
|
81
|
+
console.log();
|