@outfitter/tui 0.2.0
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/README.md +250 -0
- package/dist/borders/index.d.ts +3 -0
- package/dist/borders/index.js +13 -0
- package/dist/box/index.d.ts +4 -0
- package/dist/box/index.js +10 -0
- package/dist/confirm.d.ts +37 -0
- package/dist/confirm.js +36 -0
- package/dist/demo/index.d.ts +77 -0
- package/dist/demo/index.js +142 -0
- package/dist/demo/registry.d.ts +6 -0
- package/dist/demo/registry.js +28 -0
- package/dist/demo/renderers/borders.d.ts +7 -0
- package/dist/demo/renderers/borders.js +14 -0
- package/dist/demo/renderers/box.d.ts +7 -0
- package/dist/demo/renderers/box.js +15 -0
- package/dist/demo/renderers/colors.d.ts +7 -0
- package/dist/demo/renderers/colors.js +15 -0
- package/dist/demo/renderers/indicators.d.ts +7 -0
- package/dist/demo/renderers/indicators.js +14 -0
- package/dist/demo/renderers/list.d.ts +7 -0
- package/dist/demo/renderers/list.js +16 -0
- package/dist/demo/renderers/markdown.d.ts +7 -0
- package/dist/demo/renderers/markdown.js +15 -0
- package/dist/demo/renderers/progress.d.ts +7 -0
- package/dist/demo/renderers/progress.js +14 -0
- package/dist/demo/renderers/spinner.d.ts +7 -0
- package/dist/demo/renderers/spinner.js +16 -0
- package/dist/demo/renderers/table.d.ts +7 -0
- package/dist/demo/renderers/table.js +16 -0
- package/dist/demo/renderers/text.d.ts +7 -0
- package/dist/demo/renderers/text.js +13 -0
- package/dist/demo/renderers/tree.d.ts +7 -0
- package/dist/demo/renderers/tree.js +15 -0
- package/dist/demo/section.d.ts +4 -0
- package/dist/demo/section.js +20 -0
- package/dist/demo/templates.d.ts +3 -0
- package/dist/demo/templates.js +10 -0
- package/dist/demo/types.d.ts +2 -0
- package/dist/demo/types.js +8 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +77 -0
- package/dist/list/index.d.ts +3 -0
- package/dist/list/index.js +9 -0
- package/dist/preset/full.d.ts +12 -0
- package/dist/preset/full.js +37 -0
- package/dist/preset/standard.d.ts +9 -0
- package/dist/preset/standard.js +26 -0
- package/dist/prompt/confirm.d.ts +4 -0
- package/dist/prompt/confirm.js +9 -0
- package/dist/prompt/group.d.ts +4 -0
- package/dist/prompt/group.js +9 -0
- package/dist/prompt/index.d.ts +7 -0
- package/dist/prompt/index.js +32 -0
- package/dist/prompt/select.d.ts +4 -0
- package/dist/prompt/select.js +11 -0
- package/dist/prompt/text.d.ts +4 -0
- package/dist/prompt/text.js +11 -0
- package/dist/prompt/types.d.ts +3 -0
- package/dist/prompt/types.js +8 -0
- package/dist/prompt/validators.d.ts +2 -0
- package/dist/prompt/validators.js +8 -0
- package/dist/render/borders.d.ts +2 -0
- package/dist/render/borders.js +15 -0
- package/dist/render/box.d.ts +3 -0
- package/dist/render/box.js +20 -0
- package/dist/render/date.d.ts +2 -0
- package/dist/render/date.js +12 -0
- package/dist/render/format-relative.d.ts +2 -0
- package/dist/render/format-relative.js +8 -0
- package/dist/render/format.d.ts +2 -0
- package/dist/render/format.js +10 -0
- package/dist/render/heading.d.ts +3 -0
- package/dist/render/heading.js +11 -0
- package/dist/render/index.d.ts +31 -0
- package/dist/render/index.js +222 -0
- package/dist/render/indicators.d.ts +2 -0
- package/dist/render/indicators.js +16 -0
- package/dist/render/json.d.ts +2 -0
- package/dist/render/json.js +10 -0
- package/dist/render/layout.d.ts +5 -0
- package/dist/render/layout.js +22 -0
- package/dist/render/list.d.ts +2 -0
- package/dist/render/list.js +8 -0
- package/dist/render/markdown.d.ts +2 -0
- package/dist/render/markdown.js +8 -0
- package/dist/render/progress.d.ts +2 -0
- package/dist/render/progress.js +8 -0
- package/dist/render/separator.d.ts +3 -0
- package/dist/render/separator.js +11 -0
- package/dist/render/shapes.d.ts +2 -0
- package/dist/render/shapes.js +32 -0
- package/dist/render/spinner.d.ts +2 -0
- package/dist/render/spinner.js +12 -0
- package/dist/render/stack.d.ts +3 -0
- package/dist/render/stack.js +35 -0
- package/dist/render/table.d.ts +3 -0
- package/dist/render/table.js +9 -0
- package/dist/render/tree.d.ts +2 -0
- package/dist/render/tree.js +10 -0
- package/dist/render/types.d.ts +2 -0
- package/dist/render/types.js +1 -0
- package/dist/shared/@outfitter/tui-011579t8.d.ts +24 -0
- package/dist/shared/@outfitter/tui-06dntkse.js +146 -0
- package/dist/shared/@outfitter/tui-0awf316b.js +71 -0
- package/dist/shared/@outfitter/tui-0b85rht7.js +1 -0
- package/dist/shared/@outfitter/tui-0mpqnyc1.js +55 -0
- package/dist/shared/@outfitter/tui-1qh888th.d.ts +106 -0
- package/dist/shared/@outfitter/tui-1tk0kxa6.js +94 -0
- package/dist/shared/@outfitter/tui-1wggw2zj.d.ts +56 -0
- package/dist/shared/@outfitter/tui-2pwhzg55.js +123 -0
- package/dist/shared/@outfitter/tui-2tkva96b.d.ts +20 -0
- package/dist/shared/@outfitter/tui-2wfat6jb.js +22 -0
- package/dist/shared/@outfitter/tui-34q2aenf.d.ts +24 -0
- package/dist/shared/@outfitter/tui-37hjcqhb.d.ts +3 -0
- package/dist/shared/@outfitter/tui-3dyxg62j.d.ts +97 -0
- package/dist/shared/@outfitter/tui-3kza6p9k.d.ts +132 -0
- package/dist/shared/@outfitter/tui-43bnfxv9.js +30 -0
- package/dist/shared/@outfitter/tui-4yz7jcyq.js +214 -0
- package/dist/shared/@outfitter/tui-52ndmswq.js +23 -0
- package/dist/shared/@outfitter/tui-53ms1ba4.d.ts +41 -0
- package/dist/shared/@outfitter/tui-5dsn6d6d.js +86 -0
- package/dist/shared/@outfitter/tui-5pb72vf9.js +51 -0
- package/dist/shared/@outfitter/tui-6605wa2j.js +127 -0
- package/dist/shared/@outfitter/tui-6ptks7jj.js +19 -0
- package/dist/shared/@outfitter/tui-6svngg69.js +126 -0
- package/dist/shared/@outfitter/tui-733asbd8.js +37 -0
- package/dist/shared/@outfitter/tui-75bztyfp.d.ts +50 -0
- package/dist/shared/@outfitter/tui-83zaah9b.d.ts +17 -0
- package/dist/shared/@outfitter/tui-8ejx8gq5.d.ts +71 -0
- package/dist/shared/@outfitter/tui-8j1gbehy.d.ts +164 -0
- package/dist/shared/@outfitter/tui-8mypsnva.d.ts +74 -0
- package/dist/shared/@outfitter/tui-997aapz0.js +61 -0
- package/dist/shared/@outfitter/tui-9dbykt4g.d.ts +42 -0
- package/dist/shared/@outfitter/tui-9h1kdd98.d.ts +119 -0
- package/dist/shared/@outfitter/tui-9rj9yesd.js +118 -0
- package/dist/shared/@outfitter/tui-a65efhsk.d.ts +23 -0
- package/dist/shared/@outfitter/tui-a8nkrbds.js +1 -0
- package/dist/shared/@outfitter/tui-ajp1153q.d.ts +59 -0
- package/dist/shared/@outfitter/tui-ay1fv41j.d.ts +30 -0
- package/dist/shared/@outfitter/tui-azh1w4ak.js +67 -0
- package/dist/shared/@outfitter/tui-b14ry1j1.d.ts +53 -0
- package/dist/shared/@outfitter/tui-c56sxcm8.d.ts +87 -0
- package/dist/shared/@outfitter/tui-c6ft5w6q.d.ts +91 -0
- package/dist/shared/@outfitter/tui-cq7za0cz.js +62 -0
- package/dist/shared/@outfitter/tui-dh15zwg0.js +95 -0
- package/dist/shared/@outfitter/tui-esc46z2v.js +20 -0
- package/dist/shared/@outfitter/tui-f1mj6h6p.d.ts +2 -0
- package/dist/shared/@outfitter/tui-gcpz529w.js +122 -0
- package/dist/shared/@outfitter/tui-gqsdhmk9.d.ts +42 -0
- package/dist/shared/@outfitter/tui-hescagw2.js +32 -0
- package/dist/shared/@outfitter/tui-j2kd7eej.js +126 -0
- package/dist/shared/@outfitter/tui-j3bkjt2g.js +7 -0
- package/dist/shared/@outfitter/tui-jnn9d8cd.js +8 -0
- package/dist/shared/@outfitter/tui-jz5nws55.d.ts +51 -0
- package/dist/shared/@outfitter/tui-k3mby2kk.js +70 -0
- package/dist/shared/@outfitter/tui-kc4nxak0.js +20 -0
- package/dist/shared/@outfitter/tui-kcxv8txp.js +7 -0
- package/dist/shared/@outfitter/tui-kydbggmj.js +39 -0
- package/dist/shared/@outfitter/tui-mch672g9.js +30 -0
- package/dist/shared/@outfitter/tui-n9kxkdrh.js +82 -0
- package/dist/shared/@outfitter/tui-na4dnjpw.js +348 -0
- package/dist/shared/@outfitter/tui-ncaatp4j.js +25 -0
- package/dist/shared/@outfitter/tui-nce7fgtf.js +48 -0
- package/dist/shared/@outfitter/tui-ngz2fbdw.js +144 -0
- package/dist/shared/@outfitter/tui-nprd7g0d.js +52 -0
- package/dist/shared/@outfitter/tui-nr580mbv.js +272 -0
- package/dist/shared/@outfitter/tui-nr93tf31.d.ts +64 -0
- package/dist/shared/@outfitter/tui-pdwbbzwr.js +179 -0
- package/dist/shared/@outfitter/tui-qb07rtct.js +19 -0
- package/dist/shared/@outfitter/tui-qkyazctw.d.ts +36 -0
- package/dist/shared/@outfitter/tui-qn1rgz9v.d.ts +93 -0
- package/dist/shared/@outfitter/tui-qs3fhwp8.js +43 -0
- package/dist/shared/@outfitter/tui-r8hywf9m.d.ts +48 -0
- package/dist/shared/@outfitter/tui-rbbcc034.js +20 -0
- package/dist/shared/@outfitter/tui-rgkerz72.js +85 -0
- package/dist/shared/@outfitter/tui-rh52sycm.d.ts +45 -0
- package/dist/shared/@outfitter/tui-rr924x4z.d.ts +59 -0
- package/dist/shared/@outfitter/tui-rwpdjrt3.js +20 -0
- package/dist/shared/@outfitter/tui-rx9xq9s7.d.ts +26 -0
- package/dist/shared/@outfitter/tui-sk05ye6g.js +1 -0
- package/dist/shared/@outfitter/tui-sv2xmh3w.d.ts +26 -0
- package/dist/shared/@outfitter/tui-t1a9xgbd.js +111 -0
- package/dist/shared/@outfitter/tui-t9vd88jr.js +273 -0
- package/dist/shared/@outfitter/tui-tq1z78x0.js +11 -0
- package/dist/shared/@outfitter/tui-ts1f957s.d.ts +128 -0
- package/dist/shared/@outfitter/tui-ve0083wa.d.ts +61 -0
- package/dist/shared/@outfitter/tui-vt0wg6c4.js +54 -0
- package/dist/shared/@outfitter/tui-wfnnq0zq.d.ts +328 -0
- package/dist/shared/@outfitter/tui-x3yg0887.d.ts +223 -0
- package/dist/shared/@outfitter/tui-x9vvtfkk.js +20 -0
- package/dist/shared/@outfitter/tui-xbvz707j.js +20 -0
- package/dist/shared/@outfitter/tui-xbx6d4t7.js +1 -0
- package/dist/shared/@outfitter/tui-xph95b7h.js +67 -0
- package/dist/shared/@outfitter/tui-xqjx7d1f.js +135 -0
- package/dist/shared/@outfitter/tui-xrgrp99k.d.ts +112 -0
- package/dist/shared/@outfitter/tui-xzjv96ps.d.ts +300 -0
- package/dist/shared/@outfitter/tui-ykzs6v4v.d.ts +66 -0
- package/dist/shared/@outfitter/tui-yw9n5h6q.d.ts +54 -0
- package/dist/shared/@outfitter/tui-ywmakc6b.js +30 -0
- package/dist/shared/@outfitter/tui-z6nf9b9h.js +7 -0
- package/dist/shared/@outfitter/tui-zcspg6z6.d.ts +190 -0
- package/dist/shared/@outfitter/tui-zhbsnj7w.js +1 -0
- package/dist/streaming/ansi.d.ts +2 -0
- package/dist/streaming/ansi.js +8 -0
- package/dist/streaming/index.d.ts +4 -0
- package/dist/streaming/index.js +17 -0
- package/dist/streaming/spinner.d.ts +3 -0
- package/dist/streaming/spinner.js +10 -0
- package/dist/streaming/writer.d.ts +2 -0
- package/dist/streaming/writer.js +9 -0
- package/dist/table/index.d.ts +4 -0
- package/dist/table/index.js +10 -0
- package/dist/theme/context.d.ts +9 -0
- package/dist/theme/context.js +12 -0
- package/dist/theme/create.d.ts +8 -0
- package/dist/theme/create.js +10 -0
- package/dist/theme/index.d.ts +17 -0
- package/dist/theme/index.js +40 -0
- package/dist/theme/presets/bold.d.ts +8 -0
- package/dist/theme/presets/bold.js +10 -0
- package/dist/theme/presets/default.d.ts +8 -0
- package/dist/theme/presets/default.js +9 -0
- package/dist/theme/presets/index.d.ts +12 -0
- package/dist/theme/presets/index.js +22 -0
- package/dist/theme/presets/minimal.d.ts +8 -0
- package/dist/theme/presets/minimal.js +10 -0
- package/dist/theme/presets/rounded.d.ts +8 -0
- package/dist/theme/presets/rounded.js +10 -0
- package/dist/theme/resolve.d.ts +8 -0
- package/dist/theme/resolve.js +11 -0
- package/dist/theme/types.d.ts +7 -0
- package/dist/theme/types.js +1 -0
- package/dist/tree/index.d.ts +3 -0
- package/dist/tree/index.js +11 -0
- package/package.json +263 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
renderTree
|
|
4
|
+
} from "./tui-kydbggmj.js";
|
|
5
|
+
import {
|
|
6
|
+
renderJson,
|
|
7
|
+
renderText
|
|
8
|
+
} from "./tui-tq1z78x0.js";
|
|
9
|
+
import {
|
|
10
|
+
renderMarkdown
|
|
11
|
+
} from "./tui-vt0wg6c4.js";
|
|
12
|
+
import {
|
|
13
|
+
renderList
|
|
14
|
+
} from "./tui-5dsn6d6d.js";
|
|
15
|
+
import {
|
|
16
|
+
renderTable
|
|
17
|
+
} from "./tui-t1a9xgbd.js";
|
|
18
|
+
|
|
19
|
+
// packages/tui/src/render/shapes.ts
|
|
20
|
+
function isCollection(shape) {
|
|
21
|
+
return shape.type === "collection";
|
|
22
|
+
}
|
|
23
|
+
function isHierarchy(shape) {
|
|
24
|
+
return shape.type === "hierarchy";
|
|
25
|
+
}
|
|
26
|
+
function isKeyValue(shape) {
|
|
27
|
+
return shape.type === "keyvalue";
|
|
28
|
+
}
|
|
29
|
+
function isResource(shape) {
|
|
30
|
+
return shape.type === "resource";
|
|
31
|
+
}
|
|
32
|
+
function treeNodeToRecord(node) {
|
|
33
|
+
if (node.children.length === 0) {
|
|
34
|
+
return { [node.name]: null };
|
|
35
|
+
}
|
|
36
|
+
const childRecord = {};
|
|
37
|
+
for (const child of node.children) {
|
|
38
|
+
const childObj = treeNodeToRecord(child);
|
|
39
|
+
Object.assign(childRecord, childObj);
|
|
40
|
+
}
|
|
41
|
+
return { [node.name]: childRecord };
|
|
42
|
+
}
|
|
43
|
+
function isPlainObject(item) {
|
|
44
|
+
return item !== null && typeof item === "object" && !Array.isArray(item);
|
|
45
|
+
}
|
|
46
|
+
var customRenderers = new Map;
|
|
47
|
+
function registerRenderer(shapeType, renderer) {
|
|
48
|
+
customRenderers.set(shapeType, renderer);
|
|
49
|
+
}
|
|
50
|
+
function unregisterRenderer(shapeType) {
|
|
51
|
+
return customRenderers.delete(shapeType);
|
|
52
|
+
}
|
|
53
|
+
function clearRenderers() {
|
|
54
|
+
customRenderers.clear();
|
|
55
|
+
}
|
|
56
|
+
function render(shape, options) {
|
|
57
|
+
const customRenderer = customRenderers.get(shape.type);
|
|
58
|
+
if (customRenderer) {
|
|
59
|
+
return customRenderer(shape, options);
|
|
60
|
+
}
|
|
61
|
+
const format = options?.format;
|
|
62
|
+
if (format === "json") {
|
|
63
|
+
if (isCollection(shape)) {
|
|
64
|
+
return renderJson(shape.items);
|
|
65
|
+
}
|
|
66
|
+
if (isHierarchy(shape)) {
|
|
67
|
+
return renderJson(treeNodeToRecord(shape.root));
|
|
68
|
+
}
|
|
69
|
+
if (isKeyValue(shape)) {
|
|
70
|
+
return renderJson(shape.entries);
|
|
71
|
+
}
|
|
72
|
+
if (isResource(shape)) {
|
|
73
|
+
return renderJson(shape.data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (format === "list" && isCollection(shape)) {
|
|
77
|
+
const listItems = shape.items.map((item) => {
|
|
78
|
+
if (typeof item === "string") {
|
|
79
|
+
return item;
|
|
80
|
+
}
|
|
81
|
+
if (isPlainObject(item)) {
|
|
82
|
+
const name = item.name;
|
|
83
|
+
if (typeof name === "string") {
|
|
84
|
+
return name;
|
|
85
|
+
}
|
|
86
|
+
return JSON.stringify(item);
|
|
87
|
+
}
|
|
88
|
+
return String(item);
|
|
89
|
+
});
|
|
90
|
+
return renderList(listItems);
|
|
91
|
+
}
|
|
92
|
+
if (format === "table" && isCollection(shape)) {
|
|
93
|
+
const items = shape.items.filter(isPlainObject);
|
|
94
|
+
return renderTable(items, shape.headers ? { headers: shape.headers } : undefined);
|
|
95
|
+
}
|
|
96
|
+
if (format === "tree" && isHierarchy(shape)) {
|
|
97
|
+
return renderTree(treeNodeToRecord(shape.root));
|
|
98
|
+
}
|
|
99
|
+
if (format === "text" && isResource(shape)) {
|
|
100
|
+
return renderText(String(shape.data));
|
|
101
|
+
}
|
|
102
|
+
if (isCollection(shape)) {
|
|
103
|
+
const hasObjectItems = shape.items.every(isPlainObject);
|
|
104
|
+
if (hasObjectItems) {
|
|
105
|
+
const items = shape.items;
|
|
106
|
+
return renderTable(items, shape.headers ? { headers: shape.headers } : undefined);
|
|
107
|
+
}
|
|
108
|
+
const listItems = shape.items.map((item) => {
|
|
109
|
+
if (typeof item === "string") {
|
|
110
|
+
return item;
|
|
111
|
+
}
|
|
112
|
+
return String(item);
|
|
113
|
+
});
|
|
114
|
+
return renderList(listItems);
|
|
115
|
+
}
|
|
116
|
+
if (isHierarchy(shape)) {
|
|
117
|
+
return renderTree(treeNodeToRecord(shape.root));
|
|
118
|
+
}
|
|
119
|
+
if (isKeyValue(shape)) {
|
|
120
|
+
return renderJson(shape.entries);
|
|
121
|
+
}
|
|
122
|
+
if (isResource(shape)) {
|
|
123
|
+
const resourceFormat = shape.format ?? "json";
|
|
124
|
+
if (resourceFormat === "markdown") {
|
|
125
|
+
return renderMarkdown(String(shape.data));
|
|
126
|
+
}
|
|
127
|
+
if (resourceFormat === "text") {
|
|
128
|
+
return renderText(String(shape.data));
|
|
129
|
+
}
|
|
130
|
+
return renderJson(shape.data);
|
|
131
|
+
}
|
|
132
|
+
return renderJson(shape);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { isCollection, isHierarchy, isKeyValue, isResource, treeNodeToRecord, isPlainObject, registerRenderer, unregisterRenderer, clearRenderers, render };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Theme } from "@outfitter/cli/colors";
|
|
2
|
+
/**
|
|
3
|
+
* Available primitive types for demos.
|
|
4
|
+
*/
|
|
5
|
+
type PrimitiveId = "colors" | "borders" | "spinner" | "list" | "box" | "table" | "progress" | "tree" | "text" | "markdown" | "indicators";
|
|
6
|
+
/**
|
|
7
|
+
* Categorization for theme methods.
|
|
8
|
+
*/
|
|
9
|
+
type ThemeMethodCategory = "semantic" | "utility";
|
|
10
|
+
/**
|
|
11
|
+
* Metadata for a theme method.
|
|
12
|
+
*/
|
|
13
|
+
interface ThemeMethodMeta {
|
|
14
|
+
/** Method category (semantic or utility) */
|
|
15
|
+
category: ThemeMethodCategory;
|
|
16
|
+
/** Human-readable description */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Default example text */
|
|
19
|
+
defaultExample: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Metadata for a primitive variant (spinner styles, border styles, etc).
|
|
23
|
+
*/
|
|
24
|
+
interface VariantMeta<T extends string> {
|
|
25
|
+
/** The variant value */
|
|
26
|
+
value: T;
|
|
27
|
+
/** Human-readable label */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Description of the variant */
|
|
30
|
+
description: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Metadata describing a primitive for demo generation.
|
|
34
|
+
*/
|
|
35
|
+
interface PrimitiveMeta {
|
|
36
|
+
/** Primitive identifier */
|
|
37
|
+
id: PrimitiveId;
|
|
38
|
+
/** Human-readable name */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Brief description */
|
|
41
|
+
description: string;
|
|
42
|
+
/** Import statement example */
|
|
43
|
+
importExample: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Customizable example texts for demos.
|
|
47
|
+
*
|
|
48
|
+
* Keys correspond to theme method names and other example contexts.
|
|
49
|
+
*/
|
|
50
|
+
interface ExampleTexts {
|
|
51
|
+
success: string;
|
|
52
|
+
warning: string;
|
|
53
|
+
error: string;
|
|
54
|
+
info: string;
|
|
55
|
+
primary: string;
|
|
56
|
+
secondary: string;
|
|
57
|
+
muted: string;
|
|
58
|
+
accent: string;
|
|
59
|
+
highlight: string;
|
|
60
|
+
link: string;
|
|
61
|
+
destructive: string;
|
|
62
|
+
subtle: string;
|
|
63
|
+
bold: string;
|
|
64
|
+
italic: string;
|
|
65
|
+
underline: string;
|
|
66
|
+
dim: string;
|
|
67
|
+
boxContent: string;
|
|
68
|
+
boxTitle: string;
|
|
69
|
+
spinnerMessage: string;
|
|
70
|
+
progressLabel: string;
|
|
71
|
+
listItems: string[];
|
|
72
|
+
tableData: Record<string, unknown>[];
|
|
73
|
+
treeData: Record<string, unknown>;
|
|
74
|
+
markdownSample: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Configuration for demo rendering.
|
|
78
|
+
*/
|
|
79
|
+
interface DemoConfig {
|
|
80
|
+
/**
|
|
81
|
+
* Override default example texts.
|
|
82
|
+
*/
|
|
83
|
+
examples?: Partial<ExampleTexts>;
|
|
84
|
+
/**
|
|
85
|
+
* Whether to show import statements.
|
|
86
|
+
* @default true
|
|
87
|
+
*/
|
|
88
|
+
showCode?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Whether to show method/variant descriptions.
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
showDescriptions?: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Function that renders a demo section for a primitive.
|
|
97
|
+
*/
|
|
98
|
+
type DemoRenderer = (config: DemoConfig, theme: Theme) => string;
|
|
99
|
+
/**
|
|
100
|
+
* Registry entry for a primitive demo.
|
|
101
|
+
*/
|
|
102
|
+
interface DemoRegistryEntry {
|
|
103
|
+
/** Primitive metadata */
|
|
104
|
+
meta: PrimitiveMeta;
|
|
105
|
+
/** Render function */
|
|
106
|
+
render: DemoRenderer;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Checks if a string is a valid PrimitiveId.
|
|
110
|
+
*/
|
|
111
|
+
declare function isPrimitiveId(value: string): value is PrimitiveId;
|
|
112
|
+
export { PrimitiveId, ThemeMethodCategory, ThemeMethodMeta, VariantMeta, PrimitiveMeta, ExampleTexts, DemoConfig, DemoRenderer, DemoRegistryEntry, isPrimitiveId };
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tree node for hierarchical data structures.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* const tree: TreeNode = {
|
|
7
|
+
* name: "src",
|
|
8
|
+
* children: [
|
|
9
|
+
* { name: "index.ts", children: [] },
|
|
10
|
+
* { name: "utils", children: [{ name: "helpers.ts", children: [] }] },
|
|
11
|
+
* ],
|
|
12
|
+
* };
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
interface TreeNode {
|
|
16
|
+
/** The name/label of this node */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Child nodes (empty array for leaf nodes) */
|
|
19
|
+
children: TreeNode[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A collection of items, rendered as table (objects) or list (primitives).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Table rendering (array of objects)
|
|
27
|
+
* const users: Collection = {
|
|
28
|
+
* type: "collection",
|
|
29
|
+
* items: [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }],
|
|
30
|
+
* headers: { name: "Name", age: "Age" },
|
|
31
|
+
* };
|
|
32
|
+
*
|
|
33
|
+
* // List rendering (array of primitives)
|
|
34
|
+
* const tasks: Collection = {
|
|
35
|
+
* type: "collection",
|
|
36
|
+
* items: ["Task 1", "Task 2", "Task 3"],
|
|
37
|
+
* };
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
interface Collection {
|
|
41
|
+
/** Discriminant for Collection type */
|
|
42
|
+
type: "collection";
|
|
43
|
+
/** Array of items to render */
|
|
44
|
+
items: unknown[];
|
|
45
|
+
/** Optional custom headers for table rendering */
|
|
46
|
+
headers?: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A hierarchical tree structure.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const fileTree: Hierarchy = {
|
|
54
|
+
* type: "hierarchy",
|
|
55
|
+
* root: {
|
|
56
|
+
* name: "project",
|
|
57
|
+
* children: [
|
|
58
|
+
* { name: "src", children: [{ name: "index.ts", children: [] }] },
|
|
59
|
+
* { name: "package.json", children: [] },
|
|
60
|
+
* ],
|
|
61
|
+
* },
|
|
62
|
+
* };
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
interface Hierarchy {
|
|
66
|
+
/** Discriminant for Hierarchy type */
|
|
67
|
+
type: "hierarchy";
|
|
68
|
+
/** Root node of the tree */
|
|
69
|
+
root: TreeNode;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Key-value pairs for displaying configuration or metadata.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const config: KeyValue = {
|
|
77
|
+
* type: "keyvalue",
|
|
78
|
+
* entries: {
|
|
79
|
+
* name: "my-app",
|
|
80
|
+
* version: "1.0.0",
|
|
81
|
+
* debug: true,
|
|
82
|
+
* },
|
|
83
|
+
* };
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
interface KeyValue {
|
|
87
|
+
/** Discriminant for KeyValue type */
|
|
88
|
+
type: "keyvalue";
|
|
89
|
+
/** Key-value entries to display */
|
|
90
|
+
entries: Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A resource with content in a specific format.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const jsonResource: Resource = {
|
|
98
|
+
* type: "resource",
|
|
99
|
+
* data: { name: "test", value: 42 },
|
|
100
|
+
* format: "json",
|
|
101
|
+
* };
|
|
102
|
+
*
|
|
103
|
+
* const markdownResource: Resource = {
|
|
104
|
+
* type: "resource",
|
|
105
|
+
* data: "# Heading\n\nSome **bold** text",
|
|
106
|
+
* format: "markdown",
|
|
107
|
+
* };
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
interface Resource {
|
|
111
|
+
/** Discriminant for Resource type */
|
|
112
|
+
type: "resource";
|
|
113
|
+
/** The content to render */
|
|
114
|
+
data: unknown;
|
|
115
|
+
/** Output format (defaults to "json") */
|
|
116
|
+
format?: "json" | "markdown" | "text";
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Discriminated union of all output shape types.
|
|
120
|
+
*
|
|
121
|
+
* Use the type guards {@link isCollection}, {@link isHierarchy},
|
|
122
|
+
* {@link isKeyValue}, and {@link isResource} for type narrowing.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* function processShape(shape: Shape) {
|
|
127
|
+
* if (isCollection(shape)) {
|
|
128
|
+
* console.log(`Collection with ${shape.items.length} items`);
|
|
129
|
+
* } else if (isHierarchy(shape)) {
|
|
130
|
+
* console.log(`Tree rooted at ${shape.root.name}`);
|
|
131
|
+
* }
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
type Shape = Collection | Hierarchy | KeyValue | Resource;
|
|
136
|
+
/**
|
|
137
|
+
* Options for the unified {@link render} function.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const options: RenderOptions = {
|
|
142
|
+
* width: 80,
|
|
143
|
+
* color: true,
|
|
144
|
+
* format: "json",
|
|
145
|
+
* };
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
interface RenderOptions {
|
|
149
|
+
/** Maximum width for output (used by some renderers) */
|
|
150
|
+
width?: number;
|
|
151
|
+
/** Whether to use ANSI colors in output */
|
|
152
|
+
color?: boolean;
|
|
153
|
+
/** Force a specific output format, overriding auto-selection */
|
|
154
|
+
format?: "table" | "list" | "tree" | "json" | "text";
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Type guard for {@link Collection} shapes.
|
|
158
|
+
*
|
|
159
|
+
* @param shape - Shape to check
|
|
160
|
+
* @returns `true` if the shape is a Collection
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* if (isCollection(shape)) {
|
|
165
|
+
* console.log(`Has ${shape.items.length} items`);
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
declare function isCollection(shape: Shape): shape is Collection;
|
|
170
|
+
/**
|
|
171
|
+
* Type guard for {@link Hierarchy} shapes.
|
|
172
|
+
*
|
|
173
|
+
* @param shape - Shape to check
|
|
174
|
+
* @returns `true` if the shape is a Hierarchy
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```typescript
|
|
178
|
+
* if (isHierarchy(shape)) {
|
|
179
|
+
* console.log(`Root: ${shape.root.name}`);
|
|
180
|
+
* }
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
declare function isHierarchy(shape: Shape): shape is Hierarchy;
|
|
184
|
+
/**
|
|
185
|
+
* Type guard for {@link KeyValue} shapes.
|
|
186
|
+
*
|
|
187
|
+
* @param shape - Shape to check
|
|
188
|
+
* @returns `true` if the shape is a KeyValue
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* if (isKeyValue(shape)) {
|
|
193
|
+
* console.log(`Keys: ${Object.keys(shape.entries).join(", ")}`);
|
|
194
|
+
* }
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
declare function isKeyValue(shape: Shape): shape is KeyValue;
|
|
198
|
+
/**
|
|
199
|
+
* Type guard for {@link Resource} shapes.
|
|
200
|
+
*
|
|
201
|
+
* @param shape - Shape to check
|
|
202
|
+
* @returns `true` if the shape is a Resource
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* if (isResource(shape)) {
|
|
207
|
+
* console.log(`Format: ${shape.format ?? "json"}`);
|
|
208
|
+
* }
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
declare function isResource(shape: Shape): shape is Resource;
|
|
212
|
+
/**
|
|
213
|
+
* Converts a TreeNode to the Record format expected by renderTree.
|
|
214
|
+
*/
|
|
215
|
+
declare function treeNodeToRecord(node: TreeNode): Record<string, unknown>;
|
|
216
|
+
/**
|
|
217
|
+
* Checks if an item is a plain object (not null, not array, not primitive).
|
|
218
|
+
*/
|
|
219
|
+
declare function isPlainObject(item: unknown): item is Record<string, unknown>;
|
|
220
|
+
/**
|
|
221
|
+
* A function that renders a shape to a string.
|
|
222
|
+
*
|
|
223
|
+
* @typeParam S - The specific shape type this renderer handles
|
|
224
|
+
*/
|
|
225
|
+
type ShapeRenderer<S extends Shape = Shape> = (shape: S, options?: RenderOptions) => string;
|
|
226
|
+
/**
|
|
227
|
+
* Registers a custom renderer for a shape type.
|
|
228
|
+
* Custom renderers take precedence over built-in renderers.
|
|
229
|
+
*
|
|
230
|
+
* @param shapeType - The shape type to register (e.g., "collection", "hierarchy")
|
|
231
|
+
* @param renderer - The renderer function to use for this shape type
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* registerRenderer("collection", (shape, opts) => {
|
|
236
|
+
* return shape.items.map(item => `- ${item}`).join("\n");
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
declare function registerRenderer<S extends Shape>(shapeType: S["type"], renderer: ShapeRenderer<S>): void;
|
|
241
|
+
/**
|
|
242
|
+
* Removes a custom renderer, reverting to built-in behavior.
|
|
243
|
+
*
|
|
244
|
+
* @param shapeType - The shape type to unregister
|
|
245
|
+
* @returns `true` if a renderer was removed, `false` if none existed
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* unregisterRenderer("collection"); // Reverts to built-in table/list rendering
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
declare function unregisterRenderer(shapeType: string): boolean;
|
|
253
|
+
/**
|
|
254
|
+
* Clears all custom renderers, reverting to built-in behavior.
|
|
255
|
+
* Useful for testing to ensure clean state between tests.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* afterEach(() => {
|
|
260
|
+
* clearRenderers();
|
|
261
|
+
* });
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
declare function clearRenderers(): void;
|
|
265
|
+
/**
|
|
266
|
+
* Unified render function that auto-selects the appropriate renderer based on shape type.
|
|
267
|
+
*
|
|
268
|
+
* Auto-selection logic:
|
|
269
|
+
* - **Collection**: Uses {@link renderTable} for object items, {@link renderList} for primitives
|
|
270
|
+
* - **Hierarchy**: Uses {@link renderTree}
|
|
271
|
+
* - **KeyValue**: Renders as formatted key-value pairs (JSON-like)
|
|
272
|
+
* - **Resource**: Uses {@link renderJson}, {@link renderMarkdown}, or {@link renderText} based on format
|
|
273
|
+
*
|
|
274
|
+
* The `options.format` parameter can override auto-selection.
|
|
275
|
+
*
|
|
276
|
+
* @param shape - The shape to render
|
|
277
|
+
* @param options - Rendering options
|
|
278
|
+
* @returns Rendered string output
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* // Collection auto-selects table or list
|
|
283
|
+
* render({ type: "collection", items: [{ name: "Alice" }] });
|
|
284
|
+
* render({ type: "collection", items: ["item1", "item2"] });
|
|
285
|
+
*
|
|
286
|
+
* // Hierarchy uses tree rendering
|
|
287
|
+
* render({ type: "hierarchy", root: { name: "src", children: [] } });
|
|
288
|
+
*
|
|
289
|
+
* // KeyValue renders formatted pairs
|
|
290
|
+
* render({ type: "keyvalue", entries: { key: "value" } });
|
|
291
|
+
*
|
|
292
|
+
* // Resource respects format option
|
|
293
|
+
* render({ type: "resource", data: obj, format: "json" });
|
|
294
|
+
*
|
|
295
|
+
* // Override with options.format
|
|
296
|
+
* render({ type: "collection", items: [{ a: 1 }] }, { format: "json" });
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
declare function render(shape: Shape, options?: RenderOptions): string;
|
|
300
|
+
export { TreeNode, Collection, Hierarchy, KeyValue, Resource, Shape, RenderOptions, isCollection, isHierarchy, isKeyValue, isResource, treeNodeToRecord, isPlainObject, ShapeRenderer, registerRenderer, unregisterRenderer, clearRenderers, render };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { WritableStream } from "./tui-b14ry1j1";
|
|
2
|
+
/**
|
|
3
|
+
* Spinner frame sets.
|
|
4
|
+
*/
|
|
5
|
+
declare const SPINNER_FRAMES: {
|
|
6
|
+
readonly dots: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
7
|
+
readonly line: readonly ["-", "\\", "|", "/"];
|
|
8
|
+
readonly simple: readonly ["◐", "◓", "◑", "◒"];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Available spinner styles.
|
|
12
|
+
*/
|
|
13
|
+
type SpinnerStyle = keyof typeof SPINNER_FRAMES;
|
|
14
|
+
/**
|
|
15
|
+
* Options for creating a spinner.
|
|
16
|
+
*/
|
|
17
|
+
interface SpinnerOptions {
|
|
18
|
+
/** Spinner style */
|
|
19
|
+
style?: SpinnerStyle;
|
|
20
|
+
/** Target stream */
|
|
21
|
+
stream?: WritableStream;
|
|
22
|
+
/** Frame interval in ms */
|
|
23
|
+
interval?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Spinner interface for animated progress indication.
|
|
27
|
+
*/
|
|
28
|
+
interface Spinner {
|
|
29
|
+
/** Start the spinner */
|
|
30
|
+
start(): void;
|
|
31
|
+
/** Update the spinner message */
|
|
32
|
+
update(message: string): void;
|
|
33
|
+
/** Stop with success state */
|
|
34
|
+
succeed(message?: string): void;
|
|
35
|
+
/** Stop with failure state */
|
|
36
|
+
fail(message?: string): void;
|
|
37
|
+
/** Stop the spinner */
|
|
38
|
+
stop(): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates an animated spinner for indicating progress.
|
|
42
|
+
*
|
|
43
|
+
* In TTY mode, shows an animated spinner. In non-TTY mode,
|
|
44
|
+
* falls back to static output.
|
|
45
|
+
*
|
|
46
|
+
* @param message - Initial spinner message
|
|
47
|
+
* @param options - Spinner configuration
|
|
48
|
+
* @returns Spinner instance
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { createSpinner } from "@outfitter/tui/streaming";
|
|
53
|
+
*
|
|
54
|
+
* const spinner = createSpinner("Installing dependencies");
|
|
55
|
+
* spinner.start();
|
|
56
|
+
*
|
|
57
|
+
* try {
|
|
58
|
+
* await install();
|
|
59
|
+
* spinner.succeed("Dependencies installed");
|
|
60
|
+
* } catch (error) {
|
|
61
|
+
* spinner.fail("Installation failed");
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function createSpinner(message: string, options?: SpinnerOptions): Spinner;
|
|
66
|
+
export { SpinnerStyle, SpinnerOptions, Spinner, createSpinner };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI escape sequences for terminal control.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ANSI escape sequences for cursor and screen control.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { ANSI } from "@outfitter/tui/streaming";
|
|
12
|
+
*
|
|
13
|
+
* // Move cursor up 2 lines and clear
|
|
14
|
+
* process.stdout.write(ANSI.cursorUp(2) + ANSI.clearLine);
|
|
15
|
+
*
|
|
16
|
+
* // Hide cursor during animation
|
|
17
|
+
* process.stdout.write(ANSI.hideCursor);
|
|
18
|
+
* // ... do animation ...
|
|
19
|
+
* process.stdout.write(ANSI.showCursor);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const ANSI: {
|
|
23
|
+
/** Move cursor up n lines */
|
|
24
|
+
readonly cursorUp: (n: number) => string;
|
|
25
|
+
/** Move cursor down n lines */
|
|
26
|
+
readonly cursorDown: (n: number) => string;
|
|
27
|
+
/** Move cursor right n columns */
|
|
28
|
+
readonly cursorRight: (n: number) => string;
|
|
29
|
+
/** Move cursor left n columns */
|
|
30
|
+
readonly cursorLeft: (n: number) => string;
|
|
31
|
+
/** Move cursor to beginning of line */
|
|
32
|
+
readonly cursorToStart: "\x1B[G";
|
|
33
|
+
/** Clear entire line */
|
|
34
|
+
readonly clearLine: "\x1B[2K";
|
|
35
|
+
/** Clear from cursor to end of screen */
|
|
36
|
+
readonly clearToEnd: "\x1B[0J";
|
|
37
|
+
/** Clear from cursor to beginning of screen */
|
|
38
|
+
readonly clearToStart: "\x1B[1J";
|
|
39
|
+
/** Clear entire screen */
|
|
40
|
+
readonly clearScreen: "\x1B[2J";
|
|
41
|
+
/** Hide cursor */
|
|
42
|
+
readonly hideCursor: "\x1B[?25l";
|
|
43
|
+
/** Show cursor */
|
|
44
|
+
readonly showCursor: "\x1B[?25h";
|
|
45
|
+
/** Save cursor position */
|
|
46
|
+
readonly saveCursor: "\x1B[s";
|
|
47
|
+
/** Restore cursor position */
|
|
48
|
+
readonly restoreCursor: "\x1B[u";
|
|
49
|
+
/** Carriage return (move to start of line) */
|
|
50
|
+
readonly carriageReturn: "\r";
|
|
51
|
+
/** New line */
|
|
52
|
+
readonly newLine: "\n";
|
|
53
|
+
};
|
|
54
|
+
export { ANSI };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tui/src/render/format.ts
|
|
3
|
+
function formatDuration(ms) {
|
|
4
|
+
if (ms < 1000)
|
|
5
|
+
return `${ms}ms`;
|
|
6
|
+
const seconds = Math.floor(ms / 1000) % 60;
|
|
7
|
+
const minutes = Math.floor(ms / 60000) % 60;
|
|
8
|
+
const hours = Math.floor(ms / 3600000);
|
|
9
|
+
const parts = [];
|
|
10
|
+
if (hours > 0)
|
|
11
|
+
parts.push(`${hours}h`);
|
|
12
|
+
if (minutes > 0)
|
|
13
|
+
parts.push(`${minutes}m`);
|
|
14
|
+
if (seconds > 0 || parts.length === 0)
|
|
15
|
+
parts.push(`${seconds}s`);
|
|
16
|
+
return parts.join(" ");
|
|
17
|
+
}
|
|
18
|
+
function formatBytes(bytes) {
|
|
19
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
20
|
+
let unitIndex = 0;
|
|
21
|
+
let size = bytes;
|
|
22
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
23
|
+
size /= 1024;
|
|
24
|
+
unitIndex++;
|
|
25
|
+
}
|
|
26
|
+
const formatted = unitIndex === 0 ? size.toString() : size.toFixed(1).replace(/\.0$/, "");
|
|
27
|
+
return `${formatted} ${units[unitIndex]}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { formatDuration, formatBytes };
|