@gomjellie/lazyapi 0.0.2 → 0.0.4
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.md +6 -7
- package/dist/App.js +1 -1
- package/dist/components/common/LoadingSpinner.js +8 -4
- package/dist/components/common/StatusBar.js +3 -0
- package/dist/components/detail-view/DetailView.js +38 -38
- package/dist/components/detail-view/LeftPanel.d.ts +4 -1
- package/dist/components/detail-view/LeftPanel.js +402 -72
- package/dist/components/detail-view/RightPanel.d.ts +4 -2
- package/dist/components/detail-view/RightPanel.js +142 -43
- package/dist/components/list-view/EndpointList.js +16 -11
- package/dist/components/list-view/ListView.js +41 -59
- package/dist/components/list-view/MethodsBar.d.ts +14 -0
- package/dist/components/list-view/MethodsBar.js +36 -0
- package/dist/hooks/useKeyPress.js +66 -23
- package/dist/services/openapi-parser.d.ts +10 -2
- package/dist/services/openapi-parser.js +104 -10
- package/dist/store/appSlice.d.ts +3 -3
- package/dist/store/appSlice.js +49 -5
- package/dist/store/hooks.d.ts +1 -1
- package/dist/store/index.d.ts +3 -3
- package/dist/store/navigationActions.d.ts +9 -0
- package/dist/store/navigationActions.js +227 -0
- package/dist/types/app-state.d.ts +17 -4
- package/dist/types/openapi.d.ts +18 -1
- package/dist/utils/schema-formatter.js +19 -5
- package/dist/utils/schema-scaffolder.d.ts +7 -0
- package/dist/utils/schema-scaffolder.js +61 -0
- package/package.json +5 -1
- package/dist/components/list-view/FilterBar.d.ts +0 -12
- package/dist/components/list-view/FilterBar.js +0 -33
|
@@ -2,56 +2,99 @@
|
|
|
2
2
|
* 키보드 입력 처리 Hook
|
|
3
3
|
*/
|
|
4
4
|
import { useInput } from 'ink';
|
|
5
|
-
import { useCallback } from 'react';
|
|
5
|
+
import { useCallback, useRef, useEffect } from 'react';
|
|
6
|
+
import { useAppDispatch, useAppSelector } from '../store/hooks.js';
|
|
7
|
+
import { setNavigationCount } from '../store/appSlice.js';
|
|
6
8
|
/**
|
|
7
9
|
* 키보드 입력을 처리하는 Hook
|
|
8
10
|
*/
|
|
9
11
|
export function useKeyPress(bindings, isEnabled = true) {
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
const navigationCount = useAppSelector((state) => state.app.navigationCount);
|
|
14
|
+
const mode = useAppSelector((state) => state.app.mode);
|
|
15
|
+
// useInput 핸들러가 최신 상태를 참조할 수 있도록 useRef 사용
|
|
16
|
+
const stateRef = useRef({ navigationCount, mode, bindings, isEnabled });
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
stateRef.current = { navigationCount, mode, bindings, isEnabled };
|
|
19
|
+
}, [navigationCount, mode, bindings, isEnabled]);
|
|
10
20
|
const handleInput = useCallback((input, key) => {
|
|
11
|
-
|
|
21
|
+
const { navigationCount: currentCount, mode: currentMode, bindings: currentBindings, isEnabled: currentEnabled } = stateRef.current;
|
|
22
|
+
if (!currentEnabled)
|
|
12
23
|
return;
|
|
24
|
+
// 숫자 접두사 처리 (NORMAL 모드에서만)
|
|
25
|
+
if (currentMode === 'NORMAL' && input && /^[0-9]$/.test(input)) {
|
|
26
|
+
// 첫 숫자가 0인 경우는 접두사로 쓰이지 않음 (Vim 스타일)
|
|
27
|
+
if (input === '0' && currentCount === null) {
|
|
28
|
+
// 0은 바인딩된 기능이 있으면 실행 (보통 줄 맨 앞으로 이동)
|
|
29
|
+
if (currentBindings['0']) {
|
|
30
|
+
currentBindings['0'](input, key);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const digit = parseInt(input, 10);
|
|
35
|
+
if (!isNaN(digit)) {
|
|
36
|
+
const newCount = currentCount === null ? digit : currentCount * 10 + digit;
|
|
37
|
+
dispatch(setNavigationCount(newCount));
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const executeBinding = (handler, inputStr, keyObj) => {
|
|
42
|
+
if (!handler)
|
|
43
|
+
return;
|
|
44
|
+
const count = currentCount || 1;
|
|
45
|
+
for (let i = 0; i < count; i++) {
|
|
46
|
+
handler(inputStr, keyObj);
|
|
47
|
+
}
|
|
48
|
+
if (currentCount !== null) {
|
|
49
|
+
dispatch(setNavigationCount(null));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
13
52
|
// 특수 키 처리
|
|
14
53
|
if (key.tab) {
|
|
15
|
-
if (key.shift &&
|
|
16
|
-
|
|
54
|
+
if (key.shift && currentBindings.onShiftTab) {
|
|
55
|
+
executeBinding(currentBindings.onShiftTab, input, key);
|
|
17
56
|
}
|
|
18
|
-
else if (
|
|
19
|
-
|
|
57
|
+
else if (currentBindings.onTab) {
|
|
58
|
+
executeBinding(currentBindings.onTab, input, key);
|
|
20
59
|
}
|
|
21
60
|
return;
|
|
22
61
|
}
|
|
23
|
-
if (key.escape &&
|
|
24
|
-
|
|
62
|
+
if (key.escape && currentBindings.onEscape) {
|
|
63
|
+
executeBinding(currentBindings.onEscape, input, key);
|
|
25
64
|
return;
|
|
26
65
|
}
|
|
27
|
-
if (key.return &&
|
|
28
|
-
|
|
66
|
+
if (key.return && currentBindings.onReturn) {
|
|
67
|
+
executeBinding(currentBindings.onReturn, input, key);
|
|
29
68
|
return;
|
|
30
69
|
}
|
|
31
|
-
if (key.backspace &&
|
|
32
|
-
|
|
70
|
+
if (key.backspace && currentBindings.onBackspace) {
|
|
71
|
+
executeBinding(currentBindings.onBackspace, input, key);
|
|
33
72
|
return;
|
|
34
73
|
}
|
|
35
|
-
if (key.upArrow &&
|
|
36
|
-
|
|
74
|
+
if (key.upArrow && currentBindings.onUpArrow) {
|
|
75
|
+
executeBinding(currentBindings.onUpArrow, input, key);
|
|
37
76
|
return;
|
|
38
77
|
}
|
|
39
|
-
if (key.downArrow &&
|
|
40
|
-
|
|
78
|
+
if (key.downArrow && currentBindings.onDownArrow) {
|
|
79
|
+
executeBinding(currentBindings.onDownArrow, input, key);
|
|
41
80
|
return;
|
|
42
81
|
}
|
|
43
|
-
if (key.leftArrow &&
|
|
44
|
-
|
|
82
|
+
if (key.leftArrow && currentBindings.onLeftArrow) {
|
|
83
|
+
executeBinding(currentBindings.onLeftArrow, input, key);
|
|
45
84
|
return;
|
|
46
85
|
}
|
|
47
|
-
if (key.rightArrow &&
|
|
48
|
-
|
|
86
|
+
if (key.rightArrow && currentBindings.onRightArrow) {
|
|
87
|
+
executeBinding(currentBindings.onRightArrow, input, key);
|
|
49
88
|
return;
|
|
50
89
|
}
|
|
51
90
|
// 일반 키 입력 처리
|
|
52
|
-
if (input &&
|
|
53
|
-
|
|
91
|
+
if (input && currentBindings[input]) {
|
|
92
|
+
executeBinding(currentBindings[input], input, key);
|
|
93
|
+
}
|
|
94
|
+
else if (input && currentCount !== null) {
|
|
95
|
+
// 바인딩되지 않은 키가 입력되면 Count 초기화 (Vim 스타일)
|
|
96
|
+
dispatch(setNavigationCount(null));
|
|
54
97
|
}
|
|
55
|
-
}, [
|
|
98
|
+
}, [dispatch]);
|
|
56
99
|
useInput(handleInput, { isActive: isEnabled });
|
|
57
100
|
}
|
|
@@ -20,6 +20,14 @@ export declare class OpenAPIParserService {
|
|
|
20
20
|
* Swagger 2.0 Operation 객체에서 Endpoint 생성
|
|
21
21
|
*/
|
|
22
22
|
private createEndpointV2;
|
|
23
|
+
/**
|
|
24
|
+
* OpenAPI v3 Response 객체 정규화
|
|
25
|
+
*/
|
|
26
|
+
private normalizeResponseV3;
|
|
27
|
+
/**
|
|
28
|
+
* Swagger 2.0 Response 객체 정규화
|
|
29
|
+
*/
|
|
30
|
+
private normalizeResponseV2;
|
|
23
31
|
/**
|
|
24
32
|
* OpenAPI v3 파라미터를 정규화
|
|
25
33
|
*/
|
|
@@ -33,9 +41,9 @@ export declare class OpenAPIParserService {
|
|
|
33
41
|
*/
|
|
34
42
|
private normalizeRequestBodyV3;
|
|
35
43
|
/**
|
|
36
|
-
* 엔드포인트 필터링
|
|
44
|
+
* 엔드포인트 메서드별 필터링
|
|
37
45
|
*/
|
|
38
|
-
|
|
46
|
+
filterByMethod(endpoints: Endpoint[], method: HttpMethod | 'ALL'): Endpoint[];
|
|
39
47
|
/**
|
|
40
48
|
* 엔드포인트 검색
|
|
41
49
|
*/
|
|
@@ -64,7 +64,8 @@ export class OpenAPIParserService {
|
|
|
64
64
|
for (const method of methods) {
|
|
65
65
|
const operation = pathItem[method];
|
|
66
66
|
if (operation) {
|
|
67
|
-
endpoints.push(this.createEndpointV2(path, method.toUpperCase(), operation, pathItem
|
|
67
|
+
endpoints.push(this.createEndpointV2(path, method.toUpperCase(), operation, pathItem, document // document 인자 전달
|
|
68
|
+
));
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -123,6 +124,24 @@ export class OpenAPIParserService {
|
|
|
123
124
|
requestBody = this.normalizeRequestBodyV3(operation.requestBody);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
// Responses 정규화
|
|
128
|
+
const responses = Object.entries(operation.responses || {})
|
|
129
|
+
.map(([status, response]) => {
|
|
130
|
+
if (!response)
|
|
131
|
+
return null;
|
|
132
|
+
if (isReferenceObject(response)) {
|
|
133
|
+
if (document) {
|
|
134
|
+
const resolved = resolveRef(response.$ref, document);
|
|
135
|
+
if (resolved && !isReferenceObject(resolved)) {
|
|
136
|
+
return this.normalizeResponseV3(status, resolved);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Resolve 실패 시 기본값 (설명 없음)
|
|
140
|
+
return { status, description: 'Reference not found' };
|
|
141
|
+
}
|
|
142
|
+
return this.normalizeResponseV3(status, response);
|
|
143
|
+
})
|
|
144
|
+
.filter((r) => r !== null);
|
|
126
145
|
return {
|
|
127
146
|
id,
|
|
128
147
|
method,
|
|
@@ -132,7 +151,7 @@ export class OpenAPIParserService {
|
|
|
132
151
|
tags: operation.tags || [],
|
|
133
152
|
parameters,
|
|
134
153
|
requestBody,
|
|
135
|
-
responses
|
|
154
|
+
responses,
|
|
136
155
|
operationId: operation.operationId,
|
|
137
156
|
deprecated: operation.deprecated,
|
|
138
157
|
security: operation.security,
|
|
@@ -141,8 +160,9 @@ export class OpenAPIParserService {
|
|
|
141
160
|
/**
|
|
142
161
|
* Swagger 2.0 Operation 객체에서 Endpoint 생성
|
|
143
162
|
*/
|
|
144
|
-
createEndpointV2(path, method, operation, pathItem) {
|
|
163
|
+
createEndpointV2(path, method, operation, pathItem, document) {
|
|
145
164
|
const id = `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
165
|
+
// ... 파라미터 처리 (생략) ...
|
|
146
166
|
// 파라미터 병합 및 정규화
|
|
147
167
|
const pathParameters = pathItem.parameters || [];
|
|
148
168
|
const operationParameters = operation.parameters || [];
|
|
@@ -153,23 +173,36 @@ export class OpenAPIParserService {
|
|
|
153
173
|
if (isReferenceObject(p))
|
|
154
174
|
continue;
|
|
155
175
|
const param = p;
|
|
156
|
-
// Swagger 2.0의 body 파라미터 처리
|
|
157
176
|
if (param.in === 'body') {
|
|
158
177
|
const bodyParam = param;
|
|
159
|
-
// requestBody로도 저장
|
|
160
178
|
requestBody = {
|
|
161
179
|
description: bodyParam.description,
|
|
162
180
|
required: bodyParam.required,
|
|
163
181
|
schema: bodyParam.schema,
|
|
164
182
|
};
|
|
165
|
-
// UI 표시를 위해 parameters에도 포함
|
|
166
183
|
parameters.push(this.normalizeParameterV2(param));
|
|
167
184
|
}
|
|
168
185
|
else {
|
|
169
|
-
// 나머지 파라미터들 (path, query, header, formData)
|
|
170
186
|
parameters.push(this.normalizeParameterV2(param));
|
|
171
187
|
}
|
|
172
188
|
}
|
|
189
|
+
// Responses 정규화
|
|
190
|
+
const responses = Object.entries(operation.responses || {})
|
|
191
|
+
.map(([status, response]) => {
|
|
192
|
+
if (!response)
|
|
193
|
+
return null;
|
|
194
|
+
if (isReferenceObject(response)) {
|
|
195
|
+
if (document) {
|
|
196
|
+
const resolved = resolveRef(response.$ref, document);
|
|
197
|
+
if (resolved && !isReferenceObject(resolved)) {
|
|
198
|
+
return this.normalizeResponseV2(status, resolved);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { status, description: 'Reference not found' };
|
|
202
|
+
}
|
|
203
|
+
return this.normalizeResponseV2(status, response);
|
|
204
|
+
})
|
|
205
|
+
.filter((r) => r !== null);
|
|
173
206
|
return {
|
|
174
207
|
id,
|
|
175
208
|
method,
|
|
@@ -179,12 +212,73 @@ export class OpenAPIParserService {
|
|
|
179
212
|
tags: operation.tags || [],
|
|
180
213
|
parameters,
|
|
181
214
|
requestBody,
|
|
182
|
-
responses
|
|
215
|
+
responses,
|
|
183
216
|
operationId: operation.operationId,
|
|
184
217
|
deprecated: operation.deprecated,
|
|
185
218
|
security: operation.security,
|
|
186
219
|
};
|
|
187
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* OpenAPI v3 Response 객체 정규화
|
|
223
|
+
*/
|
|
224
|
+
normalizeResponseV3(status, response) {
|
|
225
|
+
const content = response.content
|
|
226
|
+
? Object.entries(response.content).map(([mediaType, mediaObj]) => ({
|
|
227
|
+
mediaType,
|
|
228
|
+
schema: isReferenceObject(mediaObj.schema) ? undefined : mediaObj.schema,
|
|
229
|
+
examples: mediaObj.examples || mediaObj.example,
|
|
230
|
+
}))
|
|
231
|
+
: undefined;
|
|
232
|
+
const headers = response.headers
|
|
233
|
+
? Object.entries(response.headers).map(([name, header]) => {
|
|
234
|
+
if (isReferenceObject(header)) {
|
|
235
|
+
return { name, description: 'Reference header not supported yet' };
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
name,
|
|
239
|
+
description: header.description,
|
|
240
|
+
schema: isReferenceObject(header.schema) ? undefined : header.schema,
|
|
241
|
+
};
|
|
242
|
+
})
|
|
243
|
+
: undefined;
|
|
244
|
+
return {
|
|
245
|
+
status,
|
|
246
|
+
description: response.description,
|
|
247
|
+
content,
|
|
248
|
+
headers,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Swagger 2.0 Response 객체 정규화
|
|
253
|
+
*/
|
|
254
|
+
normalizeResponseV2(status, response) {
|
|
255
|
+
// V2는 content가 없고 schema가 바로 있음. 이를 application/json (또는 produces) content로 변환
|
|
256
|
+
let content;
|
|
257
|
+
if (response.schema) {
|
|
258
|
+
// V2 스키마를 V3 스타일 content로 래핑
|
|
259
|
+
content = [
|
|
260
|
+
{
|
|
261
|
+
mediaType: 'application/json', // 기본값
|
|
262
|
+
schema: response.schema,
|
|
263
|
+
examples: response.examples,
|
|
264
|
+
},
|
|
265
|
+
];
|
|
266
|
+
}
|
|
267
|
+
const headers = response.headers
|
|
268
|
+
? Object.entries(response.headers).map(([name, header]) => ({
|
|
269
|
+
name,
|
|
270
|
+
description: header.description,
|
|
271
|
+
type: header.type,
|
|
272
|
+
// V2 헤더는 schema 대신 type/format 등을 가짐. 이를 schema 형태로 변환 가능하지만 일단 type만 유지
|
|
273
|
+
}))
|
|
274
|
+
: undefined;
|
|
275
|
+
return {
|
|
276
|
+
status,
|
|
277
|
+
description: response.description,
|
|
278
|
+
content,
|
|
279
|
+
headers,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
188
282
|
/**
|
|
189
283
|
* OpenAPI v3 파라미터를 정규화
|
|
190
284
|
*/
|
|
@@ -266,9 +360,9 @@ export class OpenAPIParserService {
|
|
|
266
360
|
};
|
|
267
361
|
}
|
|
268
362
|
/**
|
|
269
|
-
* 엔드포인트 필터링
|
|
363
|
+
* 엔드포인트 메서드별 필터링
|
|
270
364
|
*/
|
|
271
|
-
|
|
365
|
+
filterByMethod(endpoints, method) {
|
|
272
366
|
if (method === 'ALL') {
|
|
273
367
|
return endpoints;
|
|
274
368
|
}
|
package/dist/store/appSlice.d.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { AppState, AppMode, Screen, ApiResponse } from '../types/app-state.js';
|
|
5
5
|
import { Endpoint, HttpMethod, OpenAPIDocument } from '../types/openapi.js';
|
|
6
|
-
export declare const setDocument: import("@reduxjs/toolkit").ActionCreatorWithPayload<OpenAPIDocument, "app/setDocument">, setEndpoints: import("@reduxjs/toolkit").ActionCreatorWithPayload<Endpoint[], "app/setEndpoints">, setScreen: import("@reduxjs/toolkit").ActionCreatorWithPayload<Screen, "app/setScreen">, setMode: import("@reduxjs/toolkit").ActionCreatorWithPayload<AppMode, "app/setMode">, setCommandInput: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/setCommandInput">, setError: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "app/setError">, setLoading: import("@reduxjs/toolkit").ActionCreatorWithPayload<boolean, "app/setLoading">, listSelect: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listSelect">,
|
|
6
|
+
export declare const setDocument: import("@reduxjs/toolkit").ActionCreatorWithPayload<OpenAPIDocument, "app/setDocument">, setEndpoints: import("@reduxjs/toolkit").ActionCreatorWithPayload<Endpoint[], "app/setEndpoints">, setScreen: import("@reduxjs/toolkit").ActionCreatorWithPayload<Screen, "app/setScreen">, setMode: import("@reduxjs/toolkit").ActionCreatorWithPayload<AppMode, "app/setMode">, setCommandInput: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/setCommandInput">, setNavigationCount: import("@reduxjs/toolkit").ActionCreatorWithPayload<number | null, "app/setNavigationCount">, setError: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "app/setError">, setLoading: import("@reduxjs/toolkit").ActionCreatorWithPayload<boolean, "app/setLoading">, listSelect: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listSelect">, listSetMethodFilter: import("@reduxjs/toolkit").ActionCreatorWithPayload<HttpMethod | "ALL", "app/listSetMethodFilter">, listSetTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "app/listSetTag">, listSetFocus: import("@reduxjs/toolkit").ActionCreatorWithPayload<"LIST" | "TAGS" | "METHODS" | "TITLE", "app/listSetFocus">, listSelectTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listSelectTag">, listSelectMethod: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listSelectMethod">, listSetSearch: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/listSetSearch">, listSetTagSearch: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/listSetTagSearch">, listScroll: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listScroll">, detailSet: import("@reduxjs/toolkit").ActionCreatorWithPayload<Endpoint, "app/detailSet">, detailSetFocus: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/detailSetFocus">, detailSetParam: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
7
7
|
key: string;
|
|
8
8
|
value: string;
|
|
9
9
|
}, "app/detailSetParam">, detailSetBody: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/detailSetBody">, detailSetHeader: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
10
10
|
key: string;
|
|
11
11
|
value: string;
|
|
12
|
-
}, "app/detailSetHeader">, detailSetPanel: import("@reduxjs/toolkit").ActionCreatorWithPayload<"left" | "request" | "response", "app/detailSetPanel">, detailSetRequestTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"headers" | "query" | "body", "app/detailSetRequestTab">, detailSetResponseTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"headers" | "body" | "cookies" | "curl", "app/detailSetResponseTab">, detailSetResponse: import("@reduxjs/toolkit").ActionCreatorWithPayload<ApiResponse | null, "app/detailSetResponse">, reset: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"app/reset">;
|
|
13
|
-
declare const _default: import("
|
|
12
|
+
}, "app/detailSetHeader">, detailSetPanel: import("@reduxjs/toolkit").ActionCreatorWithPayload<"left" | "request" | "response", "app/detailSetPanel">, detailSetRequestTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"headers" | "query" | "body", "app/detailSetRequestTab">, detailSetResponseTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"headers" | "body" | "cookies" | "curl", "app/detailSetResponseTab">, detailSetSubFocus: import("@reduxjs/toolkit").ActionCreatorWithPayload<"TITLE" | "TABS" | "CONTENT", "app/detailSetSubFocus">, detailSetResponse: import("@reduxjs/toolkit").ActionCreatorWithPayload<ApiResponse | null, "app/detailSetResponse">, reset: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"app/reset">;
|
|
13
|
+
declare const _default: import("redux").Reducer<AppState>;
|
|
14
14
|
export default _default;
|
package/dist/store/appSlice.js
CHANGED
|
@@ -11,18 +11,21 @@ const initialState = {
|
|
|
11
11
|
list: {
|
|
12
12
|
selectedIndex: 0,
|
|
13
13
|
filteredEndpoints: [],
|
|
14
|
-
|
|
14
|
+
activeMethod: 'ALL',
|
|
15
15
|
activeTag: null,
|
|
16
16
|
focus: 'LIST',
|
|
17
17
|
selectedTagIndex: 0,
|
|
18
|
+
focusedMethodIndex: 0,
|
|
18
19
|
searchQuery: '',
|
|
19
20
|
tagSearchQuery: '',
|
|
20
21
|
scrollOffset: 0,
|
|
22
|
+
methodHistory: {},
|
|
21
23
|
},
|
|
22
24
|
detail: null,
|
|
23
25
|
error: null,
|
|
24
26
|
loading: false,
|
|
25
27
|
commandInput: '',
|
|
28
|
+
navigationCount: null,
|
|
26
29
|
};
|
|
27
30
|
const appSlice = createSlice({
|
|
28
31
|
name: 'app',
|
|
@@ -43,10 +46,15 @@ const appSlice = createSlice({
|
|
|
43
46
|
if (action.payload === 'COMMAND') {
|
|
44
47
|
state.commandInput = '';
|
|
45
48
|
}
|
|
49
|
+
// 모드 변경 시 Count 초기화
|
|
50
|
+
state.navigationCount = null;
|
|
46
51
|
},
|
|
47
52
|
setCommandInput: (state, action) => {
|
|
48
53
|
state.commandInput = action.payload;
|
|
49
54
|
},
|
|
55
|
+
setNavigationCount: (state, action) => {
|
|
56
|
+
state.navigationCount = action.payload;
|
|
57
|
+
},
|
|
50
58
|
setError: (state, action) => {
|
|
51
59
|
state.error = action.payload;
|
|
52
60
|
state.loading = false;
|
|
@@ -56,10 +64,32 @@ const appSlice = createSlice({
|
|
|
56
64
|
},
|
|
57
65
|
listSelect: (state, action) => {
|
|
58
66
|
state.list.selectedIndex = Math.max(0, Math.min(action.payload, state.list.filteredEndpoints.length - 1));
|
|
67
|
+
// 히스토리 업데이트
|
|
68
|
+
state.list.methodHistory[state.list.activeMethod] = {
|
|
69
|
+
selectedIndex: state.list.selectedIndex,
|
|
70
|
+
scrollOffset: state.list.scrollOffset,
|
|
71
|
+
};
|
|
59
72
|
},
|
|
60
|
-
|
|
61
|
-
state.list.
|
|
62
|
-
|
|
73
|
+
listSetMethodFilter: (state, action) => {
|
|
74
|
+
if (state.list.activeMethod !== action.payload) {
|
|
75
|
+
// 현재 상태 저장
|
|
76
|
+
state.list.methodHistory[state.list.activeMethod] = {
|
|
77
|
+
selectedIndex: state.list.selectedIndex,
|
|
78
|
+
scrollOffset: state.list.scrollOffset,
|
|
79
|
+
};
|
|
80
|
+
// 메서드 변경
|
|
81
|
+
state.list.activeMethod = action.payload;
|
|
82
|
+
// 히스토리 복원
|
|
83
|
+
const history = state.list.methodHistory[action.payload];
|
|
84
|
+
if (history) {
|
|
85
|
+
state.list.selectedIndex = history.selectedIndex;
|
|
86
|
+
state.list.scrollOffset = history.scrollOffset;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
state.list.selectedIndex = 0;
|
|
90
|
+
state.list.scrollOffset = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
63
93
|
},
|
|
64
94
|
listSetTag: (state, action) => {
|
|
65
95
|
state.list.activeTag = action.payload;
|
|
@@ -71,6 +101,9 @@ const appSlice = createSlice({
|
|
|
71
101
|
listSelectTag: (state, action) => {
|
|
72
102
|
state.list.selectedTagIndex = action.payload;
|
|
73
103
|
},
|
|
104
|
+
listSelectMethod: (state, action) => {
|
|
105
|
+
state.list.focusedMethodIndex = action.payload;
|
|
106
|
+
},
|
|
74
107
|
listSetSearch: (state, action) => {
|
|
75
108
|
state.list.searchQuery = action.payload;
|
|
76
109
|
state.list.selectedIndex = 0;
|
|
@@ -81,6 +114,11 @@ const appSlice = createSlice({
|
|
|
81
114
|
},
|
|
82
115
|
listScroll: (state, action) => {
|
|
83
116
|
state.list.scrollOffset = action.payload;
|
|
117
|
+
// 히스토리 업데이트
|
|
118
|
+
state.list.methodHistory[state.list.activeMethod] = {
|
|
119
|
+
selectedIndex: state.list.selectedIndex,
|
|
120
|
+
scrollOffset: state.list.scrollOffset,
|
|
121
|
+
};
|
|
84
122
|
},
|
|
85
123
|
detailSet: (state, action) => {
|
|
86
124
|
const endpoint = action.payload;
|
|
@@ -94,6 +132,7 @@ const appSlice = createSlice({
|
|
|
94
132
|
activePanel: 'left',
|
|
95
133
|
activeRequestTab: 'headers',
|
|
96
134
|
activeResponseTab: 'body',
|
|
135
|
+
subFocus: 'TABS',
|
|
97
136
|
response: null,
|
|
98
137
|
};
|
|
99
138
|
},
|
|
@@ -132,6 +171,11 @@ const appSlice = createSlice({
|
|
|
132
171
|
state.detail.activeResponseTab = action.payload;
|
|
133
172
|
}
|
|
134
173
|
},
|
|
174
|
+
detailSetSubFocus: (state, action) => {
|
|
175
|
+
if (state.detail) {
|
|
176
|
+
state.detail.subFocus = action.payload;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
135
179
|
detailSetResponse: (state, action) => {
|
|
136
180
|
if (state.detail) {
|
|
137
181
|
state.detail.response = action.payload;
|
|
@@ -140,5 +184,5 @@ const appSlice = createSlice({
|
|
|
140
184
|
reset: () => initialState,
|
|
141
185
|
},
|
|
142
186
|
});
|
|
143
|
-
export const { setDocument, setEndpoints, setScreen, setMode, setCommandInput, setError, setLoading, listSelect,
|
|
187
|
+
export const { setDocument, setEndpoints, setScreen, setMode, setCommandInput, setNavigationCount, setError, setLoading, listSelect, listSetMethodFilter, listSetTag, listSetFocus, listSelectTag, listSelectMethod, listSetSearch, listSetTagSearch, listScroll, detailSet, detailSetFocus, detailSetParam, detailSetBody, detailSetHeader, detailSetPanel, detailSetRequestTab, detailSetResponseTab, detailSetSubFocus, detailSetResponse, reset, } = appSlice.actions;
|
|
144
188
|
export default appSlice.reducer;
|
package/dist/store/hooks.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare const useAppDispatch: import("react-redux").UseDispatch<import("@reduxjs/toolkit").ThunkDispatch<{
|
|
5
5
|
app: import("../types/app-state.js").AppState;
|
|
6
|
-
}, undefined, import("
|
|
6
|
+
}, undefined, import("redux").UnknownAction> & import("redux").Dispatch<import("redux").UnknownAction>>;
|
|
7
7
|
export declare const useAppSelector: import("react-redux").UseSelector<{
|
|
8
8
|
app: import("../types/app-state.js").AppState;
|
|
9
9
|
}>;
|
package/dist/store/index.d.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare const store: import("@reduxjs/toolkit").EnhancedStore<{
|
|
5
5
|
app: import("../types/app-state.js").AppState;
|
|
6
|
-
}, import("
|
|
6
|
+
}, import("redux").UnknownAction, import("@reduxjs/toolkit").Tuple<[import("redux").StoreEnhancer<{
|
|
7
7
|
dispatch: import("@reduxjs/toolkit").ThunkDispatch<{
|
|
8
8
|
app: import("../types/app-state.js").AppState;
|
|
9
|
-
}, undefined, import("
|
|
10
|
-
}>, import("
|
|
9
|
+
}, undefined, import("redux").UnknownAction>;
|
|
10
|
+
}>, import("redux").StoreEnhancer]>>;
|
|
11
11
|
export type RootState = ReturnType<typeof store.getState>;
|
|
12
12
|
export type AppDispatch = typeof store.dispatch;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ThunkAction } from '@reduxjs/toolkit';
|
|
2
|
+
import { AnyAction } from 'redux';
|
|
3
|
+
import { RootState } from './index.js';
|
|
4
|
+
type AppThunk = ThunkAction<void, RootState, unknown, AnyAction>;
|
|
5
|
+
export declare const navigateUp: () => AppThunk;
|
|
6
|
+
export declare const navigateDown: () => AppThunk;
|
|
7
|
+
export declare const navigateLeft: () => AppThunk;
|
|
8
|
+
export declare const navigateRight: () => AppThunk;
|
|
9
|
+
export {};
|