@gomjellie/lazyapi 0.0.2 → 0.0.3

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/App.js CHANGED
@@ -41,7 +41,7 @@ function AppContent({ filePath }) {
41
41
  // 로딩 상태
42
42
  if (loading) {
43
43
  return (React.createElement(Box, { flexDirection: "column", padding: 1, height: terminalHeight },
44
- React.createElement(LoadingSpinner, { message: "OpenAPI \uBB38\uC11C\uB97C \uB85C\uB529\uD558\uB294 \uC911..." })));
44
+ React.createElement(LoadingSpinner, { message: "Fetching OpenAPI document from URL..." })));
45
45
  }
46
46
  // 에러 상태
47
47
  if (error) {
@@ -4,12 +4,16 @@
4
4
  import React from 'react';
5
5
  import { Box, Text } from 'ink';
6
6
  import Spinner from 'ink-spinner';
7
+ import BigText from 'ink-big-text';
8
+ import Gradient from 'ink-gradient';
7
9
  export default function LoadingSpinner({ message = '로딩 중...' }) {
8
- return (React.createElement(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: 10 },
9
- React.createElement(Box, { marginBottom: 1 },
10
- React.createElement(Text, { color: "green" },
10
+ return (React.createElement(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1 },
11
+ React.createElement(Gradient, { name: "rainbow" },
12
+ React.createElement(BigText, { text: "LAZYAPI" })),
13
+ React.createElement(Box, { marginTop: 1 },
14
+ React.createElement(Text, { color: "cyan" },
11
15
  React.createElement(Spinner, { type: "dots" })),
12
- React.createElement(Text, { color: "green", bold: true },
16
+ React.createElement(Text, { italic: true, color: "gray" },
13
17
  ' ',
14
18
  message))));
15
19
  }
@@ -3,7 +3,9 @@
3
3
  */
4
4
  import React from 'react';
5
5
  import { Box, Text } from 'ink';
6
+ import { useAppSelector } from '../../store/hooks.js';
6
7
  export default function StatusBar({ title, version, currentPath, mode, status }) {
8
+ const navigationCount = useAppSelector((state) => state.app.navigationCount);
7
9
  const getModeColor = () => {
8
10
  switch (mode) {
9
11
  case 'INSERT':
@@ -29,6 +31,7 @@ export default function StatusBar({ title, version, currentPath, mode, status })
29
31
  status && React.createElement(Text, { color: "green" },
30
32
  "\u25CF ",
31
33
  status),
34
+ navigationCount !== null && (React.createElement(Text, { color: "yellow", bold: true }, navigationCount)),
32
35
  React.createElement(Text, { bold: true, color: getModeColor() },
33
36
  "[",
34
37
  mode,
@@ -4,7 +4,8 @@
4
4
  import React from 'react';
5
5
  import { Box, Text, useStdout } from 'ink';
6
6
  import { useAppSelector, useAppDispatch } from '../../store/hooks.js';
7
- import { setMode, detailSetFocus, detailSetPanel, detailSetRequestTab, detailSetResponseTab, detailSetResponse, detailSetParam, } from '../../store/appSlice.js';
7
+ import { setMode, detailSetFocus, detailSetPanel, detailSetRequestTab, detailSetResponseTab, detailSetResponse, detailSetParam, detailSetBody, } from '../../store/appSlice.js';
8
+ import { navigateUp, navigateDown, navigateLeft, navigateRight, } from '../../store/navigationActions.js';
8
9
  import { useNavigation } from '../../hooks/useNavigation.js';
9
10
  import { useKeyPress } from '../../hooks/useKeyPress.js';
10
11
  import { useApiClient } from '../../hooks/useApiClient.js';
@@ -38,7 +39,9 @@ export default function DetailView() {
38
39
  queryParams.length +
39
40
  headerParams.length +
40
41
  bodyParams.length +
41
- formDataParams.length;
42
+ formDataParams.length +
43
+ (endpoint?.requestBody && bodyParams.length === 0 ? 1 : 0) +
44
+ (endpoint?.responses?.length || 0);
42
45
  const handleExecuteRequest = async () => {
43
46
  if (!detail || !document)
44
47
  return;
@@ -60,8 +63,8 @@ export default function DetailView() {
60
63
  if (cmd === 'q' || cmd === 'quit' || cmd === 'exit') {
61
64
  process.exit(0);
62
65
  }
63
- // 2. 접두사 있는 명령어 처리 (pp1, qp1, h1, rb1, rs1, rq1...)
64
- const match = cmd.match(/^(pp|qp|h|rb|rs|rq)(\d+)$/);
66
+ // 2. 접두사 있는 명령어 처리 (pp1, qp1, h1, rqb1, rs1, rq1, rb1...)
67
+ const match = cmd.match(/^(pp|qp|h|rqb|rs|rq|rb)(\d+)$/);
65
68
  if (match) {
66
69
  const type = match[1];
67
70
  const index = parseInt(match[2], 10);
@@ -97,10 +100,9 @@ export default function DetailView() {
97
100
  dispatch(detailSetPanel('request'));
98
101
  }
99
102
  }
100
- else if (type === 'rb') {
101
- // Request Body
103
+ else if (type === 'rqb') {
104
+ // Request Body (기존 rb -> rqb)
102
105
  const bodyStartIndex = pathParams.length + queryParams.length + headerParams.length;
103
- // 만약 bodyParams가 있으면 그쪽으로, 아니면 requestBody(Raw)로
104
106
  if (bodyParams.length > 0) {
105
107
  if (targetIndex >= 0 && targetIndex < bodyParams.length) {
106
108
  dispatch(detailSetFocus(bodyStartIndex + targetIndex));
@@ -108,14 +110,26 @@ export default function DetailView() {
108
110
  }
109
111
  }
110
112
  else if (endpoint?.requestBody) {
111
- // Form Data가 있으면 그 뒤
112
113
  const rawBodyIndex = bodyStartIndex + formDataParams.length;
113
- if (targetIndex === 0) { // rb1 only for raw body
114
+ if (targetIndex === 0) {
114
115
  dispatch(detailSetFocus(rawBodyIndex));
115
116
  dispatch(detailSetPanel('left'));
116
117
  }
117
118
  }
118
119
  }
120
+ else if (type === 'rb') {
121
+ // Responses (사용자 요청: rb)
122
+ const responsesStartIndex = pathParams.length +
123
+ queryParams.length +
124
+ headerParams.length +
125
+ bodyParams.length +
126
+ formDataParams.length +
127
+ (endpoint?.requestBody && bodyParams.length === 0 ? 1 : 0);
128
+ if (targetIndex >= 0 && targetIndex < (endpoint?.responses?.length || 0)) {
129
+ dispatch(detailSetFocus(responsesStartIndex + targetIndex));
130
+ dispatch(detailSetPanel('left'));
131
+ }
132
+ }
119
133
  else if (type === 'rs') {
120
134
  // Response Tabs
121
135
  const tabs = [
@@ -166,28 +180,16 @@ export default function DetailView() {
166
180
  // 키보드 입력 처리 (NORMAL 모드)
167
181
  useKeyPress({
168
182
  j: () => {
169
- if (activePanel === 'left' && totalFields > 0) {
170
- // 다음 필드로
171
- const nextIndex = Math.min(detail.focusedFieldIndex + 1, totalFields - 1);
172
- dispatch(detailSetFocus(nextIndex));
173
- }
183
+ dispatch(navigateDown());
174
184
  },
175
185
  k: () => {
176
- if (activePanel === 'left' && totalFields > 0) {
177
- // 이전 필드로
178
- const prevIndex = Math.max(detail.focusedFieldIndex - 1, 0);
179
- dispatch(detailSetFocus(prevIndex));
180
- }
186
+ dispatch(navigateUp());
181
187
  },
182
188
  h: () => {
183
- // 왼쪽 패널로 (어디서든)
184
- dispatch(detailSetPanel('left'));
189
+ dispatch(navigateLeft());
185
190
  },
186
191
  l: () => {
187
- // 오른쪽(Request) 패널로
188
- if (activePanel === 'left') {
189
- dispatch(detailSetPanel('request'));
190
- }
192
+ dispatch(navigateRight());
191
193
  },
192
194
  onTab: () => {
193
195
  // 패널 순환: Left -> Request -> Response -> Left
@@ -201,12 +203,6 @@ export default function DetailView() {
201
203
  dispatch(detailSetPanel('left'));
202
204
  }
203
205
  },
204
- i: () => {
205
- // INSERT 모드로 전환 (왼쪽 패널에서만)
206
- if (activePanel === 'left') {
207
- dispatch(setMode('INSERT'));
208
- }
209
- },
210
206
  u: () => {
211
207
  // 뒤로 가기
212
208
  goBack();
@@ -224,7 +220,7 @@ export default function DetailView() {
224
220
  onEscape: () => {
225
221
  dispatch(setMode('NORMAL'));
226
222
  dispatch(setCommandInput(''));
227
- }
223
+ },
228
224
  }, mode === 'COMMAND');
229
225
  // INSERT 모드 키보드 입력 처리
230
226
  useKeyPress({
@@ -249,18 +245,22 @@ export default function DetailView() {
249
245
  ];
250
246
  const baseURL = document ? getServerUrl(document) || '' : '';
251
247
  const terminalHeight = stdout?.rows || 24;
252
- // detail 또는 endpoint가 없는 경우 에러 표시
253
- if (!detail || !endpoint) {
248
+ // detail 또는 endpoint 또는 document가 없는 경우 에러 표시
249
+ if (!detail || !endpoint || !document) {
254
250
  return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
255
251
  React.createElement(StatusBar, { title: "API DETAIL", currentPath: "", mode: mode, status: "ERROR" }),
256
252
  React.createElement(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center" },
257
- React.createElement(Text, { color: "red" }, "\uC0C1\uC138 \uC815\uBCF4\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.")),
253
+ React.createElement(Text, { color: "red" }, "\uC0C1\uC138 \uC815\uBCF4\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uBB38\uC11C \uB610\uB294 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uC815\uBCF4 \uC5C6\uC74C)")),
258
254
  React.createElement(KeybindingFooter, { bindings: [], mode: mode })));
259
255
  }
260
256
  return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
261
257
  React.createElement(StatusBar, { title: "API DETAIL", currentPath: endpoint.path, mode: mode, status: loading ? 'LOADING...' : 'READY' }),
262
- React.createElement(Box, { flexGrow: 1, overflow: "hidden" },
263
- React.createElement(LeftPanel, { endpoint: endpoint, parameterValues: detail.parameterValues, focusedFieldIndex: detail.focusedFieldIndex, mode: mode, isFocused: activePanel === 'left', document: document, commandMode: mode === 'COMMAND', onParameterChange: (key, value) => dispatch(detailSetParam({ key, value })), onExitInsertMode: () => dispatch(setMode('NORMAL')) }),
264
- React.createElement(RightPanel, { activeRequestTab: activeRequestTab, activeResponseTab: activeResponseTab, activePanel: activePanel, response: detail.response, endpoint: endpoint, parameterValues: detail.parameterValues, requestBody: detail.requestBody, requestHeaders: detail.headers, baseURL: baseURL, mode: mode, isLoading: loading, commandMode: mode === 'COMMAND' })),
258
+ React.createElement(Box, { flexGrow: 1 },
259
+ React.createElement(LeftPanel, { endpoint: endpoint, parameterValues: detail.parameterValues, requestBodyValue: detail.requestBody, focusedFieldIndex: detail.focusedFieldIndex, mode: mode, isFocused: activePanel === 'left', document: document, commandMode: mode === 'COMMAND', onParameterChange: (key, value) => dispatch(detailSetParam({ key, value })), onExitInsertMode: () => dispatch(setMode('NORMAL')), onRequestBodyEdit: (value) => {
260
+ dispatch(detailSetBody(value));
261
+ dispatch(detailSetPanel('request'));
262
+ dispatch(detailSetRequestTab('body'));
263
+ }, onSetMode: (m) => dispatch(setMode(m)) }),
264
+ React.createElement(RightPanel, { activeRequestTab: activeRequestTab, activeResponseTab: activeResponseTab, activePanel: activePanel, response: detail.response, endpoint: endpoint, document: document, parameterValues: detail.parameterValues, requestBody: detail.requestBody, requestHeaders: detail.headers, baseURL: baseURL, mode: mode, isLoading: loading, commandMode: mode === 'COMMAND' })),
265
265
  React.createElement(KeybindingFooter, { bindings: keyBindings, mode: mode, commandInput: commandInput, onCommandChange: (value) => dispatch(setCommandInput(value)), onCommandSubmit: handleCommandSubmit })));
266
266
  }
@@ -7,6 +7,7 @@ import { AppMode } from '../../types/app-state.js';
7
7
  interface LeftPanelProps {
8
8
  endpoint: Endpoint;
9
9
  parameterValues: Record<string, string>;
10
+ requestBodyValue?: string;
10
11
  focusedFieldIndex: number;
11
12
  mode: AppMode;
12
13
  isFocused: boolean;
@@ -14,6 +15,8 @@ interface LeftPanelProps {
14
15
  commandMode?: boolean;
15
16
  onParameterChange: (key: string, value: string) => void;
16
17
  onExitInsertMode?: () => void;
18
+ onRequestBodyEdit?: (value: string) => void;
19
+ onSetMode?: (mode: AppMode) => void;
17
20
  }
18
- export default function LeftPanel({ endpoint, parameterValues, focusedFieldIndex, mode, isFocused, document, commandMode, onParameterChange, onExitInsertMode, }: LeftPanelProps): React.JSX.Element;
21
+ export default function LeftPanel({ endpoint, parameterValues, requestBodyValue, focusedFieldIndex, mode, isFocused, document, commandMode, onParameterChange, onExitInsertMode, onRequestBodyEdit, onSetMode, }: LeftPanelProps): React.JSX.Element;
19
22
  export {};