@positronic/cli 0.0.50 → 0.0.52
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/dist/src/cli.js +37 -68
- package/dist/src/commands/brain.js +14 -23
- package/dist/src/commands/server.js +28 -41
- package/dist/src/components/brain-rerun.js +1 -1
- package/dist/src/components/brain-resolver.js +18 -49
- package/dist/src/components/brain-run.js +26 -43
- package/dist/src/components/brain-top-table.js +153 -0
- package/dist/src/components/brain-top.js +139 -0
- package/dist/src/components/project-select.js +32 -52
- package/dist/src/components/select-list.js +102 -0
- package/dist/src/components/top-navigator.js +259 -0
- package/dist/src/components/watch-resolver.js +592 -0
- package/dist/src/components/watch.js +114 -112
- package/dist/src/hooks/useBrainMachine.js +19 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/commands/brain.d.ts +6 -3
- package/dist/types/commands/brain.d.ts.map +1 -1
- package/dist/types/commands/server.d.ts.map +1 -1
- package/dist/types/components/brain-resolver.d.ts.map +1 -1
- package/dist/types/components/brain-run.d.ts.map +1 -1
- package/dist/types/components/brain-top-table.d.ts +22 -0
- package/dist/types/components/brain-top-table.d.ts.map +1 -0
- package/dist/types/components/brain-top.d.ts +6 -0
- package/dist/types/components/brain-top.d.ts.map +1 -0
- package/dist/types/components/project-select.d.ts.map +1 -1
- package/dist/types/components/select-list.d.ts +23 -0
- package/dist/types/components/select-list.d.ts.map +1 -0
- package/dist/types/components/top-navigator.d.ts +6 -0
- package/dist/types/components/top-navigator.d.ts.map +1 -0
- package/dist/types/components/watch-resolver.d.ts +15 -0
- package/dist/types/components/watch-resolver.d.ts.map +1 -0
- package/dist/types/components/watch.d.ts +3 -1
- package/dist/types/components/watch.d.ts.map +1 -1
- package/dist/types/hooks/useBrainMachine.d.ts +37 -0
- package/dist/types/hooks/useBrainMachine.d.ts.map +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
import { STATUS } from '@positronic/core';
|
|
4
|
+
// Helper to format relative time
|
|
5
|
+
var formatRelativeTime = function(timestamp) {
|
|
6
|
+
var now = Date.now();
|
|
7
|
+
var diffMs = now - timestamp;
|
|
8
|
+
var diffSecs = Math.floor(diffMs / 1000);
|
|
9
|
+
var diffMins = Math.floor(diffMs / (1000 * 60));
|
|
10
|
+
var diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
11
|
+
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
12
|
+
if (diffSecs < 60) {
|
|
13
|
+
return 'just now';
|
|
14
|
+
} else if (diffMins < 60) {
|
|
15
|
+
return "".concat(diffMins, " min ago");
|
|
16
|
+
} else if (diffHours < 24) {
|
|
17
|
+
return "".concat(diffHours, " hr ago");
|
|
18
|
+
} else {
|
|
19
|
+
return "".concat(diffDays, " day").concat(diffDays === 1 ? '' : 's', " ago");
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
// Helper to format live duration
|
|
23
|
+
var formatDuration = function(startedAt) {
|
|
24
|
+
var durationMs = Date.now() - startedAt;
|
|
25
|
+
var totalSeconds = Math.floor(durationMs / 1000);
|
|
26
|
+
var seconds = totalSeconds % 60;
|
|
27
|
+
var minutes = Math.floor(totalSeconds / 60) % 60;
|
|
28
|
+
var hours = Math.floor(totalSeconds / 3600);
|
|
29
|
+
if (hours > 0) {
|
|
30
|
+
return "".concat(hours, "h ").concat(minutes, "m");
|
|
31
|
+
} else if (minutes > 0) {
|
|
32
|
+
return "".concat(minutes, "m ").concat(seconds, "s");
|
|
33
|
+
} else {
|
|
34
|
+
return "".concat(seconds, "s");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
// Helper to get status color
|
|
38
|
+
var getStatusColor = function(status) {
|
|
39
|
+
switch(status){
|
|
40
|
+
case STATUS.COMPLETE:
|
|
41
|
+
return 'green';
|
|
42
|
+
case STATUS.ERROR:
|
|
43
|
+
return 'red';
|
|
44
|
+
case STATUS.RUNNING:
|
|
45
|
+
return 'yellow';
|
|
46
|
+
case STATUS.CANCELLED:
|
|
47
|
+
return 'gray';
|
|
48
|
+
default:
|
|
49
|
+
return 'white';
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// Helper to pad text to column width
|
|
53
|
+
var padRight = function(text, width) {
|
|
54
|
+
return text + ' '.repeat(Math.max(0, width - text.length));
|
|
55
|
+
};
|
|
56
|
+
// Helper to truncate text
|
|
57
|
+
var truncate = function(text, maxWidth) {
|
|
58
|
+
if (text.length <= maxWidth) return text;
|
|
59
|
+
return text.substring(0, maxWidth - 3) + '...';
|
|
60
|
+
};
|
|
61
|
+
// Column definitions
|
|
62
|
+
var columns = {
|
|
63
|
+
brain: {
|
|
64
|
+
header: 'Brain',
|
|
65
|
+
width: 25
|
|
66
|
+
},
|
|
67
|
+
runId: {
|
|
68
|
+
header: 'Run ID',
|
|
69
|
+
width: 36
|
|
70
|
+
},
|
|
71
|
+
status: {
|
|
72
|
+
header: 'Status',
|
|
73
|
+
width: 10
|
|
74
|
+
},
|
|
75
|
+
started: {
|
|
76
|
+
header: 'Started',
|
|
77
|
+
width: 12
|
|
78
|
+
},
|
|
79
|
+
duration: {
|
|
80
|
+
header: 'Duration',
|
|
81
|
+
width: 10
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
export var BrainTopTable = function(param) {
|
|
85
|
+
var runningBrains = param.runningBrains, selectedIndex = param.selectedIndex, _param_interactive = param.interactive, interactive = _param_interactive === void 0 ? false : _param_interactive, brainFilter = param.brainFilter, _param_footer = param.footer, footer = _param_footer === void 0 ? 'Updates automatically. Press Ctrl+C to exit.' : _param_footer;
|
|
86
|
+
// Filter brains client-side
|
|
87
|
+
var filteredBrains = brainFilter ? runningBrains.filter(function(b) {
|
|
88
|
+
return b.brainTitle.toLowerCase().includes(brainFilter.toLowerCase());
|
|
89
|
+
}) : runningBrains;
|
|
90
|
+
if (filteredBrains.length === 0) {
|
|
91
|
+
return /*#__PURE__*/ React.createElement(Box, {
|
|
92
|
+
flexDirection: "column"
|
|
93
|
+
}, /*#__PURE__*/ React.createElement(Text, null, "No running brains", brainFilter ? ' matching "'.concat(brainFilter, '"') : ''), /*#__PURE__*/ React.createElement(Box, {
|
|
94
|
+
marginTop: 1
|
|
95
|
+
}, /*#__PURE__*/ React.createElement(Text, {
|
|
96
|
+
dimColor: true
|
|
97
|
+
}, 'Tip: Run a brain with "px run ', '<brain-name>', '" to see it here')));
|
|
98
|
+
}
|
|
99
|
+
// Selection indicator width (for interactive mode)
|
|
100
|
+
var selectorWidth = interactive ? 2 : 0;
|
|
101
|
+
return /*#__PURE__*/ React.createElement(Box, {
|
|
102
|
+
flexDirection: "column",
|
|
103
|
+
paddingTop: 1,
|
|
104
|
+
paddingBottom: 1
|
|
105
|
+
}, /*#__PURE__*/ React.createElement(Text, {
|
|
106
|
+
bold: true
|
|
107
|
+
}, "Running brains (", filteredBrains.length, ")", brainFilter ? ' matching "'.concat(brainFilter, '"') : '', ":"), /*#__PURE__*/ React.createElement(Box, {
|
|
108
|
+
marginTop: 1,
|
|
109
|
+
flexDirection: "column"
|
|
110
|
+
}, /*#__PURE__*/ React.createElement(Box, null, interactive && /*#__PURE__*/ React.createElement(Text, null, ' '), /*#__PURE__*/ React.createElement(Text, {
|
|
111
|
+
bold: true,
|
|
112
|
+
color: "cyan"
|
|
113
|
+
}, padRight(columns.brain.header, columns.brain.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
114
|
+
bold: true,
|
|
115
|
+
color: "cyan"
|
|
116
|
+
}, padRight(columns.runId.header, columns.runId.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
117
|
+
bold: true,
|
|
118
|
+
color: "cyan"
|
|
119
|
+
}, padRight(columns.status.header, columns.status.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
120
|
+
bold: true,
|
|
121
|
+
color: "cyan"
|
|
122
|
+
}, padRight(columns.started.header, columns.started.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
123
|
+
bold: true,
|
|
124
|
+
color: "cyan"
|
|
125
|
+
}, padRight(columns.duration.header, columns.duration.width))), /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
|
|
126
|
+
dimColor: true
|
|
127
|
+
}, '─'.repeat(97 + selectorWidth))), filteredBrains.map(function(brain, index) {
|
|
128
|
+
var duration = brain.startedAt ? formatDuration(brain.startedAt) : 'N/A';
|
|
129
|
+
var started = brain.startedAt ? formatRelativeTime(brain.startedAt) : formatRelativeTime(brain.createdAt);
|
|
130
|
+
var isSelected = interactive && index === selectedIndex;
|
|
131
|
+
return /*#__PURE__*/ React.createElement(Box, {
|
|
132
|
+
key: brain.brainRunId
|
|
133
|
+
}, interactive && /*#__PURE__*/ React.createElement(Text, {
|
|
134
|
+
color: isSelected ? 'cyan' : undefined
|
|
135
|
+
}, isSelected ? '▶ ' : ' '), /*#__PURE__*/ React.createElement(Text, {
|
|
136
|
+
color: isSelected ? 'cyan' : undefined
|
|
137
|
+
}, padRight(truncate(brain.brainTitle, columns.brain.width), columns.brain.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
138
|
+
dimColor: !isSelected,
|
|
139
|
+
color: isSelected ? 'cyan' : undefined
|
|
140
|
+
}, padRight(brain.brainRunId, columns.runId.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
141
|
+
color: isSelected ? 'cyan' : getStatusColor(brain.status)
|
|
142
|
+
}, padRight(brain.status, columns.status.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
143
|
+
dimColor: !isSelected,
|
|
144
|
+
color: isSelected ? 'cyan' : undefined
|
|
145
|
+
}, padRight(started, columns.started.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
|
|
146
|
+
color: isSelected ? 'cyan' : undefined
|
|
147
|
+
}, padRight(duration, columns.duration.width)));
|
|
148
|
+
})), /*#__PURE__*/ React.createElement(Box, {
|
|
149
|
+
marginTop: 1
|
|
150
|
+
}, /*#__PURE__*/ React.createElement(Text, {
|
|
151
|
+
dimColor: true
|
|
152
|
+
}, footer)));
|
|
153
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
function _array_like_to_array(arr, len) {
|
|
2
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
3
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
4
|
+
return arr2;
|
|
5
|
+
}
|
|
6
|
+
function _array_with_holes(arr) {
|
|
7
|
+
if (Array.isArray(arr)) return arr;
|
|
8
|
+
}
|
|
9
|
+
function _iterable_to_array_limit(arr, i) {
|
|
10
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
11
|
+
if (_i == null) return;
|
|
12
|
+
var _arr = [];
|
|
13
|
+
var _n = true;
|
|
14
|
+
var _d = false;
|
|
15
|
+
var _s, _e;
|
|
16
|
+
try {
|
|
17
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
18
|
+
_arr.push(_s.value);
|
|
19
|
+
if (i && _arr.length === i) break;
|
|
20
|
+
}
|
|
21
|
+
} catch (err) {
|
|
22
|
+
_d = true;
|
|
23
|
+
_e = err;
|
|
24
|
+
} finally{
|
|
25
|
+
try {
|
|
26
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
27
|
+
} finally{
|
|
28
|
+
if (_d) throw _e;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return _arr;
|
|
32
|
+
}
|
|
33
|
+
function _non_iterable_rest() {
|
|
34
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
35
|
+
}
|
|
36
|
+
function _sliced_to_array(arr, i) {
|
|
37
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
38
|
+
}
|
|
39
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
40
|
+
if (!o) return;
|
|
41
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
42
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
43
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
44
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
45
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
46
|
+
}
|
|
47
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
48
|
+
import { Text, Box, useStdout } from 'ink';
|
|
49
|
+
import { EventSource } from 'eventsource';
|
|
50
|
+
import { getApiBaseUrl, isApiLocalDevMode } from '../commands/helpers.js';
|
|
51
|
+
import { ErrorComponent } from './error.js';
|
|
52
|
+
import { BrainTopTable } from './brain-top-table.js';
|
|
53
|
+
export var BrainTop = function(param) {
|
|
54
|
+
var brainFilter = param.brainFilter;
|
|
55
|
+
var write = useStdout().write;
|
|
56
|
+
var _useState = _sliced_to_array(useState([]), 2), runningBrains = _useState[0], setRunningBrains = _useState[1];
|
|
57
|
+
var _useState1 = _sliced_to_array(useState(null), 2), error = _useState1[0], setError = _useState1[1];
|
|
58
|
+
var _useState2 = _sliced_to_array(useState(false), 2), isConnected = _useState2[0], setIsConnected = _useState2[1];
|
|
59
|
+
var _useState3 = _sliced_to_array(useState(0), 2), setTick = _useState3[1];
|
|
60
|
+
var eventSourceRef = useRef(null);
|
|
61
|
+
var hasReceivedDataRef = useRef(false);
|
|
62
|
+
// Enter alternate screen buffer on mount, exit on unmount
|
|
63
|
+
// Skip in test environment to avoid interfering with test output capture
|
|
64
|
+
useEffect(function() {
|
|
65
|
+
if (process.env.NODE_ENV === 'test') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Enter alternate screen buffer and clear
|
|
69
|
+
write('\x1B[?1049h\x1B[2J\x1B[H');
|
|
70
|
+
return function() {
|
|
71
|
+
// Exit alternate screen buffer
|
|
72
|
+
write('\x1B[?1049l');
|
|
73
|
+
};
|
|
74
|
+
}, [
|
|
75
|
+
write
|
|
76
|
+
]);
|
|
77
|
+
// Update tick every second to refresh duration display
|
|
78
|
+
useEffect(function() {
|
|
79
|
+
var interval = setInterval(function() {
|
|
80
|
+
setTick(function(t) {
|
|
81
|
+
return t + 1;
|
|
82
|
+
});
|
|
83
|
+
}, 1000);
|
|
84
|
+
return function() {
|
|
85
|
+
return clearInterval(interval);
|
|
86
|
+
};
|
|
87
|
+
}, []);
|
|
88
|
+
// Connect to EventSource for live updates
|
|
89
|
+
useEffect(function() {
|
|
90
|
+
var baseUrl = getApiBaseUrl();
|
|
91
|
+
var url = "".concat(baseUrl, "/brains/watch");
|
|
92
|
+
var es = new EventSource(url);
|
|
93
|
+
eventSourceRef.current = es;
|
|
94
|
+
setIsConnected(false);
|
|
95
|
+
setError(null);
|
|
96
|
+
es.onopen = function() {
|
|
97
|
+
setIsConnected(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
};
|
|
100
|
+
es.onmessage = function(event) {
|
|
101
|
+
try {
|
|
102
|
+
var data = JSON.parse(event.data);
|
|
103
|
+
setRunningBrains(data.runningBrains || []);
|
|
104
|
+
hasReceivedDataRef.current = true;
|
|
105
|
+
} catch (e) {
|
|
106
|
+
setError(new Error("Error parsing event data: ".concat(e.message)));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
es.onerror = function() {
|
|
110
|
+
// Only show error if we haven't received any data yet
|
|
111
|
+
// (nock closes connection after sending data, triggering reconnect error)
|
|
112
|
+
if (!hasReceivedDataRef.current) {
|
|
113
|
+
var errorMessage = isApiLocalDevMode() ? 'Error connecting to the local development server. Is it running? Start it with "positronic server" or "px s".' : 'Connection failed. Please check your network connection and verify the project URL is correct.';
|
|
114
|
+
setError(new Error(errorMessage));
|
|
115
|
+
setIsConnected(false);
|
|
116
|
+
}
|
|
117
|
+
es.close();
|
|
118
|
+
};
|
|
119
|
+
return function() {
|
|
120
|
+
es.close();
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
123
|
+
if (error) {
|
|
124
|
+
return /*#__PURE__*/ React.createElement(ErrorComponent, {
|
|
125
|
+
error: {
|
|
126
|
+
title: 'Connection Error',
|
|
127
|
+
message: error.message,
|
|
128
|
+
details: error.stack
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (!isConnected) {
|
|
133
|
+
return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, null, "Connecting to watch service..."));
|
|
134
|
+
}
|
|
135
|
+
return /*#__PURE__*/ React.createElement(BrainTopTable, {
|
|
136
|
+
runningBrains: runningBrains,
|
|
137
|
+
brainFilter: brainFilter
|
|
138
|
+
});
|
|
139
|
+
};
|
|
@@ -45,38 +45,15 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
45
45
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
46
46
|
}
|
|
47
47
|
import React, { useState, useEffect } from 'react';
|
|
48
|
-
import { Box, Text,
|
|
49
|
-
|
|
48
|
+
import { Box, Text, useStdin } from 'ink';
|
|
49
|
+
import { SelectList } from './select-list.js';
|
|
50
|
+
// Separate component for interactive selection
|
|
50
51
|
var InteractiveProjectSelect = function(param) {
|
|
51
52
|
var projects = param.projects, currentProject = param.currentProject, projectConfig = param.projectConfig;
|
|
52
|
-
var _useState = _sliced_to_array(useState(
|
|
53
|
-
|
|
54
|
-
return p.name === currentProject;
|
|
55
|
-
});
|
|
56
|
-
return currentIndex >= 0 ? currentIndex : 0;
|
|
57
|
-
}), 2), selectedIndex = _useState[0], setSelectedIndex = _useState[1];
|
|
58
|
-
var _useState1 = _sliced_to_array(useState(null), 2), result = _useState1[0], setResult = _useState1[1];
|
|
59
|
-
var exit = useApp().exit;
|
|
60
|
-
useInput(function(input, key) {
|
|
61
|
-
if (key.upArrow) {
|
|
62
|
-
setSelectedIndex(function(prev) {
|
|
63
|
-
return (prev - 1 + projects.length) % projects.length;
|
|
64
|
-
});
|
|
65
|
-
} else if (key.downArrow) {
|
|
66
|
-
setSelectedIndex(function(prev) {
|
|
67
|
-
return (prev + 1) % projects.length;
|
|
68
|
-
});
|
|
69
|
-
} else if (key.return) {
|
|
70
|
-
var selectedProject = projects[selectedIndex];
|
|
71
|
-
var selectResult = projectConfig.selectProject(selectedProject.name);
|
|
72
|
-
setResult(selectResult);
|
|
73
|
-
} else if (input === 'q' || key.escape) {
|
|
74
|
-
exit();
|
|
75
|
-
}
|
|
76
|
-
});
|
|
53
|
+
var _useState = _sliced_to_array(useState(null), 2), result = _useState[0], setResult = _useState[1];
|
|
54
|
+
var _useState1 = _sliced_to_array(useState(null), 2), selectedProject = _useState1[0], setSelectedProject = _useState1[1];
|
|
77
55
|
// If selection was made, show success
|
|
78
|
-
if (result && result.success) {
|
|
79
|
-
var selectedProject = projects[selectedIndex];
|
|
56
|
+
if (result && result.success && selectedProject) {
|
|
80
57
|
return /*#__PURE__*/ React.createElement(Box, {
|
|
81
58
|
flexDirection: "column"
|
|
82
59
|
}, /*#__PURE__*/ React.createElement(Text, {
|
|
@@ -91,29 +68,32 @@ var InteractiveProjectSelect = function(param) {
|
|
|
91
68
|
bold: true
|
|
92
69
|
}, "URL:"), " ", selectedProject.url)));
|
|
93
70
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
71
|
+
var currentIndex = projects.findIndex(function(p) {
|
|
72
|
+
return p.name === currentProject;
|
|
73
|
+
});
|
|
74
|
+
return /*#__PURE__*/ React.createElement(SelectList, {
|
|
75
|
+
items: projects.map(function(p) {
|
|
76
|
+
return {
|
|
77
|
+
id: p.name,
|
|
78
|
+
label: p.name,
|
|
79
|
+
extra: p.name === currentProject ? /*#__PURE__*/ React.createElement(Text, {
|
|
80
|
+
color: "green"
|
|
81
|
+
}, " (current)") : undefined
|
|
82
|
+
};
|
|
83
|
+
}),
|
|
84
|
+
header: "Select a project:",
|
|
85
|
+
initialIndex: currentIndex >= 0 ? currentIndex : 0,
|
|
86
|
+
onSelect: function(item) {
|
|
87
|
+
var project = projects.find(function(p) {
|
|
88
|
+
return p.name === item.label;
|
|
89
|
+
});
|
|
90
|
+
if (project) {
|
|
91
|
+
setSelectedProject(project);
|
|
92
|
+
var selectResult = projectConfig.selectProject(project.name);
|
|
93
|
+
setResult(selectResult);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
117
97
|
};
|
|
118
98
|
export var ProjectSelect = function(param) {
|
|
119
99
|
var name = param.name, projectConfig = param.projectConfig;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
function _array_like_to_array(arr, len) {
|
|
2
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
3
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
4
|
+
return arr2;
|
|
5
|
+
}
|
|
6
|
+
function _array_with_holes(arr) {
|
|
7
|
+
if (Array.isArray(arr)) return arr;
|
|
8
|
+
}
|
|
9
|
+
function _iterable_to_array_limit(arr, i) {
|
|
10
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
11
|
+
if (_i == null) return;
|
|
12
|
+
var _arr = [];
|
|
13
|
+
var _n = true;
|
|
14
|
+
var _d = false;
|
|
15
|
+
var _s, _e;
|
|
16
|
+
try {
|
|
17
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
18
|
+
_arr.push(_s.value);
|
|
19
|
+
if (i && _arr.length === i) break;
|
|
20
|
+
}
|
|
21
|
+
} catch (err) {
|
|
22
|
+
_d = true;
|
|
23
|
+
_e = err;
|
|
24
|
+
} finally{
|
|
25
|
+
try {
|
|
26
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
27
|
+
} finally{
|
|
28
|
+
if (_d) throw _e;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return _arr;
|
|
32
|
+
}
|
|
33
|
+
function _non_iterable_rest() {
|
|
34
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
35
|
+
}
|
|
36
|
+
function _sliced_to_array(arr, i) {
|
|
37
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
38
|
+
}
|
|
39
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
40
|
+
if (!o) return;
|
|
41
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
42
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
43
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
44
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
45
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
46
|
+
}
|
|
47
|
+
import React, { useState } from 'react';
|
|
48
|
+
import { Box, Text, useInput, useApp } from 'ink';
|
|
49
|
+
/**
|
|
50
|
+
* SelectList - A reusable component for keyboard-navigable list selection.
|
|
51
|
+
*
|
|
52
|
+
* Manages its own selectedIndex state and handles keyboard navigation.
|
|
53
|
+
* Use arrow keys to navigate, Enter to select, q/Escape to cancel.
|
|
54
|
+
*/ export var SelectList = function(param) {
|
|
55
|
+
var items = param.items, header = param.header, onSelect = param.onSelect, onCancel = param.onCancel, _param_initialIndex = param.initialIndex, initialIndex = _param_initialIndex === void 0 ? 0 : _param_initialIndex, _param_footer = param.footer, footer = _param_footer === void 0 ? 'Use arrow keys to navigate, Enter to select, q to quit' : _param_footer;
|
|
56
|
+
var _useState = _sliced_to_array(useState(initialIndex), 2), selectedIndex = _useState[0], setSelectedIndex = _useState[1];
|
|
57
|
+
var exit = useApp().exit;
|
|
58
|
+
useInput(function(input, key) {
|
|
59
|
+
if (key.upArrow) {
|
|
60
|
+
setSelectedIndex(function(prev) {
|
|
61
|
+
return (prev - 1 + items.length) % items.length;
|
|
62
|
+
});
|
|
63
|
+
} else if (key.downArrow) {
|
|
64
|
+
setSelectedIndex(function(prev) {
|
|
65
|
+
return (prev + 1) % items.length;
|
|
66
|
+
});
|
|
67
|
+
} else if (key.return) {
|
|
68
|
+
onSelect(items[selectedIndex], selectedIndex);
|
|
69
|
+
} else if (input === 'q' || key.escape) {
|
|
70
|
+
if (onCancel) {
|
|
71
|
+
onCancel();
|
|
72
|
+
} else {
|
|
73
|
+
exit();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return /*#__PURE__*/ React.createElement(Box, {
|
|
78
|
+
flexDirection: "column"
|
|
79
|
+
}, /*#__PURE__*/ React.createElement(Text, {
|
|
80
|
+
bold: true
|
|
81
|
+
}, header), /*#__PURE__*/ React.createElement(Box, {
|
|
82
|
+
marginTop: 1,
|
|
83
|
+
flexDirection: "column"
|
|
84
|
+
}, items.map(function(item, index) {
|
|
85
|
+
var isSelected = index === selectedIndex;
|
|
86
|
+
return /*#__PURE__*/ React.createElement(Box, {
|
|
87
|
+
key: item.id,
|
|
88
|
+
flexDirection: "column",
|
|
89
|
+
marginBottom: item.description ? 1 : 0
|
|
90
|
+
}, /*#__PURE__*/ React.createElement(Text, {
|
|
91
|
+
color: isSelected ? 'cyan' : undefined
|
|
92
|
+
}, isSelected ? '▶ ' : ' ', /*#__PURE__*/ React.createElement(Text, {
|
|
93
|
+
bold: true
|
|
94
|
+
}, item.label), item.extra), item.description && /*#__PURE__*/ React.createElement(Text, {
|
|
95
|
+
dimColor: true
|
|
96
|
+
}, ' ', item.description));
|
|
97
|
+
})), /*#__PURE__*/ React.createElement(Box, {
|
|
98
|
+
marginTop: 1
|
|
99
|
+
}, /*#__PURE__*/ React.createElement(Text, {
|
|
100
|
+
dimColor: true
|
|
101
|
+
}, footer)));
|
|
102
|
+
};
|