cdd-cli 4.1.0 → 4.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.es.md +40 -30
- package/README.md +37 -27
- package/dist/App.js +1 -0
- package/dist/components/ContainerCreationPrompt.js +14 -0
- package/dist/components/PromptField.js +13 -6
- package/dist/components/TextField.js +54 -0
- package/dist/helpers/textEditing.js +293 -0
- package/dist/hooks/creation/useContainerCreation.js +107 -9
- package/dist/hooks/useControls.js +16 -26
- package/package.json +1 -1
package/README.es.md
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://img.shields.io/npm/v/cdd-cli?color=blue&label=npm%20package" alt="npm version"/>
|
|
5
5
|
<img src="https://img.shields.io/npm/dt/cdd-cli?color=green&label=downloads" alt="npm downloads"/>
|
|
6
|
-
<a href="https://github.com/caertos/cdd/actions/workflows/ci.yml">
|
|
7
|
-
<img src="https://github.com/caertos/cdd/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
|
|
8
|
-
</a>
|
|
9
6
|
<a href="https://deepwiki.com/Caertos/cdd"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
10
7
|
</p>
|
|
11
8
|
|
|
@@ -13,20 +10,33 @@
|
|
|
13
10
|
|
|
14
11
|
---
|
|
15
12
|
|
|
16
|
-
## 🎉 Novedades en v4.
|
|
13
|
+
## 🎉 Novedades en v4.2
|
|
17
14
|
|
|
18
|
-
**
|
|
15
|
+
**Campos de texto editables con cursor visible en el asistente de creación.**
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
Ahora los campos de texto del asistente se comportan como un campo de texto normal: un cursor que se ve, que se mueve con las flechas, que inserta y borra donde está.
|
|
21
18
|
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
19
|
+
- **Navegación con cursor** — `←` / `→` mueven el cursor dentro del campo
|
|
20
|
+
- **Inicio / Fin** — saltan al principio o al final del texto
|
|
21
|
+
- **Edición completa** — `Backspace` y `Delete` funcionan en cualquier posición
|
|
22
|
+
- **Atajos de teclado** — `Ctrl+A/E/W/U/K` para edición rápida
|
|
23
|
+
- **Pegado** — pegar texto lo inserta completo en la posición del cursor
|
|
24
|
+
- **Acentos y emojis** — manejo correcto por punto de código
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
28
|
## Versiones anteriores
|
|
29
29
|
|
|
30
|
+
### v4.1 — Shell interactivo
|
|
31
|
+
|
|
32
|
+
**Abre un shell dentro de cualquier contenedor con una sola tecla.**
|
|
33
|
+
|
|
34
|
+
Presiona `S` y CDD te lleva a un shell interactivo completo (`bash` o `sh`) dentro del contenedor seleccionado — sin necesidad de escribir `docker exec`.
|
|
35
|
+
|
|
36
|
+
- **Shell auto-detectado** — CDD sondea el contenedor y elige `bash` o `sh` automáticamente
|
|
37
|
+
- **Soporte de terminal completo** — ejecuta `psql`, `python3`, `node`, `redis-cli`, o cualquier comando dentro del contenedor
|
|
38
|
+
- **Salida limpia** — escribe `exit` o presiona `Ctrl+D` para volver al dashboard
|
|
39
|
+
|
|
30
40
|
### v3.2 — Asistente de creación interactivo
|
|
31
41
|
|
|
32
42
|
Olvídate de los flags de `docker run`, las variables de entorno olvidadas y los tags `:latest` que fallan en silencio. Presiona `C` y CDD te guía para crear un contenedor en segundos:
|
|
@@ -85,18 +95,18 @@ cdd
|
|
|
85
95
|
|
|
86
96
|
Usa `↑` / `↓` para navegar por los contenedores. Atajos de teclado disponibles:
|
|
87
97
|
|
|
88
|
-
| Tecla
|
|
89
|
-
|
|
90
|
-
| `↑` / `↓` | Navegar la lista de contenedores
|
|
91
|
-
| `I`
|
|
92
|
-
| `P`
|
|
93
|
-
| `R`
|
|
94
|
-
| `C`
|
|
95
|
-
| `L`
|
|
96
|
-
| `S`
|
|
97
|
-
| `E`
|
|
98
|
-
| `D`
|
|
99
|
-
| `Q`
|
|
98
|
+
| Tecla | Acción |
|
|
99
|
+
| --------- | ----------------------------------------------------------- |
|
|
100
|
+
| `↑` / `↓` | Navegar la lista de contenedores |
|
|
101
|
+
| `I` | Iniciar el contenedor seleccionado |
|
|
102
|
+
| `P` | Detener el contenedor seleccionado |
|
|
103
|
+
| `R` | Reiniciar el contenedor seleccionado |
|
|
104
|
+
| `C` | Abrir el asistente de creación |
|
|
105
|
+
| `L` | Ver logs del contenedor seleccionado en tiempo real |
|
|
106
|
+
| `S` | Abrir shell interactivo dentro del contenedor seleccionado |
|
|
107
|
+
| `E` | Eliminar el contenedor seleccionado — requiere confirmación |
|
|
108
|
+
| `D` | Activar/desactivar panel de debug en vivo |
|
|
109
|
+
| `Q` | Salir |
|
|
100
110
|
|
|
101
111
|
---
|
|
102
112
|
|
|
@@ -124,13 +134,13 @@ Ingresa un mapeo de puertos en formato `HOST:CONTENEDOR`, por ejemplo `8080:80`,
|
|
|
124
134
|
|
|
125
135
|
Ingresa pares `CLAVE=VALOR` de a uno. Las **sugerencias contextuales** muestran variables recomendadas para la imagen seleccionada:
|
|
126
136
|
|
|
127
|
-
| Imagen
|
|
128
|
-
|
|
129
|
-
| postgres
|
|
130
|
-
| mysql
|
|
131
|
-
| redis
|
|
132
|
-
| mongo
|
|
133
|
-
| node / nginx / otros | Variables de runtime comunes según corresponda
|
|
137
|
+
| Imagen | Variables sugeridas |
|
|
138
|
+
| -------------------- | ---------------------------------------------------------- |
|
|
139
|
+
| postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
|
|
140
|
+
| mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
|
|
141
|
+
| redis | _(sin variables requeridas)_ |
|
|
142
|
+
| mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
|
|
143
|
+
| node / nginx / otros | Variables de runtime comunes según corresponda |
|
|
134
144
|
|
|
135
145
|
Presiona `Enter` en una línea vacía para terminar y crear el contenedor.
|
|
136
146
|
|
|
@@ -144,9 +154,9 @@ Presiona `S` desde el dashboard con un contenedor en ejecución seleccionado. CD
|
|
|
144
154
|
2. Abrirá una sesión de terminal interactiva completa
|
|
145
155
|
3. Te dejará en el shell del contenedor
|
|
146
156
|
|
|
147
|
-
Desde ahí
|
|
157
|
+
Desde ahí puedes ejecutar cualquier comando — `psql` para PostgreSQL, `python3` para Python, `node` para Node.js, `redis-cli` para Redis, etc.
|
|
148
158
|
|
|
149
|
-
|
|
159
|
+
Escribe `exit` o presiona `Ctrl+D` para salir del shell y volver al dashboard de CDD.
|
|
150
160
|
|
|
151
161
|
---
|
|
152
162
|
|
package/README.md
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://img.shields.io/npm/v/cdd-cli?color=blue&label=npm%20package" alt="npm version"/>
|
|
5
5
|
<img src="https://img.shields.io/npm/dt/cdd-cli?color=green&label=downloads" alt="npm downloads"/>
|
|
6
|
-
<a href="https://github.com/caertos/cdd/actions/workflows/ci.yml">
|
|
7
|
-
<img src="https://github.com/caertos/cdd/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
|
|
8
|
-
</a>
|
|
9
6
|
<a href="https://deepwiki.com/Caertos/cdd"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
10
7
|
</p>
|
|
11
8
|
|
|
@@ -13,7 +10,24 @@
|
|
|
13
10
|
|
|
14
11
|
---
|
|
15
12
|
|
|
16
|
-
## 🎉 What's new in v4.
|
|
13
|
+
## 🎉 What's new in v4.2
|
|
14
|
+
|
|
15
|
+
**Editable text fields with visible cursor in the creation wizard.**
|
|
16
|
+
|
|
17
|
+
Text fields in the wizard now behave like a proper text input: a visible cursor that moves with arrow keys, inserts and deletes where it stands.
|
|
18
|
+
|
|
19
|
+
- **Cursor navigation** — `←` / `→` move the cursor within the field
|
|
20
|
+
- **Home / End** — jump to the start or end of the text
|
|
21
|
+
- **Full editing** — `Backspace` and `Delete` work at any cursor position
|
|
22
|
+
- **Keyboard shortcuts** — `Ctrl+A/E/W/U/K` for fast text editing
|
|
23
|
+
- **Paste support** — pasted text inserts at the cursor position
|
|
24
|
+
- **Accents and emojis** — correct handling via code-point segmentation
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Previous releases
|
|
29
|
+
|
|
30
|
+
### v4.1 — Interactive shell
|
|
17
31
|
|
|
18
32
|
**Open a shell inside any container with a single keystroke.**
|
|
19
33
|
|
|
@@ -23,10 +37,6 @@ Press `S` and CDD drops you into a full interactive shell (`bash` or `sh`) insid
|
|
|
23
37
|
- **Full terminal support** — run `psql`, `python3`, `node`, `redis-cli`, or any command inside the container
|
|
24
38
|
- **Clean exit** — type `exit` or press `Ctrl+D` to return to the dashboard
|
|
25
39
|
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Previous releases
|
|
29
|
-
|
|
30
40
|
### v3.2 — Interactive creation wizard
|
|
31
41
|
|
|
32
42
|
Forget `docker run` flags, forgotten env vars, and broken `:latest` tags. Press `C` and CDD guides you through creating a container in seconds:
|
|
@@ -85,18 +95,18 @@ cdd
|
|
|
85
95
|
|
|
86
96
|
Use `↑` / `↓` to navigate containers. Available keyboard shortcuts:
|
|
87
97
|
|
|
88
|
-
| Key
|
|
89
|
-
|
|
90
|
-
| `↑` / `↓` | Navigate container list
|
|
91
|
-
| `I`
|
|
92
|
-
| `P`
|
|
93
|
-
| `R`
|
|
94
|
-
| `C`
|
|
95
|
-
| `L`
|
|
96
|
-
| `S`
|
|
97
|
-
| `E`
|
|
98
|
-
| `D`
|
|
99
|
-
| `Q`
|
|
98
|
+
| Key | Action |
|
|
99
|
+
| --------- | --------------------------------------------------------- |
|
|
100
|
+
| `↑` / `↓` | Navigate container list |
|
|
101
|
+
| `I` | Start selected container |
|
|
102
|
+
| `P` | Stop selected container |
|
|
103
|
+
| `R` | Restart selected container |
|
|
104
|
+
| `C` | Open creation wizard |
|
|
105
|
+
| `L` | Stream logs for selected container |
|
|
106
|
+
| `S` | Open interactive shell inside selected container |
|
|
107
|
+
| `E` | Erase (remove) selected container — confirmation required |
|
|
108
|
+
| `D` | Toggle live debug panel |
|
|
109
|
+
| `Q` | Quit |
|
|
100
110
|
|
|
101
111
|
---
|
|
102
112
|
|
|
@@ -124,13 +134,13 @@ Enter a port mapping in `HOST:CONTAINER` format, e.g. `8080:80`, `5432:5432`. Le
|
|
|
124
134
|
|
|
125
135
|
Enter `KEY=VALUE` pairs one at a time. **Contextual hints** show recommended variables for the selected image:
|
|
126
136
|
|
|
127
|
-
| Image
|
|
128
|
-
|
|
129
|
-
| postgres
|
|
130
|
-
| mysql
|
|
131
|
-
| redis
|
|
132
|
-
| mongo
|
|
133
|
-
| node / nginx / others | Common runtime vars as applicable
|
|
137
|
+
| Image | Suggested vars |
|
|
138
|
+
| --------------------- | ---------------------------------------------------------- |
|
|
139
|
+
| postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
|
|
140
|
+
| mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
|
|
141
|
+
| redis | _(no required vars)_ |
|
|
142
|
+
| mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
|
|
143
|
+
| node / nginx / others | Common runtime vars as applicable |
|
|
134
144
|
|
|
135
145
|
Press `Enter` on an empty line to finish and create the container.
|
|
136
146
|
|
package/dist/App.js
CHANGED
|
@@ -31,6 +31,7 @@ export default function App() {
|
|
|
31
31
|
containerName: controls.containerNameInput,
|
|
32
32
|
portInput: controls.portInput,
|
|
33
33
|
envInput: controls.envInput,
|
|
34
|
+
cursors: controls.creation.cursors,
|
|
34
35
|
message: controls.message,
|
|
35
36
|
messageColor: controls.messageColor,
|
|
36
37
|
suggestions: controls.creation.suggestions,
|
|
@@ -29,6 +29,8 @@ export default function ContainerCreationPrompt(props) {
|
|
|
29
29
|
containerName = props.containerName,
|
|
30
30
|
portInput = props.portInput,
|
|
31
31
|
envInput = props.envInput,
|
|
32
|
+
_props$cursors = props.cursors,
|
|
33
|
+
cursors = _props$cursors === void 0 ? {} : _props$cursors,
|
|
32
34
|
message = props.message,
|
|
33
35
|
messageColor = props.messageColor,
|
|
34
36
|
_props$suggestions = props.suggestions,
|
|
@@ -46,23 +48,28 @@ export default function ContainerCreationPrompt(props) {
|
|
|
46
48
|
var prompts = [{
|
|
47
49
|
label: 'Name of the image to create (e.g., nginx:1.27-alpine):',
|
|
48
50
|
value: imageName,
|
|
51
|
+
cursor: cursors.imageName,
|
|
49
52
|
required: true
|
|
50
53
|
}, {
|
|
51
54
|
label: 'Name of the container (optional):',
|
|
52
55
|
value: containerName,
|
|
56
|
+
cursor: cursors.containerName,
|
|
53
57
|
required: false
|
|
54
58
|
}, {
|
|
55
59
|
label: 'Ports (optional, format 8080:80,443:443):',
|
|
56
60
|
value: portInput,
|
|
61
|
+
cursor: cursors.portInput,
|
|
57
62
|
required: false
|
|
58
63
|
}, {
|
|
59
64
|
label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
|
|
60
65
|
value: envInput,
|
|
66
|
+
cursor: cursors.envInput,
|
|
61
67
|
required: false
|
|
62
68
|
}];
|
|
63
69
|
var _ref = prompts[step] || {},
|
|
64
70
|
label = _ref.label,
|
|
65
71
|
value = _ref.value,
|
|
72
|
+
cursor = _ref.cursor,
|
|
66
73
|
required = _ref.required;
|
|
67
74
|
var activeItems = hubResults !== null && hubResults !== void 0 ? hubResults : suggestions;
|
|
68
75
|
var showSuggestions = step === 0 && (isSearchingHub || activeItems.length > 0);
|
|
@@ -75,6 +82,7 @@ export default function ContainerCreationPrompt(props) {
|
|
|
75
82
|
}, /*#__PURE__*/React.createElement(PromptField, {
|
|
76
83
|
label: label,
|
|
77
84
|
value: value,
|
|
85
|
+
cursor: cursor,
|
|
78
86
|
required: required
|
|
79
87
|
}), showSuggestions && /*#__PURE__*/React.createElement(SuggestionPanel, {
|
|
80
88
|
items: activeItems,
|
|
@@ -97,6 +105,12 @@ ContainerCreationPrompt.propTypes = {
|
|
|
97
105
|
containerName: PropTypes.string,
|
|
98
106
|
portInput: PropTypes.string,
|
|
99
107
|
envInput: PropTypes.string,
|
|
108
|
+
cursors: PropTypes.shape({
|
|
109
|
+
imageName: PropTypes.number,
|
|
110
|
+
containerName: PropTypes.number,
|
|
111
|
+
portInput: PropTypes.number,
|
|
112
|
+
envInput: PropTypes.number
|
|
113
|
+
}),
|
|
100
114
|
message: PropTypes.string,
|
|
101
115
|
messageColor: PropTypes.string,
|
|
102
116
|
suggestions: PropTypes.arrayOf(PropTypes.string),
|
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { TextField } from './TextField.js';
|
|
2
3
|
import { Text } from 'ink';
|
|
3
4
|
import PropTypes from 'prop-types';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* PromptField shows a label and the current typed value for an input field.
|
|
7
|
-
*
|
|
8
|
+
* Wraps TextField with cursor rendering.
|
|
8
9
|
*
|
|
9
10
|
* @param {Object} props
|
|
10
11
|
* @param {string} props.label - Label shown above the input value
|
|
11
12
|
* @param {string} props.value - Current value of the input
|
|
13
|
+
* @param {number} [props.cursor] - Cursor position (0..value.length). When
|
|
14
|
+
* omitted the cursor defaults to the end of the value.
|
|
12
15
|
* @param {boolean} [props.required=false] - Whether the field is required
|
|
13
16
|
* @returns {JSX.Element}
|
|
14
17
|
*/
|
|
15
18
|
export function PromptField(_ref) {
|
|
16
19
|
var label = _ref.label,
|
|
17
20
|
value = _ref.value,
|
|
21
|
+
cursor = _ref.cursor,
|
|
18
22
|
required = _ref.required;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
var cursorPos = cursor !== undefined ? cursor : (value || '').length;
|
|
24
|
+
return /*#__PURE__*/React.createElement(TextField, {
|
|
25
|
+
label: label,
|
|
26
|
+
value: value || '',
|
|
27
|
+
cursor: cursorPos,
|
|
28
|
+
required: required
|
|
29
|
+
});
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
/**
|
|
@@ -42,6 +48,7 @@ export function PromptMessage(_ref2) {
|
|
|
42
48
|
PromptField.propTypes = {
|
|
43
49
|
label: PropTypes.string,
|
|
44
50
|
value: PropTypes.string,
|
|
51
|
+
cursor: PropTypes.number,
|
|
45
52
|
required: PropTypes.bool
|
|
46
53
|
};
|
|
47
54
|
PromptMessage.propTypes = {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TextField renders a single-line text input with a visible cursor.
|
|
7
|
+
* Purely presentational — does not capture keyboard input.
|
|
8
|
+
*
|
|
9
|
+
* The cursor inverts the character under it. When the cursor is at the end
|
|
10
|
+
* of the text, a block cursor (inverted space) is shown.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} props
|
|
13
|
+
* @param {string} props.label - Label shown above the field
|
|
14
|
+
* @param {string} props.value - Current text content
|
|
15
|
+
* @param {number} props.cursor - Cursor position (0..value.length)
|
|
16
|
+
* @param {boolean} [props.required=false] - Show in red if empty
|
|
17
|
+
* @param {boolean} [props.masked=false] - Reserved for TASK-5 (secrets)
|
|
18
|
+
* @param {string} [props.placeholder] - Dimmed text when empty
|
|
19
|
+
*/
|
|
20
|
+
export function TextField(_ref) {
|
|
21
|
+
var _chars$clampedCursor;
|
|
22
|
+
var label = _ref.label,
|
|
23
|
+
_ref$value = _ref.value,
|
|
24
|
+
value = _ref$value === void 0 ? '' : _ref$value,
|
|
25
|
+
_ref$cursor = _ref.cursor,
|
|
26
|
+
cursor = _ref$cursor === void 0 ? 0 : _ref$cursor,
|
|
27
|
+
_ref$required = _ref.required,
|
|
28
|
+
required = _ref$required === void 0 ? false : _ref$required,
|
|
29
|
+
_ref$masked = _ref.masked,
|
|
30
|
+
masked = _ref$masked === void 0 ? false : _ref$masked;
|
|
31
|
+
var isEmpty = required && !value.trim();
|
|
32
|
+
var displayValue = masked ? '*'.repeat(value.length) : value;
|
|
33
|
+
var chars = Array.from(displayValue);
|
|
34
|
+
var clampedCursor = Math.max(0, Math.min(chars.length, cursor));
|
|
35
|
+
var before = chars.slice(0, clampedCursor).join('');
|
|
36
|
+
var atCursor = (_chars$clampedCursor = chars[clampedCursor]) !== null && _chars$clampedCursor !== void 0 ? _chars$clampedCursor : '';
|
|
37
|
+
var after = chars.slice(clampedCursor + 1).join('');
|
|
38
|
+
var color = isEmpty ? 'red' : 'cyan';
|
|
39
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, label), /*#__PURE__*/React.createElement(Text, {
|
|
40
|
+
color: color
|
|
41
|
+
}, before, atCursor ? /*#__PURE__*/React.createElement(Text, {
|
|
42
|
+
inverse: true
|
|
43
|
+
}, atCursor) : /*#__PURE__*/React.createElement(Text, {
|
|
44
|
+
inverse: true
|
|
45
|
+
}, " "), after));
|
|
46
|
+
}
|
|
47
|
+
TextField.propTypes = {
|
|
48
|
+
label: PropTypes.string.isRequired,
|
|
49
|
+
value: PropTypes.string,
|
|
50
|
+
cursor: PropTypes.number,
|
|
51
|
+
required: PropTypes.bool,
|
|
52
|
+
masked: PropTypes.bool,
|
|
53
|
+
placeholder: PropTypes.string
|
|
54
|
+
};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
5
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
6
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
7
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
8
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
9
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
10
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
11
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
12
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} TextState
|
|
15
|
+
* @property {string} value - Current field content
|
|
16
|
+
* @property {number} cursor - Cursor position (0..value.length)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} EditResult
|
|
21
|
+
* @property {TextState} state - Resulting state (same object if no change)
|
|
22
|
+
* @property {boolean} handled - true if the key was consumed by the editor
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
var BREAK_CHARS = ' \t./\\,;:|(){}[]<>!@#$%^&*+=~`"\'-';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Splits a string into code points (handles emojis via Array.from).
|
|
29
|
+
* @param {string} s
|
|
30
|
+
* @returns {string[]}
|
|
31
|
+
*/
|
|
32
|
+
function chars(s) {
|
|
33
|
+
return Array.from(s);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns the index of the start of the word before cursor.
|
|
38
|
+
* A word boundary is any non-alphanumeric, non-underscore character.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} value
|
|
41
|
+
* @param {number} cursor
|
|
42
|
+
* @returns {number}
|
|
43
|
+
*/
|
|
44
|
+
export function wordStartBefore(value, cursor) {
|
|
45
|
+
var arr = chars(value);
|
|
46
|
+
if (cursor <= 0) return 0;
|
|
47
|
+
var i = cursor - 1;
|
|
48
|
+
// Skip trailing non-word chars
|
|
49
|
+
while (i > 0 && BREAK_CHARS.includes(arr[i])) i--;
|
|
50
|
+
// Skip word chars
|
|
51
|
+
while (i > 0 && !BREAK_CHARS.includes(arr[i - 1])) i--;
|
|
52
|
+
return i;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the index of the start of the word after cursor.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} value
|
|
59
|
+
* @param {number} cursor
|
|
60
|
+
* @returns {number}
|
|
61
|
+
*/
|
|
62
|
+
export function wordStartAfter(value, cursor) {
|
|
63
|
+
var arr = chars(value);
|
|
64
|
+
if (cursor >= arr.length) return arr.length;
|
|
65
|
+
var i = cursor;
|
|
66
|
+
// Skip current word chars
|
|
67
|
+
while (i < arr.length && !BREAK_CHARS.includes(arr[i])) i++;
|
|
68
|
+
// Skip non-word chars (whitespace, punctuation)
|
|
69
|
+
while (i < arr.length && BREAK_CHARS.includes(arr[i])) i++;
|
|
70
|
+
return i;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Inserts text at the cursor position and leaves the cursor after the insertion.
|
|
75
|
+
*
|
|
76
|
+
* @param {TextState} state
|
|
77
|
+
* @param {string} text
|
|
78
|
+
* @returns {TextState}
|
|
79
|
+
*/
|
|
80
|
+
export function insertAt(state, text) {
|
|
81
|
+
if (!text) return state;
|
|
82
|
+
var arr = chars(state.value);
|
|
83
|
+
var before = arr.slice(0, state.cursor);
|
|
84
|
+
var after = arr.slice(state.cursor);
|
|
85
|
+
var result = [].concat(_toConsumableArray(before), _toConsumableArray(chars(text)), _toConsumableArray(after)).join('');
|
|
86
|
+
return {
|
|
87
|
+
value: result,
|
|
88
|
+
cursor: state.cursor + chars(text).length
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Deletes the character before the cursor.
|
|
94
|
+
*
|
|
95
|
+
* @param {TextState} state
|
|
96
|
+
* @returns {TextState}
|
|
97
|
+
*/
|
|
98
|
+
export function deleteBackward(state) {
|
|
99
|
+
var arr = chars(state.value);
|
|
100
|
+
if (state.cursor <= 0) return state;
|
|
101
|
+
var before = arr.slice(0, state.cursor - 1);
|
|
102
|
+
var after = arr.slice(state.cursor);
|
|
103
|
+
return {
|
|
104
|
+
value: before.concat(after).join(''),
|
|
105
|
+
cursor: state.cursor - 1
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Deletes the character under the cursor.
|
|
111
|
+
*
|
|
112
|
+
* @param {TextState} state
|
|
113
|
+
* @returns {TextState}
|
|
114
|
+
*/
|
|
115
|
+
export function deleteForward(state) {
|
|
116
|
+
var arr = chars(state.value);
|
|
117
|
+
if (state.cursor >= arr.length) return state;
|
|
118
|
+
var before = arr.slice(0, state.cursor);
|
|
119
|
+
var after = arr.slice(state.cursor + 1);
|
|
120
|
+
return {
|
|
121
|
+
value: before.concat(after).join(''),
|
|
122
|
+
cursor: state.cursor
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Applies a keypress to a text state.
|
|
128
|
+
* Pure function: reads and writes nothing outside its arguments.
|
|
129
|
+
*
|
|
130
|
+
* @param {TextState} state
|
|
131
|
+
* @param {string} input - Raw Ink input (1 char, a paste, or '')
|
|
132
|
+
* @param {import('ink').Key} key - Ink special key object
|
|
133
|
+
* @returns {EditResult}
|
|
134
|
+
*/
|
|
135
|
+
export function applyKeyToText(state, input, key) {
|
|
136
|
+
// No key combinations from this function
|
|
137
|
+
if (key.ctrl) {
|
|
138
|
+
if (input === 'a') {
|
|
139
|
+
// Ctrl+A → move to start
|
|
140
|
+
return {
|
|
141
|
+
state: _objectSpread(_objectSpread({}, state), {}, {
|
|
142
|
+
cursor: 0
|
|
143
|
+
}),
|
|
144
|
+
handled: true
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (input === 'e') {
|
|
148
|
+
// Ctrl+E → move to end
|
|
149
|
+
return {
|
|
150
|
+
state: _objectSpread(_objectSpread({}, state), {}, {
|
|
151
|
+
cursor: chars(state.value).length
|
|
152
|
+
}),
|
|
153
|
+
handled: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (input === 'w') {
|
|
157
|
+
// Ctrl+W → delete word before cursor
|
|
158
|
+
var newCursor = wordStartBefore(state.value, state.cursor);
|
|
159
|
+
var arr = chars(state.value);
|
|
160
|
+
var before = arr.slice(0, newCursor);
|
|
161
|
+
var after = arr.slice(state.cursor);
|
|
162
|
+
return {
|
|
163
|
+
state: {
|
|
164
|
+
value: before.concat(after).join(''),
|
|
165
|
+
cursor: newCursor
|
|
166
|
+
},
|
|
167
|
+
handled: true
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (input === 'u') {
|
|
171
|
+
// Ctrl+U → delete from start to cursor
|
|
172
|
+
var _arr = chars(state.value);
|
|
173
|
+
var _after = _arr.slice(state.cursor);
|
|
174
|
+
return {
|
|
175
|
+
state: {
|
|
176
|
+
value: _after.join(''),
|
|
177
|
+
cursor: 0
|
|
178
|
+
},
|
|
179
|
+
handled: true
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (input === 'k') {
|
|
183
|
+
// Ctrl+K → delete from cursor to end
|
|
184
|
+
var _arr2 = chars(state.value);
|
|
185
|
+
var _before = _arr2.slice(0, state.cursor);
|
|
186
|
+
return {
|
|
187
|
+
state: {
|
|
188
|
+
value: _before.join(''),
|
|
189
|
+
cursor: state.cursor
|
|
190
|
+
},
|
|
191
|
+
handled: true
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
// Other ctrl combos: not handled
|
|
195
|
+
return {
|
|
196
|
+
state: state,
|
|
197
|
+
handled: false
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (key.meta) {
|
|
201
|
+
return {
|
|
202
|
+
state: state,
|
|
203
|
+
handled: false
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (key.leftArrow) {
|
|
207
|
+
var _newCursor = Math.max(0, state.cursor - 1);
|
|
208
|
+
return {
|
|
209
|
+
state: _objectSpread(_objectSpread({}, state), {}, {
|
|
210
|
+
cursor: _newCursor
|
|
211
|
+
}),
|
|
212
|
+
handled: true
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (key.rightArrow) {
|
|
216
|
+
var len = chars(state.value).length;
|
|
217
|
+
var _newCursor2 = Math.min(len, state.cursor + 1);
|
|
218
|
+
return {
|
|
219
|
+
state: _objectSpread(_objectSpread({}, state), {}, {
|
|
220
|
+
cursor: _newCursor2
|
|
221
|
+
}),
|
|
222
|
+
handled: true
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (key.home) {
|
|
226
|
+
return {
|
|
227
|
+
state: _objectSpread(_objectSpread({}, state), {}, {
|
|
228
|
+
cursor: 0
|
|
229
|
+
}),
|
|
230
|
+
handled: true
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (key.end) {
|
|
234
|
+
return {
|
|
235
|
+
state: _objectSpread(_objectSpread({}, state), {}, {
|
|
236
|
+
cursor: chars(state.value).length
|
|
237
|
+
}),
|
|
238
|
+
handled: true
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (key.backspace) {
|
|
242
|
+
var newState = deleteBackward(state);
|
|
243
|
+
return {
|
|
244
|
+
state: newState,
|
|
245
|
+
handled: true
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (key["delete"]) {
|
|
249
|
+
var _newState = deleteForward(state);
|
|
250
|
+
return {
|
|
251
|
+
state: _newState,
|
|
252
|
+
handled: true
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Safety net: \x7f (DEL) is what most terminals send for Backspace.
|
|
257
|
+
// Ink v6 maps it to key.delete, but if we receive the raw char directly
|
|
258
|
+
// (e.g. from tests or non-Ink callers), handle it as backspace.
|
|
259
|
+
if (input === '\x7f' || input === '\b') {
|
|
260
|
+
var _newState2 = deleteBackward(state);
|
|
261
|
+
return {
|
|
262
|
+
state: _newState2,
|
|
263
|
+
handled: true
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Regular text input (single char or paste)
|
|
268
|
+
if (input && input.length >= 1) {
|
|
269
|
+
var _newState3 = insertAt(state, input);
|
|
270
|
+
return {
|
|
271
|
+
state: _newState3,
|
|
272
|
+
handled: true
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
state: state,
|
|
277
|
+
handled: false
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Creates a TextState from a string, with cursor at the end.
|
|
283
|
+
*
|
|
284
|
+
* @param {string} value
|
|
285
|
+
* @returns {TextState}
|
|
286
|
+
*/
|
|
287
|
+
export function textStateOf() {
|
|
288
|
+
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
289
|
+
return {
|
|
290
|
+
value: value,
|
|
291
|
+
cursor: chars(value).length
|
|
292
|
+
};
|
|
293
|
+
}
|
|
@@ -19,6 +19,7 @@ import { validatePorts, validateEnvVars } from '../../helpers/validationHelpers.
|
|
|
19
19
|
import { IMAGE_PROFILES, resolveImageTag } from '../../helpers/constants.js';
|
|
20
20
|
import { safeCall } from '../../helpers/safeCall.js';
|
|
21
21
|
import { searchDockerHub, formatHubResult } from '../../helpers/dockerHubService.js';
|
|
22
|
+
import { applyKeyToText } from '../../helpers/textEditing.js';
|
|
22
23
|
var MAX_VISIBLE = 6;
|
|
23
24
|
var INITIAL_FORM = {
|
|
24
25
|
step: 0,
|
|
@@ -26,6 +27,12 @@ var INITIAL_FORM = {
|
|
|
26
27
|
containerName: '',
|
|
27
28
|
portInput: '',
|
|
28
29
|
envInput: '',
|
|
30
|
+
cursors: {
|
|
31
|
+
imageName: 0,
|
|
32
|
+
containerName: 0,
|
|
33
|
+
portInput: 0,
|
|
34
|
+
envInput: 0
|
|
35
|
+
},
|
|
29
36
|
message: '',
|
|
30
37
|
messageColor: 'yellow',
|
|
31
38
|
suggestions: [],
|
|
@@ -76,6 +83,7 @@ export function useContainerCreation(_ref) {
|
|
|
76
83
|
containerName = form.containerName,
|
|
77
84
|
portInput = form.portInput,
|
|
78
85
|
envInput = form.envInput,
|
|
86
|
+
cursors = form.cursors,
|
|
79
87
|
message = form.message,
|
|
80
88
|
messageColor = form.messageColor,
|
|
81
89
|
suggestions = form.suggestions,
|
|
@@ -183,6 +191,76 @@ export function useContainerCreation(_ref) {
|
|
|
183
191
|
}
|
|
184
192
|
};
|
|
185
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Sets a field's text value and cursor position together.
|
|
196
|
+
* @param {'imageName'|'containerName'|'portInput'|'envInput'} fieldName
|
|
197
|
+
* @param {string} value
|
|
198
|
+
* @param {number} cursor
|
|
199
|
+
*/
|
|
200
|
+
function setFieldText(fieldName, value, cursor) {
|
|
201
|
+
dispatch({
|
|
202
|
+
type: 'SET',
|
|
203
|
+
payload: _defineProperty(_defineProperty({}, fieldName, value), "cursors", _objectSpread(_objectSpread({}, form.cursors), {}, _defineProperty({}, fieldName, cursor)))
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Returns the field name associated with a wizard step.
|
|
209
|
+
* @param {number} step
|
|
210
|
+
* @returns {'imageName'|'containerName'|'portInput'|'envInput'|null}
|
|
211
|
+
*/
|
|
212
|
+
function fieldForStep(step) {
|
|
213
|
+
var _step;
|
|
214
|
+
return (_step = ['imageName', 'containerName', 'portInput', 'envInput'][step]) !== null && _step !== void 0 ? _step : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Applies a keypress to the field associated with the current step.
|
|
219
|
+
* When the edited field is imageName, recalculates suggestions.
|
|
220
|
+
* @param {string} input - Raw Ink input
|
|
221
|
+
* @param {import('ink').Key} key - Ink key object
|
|
222
|
+
* @returns {boolean} true if the key was consumed
|
|
223
|
+
*/
|
|
224
|
+
function handleFieldKey(input, key) {
|
|
225
|
+
var fieldName = fieldForStep(step);
|
|
226
|
+
if (!fieldName) return false;
|
|
227
|
+
var currentState = {
|
|
228
|
+
value: form[fieldName],
|
|
229
|
+
cursor: cursors[fieldName]
|
|
230
|
+
};
|
|
231
|
+
var _applyKeyToText = applyKeyToText(currentState, input, key),
|
|
232
|
+
newState = _applyKeyToText.state,
|
|
233
|
+
handled = _applyKeyToText.handled;
|
|
234
|
+
if (!handled) return false;
|
|
235
|
+
if (fieldName === 'imageName') {
|
|
236
|
+
var _activeHubRequestRef$;
|
|
237
|
+
// Recalculate suggestions when image name changes
|
|
238
|
+
(_activeHubRequestRef$ = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$ === void 0 || _activeHubRequestRef$.abort();
|
|
239
|
+
activeHubRequestRef.current.requestId += 1;
|
|
240
|
+
setIsSearchingHub(false);
|
|
241
|
+
setHubResults(null);
|
|
242
|
+
var lower = newState.value.toLowerCase();
|
|
243
|
+
var matches = newState.value.trim() ? Object.keys(imageProfiles).filter(function (key) {
|
|
244
|
+
return key.includes(lower);
|
|
245
|
+
}) : [];
|
|
246
|
+
dispatch({
|
|
247
|
+
type: 'SET',
|
|
248
|
+
payload: {
|
|
249
|
+
imageName: newState.value,
|
|
250
|
+
cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
|
|
251
|
+
imageName: newState.cursor
|
|
252
|
+
}),
|
|
253
|
+
selectedSuggestionIndex: -1,
|
|
254
|
+
visibleOffset: 0,
|
|
255
|
+
suggestions: matches
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
setFieldText(fieldName, newState.value, newState.cursor);
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
186
264
|
// Docker Hub search state (kept as useState because they are independent of form)
|
|
187
265
|
var _useState = useState(false),
|
|
188
266
|
_useState2 = _slicedToArray(_useState, 2),
|
|
@@ -249,15 +327,19 @@ export function useContainerCreation(_ref) {
|
|
|
249
327
|
|
|
250
328
|
/**
|
|
251
329
|
* Updates the image name input and recalculates autocomplete suggestions.
|
|
330
|
+
* Accepts either a plain string (backward compatible) or a TextState object.
|
|
252
331
|
* Resets selectedSuggestionIndex to -1 on every keystroke.
|
|
253
332
|
* Also aborts any in-flight Hub search and clears Hub results.
|
|
254
333
|
*
|
|
255
|
-
* @param {string}
|
|
334
|
+
* @param {string|TextState} input - New image name (string or {value, cursor})
|
|
256
335
|
*/
|
|
257
|
-
function updateImageInput(
|
|
258
|
-
var _activeHubRequestRef
|
|
336
|
+
function updateImageInput(input) {
|
|
337
|
+
var _input$cursor, _activeHubRequestRef$2;
|
|
338
|
+
var value = typeof input === 'string' ? input : input.value;
|
|
339
|
+
var cursor = typeof input === 'string' ? value.length : (_input$cursor = input.cursor) !== null && _input$cursor !== void 0 ? _input$cursor : value.length;
|
|
340
|
+
|
|
259
341
|
// Abort any in-flight Hub search and clear its state
|
|
260
|
-
(_activeHubRequestRef$ = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$ === void 0 || _activeHubRequestRef
|
|
342
|
+
(_activeHubRequestRef$2 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$2 === void 0 || _activeHubRequestRef$2.abort();
|
|
261
343
|
activeHubRequestRef.current.requestId += 1;
|
|
262
344
|
setIsSearchingHub(false);
|
|
263
345
|
setHubResults(null);
|
|
@@ -269,6 +351,9 @@ export function useContainerCreation(_ref) {
|
|
|
269
351
|
type: 'SET',
|
|
270
352
|
payload: {
|
|
271
353
|
imageName: value,
|
|
354
|
+
cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
|
|
355
|
+
imageName: cursor
|
|
356
|
+
}),
|
|
272
357
|
selectedSuggestionIndex: -1,
|
|
273
358
|
visibleOffset: 0,
|
|
274
359
|
suggestions: matches
|
|
@@ -293,7 +378,7 @@ export function useContainerCreation(_ref) {
|
|
|
293
378
|
*/
|
|
294
379
|
function _triggerHubSearch() {
|
|
295
380
|
_triggerHubSearch = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
296
|
-
var _activeHubRequestRef$
|
|
381
|
+
var _activeHubRequestRef$3;
|
|
297
382
|
var query, requestId, controller, results, _t;
|
|
298
383
|
return _regenerator().w(function (_context) {
|
|
299
384
|
while (1) switch (_context.p = _context.n) {
|
|
@@ -313,7 +398,7 @@ export function useContainerCreation(_ref) {
|
|
|
313
398
|
case 2:
|
|
314
399
|
// Abort previous request (should already be aborted from updateImageInput,
|
|
315
400
|
// but guard here too)
|
|
316
|
-
(_activeHubRequestRef$
|
|
401
|
+
(_activeHubRequestRef$3 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$3 === void 0 || _activeHubRequestRef$3.abort();
|
|
317
402
|
requestId = activeHubRequestRef.current.requestId + 1;
|
|
318
403
|
activeHubRequestRef.current.requestId = requestId;
|
|
319
404
|
controller = new AbortController();
|
|
@@ -386,10 +471,14 @@ export function useContainerCreation(_ref) {
|
|
|
386
471
|
function applyFocusedSuggestion() {
|
|
387
472
|
if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= suggestions.length) return;
|
|
388
473
|
var chosen = suggestions[selectedSuggestionIndex];
|
|
474
|
+
var resolved = resolveImageTag(chosen, imageProfiles);
|
|
389
475
|
dispatch({
|
|
390
476
|
type: 'SET',
|
|
391
477
|
payload: {
|
|
392
|
-
imageName:
|
|
478
|
+
imageName: resolved,
|
|
479
|
+
cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
|
|
480
|
+
imageName: resolved.length
|
|
481
|
+
}),
|
|
393
482
|
suggestions: [],
|
|
394
483
|
selectedSuggestionIndex: -1,
|
|
395
484
|
visibleOffset: 0
|
|
@@ -411,6 +500,9 @@ export function useContainerCreation(_ref) {
|
|
|
411
500
|
type: 'SET',
|
|
412
501
|
payload: {
|
|
413
502
|
imageName: resolved,
|
|
503
|
+
cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
|
|
504
|
+
imageName: resolved.length
|
|
505
|
+
}),
|
|
414
506
|
step: 1
|
|
415
507
|
}
|
|
416
508
|
});
|
|
@@ -519,7 +611,10 @@ export function useContainerCreation(_ref) {
|
|
|
519
611
|
dispatch({
|
|
520
612
|
type: 'SET',
|
|
521
613
|
payload: {
|
|
522
|
-
envInput: newEnvInput
|
|
614
|
+
envInput: newEnvInput,
|
|
615
|
+
cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
|
|
616
|
+
envInput: newEnvInput.length
|
|
617
|
+
})
|
|
523
618
|
}
|
|
524
619
|
});
|
|
525
620
|
}
|
|
@@ -540,6 +635,7 @@ export function useContainerCreation(_ref) {
|
|
|
540
635
|
setPortInput: setPortInput,
|
|
541
636
|
envInput: envInput,
|
|
542
637
|
setEnvInput: setEnvInput,
|
|
638
|
+
cursors: cursors,
|
|
543
639
|
message: message,
|
|
544
640
|
setMessage: setMessage,
|
|
545
641
|
messageColor: messageColor,
|
|
@@ -557,6 +653,8 @@ export function useContainerCreation(_ref) {
|
|
|
557
653
|
cancelCreation: cancelCreation,
|
|
558
654
|
resetCreation: resetCreation,
|
|
559
655
|
insertNextSuggestedEnv: insertNextSuggestedEnv,
|
|
560
|
-
hasSuggestedEnv: hasSuggestedEnv
|
|
656
|
+
hasSuggestedEnv: hasSuggestedEnv,
|
|
657
|
+
fieldForStep: fieldForStep,
|
|
658
|
+
handleFieldKey: handleFieldKey
|
|
561
659
|
};
|
|
562
660
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
5
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
6
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
1
7
|
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
2
8
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
9
|
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
@@ -197,14 +203,6 @@ export function useControls() {
|
|
|
197
203
|
*/
|
|
198
204
|
var processCreationInput = React.useCallback(function (input, key) {
|
|
199
205
|
var step = creation.step;
|
|
200
|
-
var removeLastChar = function removeLastChar(setter) {
|
|
201
|
-
return setter(function (value) {
|
|
202
|
-
return (value || '').slice(0, -1);
|
|
203
|
-
});
|
|
204
|
-
};
|
|
205
|
-
var appendChar = function appendChar(setter, current, ch) {
|
|
206
|
-
return setter((current || '') + ch);
|
|
207
|
-
};
|
|
208
206
|
if (key.escape) {
|
|
209
207
|
creation.cancelCreation();
|
|
210
208
|
setCreatingContainer(false);
|
|
@@ -243,24 +241,16 @@ export function useControls() {
|
|
|
243
241
|
return;
|
|
244
242
|
}
|
|
245
243
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
257
|
-
if (step === 0) {
|
|
258
|
-
creation.updateImageInput((creation.imageName || '') + input);
|
|
259
|
-
}
|
|
260
|
-
if (step === 1) appendChar(creation.setContainerName, creation.containerName, input);
|
|
261
|
-
if (step === 2) appendChar(creation.setPortInput, creation.portInput, input);
|
|
262
|
-
if (step === 3) appendChar(creation.setEnvInput, creation.envInput, input);
|
|
263
|
-
}
|
|
244
|
+
|
|
245
|
+
// Ink v6 maps \x7f (Backspace on most terminals) to key.delete instead
|
|
246
|
+
// of key.backspace. Normalize so text editing always receives backspace.
|
|
247
|
+
var normalizedKey = key["delete"] && !key.backspace ? _objectSpread(_objectSpread({}, key), {}, {
|
|
248
|
+
"delete": false,
|
|
249
|
+
backspace: true
|
|
250
|
+
}) : key;
|
|
251
|
+
|
|
252
|
+
// Delegate to the text editor for all field input
|
|
253
|
+
if (creation.handleFieldKey(input, normalizedKey)) return;
|
|
264
254
|
}, [creation, setCreatingContainer, triggerHubSearch, isSearchingHub, insertNextSuggestedEnv]);
|
|
265
255
|
|
|
266
256
|
// Single keyboard entry point
|