@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.
@@ -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
- if (!isEnabled)
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 && bindings.onShiftTab) {
16
- bindings.onShiftTab(input, key);
54
+ if (key.shift && currentBindings.onShiftTab) {
55
+ executeBinding(currentBindings.onShiftTab, input, key);
17
56
  }
18
- else if (bindings.onTab) {
19
- bindings.onTab(input, key);
57
+ else if (currentBindings.onTab) {
58
+ executeBinding(currentBindings.onTab, input, key);
20
59
  }
21
60
  return;
22
61
  }
23
- if (key.escape && bindings.onEscape) {
24
- bindings.onEscape(input, key);
62
+ if (key.escape && currentBindings.onEscape) {
63
+ executeBinding(currentBindings.onEscape, input, key);
25
64
  return;
26
65
  }
27
- if (key.return && bindings.onReturn) {
28
- bindings.onReturn(input, key);
66
+ if (key.return && currentBindings.onReturn) {
67
+ executeBinding(currentBindings.onReturn, input, key);
29
68
  return;
30
69
  }
31
- if (key.backspace && bindings.onBackspace) {
32
- bindings.onBackspace(input, key);
70
+ if (key.backspace && currentBindings.onBackspace) {
71
+ executeBinding(currentBindings.onBackspace, input, key);
33
72
  return;
34
73
  }
35
- if (key.upArrow && bindings.onUpArrow) {
36
- bindings.onUpArrow(input, key);
74
+ if (key.upArrow && currentBindings.onUpArrow) {
75
+ executeBinding(currentBindings.onUpArrow, input, key);
37
76
  return;
38
77
  }
39
- if (key.downArrow && bindings.onDownArrow) {
40
- bindings.onDownArrow(input, key);
78
+ if (key.downArrow && currentBindings.onDownArrow) {
79
+ executeBinding(currentBindings.onDownArrow, input, key);
41
80
  return;
42
81
  }
43
- if (key.leftArrow && bindings.onLeftArrow) {
44
- bindings.onLeftArrow(input, key);
82
+ if (key.leftArrow && currentBindings.onLeftArrow) {
83
+ executeBinding(currentBindings.onLeftArrow, input, key);
45
84
  return;
46
85
  }
47
- if (key.rightArrow && bindings.onRightArrow) {
48
- bindings.onRightArrow(input, key);
86
+ if (key.rightArrow && currentBindings.onRightArrow) {
87
+ executeBinding(currentBindings.onRightArrow, input, key);
49
88
  return;
50
89
  }
51
90
  // 일반 키 입력 처리
52
- if (input && bindings[input]) {
53
- bindings[input](input, key);
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
- }, [bindings, isEnabled]);
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
- filterEndpoints(endpoints: Endpoint[], method: HttpMethod | 'ALL'): Endpoint[];
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: operation.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: operation.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
- filterEndpoints(endpoints, method) {
365
+ filterByMethod(endpoints, method) {
272
366
  if (method === 'ALL') {
273
367
  return endpoints;
274
368
  }
@@ -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">, listSetFilter: import("@reduxjs/toolkit").ActionCreatorWithPayload<HttpMethod | "ALL", "app/listSetFilter">, listSetTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "app/listSetTag">, listSetFocus: import("@reduxjs/toolkit").ActionCreatorWithPayload<"LIST" | "TAGS", "app/listSetFocus">, listSelectTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listSelectTag">, 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<{
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("@reduxjs/toolkit").Reducer<AppState>;
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;
@@ -11,18 +11,21 @@ const initialState = {
11
11
  list: {
12
12
  selectedIndex: 0,
13
13
  filteredEndpoints: [],
14
- activeFilter: 'ALL',
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
- listSetFilter: (state, action) => {
61
- state.list.activeFilter = action.payload;
62
- state.list.selectedIndex = 0;
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, listSetFilter, listSetTag, listSetFocus, listSelectTag, listSetSearch, listSetTagSearch, listScroll, detailSet, detailSetFocus, detailSetParam, detailSetBody, detailSetHeader, detailSetPanel, detailSetRequestTab, detailSetResponseTab, detailSetResponse, reset, } = appSlice.actions;
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;
@@ -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("@reduxjs/toolkit").UnknownAction> & import("@reduxjs/toolkit").Dispatch<import("@reduxjs/toolkit").UnknownAction>>;
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
  }>;
@@ -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("@reduxjs/toolkit").UnknownAction, import("@reduxjs/toolkit").Tuple<[import("@reduxjs/toolkit").StoreEnhancer<{
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("@reduxjs/toolkit").UnknownAction>;
10
- }>, import("@reduxjs/toolkit").StoreEnhancer]>>;
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 {};