@akanjs/devkit 3.0.0-alpha.88 → 3.0.0-alpha.89

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.
@@ -1,242 +0,0 @@
1
- "use client";
2
- import { Box, Text, useInput } from "ink";
3
- import { useEffect, useState } from "react";
4
- import { useStdoutDimensions } from "../useStdoutDimensions";
5
-
6
- interface MultiScrollListProps {
7
- logList: {
8
- title: string;
9
- logs: {
10
- type: string;
11
- content: string;
12
- }[];
13
- color: string;
14
- }[];
15
-
16
- maxLength?: number;
17
- }
18
-
19
- /**
20
- * @param logList 로그 목록
21
- * @param maxLength 최대 로그 길이 (기본 100)
22
- */
23
- const HEADER_HEIGHT = 1;
24
- const FOOTER_HEIGHT = 1;
25
- const OUTER_BORDER_HEIGHT = 2;
26
- const BORDER_HEIGHT = 2;
27
- const ANSI_ESCAPE_RE = new RegExp(`${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\[[\\s\\S]*?[@-~])`, "g");
28
-
29
- export const MultiScrollList = ({ logList, maxLength = 100 }: MultiScrollListProps) => {
30
- // 창 너비, 높이
31
- const [width, height] = useStdoutDimensions();
32
- // 탭별 로그 렌더링
33
-
34
- const [focusLog, setFocusLog] = useState<{ type: string; content: string }[]>([]);
35
- // 탭별 로그 길이 저장 (스크롤시 로그 길이 변화로 위치 조정)
36
- const [lengthMap, setLengthMap] = useState<Map<number, number>>(new Map());
37
- // 스크롤 위치
38
- const [scrollPos, setScrollPos] = useState(0);
39
- // 현재 포커싱탭 인덱스
40
- const [tabIndex, setTabIndex] = useState<number>(0);
41
- // 스크롤 실행 여부
42
- const [isRunning, setIsRunning] = useState(false);
43
- // 박스 높이 (보더 사이즈 2, 헤더 사이즈 1, 푸터 사이즈 5 기본 1)
44
- const [boxHeight, setBoxHeight] = useState(
45
- height - HEADER_HEIGHT - OUTER_BORDER_HEIGHT - FOOTER_HEIGHT - BORDER_HEIGHT,
46
- );
47
- const boxWidth = width - 27;
48
-
49
- // maxLength에 따라 로그 배열을 제한하는 유틸리티 함수
50
- const getLimitedLogs = (logs: { type: string; content: string }[]) => {
51
- const sortedLogs = logs.reduce<{ type: string; content: string }[]>((acc, log) => {
52
- // log.content.length가 boxWidth보다 큰 경우 잘라서 줄 수 만큼 추가
53
- // ANSI 코드 보관
54
-
55
- const content = log.content.replace(ANSI_ESCAPE_RE, "");
56
- if (content.length > boxWidth) {
57
- const lines = Math.ceil(content.length / boxWidth);
58
- for (let i = 0; i < lines; i++) {
59
- acc.push({ type: log.type, content: content.slice(i * boxWidth, (i + 1) * boxWidth) });
60
- }
61
- } else {
62
- acc.push(log);
63
- }
64
- return acc;
65
- }, []);
66
- return sortedLogs.length > maxLength ? sortedLogs.slice(sortedLogs.length - maxLength) : sortedLogs;
67
- };
68
-
69
- // 입력 이벤트 처리
70
- useInput((input, key) => {
71
- // 탭 키 이벤트 처리(포커싱 탭 변경)
72
- if (key.tab) {
73
- setTabIndex((prev: number) => (prev + 1) % logList.length);
74
- setScrollPos(0);
75
- setIsRunning(false);
76
- }
77
- // 스크롤 중지 이벤트 처리 (포커싱까지 완전히 종료)
78
- if (key.escape) {
79
- setScrollPos(0);
80
- setIsRunning(false);
81
- }
82
- // 스크롤 중지 이벤트 처리 (포커싱 유지)
83
- if (input === " ") {
84
- setScrollPos(0);
85
- setIsRunning(false);
86
- }
87
- // 스크롤 다운 이벤트 처리
88
- if (key.downArrow && scrollPos > 0) {
89
- if (key.shift) {
90
- const newScrollPos = scrollPos - 10;
91
- // 스크롤 최소 위치에 도달 경우에 대한 예외 처리
92
- if (newScrollPos < 0) {
93
- setScrollPos(0);
94
- } else {
95
- setScrollPos(newScrollPos);
96
- }
97
- } else {
98
- const newScrollPos = scrollPos - 1;
99
- setScrollPos(newScrollPos);
100
- }
101
- }
102
- // 스크롤 업 이벤트 처리
103
- if (key.upArrow && scrollPos < logList[tabIndex].logs.length - boxHeight) {
104
- const limitedLogs = getLimitedLogs(logList[tabIndex].logs);
105
- if (scrollPos < limitedLogs.length - boxHeight) {
106
- // 스크롤 업 이벤트 처리 (스크롤 속도 증가)
107
- if (key.shift) {
108
- const newScrollPos = scrollPos + 10;
109
- // 스크롤 최대 위치에 도달 경우에 대한 예외 처리
110
- if (newScrollPos > limitedLogs.length - boxHeight) {
111
- setScrollPos(limitedLogs.length - boxHeight);
112
- } else {
113
- setScrollPos(newScrollPos);
114
- }
115
- } else {
116
- setScrollPos(scrollPos + 1);
117
- }
118
- if (!isRunning) setIsRunning(true);
119
- }
120
- }
121
- });
122
-
123
- useEffect(() => {
124
- // 공통 로직을 함수로 추출
125
- const getLogsToRender = (logs: { type: string; content: string }[], index: number) => {
126
- // maxLength에 따라 로그 제한
127
- const limitedLogs = getLimitedLogs(logs);
128
- // 로그 중 boxWidth보다 긴 것이 있는 경우 잘라서 출력
129
-
130
- // 활성 탭이고 스크롤 중인 특별한 경우
131
- if (scrollPos > 0 && tabIndex === index) {
132
- return limitedLogs.slice(limitedLogs.length - boxHeight - scrollPos, limitedLogs.length - scrollPos);
133
- }
134
- // 로그가 표시 영역보다 큰 경우 (공통 로직)
135
- else if (limitedLogs.length > boxHeight) {
136
- return limitedLogs.slice(limitedLogs.length - boxHeight, limitedLogs.length);
137
- }
138
- // 로그가 표시 영역에 모두 들어가는 경우 (공통 로직)
139
- else {
140
- return limitedLogs;
141
- }
142
- };
143
-
144
- // isRunning일 때 로직
145
- if (isRunning) {
146
- // 선택된 탭의 로그 길이 변화 확인 및 스크롤 포지션 업데이트
147
- if (lengthMap.has(tabIndex)) {
148
- const tabLength = lengthMap.get(tabIndex);
149
- const limitedLogsLength = Math.min(logList[tabIndex].logs.length, maxLength);
150
- if (tabLength && tabLength < limitedLogsLength) {
151
- setScrollPos(scrollPos + 1);
152
- lengthMap.set(tabIndex, limitedLogsLength);
153
- }
154
- }
155
-
156
- // 스크롤 위치에 따른 로그 렌더링 업데이트
157
-
158
- setFocusLog(getLogsToRender(logList[tabIndex].logs, tabIndex));
159
- }
160
- // isRunning이 아닐 때 로직
161
- else {
162
- // lengthMap 업데이트 및 초기 렌더링
163
- setFocusLog(getLogsToRender(logList[tabIndex].logs, tabIndex));
164
- }
165
- }, [logList, isRunning, scrollPos, tabIndex, boxHeight, maxLength]);
166
-
167
- useEffect(() => {
168
- setBoxHeight(height - HEADER_HEIGHT - OUTER_BORDER_HEIGHT - FOOTER_HEIGHT - BORDER_HEIGHT);
169
- }, [height]);
170
-
171
- // 초기 로그 사이즈 설정
172
- useEffect(() => {
173
- setLengthMap(new Map(logList.map((log, index) => [index, Math.min(log.logs.length, maxLength)])));
174
- }, [logList, maxLength]);
175
-
176
- return (
177
- <Box width={width} height={height} borderStyle="round" borderColor="blackBright" flexDirection="column">
178
- <Box width={"100%"} height={boxHeight + BORDER_HEIGHT + HEADER_HEIGHT} flexDirection="row">
179
- <Box width={30} height="100%" flexDirection="column">
180
- <Box>
181
- <Text>
182
- List {tabIndex + 1}/{logList.length}
183
- </Text>
184
- </Box>
185
- <Box borderStyle="round" borderColor="blackBright" width={"100%"} height="100%" flexDirection="column">
186
- {logList.map((log, index) => {
187
- return (
188
- <Text key={index} color={index === tabIndex ? "green" : "white"}>
189
- <Text>●</Text>&nbsp;
190
- {log.title.length > 25 ? `${log.title.slice(0, 25)}...` : log.title}
191
- </Text>
192
- );
193
- })}
194
- </Box>
195
- </Box>
196
- <Box width={"100%"} height="100%" flexDirection="column">
197
- <Box height={1}>
198
- <Text color={logList[tabIndex].color}>
199
- {logList[tabIndex].title} {logList[tabIndex].logs.length}
200
- </Text>
201
- </Box>
202
- <Box
203
- borderStyle={isRunning ? "double" : "round"}
204
- flexDirection="column"
205
- borderColor={logList[tabIndex].color}
206
- width="100%"
207
- height="100%"
208
- >
209
- {scrollPos > 0 ? (
210
- <>
211
- {focusLog.slice(0, focusLog.length - 1).map((log, index) => {
212
- return (
213
- <Text underline={false} color={log.type === "error" ? "red" : "white"} key={index}>
214
- {log.content}
215
- </Text>
216
- );
217
- })}
218
- <Text underline={false} backgroundColor="green">
219
- Scrolling... +{scrollPos}
220
- </Text>
221
- </>
222
- ) : (
223
- focusLog.map((log, index) => {
224
- return (
225
- <Text underline={false} color={log.type === "error" ? "red" : "white"} key={index}>
226
- {log.content}
227
- </Text>
228
- );
229
- })
230
- )}
231
- </Box>
232
- </Box>
233
- </Box>
234
- {/* <Text>{renderMultiLogs[tabIndex][0]}</Text> */}
235
- <Box width={"100%"} height={FOOTER_HEIGHT}>
236
- <Text dimColor={true}>
237
- Tab : switch tab | Up / Down(shift : x10) : scroll | Space : back last position | Esc : stop scrolling
238
- </Text>
239
- </Box>
240
- </Box>
241
- );
242
- };
package/ui/ScrollList.tsx DELETED
@@ -1,105 +0,0 @@
1
- "use client";
2
- import { Box, type BoxProps, Newline, Text, useInput } from "ink";
3
- import { type ReactNode, useEffect, useState } from "react";
4
- import { useStdoutDimensions } from "../useStdoutDimensions";
5
-
6
- interface ScrollListProps extends BoxProps {
7
- list: ReactNode[];
8
- }
9
-
10
- export const ScrollList = ({ list, ...props }: ScrollListProps) => {
11
- const [renderLogs, setRenderLogs] = useState<ReactNode[]>(list);
12
- const [width, height] = useStdoutDimensions();
13
- const [scrollPos, setScrollPos] = useState(0);
14
- const [isRunning, setIsRunning] = useState(false);
15
- const [boxHeight, setBoxHeight] = useState(height - 3);
16
-
17
- useInput((input, key) => {
18
- if (key.escape) {
19
- setIsRunning(false);
20
- setScrollPos(0);
21
- }
22
- if (input === " " && isRunning) {
23
- setIsRunning(false);
24
- setScrollPos(0);
25
- }
26
- if (key.downArrow && scrollPos > 0) {
27
- if (key.shift) {
28
- setScrollPos(scrollPos - 10);
29
- } else {
30
- setScrollPos(scrollPos - 1);
31
- }
32
- }
33
- if (key.upArrow && scrollPos < list.length - boxHeight) {
34
- if (key.shift) {
35
- setScrollPos(scrollPos + 10);
36
- } else {
37
- setScrollPos(scrollPos + 1);
38
- }
39
- }
40
- });
41
-
42
- useEffect(() => {
43
- //1 로그가 박스 높이보다 크면 스크롤 위치 조정
44
- //2. scrollPos값에 따라 포커싱 위치 변경하고 pos 활성시 포커싱 위치 고정
45
- if (isRunning) {
46
- setScrollPos(scrollPos + 1);
47
- return;
48
- }
49
- if (list.length > boxHeight) {
50
- setRenderLogs(list.slice(list.length - boxHeight, list.length));
51
- } else {
52
- setRenderLogs(list);
53
- }
54
- }, [list, isRunning]);
55
-
56
- useEffect(() => {
57
- setBoxHeight(Math.floor(height * 0.9));
58
- }, [height]);
59
-
60
- useEffect(() => {
61
- if (scrollPos > 0) {
62
- setRenderLogs(list.slice(list.length - boxHeight - scrollPos, list.length - scrollPos));
63
- setIsRunning(true);
64
- } else {
65
- setRenderLogs(list.slice(list.length - boxHeight, list.length));
66
- setIsRunning(false);
67
- }
68
- }, [scrollPos]);
69
-
70
- return (
71
- <Box {...props} width={width} height={"100%"} flexDirection="column">
72
- <Box borderStyle="round" width={width} height={height - 3}>
73
- <Newline />
74
- <Text>
75
- {isRunning ? (
76
- <>
77
- {renderLogs.slice(0, renderLogs.length - 1).map((log, index) => (
78
- <>
79
- <Text key={index}>{log}</Text>
80
- <Newline />
81
- </>
82
- ))}
83
- <Text backgroundColor="green">scrolling... + {scrollPos}</Text>
84
- </>
85
- ) : (
86
- renderLogs.map((log, index) => (
87
- <>
88
- <Text key={index}>{log}</Text>
89
- <Newline />
90
- </>
91
- ))
92
- )}
93
- </Text>
94
- </Box>
95
- <Box>
96
- <Text dimColor={true}>
97
- You can use the following shortcuts:
98
- <Newline />* <Text backgroundColor="green">up</Text> and <Text backgroundColor="green">down</Text> to scroll.{" "}
99
- <Text backgroundColor="green">shift</Text> to scroll faster.
100
- <Newline />* <Text backgroundColor="green">escape</Text> to stop scrolling.
101
- </Text>
102
- </Box>
103
- </Box>
104
- );
105
- };
package/ui/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from "./MultiScrollList";
2
- export * from "./ScrollList";
@@ -1,20 +0,0 @@
1
- "use client";
2
- import { useStdout } from "ink";
3
- import { useEffect, useState } from "react";
4
-
5
- export const useStdoutDimensions = (): [number, number] => {
6
- const { stdout } = useStdout();
7
- const [dimensions, setDimensions] = useState<[number, number]>([stdout.columns, stdout.rows]);
8
-
9
- useEffect(() => {
10
- const handler = () => {
11
- setDimensions([stdout.columns, stdout.rows]);
12
- };
13
- stdout.on("resize", handler);
14
- return () => {
15
- stdout.off("resize", handler);
16
- };
17
- }, [stdout]);
18
-
19
- return dimensions;
20
- };