@gxp-dev/tools 2.0.63 → 2.0.64
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 +32 -31
- package/bin/gx-devtools.js +74 -54
- package/bin/lib/cli.js +23 -21
- package/bin/lib/commands/add-dependency.js +366 -325
- package/bin/lib/commands/assets.js +137 -139
- package/bin/lib/commands/build.js +169 -174
- package/bin/lib/commands/datastore.js +181 -183
- package/bin/lib/commands/dev.js +127 -131
- package/bin/lib/commands/extensions.js +147 -149
- package/bin/lib/commands/extract-config.js +73 -67
- package/bin/lib/commands/index.js +12 -12
- package/bin/lib/commands/init.js +342 -240
- package/bin/lib/commands/publish.js +69 -75
- package/bin/lib/commands/socket.js +69 -69
- package/bin/lib/commands/ssl.js +14 -14
- package/bin/lib/constants.js +10 -24
- package/bin/lib/tui/App.tsx +761 -705
- package/bin/lib/tui/components/AIPanel.tsx +191 -171
- package/bin/lib/tui/components/CommandInput.tsx +394 -343
- package/bin/lib/tui/components/GeminiPanel.tsx +175 -151
- package/bin/lib/tui/components/Header.tsx +23 -21
- package/bin/lib/tui/components/LogPanel.tsx +244 -220
- package/bin/lib/tui/components/TabBar.tsx +50 -48
- package/bin/lib/tui/components/WelcomeScreen.tsx +126 -71
- package/bin/lib/tui/index.tsx +37 -39
- package/bin/lib/tui/services/AIService.ts +518 -462
- package/bin/lib/tui/services/ExtensionService.ts +140 -129
- package/bin/lib/tui/services/GeminiService.ts +367 -337
- package/bin/lib/tui/services/ServiceManager.ts +344 -322
- package/bin/lib/tui/services/SocketService.ts +168 -168
- package/bin/lib/tui/services/ViteService.ts +88 -88
- package/bin/lib/tui/services/index.ts +47 -22
- package/bin/lib/utils/ai-scaffold.js +291 -280
- package/bin/lib/utils/extract-config.js +157 -140
- package/bin/lib/utils/files.js +82 -86
- package/bin/lib/utils/index.js +7 -7
- package/bin/lib/utils/paths.js +34 -34
- package/bin/lib/utils/prompts.js +194 -169
- package/bin/lib/utils/ssl.js +79 -81
- package/browser-extensions/README.md +0 -1
- package/browser-extensions/chrome/background.js +244 -237
- package/browser-extensions/chrome/content.js +32 -29
- package/browser-extensions/chrome/devtools.html +7 -7
- package/browser-extensions/chrome/devtools.js +19 -19
- package/browser-extensions/chrome/inspector.js +802 -767
- package/browser-extensions/chrome/manifest.json +71 -63
- package/browser-extensions/chrome/panel.html +674 -636
- package/browser-extensions/chrome/panel.js +722 -712
- package/browser-extensions/chrome/popup.html +586 -543
- package/browser-extensions/chrome/popup.js +282 -244
- package/browser-extensions/chrome/rules.json +1 -1
- package/browser-extensions/chrome/test-chrome.html +216 -136
- package/browser-extensions/chrome/test-mixed-content.html +284 -189
- package/browser-extensions/chrome/test-uri-pattern.html +221 -198
- package/browser-extensions/firefox/README.md +9 -6
- package/browser-extensions/firefox/background.js +221 -218
- package/browser-extensions/firefox/content.js +55 -52
- package/browser-extensions/firefox/debug-errors.html +386 -228
- package/browser-extensions/firefox/debug-https.html +153 -105
- package/browser-extensions/firefox/devtools.html +7 -7
- package/browser-extensions/firefox/devtools.js +23 -20
- package/browser-extensions/firefox/inspector.js +802 -767
- package/browser-extensions/firefox/manifest.json +68 -68
- package/browser-extensions/firefox/panel.html +674 -636
- package/browser-extensions/firefox/panel.js +722 -712
- package/browser-extensions/firefox/popup.html +572 -535
- package/browser-extensions/firefox/popup.js +281 -236
- package/browser-extensions/firefox/test-gramercy.html +170 -125
- package/browser-extensions/firefox/test-imports.html +59 -55
- package/browser-extensions/firefox/test-masking.html +231 -140
- package/browser-extensions/firefox/test-uri-pattern.html +221 -198
- package/dist/tui/App.d.ts +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +154 -150
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/AIPanel.d.ts.map +1 -1
- package/dist/tui/components/AIPanel.js +42 -35
- package/dist/tui/components/AIPanel.js.map +1 -1
- package/dist/tui/components/CommandInput.d.ts +1 -1
- package/dist/tui/components/CommandInput.d.ts.map +1 -1
- package/dist/tui/components/CommandInput.js +92 -62
- package/dist/tui/components/CommandInput.js.map +1 -1
- package/dist/tui/components/GeminiPanel.d.ts.map +1 -1
- package/dist/tui/components/GeminiPanel.js +37 -30
- package/dist/tui/components/GeminiPanel.js.map +1 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/LogPanel.d.ts +1 -1
- package/dist/tui/components/LogPanel.d.ts.map +1 -1
- package/dist/tui/components/LogPanel.js +26 -24
- package/dist/tui/components/LogPanel.js.map +1 -1
- package/dist/tui/components/TabBar.d.ts +2 -2
- package/dist/tui/components/TabBar.d.ts.map +1 -1
- package/dist/tui/components/TabBar.js +11 -11
- package/dist/tui/components/TabBar.js.map +1 -1
- package/dist/tui/components/WelcomeScreen.d.ts.map +1 -1
- package/dist/tui/components/WelcomeScreen.js +6 -6
- package/dist/tui/components/WelcomeScreen.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +8 -8
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/services/AIService.d.ts +2 -2
- package/dist/tui/services/AIService.d.ts.map +1 -1
- package/dist/tui/services/AIService.js +165 -125
- package/dist/tui/services/AIService.js.map +1 -1
- package/dist/tui/services/ExtensionService.d.ts +1 -1
- package/dist/tui/services/ExtensionService.d.ts.map +1 -1
- package/dist/tui/services/ExtensionService.js +33 -26
- package/dist/tui/services/ExtensionService.js.map +1 -1
- package/dist/tui/services/GeminiService.d.ts +1 -1
- package/dist/tui/services/GeminiService.d.ts.map +1 -1
- package/dist/tui/services/GeminiService.js +87 -76
- package/dist/tui/services/GeminiService.js.map +1 -1
- package/dist/tui/services/ServiceManager.d.ts +3 -3
- package/dist/tui/services/ServiceManager.d.ts.map +1 -1
- package/dist/tui/services/ServiceManager.js +72 -58
- package/dist/tui/services/ServiceManager.js.map +1 -1
- package/dist/tui/services/SocketService.d.ts.map +1 -1
- package/dist/tui/services/SocketService.js +32 -32
- package/dist/tui/services/SocketService.js.map +1 -1
- package/dist/tui/services/ViteService.d.ts.map +1 -1
- package/dist/tui/services/ViteService.js +26 -28
- package/dist/tui/services/ViteService.js.map +1 -1
- package/dist/tui/services/index.d.ts +6 -6
- package/dist/tui/services/index.d.ts.map +1 -1
- package/dist/tui/services/index.js +6 -6
- package/dist/tui/services/index.js.map +1 -1
- package/mcp/gxp-api-server.js +83 -81
- package/package.json +109 -93
- package/runtime/PortalContainer.vue +258 -234
- package/runtime/dev-tools/DevToolsModal.vue +153 -155
- package/runtime/dev-tools/LayoutSwitcher.vue +144 -140
- package/runtime/dev-tools/MockDataEditor.vue +456 -433
- package/runtime/dev-tools/SocketSimulator.vue +379 -371
- package/runtime/dev-tools/StoreInspector.vue +517 -455
- package/runtime/dev-tools/index.js +5 -5
- package/runtime/fallback-layouts/PrivateLayout.vue +2 -2
- package/runtime/fallback-layouts/PublicLayout.vue +2 -2
- package/runtime/fallback-layouts/SystemLayout.vue +2 -2
- package/runtime/gxpStringsPlugin.js +159 -134
- package/runtime/index.html +17 -19
- package/runtime/main.js +24 -22
- package/runtime/mock-api/auth-middleware.js +15 -15
- package/runtime/mock-api/image-generator.js +46 -46
- package/runtime/mock-api/index.js +55 -55
- package/runtime/mock-api/response-generator.js +116 -105
- package/runtime/mock-api/route-generator.js +107 -84
- package/runtime/mock-api/socket-triggers.js +94 -93
- package/runtime/mock-api/spec-loader.js +79 -80
- package/runtime/package.json +3 -0
- package/runtime/server.js +68 -68
- package/runtime/stores/gxpPortalConfigStore.js +204 -186
- package/runtime/stores/index.js +2 -2
- package/runtime/vite-inspector-plugin.js +858 -707
- package/runtime/vite-source-tracker-plugin.js +132 -113
- package/runtime/vite.config.js +191 -139
- package/scripts/launch-chrome.js +41 -41
- package/scripts/pack-chrome.js +38 -39
- package/socket-events/AiSessionMessageCreated.json +17 -17
- package/socket-events/SocialStreamPostCreated.json +23 -23
- package/socket-events/SocialStreamPostVariantCompleted.json +22 -22
- package/template/.claude/agents/gxp-developer.md +100 -99
- package/template/.claude/settings.json +7 -7
- package/template/AGENTS.md +30 -23
- package/template/GEMINI.md +20 -20
- package/template/README.md +70 -53
- package/template/app-manifest.json +2 -4
- package/template/configuration.json +10 -10
- package/template/default-styling.css +1 -1
- package/template/index.html +18 -20
- package/template/main.js +24 -22
- package/template/src/DemoPage.vue +415 -362
- package/template/src/Plugin.vue +76 -85
- package/template/src/stores/index.js +3 -3
- package/template/src/stores/test-data.json +164 -172
- package/template/theme-layouts/AdditionalStyling.css +50 -50
- package/template/theme-layouts/PrivateLayout.vue +8 -12
- package/template/theme-layouts/PublicLayout.vue +8 -12
- package/template/theme-layouts/SystemLayout.vue +8 -12
- package/template/vite.extend.js +45 -0
- package/template/vite.config.js +0 -409
|
@@ -1,233 +1,257 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo, useRef, memo } from
|
|
2
|
-
import { Box, Text, useStdout, useInput } from
|
|
1
|
+
import React, { useState, useEffect, useMemo, useRef, memo } from "react"
|
|
2
|
+
import { Box, Text, useStdout, useInput } from "ink"
|
|
3
3
|
|
|
4
4
|
interface LogPanelProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
logs: string[]
|
|
6
|
+
isActive?: boolean
|
|
7
|
+
maxHeight?: number // Maximum height in rows (from parent container)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
// Memoized log line component to prevent unnecessary re-renders
|
|
11
11
|
const LogLine = memo(({ log, index }: { log: string; index: number }) => (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
))
|
|
16
|
-
LogLine.displayName =
|
|
12
|
+
<Text key={index} wrap="wrap">
|
|
13
|
+
{formatLog(log)}
|
|
14
|
+
</Text>
|
|
15
|
+
))
|
|
16
|
+
LogLine.displayName = "LogLine"
|
|
17
17
|
|
|
18
18
|
function LogPanel({ logs, isActive = true, maxHeight }: LogPanelProps) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
19
|
+
const { stdout } = useStdout()
|
|
20
|
+
const [scrollOffset, setScrollOffset] = useState(0)
|
|
21
|
+
const [autoScroll, setAutoScroll] = useState(true)
|
|
22
|
+
const mouseEnabledRef = useRef(false)
|
|
23
|
+
const logsLengthRef = useRef(logs.length)
|
|
24
|
+
const maxLinesRef = useRef(0)
|
|
25
|
+
|
|
26
|
+
// Keep refs in sync
|
|
27
|
+
logsLengthRef.current = logs.length
|
|
28
|
+
|
|
29
|
+
// Calculate visible lines based on provided maxHeight or terminal height
|
|
30
|
+
// Subtract 2 for padding, 2 for border, 1 for hint bar
|
|
31
|
+
const maxLines = useMemo(() => {
|
|
32
|
+
const defaultMaxLines = stdout ? Math.max(5, stdout.rows - 10) : 15
|
|
33
|
+
return maxHeight ? Math.max(3, maxHeight - 5) : defaultMaxLines
|
|
34
|
+
}, [stdout?.rows, maxHeight])
|
|
35
|
+
|
|
36
|
+
maxLinesRef.current = maxLines
|
|
37
|
+
|
|
38
|
+
// Reset scroll when logs are cleared
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (logs.length === 0) {
|
|
41
|
+
setScrollOffset(0)
|
|
42
|
+
setAutoScroll(true)
|
|
43
|
+
}
|
|
44
|
+
}, [logs.length])
|
|
45
|
+
|
|
46
|
+
// Auto-scroll to bottom when new logs arrive (if autoScroll is enabled)
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (autoScroll) {
|
|
49
|
+
setScrollOffset(0)
|
|
50
|
+
}
|
|
51
|
+
}, [logs.length, autoScroll])
|
|
52
|
+
|
|
53
|
+
// Scroll helper functions
|
|
54
|
+
const scrollUp = (lines: number) => {
|
|
55
|
+
const maxOffset = Math.max(0, logs.length - maxLines)
|
|
56
|
+
setScrollOffset((prev) => Math.min(prev + lines, maxOffset))
|
|
57
|
+
setAutoScroll(false)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const scrollDown = (lines: number) => {
|
|
61
|
+
setScrollOffset((prev) => {
|
|
62
|
+
const newOffset = Math.max(prev - lines, 0)
|
|
63
|
+
if (newOffset === 0) setAutoScroll(true)
|
|
64
|
+
return newOffset
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Enable mouse wheel scrolling via terminal mouse reporting
|
|
69
|
+
// We patch stdin.emit to intercept mouse sequences BEFORE Ink processes them,
|
|
70
|
+
// preventing the escape codes from appearing in the command input
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!isActive || !process.stdin.isTTY) return
|
|
73
|
+
|
|
74
|
+
const mouseRegex = /\x1b\[<(\d+);\d+;\d+[Mm]/g
|
|
75
|
+
const originalEmit = process.stdin.emit
|
|
76
|
+
|
|
77
|
+
// Patch emit to intercept mouse data before Ink sees it
|
|
78
|
+
const stdin = process.stdin
|
|
79
|
+
stdin.emit = function (
|
|
80
|
+
this: typeof stdin,
|
|
81
|
+
event: string,
|
|
82
|
+
...args: any[]
|
|
83
|
+
): boolean {
|
|
84
|
+
if (event === "data") {
|
|
85
|
+
const data = args[0]
|
|
86
|
+
const str = typeof data === "string" ? data : data.toString()
|
|
87
|
+
|
|
88
|
+
// Check for mouse sequences
|
|
89
|
+
let hasMouseEvent = false
|
|
90
|
+
let match
|
|
91
|
+
while ((match = mouseRegex.exec(str)) !== null) {
|
|
92
|
+
hasMouseEvent = true
|
|
93
|
+
const button = parseInt(match[1], 10)
|
|
94
|
+
if (button === 64) {
|
|
95
|
+
// Scroll up
|
|
96
|
+
const maxOffset = Math.max(
|
|
97
|
+
0,
|
|
98
|
+
logsLengthRef.current - maxLinesRef.current,
|
|
99
|
+
)
|
|
100
|
+
setScrollOffset((prev) => Math.min(prev + 3, maxOffset))
|
|
101
|
+
setAutoScroll(false)
|
|
102
|
+
} else if (button === 65) {
|
|
103
|
+
// Scroll down
|
|
104
|
+
setScrollOffset((prev) => {
|
|
105
|
+
const newOffset = Math.max(prev - 3, 0)
|
|
106
|
+
if (newOffset === 0) setAutoScroll(true)
|
|
107
|
+
return newOffset
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
mouseRegex.lastIndex = 0
|
|
112
|
+
|
|
113
|
+
if (hasMouseEvent) {
|
|
114
|
+
// Strip mouse sequences, forward any remaining non-mouse data to Ink
|
|
115
|
+
const remaining = str.replace(mouseRegex, "")
|
|
116
|
+
if (remaining.length > 0) {
|
|
117
|
+
return originalEmit.call(stdin, event, remaining)
|
|
118
|
+
}
|
|
119
|
+
return true // consumed entirely
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return originalEmit.apply(stdin, [event, ...args])
|
|
123
|
+
} as typeof stdin.emit
|
|
124
|
+
|
|
125
|
+
// Disable mouse mode helper - used on unmount and process exit
|
|
126
|
+
const disableMouse = () => {
|
|
127
|
+
if (mouseEnabledRef.current) {
|
|
128
|
+
process.stdout.write("\x1b[?1000l\x1b[?1006l")
|
|
129
|
+
mouseEnabledRef.current = false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Ensure mouse mode is disabled on process exit/signals
|
|
134
|
+
const onExit = () => disableMouse()
|
|
135
|
+
process.on("exit", onExit)
|
|
136
|
+
process.on("SIGINT", onExit)
|
|
137
|
+
process.on("SIGTERM", onExit)
|
|
138
|
+
|
|
139
|
+
// Enable SGR mouse mode for wheel events
|
|
140
|
+
process.stdout.write("\x1b[?1000h\x1b[?1006h")
|
|
141
|
+
mouseEnabledRef.current = true
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
// Restore original emit and disable mouse mode
|
|
145
|
+
process.stdin.emit = originalEmit
|
|
146
|
+
disableMouse()
|
|
147
|
+
process.off("exit", onExit)
|
|
148
|
+
process.off("SIGINT", onExit)
|
|
149
|
+
process.off("SIGTERM", onExit)
|
|
150
|
+
}
|
|
151
|
+
}, [isActive])
|
|
152
|
+
|
|
153
|
+
// Handle keyboard input for scrolling
|
|
154
|
+
useInput((input, key) => {
|
|
155
|
+
if (!isActive) return
|
|
156
|
+
|
|
157
|
+
// Page Up - scroll up by half a page
|
|
158
|
+
if (key.pageUp || (key.shift && key.upArrow)) {
|
|
159
|
+
scrollUp(Math.floor(maxLines / 2))
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Page Down - scroll down by half a page
|
|
164
|
+
if (key.pageDown || (key.shift && key.downArrow)) {
|
|
165
|
+
scrollDown(Math.floor(maxLines / 2))
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Home - scroll to top
|
|
170
|
+
if (key.meta && key.upArrow) {
|
|
171
|
+
const maxOffset = Math.max(0, logs.length - maxLines)
|
|
172
|
+
setScrollOffset(maxOffset)
|
|
173
|
+
setAutoScroll(false)
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// End - scroll to bottom
|
|
178
|
+
if (key.meta && key.downArrow) {
|
|
179
|
+
setScrollOffset(0)
|
|
180
|
+
setAutoScroll(true)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Calculate visible logs with scroll offset - memoized
|
|
186
|
+
const { visibleLogs, startIndex, canScrollUp, canScrollDown } =
|
|
187
|
+
useMemo(() => {
|
|
188
|
+
const totalLogs = logs.length
|
|
189
|
+
const start = Math.max(0, totalLogs - maxLines - scrollOffset)
|
|
190
|
+
const end = Math.max(0, totalLogs - scrollOffset)
|
|
191
|
+
return {
|
|
192
|
+
visibleLogs: logs.slice(start, end),
|
|
193
|
+
startIndex: start,
|
|
194
|
+
canScrollUp: start > 0,
|
|
195
|
+
canScrollDown: scrollOffset > 0,
|
|
196
|
+
}
|
|
197
|
+
}, [logs, maxLines, scrollOffset])
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<Box flexDirection="column" padding={1} flexGrow={1} overflow="hidden">
|
|
201
|
+
<Box justifyContent="flex-end">
|
|
202
|
+
<Text color="gray" dimColor>
|
|
203
|
+
Shift+↑↓ scroll Ctrl+↑↓ jump mouse wheel
|
|
204
|
+
</Text>
|
|
205
|
+
</Box>
|
|
206
|
+
{canScrollUp && (
|
|
207
|
+
<Text color="gray" dimColor>
|
|
208
|
+
↑ {startIndex} more lines
|
|
209
|
+
</Text>
|
|
210
|
+
)}
|
|
211
|
+
{visibleLogs.length === 0 ? (
|
|
212
|
+
<Text color="gray" dimColor>
|
|
213
|
+
No logs yet...
|
|
214
|
+
</Text>
|
|
215
|
+
) : (
|
|
216
|
+
visibleLogs.map((log, index) => (
|
|
217
|
+
<LogLine
|
|
218
|
+
key={startIndex + index}
|
|
219
|
+
log={log}
|
|
220
|
+
index={startIndex + index}
|
|
221
|
+
/>
|
|
222
|
+
))
|
|
223
|
+
)}
|
|
224
|
+
{canScrollDown && (
|
|
225
|
+
<Text color="gray" dimColor>
|
|
226
|
+
↓ {scrollOffset} more lines
|
|
227
|
+
</Text>
|
|
228
|
+
)}
|
|
229
|
+
</Box>
|
|
230
|
+
)
|
|
211
231
|
}
|
|
212
232
|
|
|
213
233
|
function formatLog(log: string): React.ReactNode {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
234
|
+
// Color code different log types
|
|
235
|
+
if (log.startsWith("[VITE]") || log.includes("VITE")) {
|
|
236
|
+
return <Text color="cyan">{log}</Text>
|
|
237
|
+
}
|
|
238
|
+
if (log.startsWith("[SOCKET]") || log.includes("Socket")) {
|
|
239
|
+
return <Text color="green">{log}</Text>
|
|
240
|
+
}
|
|
241
|
+
if (log.includes("error") || log.includes("Error") || log.includes("ERROR")) {
|
|
242
|
+
return <Text color="red">{log}</Text>
|
|
243
|
+
}
|
|
244
|
+
if (
|
|
245
|
+
log.includes("warning") ||
|
|
246
|
+
log.includes("Warning") ||
|
|
247
|
+
log.includes("WARN")
|
|
248
|
+
) {
|
|
249
|
+
return <Text color="yellow">{log}</Text>
|
|
250
|
+
}
|
|
251
|
+
if (log.includes("Starting") || log.includes("started")) {
|
|
252
|
+
return <Text color="blue">{log}</Text>
|
|
253
|
+
}
|
|
254
|
+
return <Text>{log}</Text>
|
|
231
255
|
}
|
|
232
256
|
|
|
233
|
-
export default memo(LogPanel)
|
|
257
|
+
export default memo(LogPanel)
|
|
@@ -1,56 +1,58 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text } from
|
|
3
|
-
import type { Service } from
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Box, Text } from "ink"
|
|
3
|
+
import type { Service } from "../App.js"
|
|
4
4
|
|
|
5
5
|
interface TabBarProps {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
services: Service[]
|
|
7
|
+
activeTab: number
|
|
8
|
+
onTabChange: (index: number) => void
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const STATUS_COLORS: Record<Service[
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
11
|
+
const STATUS_COLORS: Record<Service["status"], string> = {
|
|
12
|
+
stopped: "gray",
|
|
13
|
+
starting: "yellow",
|
|
14
|
+
running: "green",
|
|
15
|
+
error: "red",
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
const STATUS_ICONS: Record<Service[
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
18
|
+
const STATUS_ICONS: Record<Service["status"], string> = {
|
|
19
|
+
stopped: "○",
|
|
20
|
+
starting: "◐",
|
|
21
|
+
running: "●",
|
|
22
|
+
error: "✖",
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
export default function TabBar({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
export default function TabBar({
|
|
26
|
+
services,
|
|
27
|
+
activeTab,
|
|
28
|
+
onTabChange,
|
|
29
|
+
}: TabBarProps) {
|
|
30
|
+
return (
|
|
31
|
+
<Box paddingX={1} gap={2} justifyContent="space-between">
|
|
32
|
+
<Box gap={2}>
|
|
33
|
+
{services.map((service, index) => {
|
|
34
|
+
const isActive = index === activeTab
|
|
35
|
+
const statusColor = STATUS_COLORS[service.status]
|
|
36
|
+
const statusIcon = STATUS_ICONS[service.status]
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</Box>
|
|
55
|
-
);
|
|
38
|
+
return (
|
|
39
|
+
<Box key={service.id}>
|
|
40
|
+
<Text
|
|
41
|
+
color={isActive ? "white" : "gray"}
|
|
42
|
+
backgroundColor={isActive ? "blue" : undefined}
|
|
43
|
+
bold={isActive}
|
|
44
|
+
>
|
|
45
|
+
{" "}
|
|
46
|
+
<Text color={statusColor}>{statusIcon}</Text>{" "}
|
|
47
|
+
{service.name}{" "}
|
|
48
|
+
</Text>
|
|
49
|
+
</Box>
|
|
50
|
+
)
|
|
51
|
+
})}
|
|
52
|
+
</Box>
|
|
53
|
+
<Box>
|
|
54
|
+
<Text color="gray">←/→ switch tabs</Text>
|
|
55
|
+
</Box>
|
|
56
|
+
</Box>
|
|
57
|
+
)
|
|
56
58
|
}
|