@contentful/experience-design-system-cli 2.7.1-dev-build-02830eb.0 → 2.7.2-dev-build-bc65c2b.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/dist/package.json +1 -1
- package/dist/src/analyze/select/tui/App.js +441 -54
- package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +4 -3
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +2 -2
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +10 -2
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +7 -2
- package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +4 -7
- package/dist/src/analyze/select/tui/components/JsonEditor.js +125 -17
- package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/QuitDialog.js +10 -2
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +2 -0
- package/dist/src/analyze/select/tui/components/StatusBar.d.ts +2 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +0 -5
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +4 -7
- package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +23 -0
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -1
- package/dist/src/analyze/select/tui/hooks/useUndo.js +21 -15
- package/dist/src/analyze/tui/AnalyzeView.js +0 -2
- package/dist/src/import/tui/WizardApp.js +0 -2
- package/dist/src/import/tui/steps/GenerateReviewStep.js +11 -4
- package/dist/src/print/validate/tui/ValidateView.js +0 -2
- package/package.json +2 -2
- package/dist/src/analyze/select/tui/hooks/useRawMode.d.ts +0 -6
- package/dist/src/analyze/select/tui/hooks/useRawMode.js +0 -16
- package/dist/src/analyze/select/tui/hooks/useSideEffects.d.ts +0 -17
- package/dist/src/analyze/select/tui/hooks/useSideEffects.js +0 -226
- package/dist/src/analyze/select/tui/inputToAction.d.ts +0 -13
- package/dist/src/analyze/select/tui/inputToAction.js +0 -68
- package/dist/src/analyze/select/tui/state.d.ts +0 -136
- package/dist/src/analyze/select/tui/state.js +0 -385
- package/dist/src/analyze/select/tui/utils.d.ts +0 -5
- package/dist/src/analyze/select/tui/utils.js +0 -11
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
export function HelpOverlay({ mode, onClose
|
|
3
|
+
import { useImmediateInput } from '../hooks/useImmediateInput.js';
|
|
4
|
+
export function HelpOverlay({ mode, onClose }) {
|
|
5
|
+
useImmediateInput((input, key) => {
|
|
6
|
+
if (input === '?' || key.escape) {
|
|
7
|
+
onClose();
|
|
8
|
+
}
|
|
9
|
+
});
|
|
5
10
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 46, children: [_jsx(Text, { bold: true, children: '─'.repeat(18) + ' Help ' + '─'.repeat(18) }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Navigation" }), _jsx(Text, { children: ' ↑ / k / PgUp Scroll up' }), _jsx(Text, { children: ' ↓ / j / PgDn Scroll down' }), _jsx(Text, { children: ' g / Home Jump to top' }), _jsx(Text, { children: ' G / End Jump to bottom' }), mode === 'review' && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "[Review mode only]" }), _jsx(Text, { children: ' Tab Toggle sidebar/detail' }), _jsx(Text, { children: ' a Accept component' }), _jsx(Text, { children: ' r Reject component' }), _jsx(Text, { children: ' e Edit proposal' }), _jsx(Text, { children: ' s Toggle source code' }), _jsx(Text, { children: ' A Approve all' }), _jsx(Text, { children: ' F Open finalize dialog' }), _jsx(Text, { children: ' Ctrl+S Save draft' }), _jsx(Text, { children: ' Ctrl+Z Undo' }), _jsx(Text, { children: ' Esc Exit edit / close' })] })), _jsx(Text, { children: " " }), _jsx(Text, { children: ' ? Close help' }), _jsx(Text, { children: ' q Quit' })] }));
|
|
6
11
|
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { EditorState } from '../state.js';
|
|
3
2
|
type JsonEditorProps = {
|
|
4
|
-
|
|
3
|
+
value: string;
|
|
5
4
|
width: number;
|
|
6
5
|
height: number;
|
|
6
|
+
onSave: (value: string) => void;
|
|
7
|
+
onDiscard: () => void;
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
-
* Pure render component — no input handling, no hooks.
|
|
10
|
-
* All state lives in the reducer; all keys are handled by inputToAction.
|
|
11
|
-
*/
|
|
12
|
-
export declare function JsonEditor({ editorState, width, height }: JsonEditorProps): React.ReactElement;
|
|
9
|
+
export declare function JsonEditor({ value, width, height, onSave, onDiscard }: JsonEditorProps): React.ReactElement;
|
|
13
10
|
export {};
|
|
@@ -1,23 +1,131 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { useUndo } from '../hooks/useUndo.js';
|
|
5
|
+
import { useImmediateInput } from '../hooks/useImmediateInput.js';
|
|
6
|
+
export function JsonEditor({ value, width, height, onSave, onDiscard }) {
|
|
7
|
+
const undo = useUndo({
|
|
8
|
+
lines: value.split('\n'),
|
|
9
|
+
cursorRow: 0,
|
|
10
|
+
cursorCol: 0,
|
|
11
|
+
});
|
|
12
|
+
const [scrollRow, setScrollRow] = useState(0);
|
|
13
|
+
const [validationError, setValidationError] = useState(null);
|
|
14
|
+
const { lines, cursorRow, cursorCol } = undo.current;
|
|
15
|
+
const scrollCol = 0;
|
|
16
|
+
// Sync scroll with cursor — pure derivation, no setState needed when within bounds
|
|
17
|
+
let effectiveScrollRow = scrollRow;
|
|
18
|
+
if (cursorRow < scrollRow)
|
|
19
|
+
effectiveScrollRow = cursorRow;
|
|
20
|
+
if (cursorRow >= scrollRow + height)
|
|
21
|
+
effectiveScrollRow = cursorRow - height + 1;
|
|
22
|
+
if (effectiveScrollRow !== scrollRow)
|
|
23
|
+
setScrollRow(effectiveScrollRow);
|
|
24
|
+
useImmediateInput((input, key) => {
|
|
25
|
+
const currentLines = undo.current.lines;
|
|
26
|
+
const currentRow = undo.current.cursorRow;
|
|
27
|
+
const currentCol = undo.current.cursorCol;
|
|
28
|
+
if (key.ctrl && input === 's') {
|
|
29
|
+
const text = currentLines.join('\n');
|
|
30
|
+
try {
|
|
31
|
+
JSON.parse(text);
|
|
32
|
+
setValidationError(null);
|
|
33
|
+
onSave(text);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
setValidationError(e instanceof Error ? e.message : String(e));
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (key.escape) {
|
|
41
|
+
onDiscard();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (key.ctrl && input === 'z') {
|
|
45
|
+
undo.undo();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let newLines = [...currentLines];
|
|
49
|
+
let newRow = currentRow;
|
|
50
|
+
let newCol = currentCol;
|
|
51
|
+
if (key.return) {
|
|
52
|
+
const before = newLines[currentRow].slice(0, currentCol);
|
|
53
|
+
const after = newLines[currentRow].slice(currentCol);
|
|
54
|
+
newLines = [...newLines.slice(0, currentRow), before, after, ...newLines.slice(currentRow + 1)];
|
|
55
|
+
newRow = currentRow + 1;
|
|
56
|
+
newCol = 0;
|
|
57
|
+
}
|
|
58
|
+
else if (key.backspace) {
|
|
59
|
+
if (currentCol > 0) {
|
|
60
|
+
newLines[currentRow] = newLines[currentRow].slice(0, currentCol - 1) + newLines[currentRow].slice(currentCol);
|
|
61
|
+
newCol = currentCol - 1;
|
|
62
|
+
}
|
|
63
|
+
else if (currentRow > 0) {
|
|
64
|
+
const prevLen = newLines[currentRow - 1].length;
|
|
65
|
+
newLines[currentRow - 1] = newLines[currentRow - 1] + newLines[currentRow];
|
|
66
|
+
newLines = [...newLines.slice(0, currentRow), ...newLines.slice(currentRow + 1)];
|
|
67
|
+
newRow = currentRow - 1;
|
|
68
|
+
newCol = prevLen;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (key.delete) {
|
|
72
|
+
if (currentCol < newLines[currentRow].length) {
|
|
73
|
+
newLines[currentRow] = newLines[currentRow].slice(0, currentCol) + newLines[currentRow].slice(currentCol + 1);
|
|
74
|
+
}
|
|
75
|
+
else if (currentRow < newLines.length - 1) {
|
|
76
|
+
newLines[currentRow] = newLines[currentRow] + newLines[currentRow + 1];
|
|
77
|
+
newLines = [...newLines.slice(0, currentRow + 1), ...newLines.slice(currentRow + 2)];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (key.leftArrow) {
|
|
81
|
+
if (currentCol > 0)
|
|
82
|
+
newCol = currentCol - 1;
|
|
83
|
+
else if (currentRow > 0) {
|
|
84
|
+
newRow = currentRow - 1;
|
|
85
|
+
newCol = newLines[currentRow - 1].length;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (key.rightArrow) {
|
|
89
|
+
if (currentCol < newLines[currentRow].length)
|
|
90
|
+
newCol = currentCol + 1;
|
|
91
|
+
else if (currentRow < newLines.length - 1) {
|
|
92
|
+
newRow = currentRow + 1;
|
|
93
|
+
newCol = 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (key.upArrow) {
|
|
97
|
+
if (currentRow > 0) {
|
|
98
|
+
newRow = currentRow - 1;
|
|
99
|
+
newCol = Math.min(currentCol, newLines[currentRow - 1].length);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (key.downArrow) {
|
|
103
|
+
if (currentRow < newLines.length - 1) {
|
|
104
|
+
newRow = currentRow + 1;
|
|
105
|
+
newCol = Math.min(currentCol, newLines[currentRow + 1].length);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
109
|
+
newLines[currentRow] =
|
|
110
|
+
newLines[currentRow].slice(0, currentCol) + input + newLines[currentRow].slice(currentCol);
|
|
111
|
+
newCol = currentCol + 1;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
undo.push({ lines: newLines, cursorRow: newRow, cursorCol: newCol });
|
|
117
|
+
});
|
|
10
118
|
const innerWidth = Math.max(1, width - 2);
|
|
11
|
-
const visibleLines = lines.slice(
|
|
12
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: 'EDIT
|
|
13
|
-
const actualRow = displayRow +
|
|
14
|
-
const displayLine = line.slice(
|
|
119
|
+
const visibleLines = lines.slice(effectiveScrollRow, effectiveScrollRow + height);
|
|
120
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: 'EDIT [EDITING — Ctrl+S save · Esc discard]' }), visibleLines.map((line, displayRow) => {
|
|
121
|
+
const actualRow = displayRow + effectiveScrollRow;
|
|
122
|
+
const displayLine = line.slice(scrollCol, scrollCol + innerWidth);
|
|
15
123
|
if (actualRow === cursorRow) {
|
|
16
|
-
const
|
|
17
|
-
const cursorChar = displayLine[cursorCol] ?? ' ';
|
|
18
|
-
const
|
|
19
|
-
return (_jsxs(Box, { children: [_jsx(Text, { children:
|
|
124
|
+
const beforeCursor = displayLine.slice(0, cursorCol - scrollCol);
|
|
125
|
+
const cursorChar = displayLine[cursorCol - scrollCol] ?? ' ';
|
|
126
|
+
const afterCursor = displayLine.slice(cursorCol - scrollCol + 1);
|
|
127
|
+
return (_jsxs(Box, { children: [_jsx(Text, { children: beforeCursor }), _jsx(Text, { inverse: true, children: cursorChar }), _jsx(Text, { children: afterCursor })] }, displayRow));
|
|
20
128
|
}
|
|
21
|
-
return _jsx(Text, { children: displayLine },
|
|
22
|
-
}), validationError && _jsx(Text, { color: "red", children: '✗ ' + validationError })] }));
|
|
129
|
+
return _jsx(Text, { children: displayLine }, displayRow);
|
|
130
|
+
}), validationError && _jsx(Text, { color: "red", children: '✗ Invalid JSON: ' + validationError })] }));
|
|
23
131
|
}
|
|
@@ -4,5 +4,5 @@ type QuitDialogProps = {
|
|
|
4
4
|
onConfirm: () => void;
|
|
5
5
|
onCancel: () => void;
|
|
6
6
|
};
|
|
7
|
-
export declare function QuitDialog({ hasUnsavedDrafts, onConfirm
|
|
7
|
+
export declare function QuitDialog({ hasUnsavedDrafts, onConfirm, onCancel }: QuitDialogProps): React.ReactElement;
|
|
8
8
|
export {};
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
export function QuitDialog({ hasUnsavedDrafts, onConfirm
|
|
3
|
+
import { useImmediateInput } from '../hooks/useImmediateInput.js';
|
|
4
|
+
export function QuitDialog({ hasUnsavedDrafts, onConfirm, onCancel }) {
|
|
5
|
+
useImmediateInput((input, key) => {
|
|
6
|
+
if (input === 'y' || key.return) {
|
|
7
|
+
onConfirm();
|
|
8
|
+
}
|
|
9
|
+
else if (input === 'n' || key.escape) {
|
|
10
|
+
onCancel();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
5
13
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 50, children: [_jsx(Text, { bold: true, children: '─'.repeat(19) + ' Quit ' + '─'.repeat(19) }), _jsx(Text, { children: " " }), hasUnsavedDrafts ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "You have unsaved draft edits." }), _jsx(Text, { children: 'Session state is preserved — you can resume' }), _jsx(Text, { children: 'by running the same review command again.' })] })) : (_jsx(Text, { children: "Session is saved. Quit without finalizing?" })), _jsx(Text, { children: " " }), _jsx(Text, { children: ' [y / Enter] Quit [n / Esc] Stay' })] }));
|
|
6
14
|
}
|
|
@@ -4,6 +4,8 @@ type StatusBarProps = {
|
|
|
4
4
|
rejected: number;
|
|
5
5
|
reviewed: number;
|
|
6
6
|
needsReview: number;
|
|
7
|
+
onApproveAll: () => void;
|
|
8
|
+
onFinalize: () => void;
|
|
7
9
|
};
|
|
8
10
|
export declare function StatusBar({ accepted, rejected, reviewed, needsReview }: StatusBarProps): React.ReactElement;
|
|
9
11
|
export {};
|
|
@@ -19,11 +19,6 @@ type InputHandler = (input: string, key: Key) => void;
|
|
|
19
19
|
* Like Ink's useInput, but uses useLayoutEffect so the listener is registered
|
|
20
20
|
* synchronously after render. This allows stdin.write() calls in tests to work
|
|
21
21
|
* immediately after render() without awaiting effects.
|
|
22
|
-
*
|
|
23
|
-
* Raw mode is NOT toggled here — it is managed once at the App level via
|
|
24
|
-
* useRawMode. Toggling setRawMode per-hook caused brief raw-mode drops during
|
|
25
|
-
* re-renders (e.g. when FieldEditor mounted alongside useKeymap), blanking the
|
|
26
|
-
* terminal.
|
|
27
22
|
*/
|
|
28
23
|
export declare function useImmediateInput(handler: InputHandler): void;
|
|
29
24
|
export {};
|
|
@@ -47,17 +47,13 @@ function parseInput(data) {
|
|
|
47
47
|
* Like Ink's useInput, but uses useLayoutEffect so the listener is registered
|
|
48
48
|
* synchronously after render. This allows stdin.write() calls in tests to work
|
|
49
49
|
* immediately after render() without awaiting effects.
|
|
50
|
-
*
|
|
51
|
-
* Raw mode is NOT toggled here — it is managed once at the App level via
|
|
52
|
-
* useRawMode. Toggling setRawMode per-hook caused brief raw-mode drops during
|
|
53
|
-
* re-renders (e.g. when FieldEditor mounted alongside useKeymap), blanking the
|
|
54
|
-
* terminal.
|
|
55
50
|
*/
|
|
56
51
|
export function useImmediateInput(handler) {
|
|
57
|
-
const { stdin } = useStdin();
|
|
52
|
+
const { stdin, setRawMode } = useStdin();
|
|
58
53
|
const handlerRef = useRef(handler);
|
|
59
54
|
handlerRef.current = handler;
|
|
60
55
|
useLayoutEffect(() => {
|
|
56
|
+
setRawMode(true);
|
|
61
57
|
const handleData = (data) => {
|
|
62
58
|
const str = Buffer.isBuffer(data) ? data.toString('utf8') : data;
|
|
63
59
|
const { input, key } = parseInput(str);
|
|
@@ -66,6 +62,7 @@ export function useImmediateInput(handler) {
|
|
|
66
62
|
stdin.on('data', handleData);
|
|
67
63
|
return () => {
|
|
68
64
|
stdin.off('data', handleData);
|
|
65
|
+
setRawMode(false);
|
|
69
66
|
};
|
|
70
|
-
}, []);
|
|
67
|
+
}, [stdin, setRawMode]);
|
|
71
68
|
}
|
|
@@ -1 +1,24 @@
|
|
|
1
|
+
type KeymapContext = {
|
|
2
|
+
sidebarFocused: boolean;
|
|
3
|
+
editMode: boolean;
|
|
4
|
+
dialogOpen: boolean;
|
|
5
|
+
disabled: boolean;
|
|
6
|
+
};
|
|
7
|
+
type KeymapHandlers = {
|
|
8
|
+
onSidebarUp: () => void;
|
|
9
|
+
onSidebarDown: () => void;
|
|
10
|
+
onSidebarSelect: () => void;
|
|
11
|
+
onAccept: () => void;
|
|
12
|
+
onReject: () => void;
|
|
13
|
+
onEnterEditMode: () => void;
|
|
14
|
+
onToggleSource: () => void;
|
|
15
|
+
onScrollUp: () => void;
|
|
16
|
+
onScrollDown: () => void;
|
|
17
|
+
onToggleFocus: () => void;
|
|
18
|
+
onApproveAll: () => void;
|
|
19
|
+
onFinalize: () => void;
|
|
20
|
+
onQuit: () => void;
|
|
21
|
+
onToggleHelp: () => void;
|
|
22
|
+
};
|
|
23
|
+
export declare function useKeymap(context: KeymapContext, handlers: KeymapHandlers): void;
|
|
1
24
|
export {};
|
|
@@ -1 +1,67 @@
|
|
|
1
|
-
|
|
1
|
+
import { useImmediateInput } from './useImmediateInput.js';
|
|
2
|
+
export function useKeymap(context, handlers) {
|
|
3
|
+
useImmediateInput((input, key) => {
|
|
4
|
+
if (context.disabled)
|
|
5
|
+
return;
|
|
6
|
+
if (context.dialogOpen)
|
|
7
|
+
return;
|
|
8
|
+
if (context.editMode)
|
|
9
|
+
return;
|
|
10
|
+
if (input === 'q') {
|
|
11
|
+
handlers.onQuit();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (input === '?') {
|
|
15
|
+
handlers.onToggleHelp();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (key.tab) {
|
|
19
|
+
handlers.onToggleFocus();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (input === 'A') {
|
|
23
|
+
handlers.onApproveAll();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (input === 'F') {
|
|
27
|
+
handlers.onFinalize();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// a/r/e/s work regardless of sidebar focus
|
|
31
|
+
if (input === 'a') {
|
|
32
|
+
handlers.onAccept();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (input === 'r') {
|
|
36
|
+
handlers.onReject();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (input === 'e') {
|
|
40
|
+
handlers.onEnterEditMode();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (input === 's') {
|
|
44
|
+
handlers.onToggleSource();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (context.sidebarFocused) {
|
|
48
|
+
if (key.upArrow || input === 'k') {
|
|
49
|
+
handlers.onSidebarUp();
|
|
50
|
+
}
|
|
51
|
+
else if (key.downArrow || input === 'j') {
|
|
52
|
+
handlers.onSidebarDown();
|
|
53
|
+
}
|
|
54
|
+
else if (key.return) {
|
|
55
|
+
handlers.onSidebarSelect();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (key.upArrow || input === 'k') {
|
|
60
|
+
handlers.onScrollUp();
|
|
61
|
+
}
|
|
62
|
+
else if (key.downArrow || input === 'j') {
|
|
63
|
+
handlers.onScrollDown();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
// Single-state implementation — push and undo are atomic single setState calls,
|
|
3
|
-
// eliminating the double-render that caused flash in the JsonEditor.
|
|
4
2
|
export function useUndo(initial, maxSize = 50) {
|
|
5
|
-
const [
|
|
3
|
+
const [stack, setStack] = useState([]);
|
|
4
|
+
const [current, setCurrent] = useState(initial);
|
|
6
5
|
return {
|
|
7
|
-
current
|
|
8
|
-
push: (next) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
current,
|
|
7
|
+
push: (next) => {
|
|
8
|
+
setStack((prev) => {
|
|
9
|
+
const newStack = [...prev, current];
|
|
10
|
+
return newStack.length > maxSize ? newStack.slice(1) : newStack;
|
|
11
|
+
});
|
|
12
|
+
setCurrent(next);
|
|
13
|
+
},
|
|
14
|
+
undo: () => {
|
|
15
|
+
setStack((prev) => {
|
|
16
|
+
if (prev.length === 0)
|
|
17
|
+
return prev;
|
|
18
|
+
const newStack = prev.slice(0, -1);
|
|
19
|
+
setCurrent(prev[prev.length - 1]);
|
|
20
|
+
return newStack;
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
canUndo: stack.length > 0,
|
|
24
|
+
clear: () => setStack([]),
|
|
19
25
|
};
|
|
20
26
|
}
|
|
@@ -3,14 +3,12 @@ import { useState } from 'react';
|
|
|
3
3
|
import { Box, Text, useStdout } from 'ink';
|
|
4
4
|
import { TopBar } from '../select/tui/components/TopBar.js';
|
|
5
5
|
import { useImmediateInput } from '../select/tui/hooks/useImmediateInput.js';
|
|
6
|
-
import { useRawMode } from '../select/tui/hooks/useRawMode.js';
|
|
7
6
|
function truncateName(name, maxLen = 30) {
|
|
8
7
|
if (name.length <= maxLen)
|
|
9
8
|
return name;
|
|
10
9
|
return name.slice(0, maxLen - 1) + '…';
|
|
11
10
|
}
|
|
12
11
|
export function AnalyzeView({ result, onExit }) {
|
|
13
|
-
useRawMode();
|
|
14
12
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
15
13
|
const { stdout } = useStdout();
|
|
16
14
|
// Header lines: TopBar(1) + summary(3) + blank(1) + dividers+header(3) + blank(1) + footer(1) + footer-bar(1) = ~12
|
|
@@ -8,7 +8,6 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { execFile, spawn } from 'node:child_process';
|
|
10
10
|
import { TopBar } from '../../analyze/select/tui/components/TopBar.js';
|
|
11
|
-
import { useRawMode } from '../../analyze/select/tui/hooks/useRawMode.js';
|
|
12
11
|
import { WelcomeStep } from './steps/WelcomeStep.js';
|
|
13
12
|
import { PathValidationStep } from './steps/PathValidationStep.js';
|
|
14
13
|
import { RunningStep } from './steps/RunningStep.js';
|
|
@@ -45,7 +44,6 @@ function logStep(entry) {
|
|
|
45
44
|
export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialProjectPath, host, } = {}) {
|
|
46
45
|
const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
|
|
47
46
|
const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
|
|
48
|
-
useRawMode();
|
|
49
47
|
const { stdout } = useStdout();
|
|
50
48
|
const terminalWidth = stdout?.columns ?? 80;
|
|
51
49
|
const logInit = useRef(false);
|
|
@@ -8,7 +8,6 @@ import { StatusBar } from '../../../analyze/select/tui/components/StatusBar.js';
|
|
|
8
8
|
import { FinalizeDialog } from '../../../analyze/select/tui/components/FinalizeDialog.js';
|
|
9
9
|
import { QuitDialog } from '../../../analyze/select/tui/components/QuitDialog.js';
|
|
10
10
|
import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
|
|
11
|
-
import { computeScrollOffset } from '../../../analyze/select/tui/utils.js';
|
|
12
11
|
import { openPipelineDb, loadCDFComponents, storeCDFComponents } from '../../../session/db.js';
|
|
13
12
|
const VISIBLE_COUNT = 20;
|
|
14
13
|
const PANEL_HEIGHT = 22;
|
|
@@ -162,7 +161,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
|
|
|
162
161
|
const newIdx = Math.min(components.length - 1, selectedIdx + 1);
|
|
163
162
|
setSelectedIdx(newIdx);
|
|
164
163
|
setJsonScrollOffset(0);
|
|
165
|
-
setSidebarScrollOffset((prev) =>
|
|
164
|
+
setSidebarScrollOffset((prev) => (newIdx >= prev + VISIBLE_COUNT ? newIdx - VISIBLE_COUNT + 1 : prev));
|
|
166
165
|
}
|
|
167
166
|
}
|
|
168
167
|
else {
|
|
@@ -197,7 +196,15 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
|
|
|
197
196
|
const needsReview = components.filter((c) => c.status === 'needs-review').length;
|
|
198
197
|
const propCount = selected ? Object.keys(selected.entry.$properties).length : 0;
|
|
199
198
|
const slotCount = selected?.entry.$slots ? Object.keys(selected.entry.$slots).length : 0;
|
|
200
|
-
return (_jsxs(Box, { flexDirection: "column", children: [showFinalize && (_jsx(FinalizeDialog, { accepted: accepted, rejected: rejected, needsReview: needsReview, onConfirm: handleFinalizeConfirm, onCancel: () => setShowFinalize(false) })), showQuit && _jsx(QuitDialog, { hasUnsavedDrafts: false, onConfirm: onQuit, onCancel: () => setShowQuit(false) }), !dialogOpen && (_jsxs(Box, { children: [_jsx(Sidebar, { components: sidebarItems, selectedId: selected?.key ?? null, focused: sidebarFocused, scrollOffset: sidebarScrollOffset, visibleCount: VISIBLE_COUNT,
|
|
199
|
+
return (_jsxs(Box, { flexDirection: "column", children: [showFinalize && (_jsx(FinalizeDialog, { accepted: accepted, rejected: rejected, needsReview: needsReview, onConfirm: handleFinalizeConfirm, onCancel: () => setShowFinalize(false) })), showQuit && _jsx(QuitDialog, { hasUnsavedDrafts: false, onConfirm: onQuit, onCancel: () => setShowQuit(false) }), !dialogOpen && (_jsxs(Box, { children: [_jsx(Sidebar, { components: sidebarItems, selectedId: selected?.key ?? null, focused: sidebarFocused, scrollOffset: sidebarScrollOffset, visibleCount: VISIBLE_COUNT, onSelect: (id) => {
|
|
200
|
+
const idx = components.findIndex((c) => c.key === id);
|
|
201
|
+
if (idx >= 0) {
|
|
202
|
+
setSelectedIdx(idx);
|
|
203
|
+
setJsonScrollOffset(0);
|
|
204
|
+
}
|
|
205
|
+
}, onScrollChange: setSidebarScrollOffset, width: sidebarWidth }), _jsx(Box, { flexGrow: 1, paddingLeft: 1, flexDirection: "column", children: selected ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: selected.key }), _jsx(Box, { flexGrow: 1 }), _jsxs(Text, { dimColor: true, children: [propCount, " prop", propCount !== 1 ? 's' : '', slotCount > 0 ? ` · ${slotCount} slot${slotCount !== 1 ? 's' : ''}` : '', ' ', sidebarFocused ? '[Tab] focus panel' : '[Tab] focus list'] })] }), editMode ? (_jsx(FieldEditor, { value: draftValue || selectedJson, width: panelWidth, height: PANEL_HEIGHT, onChange: setDraftValue, onSave: handleEditSave, onDiscard: handleEditDiscard })) : (_jsx(JsonPanel, { label: "GENERATED DEFINITION", value: selectedJson, scrollOffset: jsonScrollOffset, width: panelWidth, height: PANEL_HEIGHT, active: !sidebarFocused })), saveError && _jsx(Text, { color: "red", children: '✗ ' + saveError }), _jsx(Text, { dimColor: true, children: editMode
|
|
201
206
|
? ' [Ctrl+S] save [Esc] discard'
|
|
202
|
-
: ' [a] accept [r] reject [e] edit [A] accept all [F] finalize [Tab] toggle focus [q] quit' })] })) : (_jsx(Text, { dimColor: true, children: "No component selected" })) })] })), !dialogOpen && _jsx(StatusBar, { accepted: accepted, rejected: rejected, reviewed: 0, needsReview: needsReview
|
|
207
|
+
: ' [a] accept [r] reject [e] edit [A] accept all [F] finalize [Tab] toggle focus [q] quit' })] })) : (_jsx(Text, { dimColor: true, children: "No component selected" })) })] })), !dialogOpen && (_jsx(StatusBar, { accepted: accepted, rejected: rejected, reviewed: 0, needsReview: needsReview, onApproveAll: () => {
|
|
208
|
+
setComponents((prev) => prev.map((c) => (c.status === 'needs-review' ? { ...c, status: 'accepted' } : c)));
|
|
209
|
+
}, onFinalize: () => setShowFinalize(true) }))] }));
|
|
203
210
|
}
|
|
@@ -3,9 +3,7 @@ import { useState } from 'react';
|
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { TopBar } from '../../../analyze/select/tui/components/TopBar.js';
|
|
5
5
|
import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
|
|
6
|
-
import { useRawMode } from '../../../analyze/select/tui/hooks/useRawMode.js';
|
|
7
6
|
export function ValidateView({ results, onExit }) {
|
|
8
|
-
useRawMode();
|
|
9
7
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
10
8
|
const allValid = results.every((r) => r.valid);
|
|
11
9
|
useImmediateInput((input, key) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2-dev-build-bc65c2b.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"react-dom": "^18.3.1",
|
|
37
37
|
"ts-morph": "^27.0.2",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
|
-
"@contentful/experience-design-system-types": "2.7.
|
|
39
|
+
"@contentful/experience-design-system-types": "2.7.2-dev-build-bc65c2b.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@tsconfig/node24": "^24.0.3",
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { useLayoutEffect } from 'react';
|
|
2
|
-
import { useStdin } from 'ink';
|
|
3
|
-
/**
|
|
4
|
-
* Enables raw terminal mode for the lifetime of the component.
|
|
5
|
-
* Call once at the root of each top-level TUI component.
|
|
6
|
-
* useImmediateInput does NOT manage raw mode — this hook does.
|
|
7
|
-
*/
|
|
8
|
-
export function useRawMode() {
|
|
9
|
-
const { setRawMode } = useStdin();
|
|
10
|
-
useLayoutEffect(() => {
|
|
11
|
-
setRawMode(true);
|
|
12
|
-
return () => {
|
|
13
|
-
setRawMode(false);
|
|
14
|
-
};
|
|
15
|
-
}, [setRawMode]);
|
|
16
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { AppState, AppAction } from '../state.js';
|
|
2
|
-
import type { ReviewSessionSnapshot } from '../../types.js';
|
|
3
|
-
type Services = {
|
|
4
|
-
sessionId: string;
|
|
5
|
-
saveState: (session: ReviewSessionSnapshot) => Promise<void>;
|
|
6
|
-
appendEvent: (event: {
|
|
7
|
-
type: string;
|
|
8
|
-
payload: Record<string, unknown>;
|
|
9
|
-
}) => Promise<void>;
|
|
10
|
-
};
|
|
11
|
-
type Dispatch = (action: AppAction) => void;
|
|
12
|
-
/**
|
|
13
|
-
* All async side effects in one place.
|
|
14
|
-
* Detects transitions by comparing state to prevState — no pending* counter signals needed.
|
|
15
|
-
*/
|
|
16
|
-
export declare function useSideEffects(state: AppState, dispatch: Dispatch, services: Services): void;
|
|
17
|
-
export {};
|