@brainfile/cli 0.8.1 → 0.9.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 +30 -0
- package/README.md +14 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +6 -701
- package/dist/commands/tui.js.map +1 -1
- package/dist/tui/BrainfileTUI.d.ts +4 -0
- package/dist/tui/BrainfileTUI.d.ts.map +1 -0
- package/dist/tui/BrainfileTUI.js +198 -0
- package/dist/tui/BrainfileTUI.js.map +1 -0
- package/dist/tui/actions.d.ts +49 -0
- package/dist/tui/actions.d.ts.map +1 -0
- package/dist/tui/actions.js +569 -0
- package/dist/tui/actions.js.map +1 -0
- package/dist/tui/components/ColumnTabs.d.ts +9 -0
- package/dist/tui/components/ColumnTabs.d.ts.map +1 -0
- package/dist/tui/components/ColumnTabs.js +30 -0
- package/dist/tui/components/ColumnTabs.js.map +1 -0
- package/dist/tui/components/Header.d.ts +12 -0
- package/dist/tui/components/Header.d.ts.map +1 -0
- package/dist/tui/components/Header.js +24 -0
- package/dist/tui/components/Header.js.map +1 -0
- package/dist/tui/components/HelpOverlay.d.ts +7 -0
- package/dist/tui/components/HelpOverlay.d.ts.map +1 -0
- package/dist/tui/components/HelpOverlay.js +56 -0
- package/dist/tui/components/HelpOverlay.js.map +1 -0
- package/dist/tui/components/Overlays.d.ts +41 -0
- package/dist/tui/components/Overlays.d.ts.map +1 -0
- package/dist/tui/components/Overlays.js +127 -0
- package/dist/tui/components/Overlays.js.map +1 -0
- package/dist/tui/components/ProgressBar.d.ts +8 -0
- package/dist/tui/components/ProgressBar.d.ts.map +1 -0
- package/dist/tui/components/ProgressBar.js +35 -0
- package/dist/tui/components/ProgressBar.js.map +1 -0
- package/dist/tui/components/SearchBar.d.ts +7 -0
- package/dist/tui/components/SearchBar.d.ts.map +1 -0
- package/dist/tui/components/SearchBar.js +22 -0
- package/dist/tui/components/SearchBar.js.map +1 -0
- package/dist/tui/components/StatusBar.d.ts +12 -0
- package/dist/tui/components/StatusBar.d.ts.map +1 -0
- package/dist/tui/components/StatusBar.js +37 -0
- package/dist/tui/components/StatusBar.js.map +1 -0
- package/dist/tui/components/TaskCard.d.ts +19 -0
- package/dist/tui/components/TaskCard.d.ts.map +1 -0
- package/dist/tui/components/TaskCard.js +105 -0
- package/dist/tui/components/TaskCard.js.map +1 -0
- package/dist/tui/components/TaskList.d.ts +11 -0
- package/dist/tui/components/TaskList.d.ts.map +1 -0
- package/dist/tui/components/TaskList.js +56 -0
- package/dist/tui/components/TaskList.js.map +1 -0
- package/dist/tui/components/index.d.ts +19 -0
- package/dist/tui/components/index.d.ts.map +1 -0
- package/dist/tui/components/index.js +26 -0
- package/dist/tui/components/index.js.map +1 -0
- package/dist/tui/hooks/index.d.ts +3 -0
- package/dist/tui/hooks/index.d.ts.map +1 -0
- package/dist/tui/hooks/index.js +8 -0
- package/dist/tui/hooks/index.js.map +1 -0
- package/dist/tui/hooks/useBrainfileLoader.d.ts +5 -0
- package/dist/tui/hooks/useBrainfileLoader.d.ts.map +1 -0
- package/dist/tui/hooks/useBrainfileLoader.js +171 -0
- package/dist/tui/hooks/useBrainfileLoader.js.map +1 -0
- package/dist/tui/hooks/useKeyboardNavigation.d.ts +16 -0
- package/dist/tui/hooks/useKeyboardNavigation.d.ts.map +1 -0
- package/dist/tui/hooks/useKeyboardNavigation.js +434 -0
- package/dist/tui/hooks/useKeyboardNavigation.js.map +1 -0
- package/dist/tui/index.d.ts +9 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +36 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/theme.d.ts +113 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +132 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/types.d.ts +30 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +6 -0
- package/dist/tui/types.js.map +1 -0
- package/dist/tui/utils.d.ts +3 -0
- package/dist/tui/utils.d.ts.map +1 -0
- package/dist/tui/utils.js +31 -0
- package/dist/tui/utils.js.map +1 -0
- package/package.json +1 -1
package/dist/commands/tui.js
CHANGED
|
@@ -32,711 +32,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.tuiCommand = tuiCommand;
|
|
37
|
-
const react_1 =
|
|
40
|
+
const react_1 = __importDefault(require("react"));
|
|
38
41
|
const ink_1 = require("ink");
|
|
39
|
-
const core_1 = require("@brainfile/core");
|
|
40
42
|
const fs = __importStar(require("fs"));
|
|
41
43
|
const path = __importStar(require("path"));
|
|
42
|
-
const
|
|
43
|
-
// Unified palette matching VSCode extension priority colors
|
|
44
|
-
// VSCode uses: critical=#d64933, high=#867530, medium=#37505C, low=#bac1b8
|
|
45
|
-
const PALETTE = {
|
|
46
|
-
bg: '#000000',
|
|
47
|
-
panel: '#1e1e1e',
|
|
48
|
-
text: 'white',
|
|
49
|
-
textMuted: 'gray',
|
|
50
|
-
border: 'gray',
|
|
51
|
-
// Priority colors (terminal equivalents)
|
|
52
|
-
critical: 'red', // #d64933
|
|
53
|
-
giga: 'magenta', // Custom high priority
|
|
54
|
-
high: 'yellow', // #867530
|
|
55
|
-
medium: 'cyan', // #37505C
|
|
56
|
-
low: 'gray', // #bac1b8
|
|
57
|
-
// UI accents
|
|
58
|
-
progress: 'blue',
|
|
59
|
-
success: 'green',
|
|
60
|
-
selected: 'white',
|
|
61
|
-
};
|
|
62
|
-
// Box drawing characters
|
|
63
|
-
const BOX = {
|
|
64
|
-
topLeft: '┌',
|
|
65
|
-
topRight: '┐',
|
|
66
|
-
bottomLeft: '└',
|
|
67
|
-
bottomRight: '┘',
|
|
68
|
-
horizontal: '─',
|
|
69
|
-
vertical: '│',
|
|
70
|
-
teeRight: '├',
|
|
71
|
-
teeLeft: '┤',
|
|
72
|
-
};
|
|
73
|
-
const HEADER_ROWS = 7; // title(1) + progress(3: padTop+content+padBottom) + tabs(2: marginTop+content) + separator(1)
|
|
74
|
-
const FOOTER_ROWS = 3; // separator + status bar (2 rows: content + bottom padding)
|
|
75
|
-
function BrainfileTUI({ filePath }) {
|
|
76
|
-
const { exit } = (0, ink_1.useApp)();
|
|
77
|
-
const { stdout } = (0, ink_1.useStdout)();
|
|
78
|
-
const termWidth = stdout?.columns ?? process.stdout.columns ?? 80;
|
|
79
|
-
const termHeight = stdout?.rows ?? process.stdout.rows ?? 24;
|
|
80
|
-
const [state, setState] = (0, react_1.useState)({
|
|
81
|
-
board: null,
|
|
82
|
-
error: null,
|
|
83
|
-
lastUpdated: new Date(),
|
|
84
|
-
activeColumnIndex: 0,
|
|
85
|
-
selectedTaskIndex: 0,
|
|
86
|
-
mode: 'browse',
|
|
87
|
-
searchQuery: '',
|
|
88
|
-
expandedTaskIds: new Set(),
|
|
89
|
-
reloadFlash: false,
|
|
90
|
-
lastContentHash: null,
|
|
91
|
-
});
|
|
92
|
-
const viewportHeight = Math.max(termHeight - HEADER_ROWS - FOOTER_ROWS, 5);
|
|
93
|
-
const loadBrainfile = (0, react_1.useCallback)((forceRefresh = false) => {
|
|
94
|
-
try {
|
|
95
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
96
|
-
// Skip redundant refreshes using content hash (like VSCode extension)
|
|
97
|
-
const contentHash = (0, core_1.hashBoardContent)(content);
|
|
98
|
-
if (!forceRefresh && state.lastContentHash === contentHash) {
|
|
99
|
-
return; // Content unchanged, skip re-render
|
|
100
|
-
}
|
|
101
|
-
const result = core_1.Brainfile.parseWithErrors(content);
|
|
102
|
-
if (result.board) {
|
|
103
|
-
setState(prev => {
|
|
104
|
-
// Preserve selection by task ID if possible
|
|
105
|
-
const prevTaskId = prev.board?.columns[prev.activeColumnIndex]?.tasks[prev.selectedTaskIndex]?.id;
|
|
106
|
-
let newSelectedIndex = 0;
|
|
107
|
-
if (prevTaskId && result.board) {
|
|
108
|
-
const col = result.board.columns[prev.activeColumnIndex];
|
|
109
|
-
if (col) {
|
|
110
|
-
const idx = col.tasks.findIndex(t => t.id === prevTaskId);
|
|
111
|
-
if (idx >= 0)
|
|
112
|
-
newSelectedIndex = idx;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
...prev,
|
|
117
|
-
board: result.board,
|
|
118
|
-
error: null,
|
|
119
|
-
lastUpdated: new Date(),
|
|
120
|
-
selectedTaskIndex: newSelectedIndex,
|
|
121
|
-
reloadFlash: true,
|
|
122
|
-
lastContentHash: contentHash,
|
|
123
|
-
};
|
|
124
|
-
});
|
|
125
|
-
// Clear reload flash after 1 second
|
|
126
|
-
setTimeout(() => {
|
|
127
|
-
setState(prev => ({ ...prev, reloadFlash: false }));
|
|
128
|
-
}, 1000);
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
// Distinguish between different error cases
|
|
132
|
-
let errorMessage;
|
|
133
|
-
if (result.data && result.type) {
|
|
134
|
-
// Valid brainfile but not a board type
|
|
135
|
-
errorMessage = `This is a '${result.type}' brainfile. The TUI currently only supports 'board' type files.`;
|
|
136
|
-
}
|
|
137
|
-
else if (result.error) {
|
|
138
|
-
// Parse error with specific message
|
|
139
|
-
errorMessage = result.error;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
// Unknown failure
|
|
143
|
-
errorMessage = 'Not a valid brainfile';
|
|
144
|
-
}
|
|
145
|
-
setState(prev => ({
|
|
146
|
-
...prev,
|
|
147
|
-
error: errorMessage,
|
|
148
|
-
lastContentHash: contentHash,
|
|
149
|
-
}));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
setState(prev => ({
|
|
154
|
-
...prev,
|
|
155
|
-
error: err instanceof Error ? err.message : String(err),
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
}, [filePath, state.lastContentHash]);
|
|
159
|
-
(0, react_1.useEffect)(() => {
|
|
160
|
-
loadBrainfile();
|
|
161
|
-
}, [loadBrainfile]);
|
|
162
|
-
(0, react_1.useEffect)(() => {
|
|
163
|
-
const watcher = chokidar.watch(filePath, {
|
|
164
|
-
persistent: true,
|
|
165
|
-
ignoreInitial: true,
|
|
166
|
-
usePolling: true,
|
|
167
|
-
interval: 750,
|
|
168
|
-
awaitWriteFinish: {
|
|
169
|
-
stabilityThreshold: 250,
|
|
170
|
-
pollInterval: 100,
|
|
171
|
-
},
|
|
172
|
-
depth: 0,
|
|
173
|
-
});
|
|
174
|
-
watcher.on('change', () => loadBrainfile());
|
|
175
|
-
watcher.on('error', (err) => {
|
|
176
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
177
|
-
setState(prev => ({
|
|
178
|
-
...prev,
|
|
179
|
-
error: `File watcher error: ${message}`,
|
|
180
|
-
}));
|
|
181
|
-
});
|
|
182
|
-
return () => {
|
|
183
|
-
watcher.close();
|
|
184
|
-
};
|
|
185
|
-
}, [filePath, loadBrainfile]);
|
|
186
|
-
// Sort columns by order property (like VSCode extension)
|
|
187
|
-
const orderedColumns = (0, react_1.useMemo)(() => {
|
|
188
|
-
if (!state.board)
|
|
189
|
-
return [];
|
|
190
|
-
return [...state.board.columns].sort((a, b) => {
|
|
191
|
-
const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
|
|
192
|
-
const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
|
|
193
|
-
return orderA - orderB;
|
|
194
|
-
});
|
|
195
|
-
}, [state.board]);
|
|
196
|
-
// Filtered columns based on search
|
|
197
|
-
const filteredColumns = (0, react_1.useMemo)(() => {
|
|
198
|
-
if (!orderedColumns.length)
|
|
199
|
-
return [];
|
|
200
|
-
if (!state.searchQuery.trim())
|
|
201
|
-
return orderedColumns;
|
|
202
|
-
const query = state.searchQuery.toLowerCase();
|
|
203
|
-
return orderedColumns.map(col => ({
|
|
204
|
-
...col,
|
|
205
|
-
tasks: col.tasks.filter(task => task.title.toLowerCase().includes(query) ||
|
|
206
|
-
task.id.toLowerCase().includes(query) ||
|
|
207
|
-
task.tags?.some(t => t.toLowerCase().includes(query)) ||
|
|
208
|
-
task.priority?.toLowerCase().includes(query)),
|
|
209
|
-
})).filter(col => col.tasks.length > 0);
|
|
210
|
-
}, [orderedColumns, state.searchQuery]);
|
|
211
|
-
// Current column and its tasks
|
|
212
|
-
const currentColumn = filteredColumns[state.activeColumnIndex];
|
|
213
|
-
const currentTasks = currentColumn?.tasks || [];
|
|
214
|
-
const maxTaskIndex = Math.max(0, currentTasks.length - 1);
|
|
215
|
-
// Keep selection in bounds
|
|
216
|
-
(0, react_1.useEffect)(() => {
|
|
217
|
-
setState(prev => ({
|
|
218
|
-
...prev,
|
|
219
|
-
activeColumnIndex: Math.min(prev.activeColumnIndex, Math.max(0, filteredColumns.length - 1)),
|
|
220
|
-
selectedTaskIndex: Math.min(prev.selectedTaskIndex, maxTaskIndex),
|
|
221
|
-
}));
|
|
222
|
-
}, [filteredColumns.length, maxTaskIndex]);
|
|
223
|
-
// Calculate stats
|
|
224
|
-
const stats = (0, react_1.useMemo)(() => {
|
|
225
|
-
if (!state.board)
|
|
226
|
-
return { total: 0, done: 0, percentage: 0 };
|
|
227
|
-
const total = state.board.columns.reduce((sum, col) => sum + col.tasks.length, 0);
|
|
228
|
-
const doneCol = state.board.columns.find(col => col.id === 'done' || col.title.toLowerCase() === 'done');
|
|
229
|
-
const done = doneCol?.tasks.length || 0;
|
|
230
|
-
const percentage = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
231
|
-
return { total, done, percentage };
|
|
232
|
-
}, [state.board]);
|
|
233
|
-
(0, ink_1.useInput)((input, key) => {
|
|
234
|
-
// Help mode - any key closes
|
|
235
|
-
if (state.mode === 'help') {
|
|
236
|
-
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
// Search mode handling
|
|
240
|
-
if (state.mode === 'search') {
|
|
241
|
-
if (key.escape) {
|
|
242
|
-
setState(prev => ({ ...prev, mode: 'browse', searchQuery: '' }));
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (key.return) {
|
|
246
|
-
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (key.backspace || key.delete) {
|
|
250
|
-
setState(prev => ({ ...prev, searchQuery: prev.searchQuery.slice(0, -1) }));
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (input && !key.ctrl && !key.meta) {
|
|
254
|
-
setState(prev => ({ ...prev, searchQuery: prev.searchQuery + input }));
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
// Browse mode
|
|
260
|
-
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
261
|
-
exit();
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (input === '?') {
|
|
265
|
-
setState(prev => ({ ...prev, mode: 'help' }));
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (input === '/') {
|
|
269
|
-
setState(prev => ({ ...prev, mode: 'search', searchQuery: '' }));
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
// Refresh (force refresh bypasses hash check)
|
|
273
|
-
if (input === 'r') {
|
|
274
|
-
loadBrainfile(true);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
// Navigation: up/down through tasks
|
|
278
|
-
if (key.downArrow || input === 'j') {
|
|
279
|
-
setState(prev => ({
|
|
280
|
-
...prev,
|
|
281
|
-
selectedTaskIndex: Math.min(prev.selectedTaskIndex + 1, maxTaskIndex),
|
|
282
|
-
}));
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (key.upArrow || input === 'k') {
|
|
286
|
-
setState(prev => ({
|
|
287
|
-
...prev,
|
|
288
|
-
selectedTaskIndex: Math.max(prev.selectedTaskIndex - 1, 0),
|
|
289
|
-
}));
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
// Page scrolling
|
|
293
|
-
if (key.ctrl && input === 'd') {
|
|
294
|
-
setState(prev => ({
|
|
295
|
-
...prev,
|
|
296
|
-
selectedTaskIndex: Math.min(prev.selectedTaskIndex + Math.floor(viewportHeight / 2), maxTaskIndex),
|
|
297
|
-
}));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (key.ctrl && input === 'u') {
|
|
301
|
-
setState(prev => ({
|
|
302
|
-
...prev,
|
|
303
|
-
selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0),
|
|
304
|
-
}));
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
// Column switching: TAB / left/right
|
|
308
|
-
if (key.tab || key.rightArrow || input === 'l') {
|
|
309
|
-
setState(prev => ({
|
|
310
|
-
...prev,
|
|
311
|
-
activeColumnIndex: (prev.activeColumnIndex + 1) % filteredColumns.length,
|
|
312
|
-
selectedTaskIndex: 0,
|
|
313
|
-
}));
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
if ((key.shift && key.tab) || key.leftArrow || input === 'h') {
|
|
317
|
-
setState(prev => ({
|
|
318
|
-
...prev,
|
|
319
|
-
activeColumnIndex: prev.activeColumnIndex === 0
|
|
320
|
-
? filteredColumns.length - 1
|
|
321
|
-
: prev.activeColumnIndex - 1,
|
|
322
|
-
selectedTaskIndex: 0,
|
|
323
|
-
}));
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
// Jump to column headers with { and }
|
|
327
|
-
if (input === '{') {
|
|
328
|
-
setState(prev => ({
|
|
329
|
-
...prev,
|
|
330
|
-
activeColumnIndex: Math.max(0, prev.activeColumnIndex - 1),
|
|
331
|
-
selectedTaskIndex: 0,
|
|
332
|
-
}));
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
if (input === '}') {
|
|
336
|
-
setState(prev => ({
|
|
337
|
-
...prev,
|
|
338
|
-
activeColumnIndex: Math.min(filteredColumns.length - 1, prev.activeColumnIndex + 1),
|
|
339
|
-
selectedTaskIndex: 0,
|
|
340
|
-
}));
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
// Home/End
|
|
344
|
-
if (input === 'g') {
|
|
345
|
-
setState(prev => ({ ...prev, selectedTaskIndex: 0 }));
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (input === 'G') {
|
|
349
|
-
setState(prev => ({ ...prev, selectedTaskIndex: maxTaskIndex }));
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
// Expand/collapse task
|
|
353
|
-
if (key.return) {
|
|
354
|
-
const task = currentTasks[state.selectedTaskIndex];
|
|
355
|
-
if (task) {
|
|
356
|
-
setState(prev => {
|
|
357
|
-
const newExpanded = new Set(prev.expandedTaskIds);
|
|
358
|
-
if (newExpanded.has(task.id)) {
|
|
359
|
-
newExpanded.delete(task.id);
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
newExpanded.add(task.id);
|
|
363
|
-
}
|
|
364
|
-
return { ...prev, expandedTaskIds: newExpanded };
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
// Escape: collapse all or clear search
|
|
370
|
-
if (key.escape) {
|
|
371
|
-
setState(prev => ({
|
|
372
|
-
...prev,
|
|
373
|
-
expandedTaskIds: new Set(),
|
|
374
|
-
searchQuery: '',
|
|
375
|
-
}));
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
// Error state
|
|
380
|
-
if (state.error) {
|
|
381
|
-
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 1 },
|
|
382
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.high, bold: true }, "Error"),
|
|
383
|
-
react_1.default.createElement(ink_1.Box, { marginTop: 1 },
|
|
384
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, state.error)),
|
|
385
|
-
react_1.default.createElement(ink_1.Box, { marginTop: 1 },
|
|
386
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, "Press q to quit, r to retry"))));
|
|
387
|
-
}
|
|
388
|
-
// Loading state
|
|
389
|
-
if (!state.board) {
|
|
390
|
-
return (react_1.default.createElement(ink_1.Box, { padding: 1 },
|
|
391
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, "Loading...")));
|
|
392
|
-
}
|
|
393
|
-
// Help overlay
|
|
394
|
-
if (state.mode === 'help') {
|
|
395
|
-
return react_1.default.createElement(HelpOverlay, { termWidth: termWidth });
|
|
396
|
-
}
|
|
397
|
-
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", width: termWidth, height: termHeight },
|
|
398
|
-
react_1.default.createElement(Header, { title: state.board.title || 'Brainfile', stats: stats, reloadFlash: state.reloadFlash }),
|
|
399
|
-
react_1.default.createElement(ProgressBar, { done: stats.done, total: stats.total, width: termWidth - 4 }),
|
|
400
|
-
state.mode === 'search' && (react_1.default.createElement(SearchBar, { query: state.searchQuery, width: termWidth - 2 })),
|
|
401
|
-
react_1.default.createElement(ColumnTabs, { columns: filteredColumns, activeIndex: state.activeColumnIndex, termWidth: termWidth }),
|
|
402
|
-
react_1.default.createElement(ink_1.Box, { paddingLeft: 1 },
|
|
403
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, BOX.horizontal.repeat(termWidth - 2))),
|
|
404
|
-
react_1.default.createElement(ink_1.Box, { flexGrow: 1, flexDirection: "column" },
|
|
405
|
-
react_1.default.createElement(TaskList, { tasks: currentTasks, selectedIndex: state.selectedTaskIndex, expandedIds: state.expandedTaskIds, viewportHeight: viewportHeight - (state.mode === 'search' ? 1 : 0), termWidth: termWidth })),
|
|
406
|
-
react_1.default.createElement(ink_1.Box, { paddingLeft: 1 },
|
|
407
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, BOX.horizontal.repeat(termWidth - 2))),
|
|
408
|
-
react_1.default.createElement(StatusBar, { mode: state.mode, columnName: currentColumn?.title || '', taskIndex: state.selectedTaskIndex + 1, taskCount: currentTasks.length, termWidth: termWidth })));
|
|
409
|
-
}
|
|
410
|
-
function Header({ title, reloadFlash }) {
|
|
411
|
-
return (react_1.default.createElement(ink_1.Box, { paddingLeft: 1, paddingRight: 1 },
|
|
412
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
|
|
413
|
-
BOX.topLeft,
|
|
414
|
-
BOX.horizontal,
|
|
415
|
-
" ",
|
|
416
|
-
title,
|
|
417
|
-
" "),
|
|
418
|
-
reloadFlash ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.success },
|
|
419
|
-
" ",
|
|
420
|
-
String.fromCharCode(0x21BB),
|
|
421
|
-
" reloaded")) : null));
|
|
422
|
-
}
|
|
423
|
-
function ProgressBar({ done, total, width }) {
|
|
424
|
-
const percentage = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
425
|
-
// Reserve space for: padding + "XX% " + " X of Y complete"
|
|
426
|
-
const textWidth = 20 + String(done).length + String(total).length;
|
|
427
|
-
const barWidth = Math.max(width - textWidth, 20);
|
|
428
|
-
const filled = Math.round((percentage / 100) * barWidth);
|
|
429
|
-
const empty = barWidth - filled;
|
|
430
|
-
return (react_1.default.createElement(ink_1.Box, { paddingLeft: 2, paddingTop: 1, paddingBottom: 1 },
|
|
431
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
|
|
432
|
-
percentage,
|
|
433
|
-
"% "),
|
|
434
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.progress }, '█'.repeat(filled)),
|
|
435
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, '░'.repeat(empty)),
|
|
436
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
|
|
437
|
-
" ",
|
|
438
|
-
done,
|
|
439
|
-
" of ",
|
|
440
|
-
total,
|
|
441
|
-
" complete")));
|
|
442
|
-
}
|
|
443
|
-
function SearchBar({ query, width }) {
|
|
444
|
-
const inputWidth = Math.min(width - 6, 60);
|
|
445
|
-
const displayQuery = query.padEnd(inputWidth, ' ').slice(0, inputWidth);
|
|
446
|
-
return (react_1.default.createElement(ink_1.Box, { paddingLeft: 1, marginTop: 0 },
|
|
447
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
|
|
448
|
-
String.fromCharCode(0x1F50D),
|
|
449
|
-
" "),
|
|
450
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, inverse: true },
|
|
451
|
-
"[",
|
|
452
|
-
displayQuery,
|
|
453
|
-
"]")));
|
|
454
|
-
}
|
|
455
|
-
function ColumnTabs({ columns, activeIndex, termWidth }) {
|
|
456
|
-
const maxTabWidth = Math.floor((termWidth - 4) / Math.max(columns.length, 1)) - 2;
|
|
457
|
-
return (react_1.default.createElement(ink_1.Box, { paddingLeft: 1, paddingRight: 1, marginTop: 1 }, columns.map((col, idx) => {
|
|
458
|
-
const isActive = idx === activeIndex;
|
|
459
|
-
const label = truncate(col.title.toUpperCase(), maxTabWidth - 4);
|
|
460
|
-
const count = col.tasks.length;
|
|
461
|
-
return (react_1.default.createElement(ink_1.Box, { key: col.id, marginRight: 1 }, isActive ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true, inverse: true },
|
|
462
|
-
" ",
|
|
463
|
-
label,
|
|
464
|
-
" ",
|
|
465
|
-
count,
|
|
466
|
-
" ")) : (react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
|
|
467
|
-
" ",
|
|
468
|
-
label,
|
|
469
|
-
" ",
|
|
470
|
-
count,
|
|
471
|
-
" "))));
|
|
472
|
-
})));
|
|
473
|
-
}
|
|
474
|
-
function TaskList({ tasks, selectedIndex, expandedIds, viewportHeight, termWidth }) {
|
|
475
|
-
// Calculate scroll offset to keep selection visible
|
|
476
|
-
const scrollOffset = Math.max(0, Math.min(selectedIndex - Math.floor(viewportHeight / 3), Math.max(0, tasks.length - Math.floor(viewportHeight / 2))));
|
|
477
|
-
const cardWidth = termWidth - 4;
|
|
478
|
-
// Build visible items with expansion
|
|
479
|
-
const visibleItems = [];
|
|
480
|
-
let linesUsed = 0;
|
|
481
|
-
for (let i = scrollOffset; i < tasks.length && linesUsed < viewportHeight; i++) {
|
|
482
|
-
const task = tasks[i];
|
|
483
|
-
const isSelected = i === selectedIndex;
|
|
484
|
-
const isExpanded = expandedIds.has(task.id);
|
|
485
|
-
// Calculate how many lines this task will take (2 lines base + expanded content)
|
|
486
|
-
const subtaskLines = isExpanded ? Math.min((task.subtasks?.length || 0), 4) : 0;
|
|
487
|
-
const expandedLines = isExpanded ? 2 + subtaskLines + (task.relatedFiles?.length ? 1 : 0) : 0;
|
|
488
|
-
const taskLines = 3 + expandedLines; // title + metadata + margin + expanded
|
|
489
|
-
if (linesUsed + taskLines <= viewportHeight) {
|
|
490
|
-
visibleItems.push({ task, isSelected, isExpanded });
|
|
491
|
-
linesUsed += taskLines;
|
|
492
|
-
}
|
|
493
|
-
else if (visibleItems.length === 0) {
|
|
494
|
-
// Always show at least one item
|
|
495
|
-
visibleItems.push({ task, isSelected, isExpanded });
|
|
496
|
-
linesUsed += taskLines;
|
|
497
|
-
break;
|
|
498
|
-
}
|
|
499
|
-
else {
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
// Calculate remaining empty lines to fill viewport
|
|
504
|
-
const emptyLines = Math.max(0, viewportHeight - linesUsed);
|
|
505
|
-
if (tasks.length === 0) {
|
|
506
|
-
return (react_1.default.createElement(ink_1.Box, { flexGrow: 1, flexDirection: "column", paddingLeft: 2 },
|
|
507
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, "No tasks in this column"),
|
|
508
|
-
Array.from({ length: viewportHeight - 1 }).map((_, i) => (react_1.default.createElement(ink_1.Box, { key: `empty-${i}` },
|
|
509
|
-
react_1.default.createElement(ink_1.Text, null, ' '))))));
|
|
510
|
-
}
|
|
511
|
-
return (react_1.default.createElement(ink_1.Box, { flexGrow: 1, flexDirection: "column", paddingLeft: 1 },
|
|
512
|
-
visibleItems.map(({ task, isSelected, isExpanded }) => (react_1.default.createElement(TaskCard, { key: task.id, task: task, isSelected: isSelected, isExpanded: isExpanded, width: cardWidth }))),
|
|
513
|
-
Array.from({ length: emptyLines }).map((_, i) => (react_1.default.createElement(ink_1.Box, { key: `empty-${i}` },
|
|
514
|
-
react_1.default.createElement(ink_1.Text, null, ' '))))));
|
|
515
|
-
}
|
|
516
|
-
function TaskCard({ task, isSelected, isExpanded, width }) {
|
|
517
|
-
const priorityColor = getPriorityColor(task.priority);
|
|
518
|
-
const priorityLabel = task.priority?.toUpperCase() || '';
|
|
519
|
-
const tags = task.tags || [];
|
|
520
|
-
const subtasks = task.subtasks || [];
|
|
521
|
-
const completedSubtasks = subtasks.filter(s => s.completed).length;
|
|
522
|
-
// Arrow indicator: colored when selected
|
|
523
|
-
const arrow = isExpanded ? '▼' : '▶';
|
|
524
|
-
const arrowColor = isSelected ? PALETTE.progress : PALETTE.textMuted;
|
|
525
|
-
const titleWidth = width - task.id.length - 10;
|
|
526
|
-
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 },
|
|
527
|
-
react_1.default.createElement(ink_1.Box, null,
|
|
528
|
-
react_1.default.createElement(ink_1.Text, { color: arrowColor, bold: isSelected }, arrow),
|
|
529
|
-
react_1.default.createElement(ink_1.Text, null, " "),
|
|
530
|
-
react_1.default.createElement(ink_1.Text, { color: isSelected ? PALETTE.text : PALETTE.textMuted, inverse: isSelected, bold: isSelected },
|
|
531
|
-
' ',
|
|
532
|
-
truncate(task.title, titleWidth),
|
|
533
|
-
' '),
|
|
534
|
-
react_1.default.createElement(ink_1.Text, null, " "),
|
|
535
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, task.id)),
|
|
536
|
-
react_1.default.createElement(ink_1.Box, { paddingLeft: 3 },
|
|
537
|
-
react_1.default.createElement(ink_1.Text, { color: priorityColor }, priorityLabel || ' '),
|
|
538
|
-
tags.length > 0 ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true },
|
|
539
|
-
' ',
|
|
540
|
-
String.fromCharCode(0xB7),
|
|
541
|
-
" ",
|
|
542
|
-
tags.slice(0, 4).join(` ${String.fromCharCode(0xB7)} `))) : null),
|
|
543
|
-
isExpanded && (react_1.default.createElement(ExpandedTaskContent, { task: task, priorityColor: priorityColor, completedSubtasks: completedSubtasks, totalSubtasks: subtasks.length, width: width }))));
|
|
544
|
-
}
|
|
545
|
-
function ExpandedTaskContent({ task, priorityColor, completedSubtasks, totalSubtasks, width }) {
|
|
546
|
-
const subtasks = task.subtasks || [];
|
|
547
|
-
const innerWidth = width - 6;
|
|
548
|
-
// Build content lines - all with consistent left border
|
|
549
|
-
const lines = [];
|
|
550
|
-
// Empty line for spacing
|
|
551
|
-
lines.push(react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, ' '));
|
|
552
|
-
if (task.description) {
|
|
553
|
-
lines.push(react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, truncate(task.description.split('\n')[0], innerWidth)));
|
|
554
|
-
}
|
|
555
|
-
if (totalSubtasks > 0) {
|
|
556
|
-
// Empty line before subtasks if there was a description
|
|
557
|
-
if (task.description) {
|
|
558
|
-
lines.push(react_1.default.createElement(ink_1.Text, null, ' '));
|
|
559
|
-
}
|
|
560
|
-
lines.push(react_1.default.createElement(react_1.default.Fragment, null,
|
|
561
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, "Subtasks: "),
|
|
562
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.success }, '●'.repeat(Math.min(completedSubtasks, 10))),
|
|
563
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, '○'.repeat(Math.min(totalSubtasks - completedSubtasks, 10))),
|
|
564
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
|
|
565
|
-
" ",
|
|
566
|
-
completedSubtasks,
|
|
567
|
-
"/",
|
|
568
|
-
totalSubtasks)));
|
|
569
|
-
subtasks.slice(0, 4).forEach((st) => {
|
|
570
|
-
lines.push(react_1.default.createElement(ink_1.Text, { color: st.completed ? PALETTE.success : PALETTE.textMuted },
|
|
571
|
-
st.completed ? '✓' : '○',
|
|
572
|
-
" ",
|
|
573
|
-
truncate(st.title, innerWidth - 2)));
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
if (task.relatedFiles && task.relatedFiles.length > 0) {
|
|
577
|
-
// Empty line before files
|
|
578
|
-
lines.push(react_1.default.createElement(ink_1.Text, null, ' '));
|
|
579
|
-
lines.push(react_1.default.createElement(ink_1.Text, { color: PALETTE.medium },
|
|
580
|
-
"Files: ",
|
|
581
|
-
truncate(task.relatedFiles.slice(0, 2).join(', '), innerWidth - 7)));
|
|
582
|
-
}
|
|
583
|
-
if (lines.length === 0)
|
|
584
|
-
return null;
|
|
585
|
-
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column" }, lines.map((content, idx) => (react_1.default.createElement(ink_1.Box, { key: idx, paddingLeft: 3 },
|
|
586
|
-
react_1.default.createElement(ink_1.Text, { color: priorityColor }, BOX.vertical),
|
|
587
|
-
react_1.default.createElement(ink_1.Text, null, " "),
|
|
588
|
-
content)))));
|
|
589
|
-
}
|
|
590
|
-
function StatusBar({ mode, columnName, taskIndex, taskCount, termWidth, isWatching = true }) {
|
|
591
|
-
// Left section: essential commands
|
|
592
|
-
const leftSection = mode === 'search' ? 'ESC:cancel' : '?:help TAB:column q:quit';
|
|
593
|
-
// Middle section: column and position
|
|
594
|
-
const position = taskCount > 0 ? `${taskIndex}/${taskCount}` : '';
|
|
595
|
-
const middleSection = columnName ? `${columnName.toUpperCase()} ${position}` : '';
|
|
596
|
-
// Right section: status
|
|
597
|
-
const rightSection = isWatching ? '● live' : '';
|
|
598
|
-
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", width: termWidth },
|
|
599
|
-
react_1.default.createElement(ink_1.Box, { width: termWidth - 2, paddingLeft: 1, paddingRight: 1 },
|
|
600
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, leftSection),
|
|
601
|
-
react_1.default.createElement(ink_1.Box, { flexGrow: 1 }),
|
|
602
|
-
middleSection ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.progress }, middleSection)) : null,
|
|
603
|
-
react_1.default.createElement(ink_1.Box, { flexGrow: 1 }),
|
|
604
|
-
rightSection ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.success, dimColor: true }, rightSection)) : null),
|
|
605
|
-
react_1.default.createElement(ink_1.Box, { paddingLeft: 1 },
|
|
606
|
-
react_1.default.createElement(ink_1.Text, null, ' '))));
|
|
607
|
-
}
|
|
608
|
-
function HelpOverlay({ termWidth }) {
|
|
609
|
-
const boxWidth = Math.min(termWidth - 4, 56);
|
|
610
|
-
const hr = BOX.horizontal.repeat(boxWidth - 2);
|
|
611
|
-
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 2 },
|
|
612
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
|
|
613
|
-
BOX.topLeft,
|
|
614
|
-
BOX.horizontal,
|
|
615
|
-
" KEYBOARD SHORTCUTS ",
|
|
616
|
-
BOX.horizontal.repeat(boxWidth - 22),
|
|
617
|
-
BOX.topRight),
|
|
618
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
619
|
-
BOX.vertical,
|
|
620
|
-
' '.repeat(boxWidth - 2),
|
|
621
|
-
BOX.vertical),
|
|
622
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
|
|
623
|
-
BOX.vertical,
|
|
624
|
-
" Navigation",
|
|
625
|
-
' '.repeat(boxWidth - 14),
|
|
626
|
-
BOX.vertical),
|
|
627
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
628
|
-
BOX.vertical,
|
|
629
|
-
" ",
|
|
630
|
-
BOX.horizontal.repeat(boxWidth - 6),
|
|
631
|
-
" ",
|
|
632
|
-
BOX.vertical),
|
|
633
|
-
react_1.default.createElement(HelpRow, { label: "j/k or \u2193/\u2191", desc: "Move up/down", width: boxWidth }),
|
|
634
|
-
react_1.default.createElement(HelpRow, { label: "h/l or \u2190/\u2192", desc: "Switch columns", width: boxWidth }),
|
|
635
|
-
react_1.default.createElement(HelpRow, { label: "TAB", desc: "Next column", width: boxWidth }),
|
|
636
|
-
react_1.default.createElement(HelpRow, { label: "g / G", desc: "Top / Bottom", width: boxWidth }),
|
|
637
|
-
react_1.default.createElement(HelpRow, { label: "Ctrl+d/u", desc: "Page down/up", width: boxWidth }),
|
|
638
|
-
react_1.default.createElement(HelpRow, { label: "{ / }", desc: "Prev/next column", width: boxWidth }),
|
|
639
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
640
|
-
BOX.vertical,
|
|
641
|
-
' '.repeat(boxWidth - 2),
|
|
642
|
-
BOX.vertical),
|
|
643
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
|
|
644
|
-
BOX.vertical,
|
|
645
|
-
" Actions",
|
|
646
|
-
' '.repeat(boxWidth - 11),
|
|
647
|
-
BOX.vertical),
|
|
648
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
649
|
-
BOX.vertical,
|
|
650
|
-
" ",
|
|
651
|
-
BOX.horizontal.repeat(boxWidth - 6),
|
|
652
|
-
" ",
|
|
653
|
-
BOX.vertical),
|
|
654
|
-
react_1.default.createElement(HelpRow, { label: "ENTER", desc: "Expand/collapse task", width: boxWidth }),
|
|
655
|
-
react_1.default.createElement(HelpRow, { label: "/", desc: "Search/filter", width: boxWidth }),
|
|
656
|
-
react_1.default.createElement(HelpRow, { label: "r", desc: "Refresh from file", width: boxWidth }),
|
|
657
|
-
react_1.default.createElement(HelpRow, { label: "ESC", desc: "Clear/collapse", width: boxWidth }),
|
|
658
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
659
|
-
BOX.vertical,
|
|
660
|
-
' '.repeat(boxWidth - 2),
|
|
661
|
-
BOX.vertical),
|
|
662
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
|
|
663
|
-
BOX.vertical,
|
|
664
|
-
" Indicators",
|
|
665
|
-
' '.repeat(boxWidth - 14),
|
|
666
|
-
BOX.vertical),
|
|
667
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
668
|
-
BOX.vertical,
|
|
669
|
-
" ",
|
|
670
|
-
BOX.horizontal.repeat(boxWidth - 6),
|
|
671
|
-
" ",
|
|
672
|
-
BOX.vertical),
|
|
673
|
-
react_1.default.createElement(HelpRow, { label: "\u25B6", desc: "Collapsed task", width: boxWidth }),
|
|
674
|
-
react_1.default.createElement(HelpRow, { label: "\u25BC", desc: "Expanded task", width: boxWidth }),
|
|
675
|
-
react_1.default.createElement(HelpRow, { label: "[inverse]", desc: "Selected task", width: boxWidth }),
|
|
676
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
677
|
-
BOX.vertical,
|
|
678
|
-
' '.repeat(boxWidth - 2),
|
|
679
|
-
BOX.vertical),
|
|
680
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
|
|
681
|
-
BOX.vertical,
|
|
682
|
-
" Quit",
|
|
683
|
-
' '.repeat(boxWidth - 7),
|
|
684
|
-
BOX.vertical),
|
|
685
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
686
|
-
BOX.vertical,
|
|
687
|
-
" ",
|
|
688
|
-
BOX.horizontal.repeat(boxWidth - 6),
|
|
689
|
-
" ",
|
|
690
|
-
BOX.vertical),
|
|
691
|
-
react_1.default.createElement(HelpRow, { label: "q / Ctrl+C", desc: "Exit", width: boxWidth }),
|
|
692
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
693
|
-
BOX.vertical,
|
|
694
|
-
' '.repeat(boxWidth - 2),
|
|
695
|
-
BOX.vertical),
|
|
696
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
|
|
697
|
-
BOX.bottomLeft,
|
|
698
|
-
hr,
|
|
699
|
-
BOX.bottomRight),
|
|
700
|
-
react_1.default.createElement(ink_1.Box, { marginTop: 1 },
|
|
701
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, "Press any key to close"))));
|
|
702
|
-
}
|
|
703
|
-
function HelpRow({ label, desc, width }) {
|
|
704
|
-
const padding = width - 6 - label.length - desc.length;
|
|
705
|
-
return (react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
|
|
706
|
-
BOX.vertical,
|
|
707
|
-
" ",
|
|
708
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.text }, label),
|
|
709
|
-
' '.repeat(Math.max(padding, 2)),
|
|
710
|
-
react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, desc),
|
|
711
|
-
" ",
|
|
712
|
-
BOX.vertical));
|
|
713
|
-
}
|
|
714
|
-
// ---------------------------------------------------------------------------
|
|
715
|
-
// Helper functions
|
|
716
|
-
// ---------------------------------------------------------------------------
|
|
717
|
-
function truncate(value, maxLength) {
|
|
718
|
-
if (!value)
|
|
719
|
-
return '';
|
|
720
|
-
if (value.length <= maxLength)
|
|
721
|
-
return value;
|
|
722
|
-
return `${value.slice(0, maxLength - 1)}…`;
|
|
723
|
-
}
|
|
724
|
-
function getPriorityColor(priority) {
|
|
725
|
-
switch (priority?.toLowerCase()) {
|
|
726
|
-
case 'critical':
|
|
727
|
-
return PALETTE.critical;
|
|
728
|
-
case 'giga':
|
|
729
|
-
return PALETTE.giga;
|
|
730
|
-
case 'high':
|
|
731
|
-
return PALETTE.high;
|
|
732
|
-
case 'medium':
|
|
733
|
-
return PALETTE.medium;
|
|
734
|
-
case 'low':
|
|
735
|
-
return PALETTE.low;
|
|
736
|
-
default:
|
|
737
|
-
return PALETTE.border;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
44
|
+
const index_js_1 = require("../tui/index.js");
|
|
740
45
|
function tuiCommand(options) {
|
|
741
46
|
const filePath = path.resolve(options.file);
|
|
742
47
|
if (!fs.existsSync(filePath)) {
|
|
@@ -753,6 +58,6 @@ function tuiCommand(options) {
|
|
|
753
58
|
console.log('Please run this command in a standard terminal (not piped or in a non-TTY context).');
|
|
754
59
|
process.exit(1);
|
|
755
60
|
}
|
|
756
|
-
(0, ink_1.render)(react_1.default.createElement(BrainfileTUI, { filePath: filePath }));
|
|
61
|
+
(0, ink_1.render)(react_1.default.createElement(index_js_1.BrainfileTUI, { filePath: filePath }));
|
|
757
62
|
}
|
|
758
63
|
//# sourceMappingURL=tui.js.map
|