brainrot-cli 0.1.0
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 +372 -0
- package/dist/AchievementNotification.d.ts +28 -0
- package/dist/AchievementNotification.d.ts.map +1 -0
- package/dist/AchievementNotification.js +74 -0
- package/dist/AchievementNotification.js.map +1 -0
- package/dist/GameSelector.d.ts +25 -0
- package/dist/GameSelector.d.ts.map +1 -0
- package/dist/GameSelector.js +105 -0
- package/dist/GameSelector.js.map +1 -0
- package/dist/HelpOverlay.d.ts +15 -0
- package/dist/HelpOverlay.d.ts.map +1 -0
- package/dist/HelpOverlay.js +134 -0
- package/dist/HelpOverlay.js.map +1 -0
- package/dist/Layout.d.ts +49 -0
- package/dist/Layout.d.ts.map +1 -0
- package/dist/Layout.js +83 -0
- package/dist/Layout.js.map +1 -0
- package/dist/Leaderboard.d.ts +46 -0
- package/dist/Leaderboard.d.ts.map +1 -0
- package/dist/Leaderboard.js +68 -0
- package/dist/Leaderboard.js.map +1 -0
- package/dist/LogViewer.d.ts +33 -0
- package/dist/LogViewer.d.ts.map +1 -0
- package/dist/LogViewer.js +179 -0
- package/dist/LogViewer.js.map +1 -0
- package/dist/LoopAlertOverlay.d.ts +15 -0
- package/dist/LoopAlertOverlay.d.ts.map +1 -0
- package/dist/LoopAlertOverlay.js +17 -0
- package/dist/LoopAlertOverlay.js.map +1 -0
- package/dist/LoopManagementPanel.d.ts +44 -0
- package/dist/LoopManagementPanel.d.ts.map +1 -0
- package/dist/LoopManagementPanel.js +220 -0
- package/dist/LoopManagementPanel.js.map +1 -0
- package/dist/SettingsMenu.d.ts +22 -0
- package/dist/SettingsMenu.d.ts.map +1 -0
- package/dist/SettingsMenu.js +367 -0
- package/dist/SettingsMenu.js.map +1 -0
- package/dist/SplitPane.d.ts +63 -0
- package/dist/SplitPane.d.ts.map +1 -0
- package/dist/SplitPane.js +104 -0
- package/dist/SplitPane.js.map +1 -0
- package/dist/StatsMenu.d.ts +15 -0
- package/dist/StatsMenu.d.ts.map +1 -0
- package/dist/StatsMenu.js +230 -0
- package/dist/StatsMenu.js.map +1 -0
- package/dist/StatusBar.d.ts +58 -0
- package/dist/StatusBar.d.ts.map +1 -0
- package/dist/StatusBar.js +106 -0
- package/dist/StatusBar.js.map +1 -0
- package/dist/__tests__/ralph-loop-parser.test.d.ts +2 -0
- package/dist/__tests__/ralph-loop-parser.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-loop-parser.test.js +143 -0
- package/dist/__tests__/ralph-loop-parser.test.js.map +1 -0
- package/dist/claude-code-process.d.ts +76 -0
- package/dist/claude-code-process.d.ts.map +1 -0
- package/dist/claude-code-process.js +221 -0
- package/dist/claude-code-process.js.map +1 -0
- package/dist/cli.d.ts +42 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +265 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +206 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +270 -0
- package/dist/config.js.map +1 -0
- package/dist/game-types.d.ts +177 -0
- package/dist/game-types.d.ts.map +1 -0
- package/dist/game-types.js +55 -0
- package/dist/game-types.js.map +1 -0
- package/dist/games/MinesweeperGame.d.ts +15 -0
- package/dist/games/MinesweeperGame.d.ts.map +1 -0
- package/dist/games/MinesweeperGame.js +555 -0
- package/dist/games/MinesweeperGame.js.map +1 -0
- package/dist/games/PongGame.d.ts +15 -0
- package/dist/games/PongGame.d.ts.map +1 -0
- package/dist/games/PongGame.js +379 -0
- package/dist/games/PongGame.js.map +1 -0
- package/dist/games/SnakeGame.d.ts +15 -0
- package/dist/games/SnakeGame.d.ts.map +1 -0
- package/dist/games/SnakeGame.js +333 -0
- package/dist/games/SnakeGame.js.map +1 -0
- package/dist/games/TetrisGame.d.ts +15 -0
- package/dist/games/TetrisGame.d.ts.map +1 -0
- package/dist/games/TetrisGame.js +654 -0
- package/dist/games/TetrisGame.js.map +1 -0
- package/dist/games/index.d.ts +23 -0
- package/dist/games/index.d.ts.map +1 -0
- package/dist/games/index.js +47 -0
- package/dist/games/index.js.map +1 -0
- package/dist/high-scores.d.ts +57 -0
- package/dist/high-scores.d.ts.map +1 -0
- package/dist/high-scores.js +230 -0
- package/dist/high-scores.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +264 -0
- package/dist/index.js.map +1 -0
- package/dist/ralph-loop-parser.d.ts +58 -0
- package/dist/ralph-loop-parser.d.ts.map +1 -0
- package/dist/ralph-loop-parser.js +315 -0
- package/dist/ralph-loop-parser.js.map +1 -0
- package/dist/stats.d.ts +142 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +521 -0
- package/dist/stats.js.map +1 -0
- package/dist/styled-components.d.ts +231 -0
- package/dist/styled-components.d.ts.map +1 -0
- package/dist/styled-components.js +192 -0
- package/dist/styled-components.js.map +1 -0
- package/dist/theme.d.ts +301 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +372 -0
- package/dist/theme.js.map +1 -0
- package/dist/themes.d.ts +117 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +296 -0
- package/dist/themes.js.map +1 -0
- package/dist/ui/index.d.ts +13 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +29 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/use-claude-code.d.ts +30 -0
- package/dist/use-claude-code.d.ts.map +1 -0
- package/dist/use-claude-code.js +84 -0
- package/dist/use-claude-code.js.map +1 -0
- package/dist/use-config.d.ts +58 -0
- package/dist/use-config.d.ts.map +1 -0
- package/dist/use-config.js +113 -0
- package/dist/use-config.js.map +1 -0
- package/dist/use-game-loop.d.ts +47 -0
- package/dist/use-game-loop.d.ts.map +1 -0
- package/dist/use-game-loop.js +136 -0
- package/dist/use-game-loop.js.map +1 -0
- package/dist/use-high-scores.d.ts +41 -0
- package/dist/use-high-scores.d.ts.map +1 -0
- package/dist/use-high-scores.js +94 -0
- package/dist/use-high-scores.js.map +1 -0
- package/dist/use-layout-state.d.ts +77 -0
- package/dist/use-layout-state.d.ts.map +1 -0
- package/dist/use-layout-state.js +160 -0
- package/dist/use-layout-state.js.map +1 -0
- package/dist/use-ralph-loop.d.ts +41 -0
- package/dist/use-ralph-loop.d.ts.map +1 -0
- package/dist/use-ralph-loop.js +106 -0
- package/dist/use-ralph-loop.js.map +1 -0
- package/dist/use-spinner.d.ts +46 -0
- package/dist/use-spinner.d.ts.map +1 -0
- package/dist/use-spinner.js +71 -0
- package/dist/use-spinner.js.map +1 -0
- package/dist/use-stats.d.ts +59 -0
- package/dist/use-stats.d.ts.map +1 -0
- package/dist/use-stats.js +150 -0
- package/dist/use-stats.js.map +1 -0
- package/dist/use-terminal-size.d.ts +29 -0
- package/dist/use-terminal-size.d.ts.map +1 -0
- package/dist/use-terminal-size.js +48 -0
- package/dist/use-terminal-size.js.map +1 -0
- package/dist/useTheme.d.ts +76 -0
- package/dist/useTheme.d.ts.map +1 -0
- package/dist/useTheme.js +136 -0
- package/dist/useTheme.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* HelpOverlay Component
|
|
4
|
+
*
|
|
5
|
+
* Modal overlay that displays comprehensive keyboard shortcuts organized by context.
|
|
6
|
+
* Triggered by ? or F1 key globally.
|
|
7
|
+
*/
|
|
8
|
+
import { Box, Text, useInput } from "ink";
|
|
9
|
+
import { useState, useMemo } from "react";
|
|
10
|
+
import { navIcons } from "./theme.js";
|
|
11
|
+
import { useThemeColors } from "./useTheme.js";
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// SHORTCUT DEFINITIONS
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const shortcutSections = [
|
|
16
|
+
{
|
|
17
|
+
id: "global",
|
|
18
|
+
title: "Global Shortcuts",
|
|
19
|
+
shortcuts: [
|
|
20
|
+
{ key: "Ctrl+C", description: "Exit application" },
|
|
21
|
+
{ key: "Ctrl+S", description: "Start/Stop Claude Code loop" },
|
|
22
|
+
{ key: "Tab", description: "Switch focus between panes" },
|
|
23
|
+
{ key: "?", description: "Toggle this help overlay" },
|
|
24
|
+
{ key: "Ctrl+,", description: "Open settings (from menu)" },
|
|
25
|
+
{ key: "Escape", description: "Close overlay/modal/exit game" },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "layout",
|
|
30
|
+
title: "Layout Controls",
|
|
31
|
+
shortcuts: [
|
|
32
|
+
{ key: "Alt+←/→", description: "Resize panes horizontally" },
|
|
33
|
+
{ key: "Alt+↑/↓", description: "Resize panes vertically" },
|
|
34
|
+
{ key: "D", description: "Toggle split direction" },
|
|
35
|
+
{ key: "H", description: "Hide/Show secondary pane" },
|
|
36
|
+
{ key: "R", description: "Reset layout to defaults" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "navigation",
|
|
41
|
+
title: "Menu Navigation",
|
|
42
|
+
shortcuts: [
|
|
43
|
+
{ key: "↑/↓ or K/J", description: "Navigate up/down" },
|
|
44
|
+
{ key: "Enter/Space", description: "Select item" },
|
|
45
|
+
{ key: "1-9", description: "Quick select game" },
|
|
46
|
+
{ key: "L", description: "View logs" },
|
|
47
|
+
{ key: "S", description: "Open stats/achievements" },
|
|
48
|
+
{ key: "Q", description: "Back to menu" },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "games",
|
|
53
|
+
title: "In-Game Controls",
|
|
54
|
+
shortcuts: [
|
|
55
|
+
{ key: "↑↓←→ or WASD", description: "Movement (game-specific)" },
|
|
56
|
+
{ key: "P", description: "Pause/Resume game" },
|
|
57
|
+
{ key: "R", description: "Restart game" },
|
|
58
|
+
{ key: "H", description: "Toggle leaderboard (game over)" },
|
|
59
|
+
{ key: "Q/Escape", description: "Exit to menu" },
|
|
60
|
+
{ key: "Enter", description: "Dismiss loop alert" },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "menus",
|
|
65
|
+
title: "Settings/Menus",
|
|
66
|
+
shortcuts: [
|
|
67
|
+
{ key: "Tab/Shift+Tab", description: "Switch tabs" },
|
|
68
|
+
{ key: "↑↓ or K/J", description: "Navigate options" },
|
|
69
|
+
{ key: "←→ or H/L", description: "Change value" },
|
|
70
|
+
{ key: "Enter", description: "Toggle boolean" },
|
|
71
|
+
{ key: "Ctrl+S", description: "Save settings" },
|
|
72
|
+
{ key: "Escape/Q", description: "Close" },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
function SectionTabs({ sections, activeSection }) {
|
|
77
|
+
const colors = useThemeColors();
|
|
78
|
+
return (_jsx(Box, { marginBottom: 1, flexWrap: "wrap", children: sections.map((section, index) => (_jsxs(Box, { children: [index > 0 && _jsx(Text, { dimColor: true, children: " | " }), _jsx(Text, { bold: activeSection === section.id, color: activeSection === section.id ? colors.primary : colors.textMuted, children: activeSection === section.id ? `[${section.title}]` : section.title })] }, section.id))) }));
|
|
79
|
+
}
|
|
80
|
+
function ShortcutList({ shortcuts }) {
|
|
81
|
+
const colors = useThemeColors();
|
|
82
|
+
return (_jsx(Box, { flexDirection: "column", children: shortcuts.map((shortcut) => (_jsxs(Box, { children: [_jsx(Box, { minWidth: 18, children: _jsx(Text, { bold: true, color: colors.primary, children: shortcut.key }) }), _jsx(Text, { color: colors.text, children: shortcut.description })] }, shortcut.key))) }));
|
|
83
|
+
}
|
|
84
|
+
function HelpFooter() {
|
|
85
|
+
const colors = useThemeColors();
|
|
86
|
+
return (_jsx(Box, { marginTop: 1, paddingTop: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: colors.border, children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: colors.primary, children: "Tab" }), ": Switch sections |", " ", _jsx(Text, { color: colors.primary, children: "Escape/?" }), ": Close help"] }) }));
|
|
87
|
+
}
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// MAIN COMPONENT
|
|
90
|
+
// ============================================================================
|
|
91
|
+
export function HelpOverlay({ hasFocus, onClose }) {
|
|
92
|
+
const [activeSection, setActiveSection] = useState("global");
|
|
93
|
+
const colors = useThemeColors();
|
|
94
|
+
const currentSection = useMemo(() => shortcutSections.find((s) => s.id === activeSection) ?? shortcutSections[0], [activeSection]);
|
|
95
|
+
// Handle tab switching
|
|
96
|
+
const handleTabChange = (direction) => {
|
|
97
|
+
const currentIdx = shortcutSections.findIndex((s) => s.id === activeSection);
|
|
98
|
+
let newIdx;
|
|
99
|
+
if (direction === "next") {
|
|
100
|
+
newIdx = (currentIdx + 1) % shortcutSections.length;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
newIdx = currentIdx <= 0 ? shortcutSections.length - 1 : currentIdx - 1;
|
|
104
|
+
}
|
|
105
|
+
setActiveSection(shortcutSections[newIdx].id);
|
|
106
|
+
};
|
|
107
|
+
// Keyboard input handling
|
|
108
|
+
useInput((input, key) => {
|
|
109
|
+
if (!hasFocus)
|
|
110
|
+
return;
|
|
111
|
+
// Close overlay
|
|
112
|
+
if (key.escape || input === "?" || input === "q" || input === "Q") {
|
|
113
|
+
onClose();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Switch tabs
|
|
117
|
+
if (key.tab) {
|
|
118
|
+
handleTabChange(key.shift ? "prev" : "next");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Arrow keys for section navigation
|
|
122
|
+
if (key.leftArrow) {
|
|
123
|
+
handleTabChange("prev");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (key.rightArrow) {
|
|
127
|
+
handleTabChange("next");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}, { isActive: hasFocus });
|
|
131
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { bold: true, color: colors.primary, children: [navIcons.arrowRight, " Keyboard Shortcuts"] }), _jsx(Text, { dimColor: true, children: " - Press ? or Escape to close" })] }), _jsx(SectionTabs, { sections: shortcutSections, activeSection: activeSection }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.border, paddingX: 1, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: colors.accent, children: currentSection.title }) }), _jsx(ShortcutList, { shortcuts: currentSection.shortcuts })] }), _jsx(HelpFooter, {})] }));
|
|
132
|
+
}
|
|
133
|
+
export default HelpOverlay;
|
|
134
|
+
//# sourceMappingURL=HelpOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HelpOverlay.js","sourceRoot":"","sources":["../src/HelpOverlay.tsx"],"names":[],"mappings":";AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AA0B/C,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E,MAAM,gBAAgB,GAAsB;IAC1C;QACE,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,kBAAkB;QACzB,SAAS,EAAE;YACT,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE;YAClD,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,6BAA6B,EAAE;YAC7D,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,4BAA4B,EAAE;YACzD,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,0BAA0B,EAAE;YACrD,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE;YAC3D,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,+BAA+B,EAAE;SAChE;KACF;IACD;QACE,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,iBAAiB;QACxB,SAAS,EAAE;YACT,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,2BAA2B,EAAE;YAC5D,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,yBAAyB,EAAE;YAC1D,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,wBAAwB,EAAE;YACnD,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,0BAA0B,EAAE;YACrD,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,0BAA0B,EAAE;SACtD;KACF;IACD;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,iBAAiB;QACxB,SAAS,EAAE;YACT,EAAE,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE;YACtD,EAAE,GAAG,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE;YAClD,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE;YAChD,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,WAAW,EAAE;YACtC,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,yBAAyB,EAAE;YACpD,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,cAAc,EAAE;SAC1C;KACF;IACD;QACE,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,kBAAkB;QACzB,SAAS,EAAE;YACT,EAAE,GAAG,EAAE,cAAc,EAAE,WAAW,EAAE,0BAA0B,EAAE;YAChE,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,mBAAmB,EAAE;YAC9C,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,cAAc,EAAE;YACzC,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,gCAAgC,EAAE;YAC3D,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE;YAChD,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE;SACpD;KACF;IACD;QACE,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,gBAAgB;QACvB,SAAS,EAAE;YACT,EAAE,GAAG,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE;YACpD,EAAE,GAAG,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE;YACrD,EAAE,GAAG,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE;YACjD,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE;YAC/C,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE;YAC/C,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE;SAC1C;KACF;CACF,CAAC;AAWF,SAAS,WAAW,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAoB;IAChE,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,OAAO,CACL,KAAC,GAAG,IAAC,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAC,MAAM,YAClC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAChC,MAAC,GAAG,eACD,KAAK,GAAG,CAAC,IAAI,KAAC,IAAI,IAAC,QAAQ,0BAAW,EACvC,KAAC,IAAI,IACH,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,EAAE,EAClC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,YAEtE,aAAa,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,GAC/D,KAPC,OAAO,CAAC,EAAE,CAQd,CACP,CAAC,GACE,CACP,CAAC;AACJ,CAAC;AAMD,SAAS,YAAY,CAAC,EAAE,SAAS,EAAqB;IACpD,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,OAAO,CACL,KAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,YACxB,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAC3B,MAAC,GAAG,eACF,KAAC,GAAG,IAAC,QAAQ,EAAE,EAAE,YACf,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAE,MAAM,CAAC,OAAO,YAC7B,QAAQ,CAAC,GAAG,GACR,GACH,EACN,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,IAAI,YAAG,QAAQ,CAAC,WAAW,GAAQ,KAN/C,QAAQ,CAAC,GAAG,CAOhB,CACP,CAAC,GACE,CACP,CAAC;AACJ,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,OAAO,CACL,KAAC,GAAG,IACF,SAAS,EAAE,CAAC,EACZ,UAAU,EAAE,CAAC,EACb,WAAW,EAAC,QAAQ,EACpB,SAAS,QACT,YAAY,EAAE,KAAK,EACnB,UAAU,EAAE,KAAK,EACjB,WAAW,EAAE,KAAK,EAClB,WAAW,EAAE,MAAM,CAAC,MAAM,YAE1B,MAAC,IAAI,IAAC,QAAQ,mBACZ,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,OAAO,oBAAY,yBAAoB,GAAG,EAC9D,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,OAAO,yBAAiB,oBACvC,GACH,CACP,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,MAAM,UAAU,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAoB;IACjE,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAc,QAAQ,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAEhC,MAAM,cAAc,GAAG,OAAO,CAC5B,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,aAAa,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,EACjF,CAAC,aAAa,CAAC,CAChB,CAAC;IAEF,uBAAuB;IACvB,MAAM,eAAe,GAAG,CAAC,SAA0B,EAAE,EAAE;QACrD,MAAM,UAAU,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,aAAa,CAAC,CAAC;QAC7E,IAAI,MAAc,CAAC;QACnB,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;YACzB,MAAM,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC;QACtD,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC;QAC1E,CAAC;QACD,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC;IAEF,0BAA0B;IAC1B,QAAQ,CACN,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACb,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,gBAAgB;QAChB,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YAClE,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,cAAc;QACd,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;YACZ,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,oCAAoC;QACpC,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClB,eAAe,CAAC,MAAM,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnB,eAAe,CAAC,MAAM,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;IACH,CAAC,EACD,EAAE,QAAQ,EAAE,QAAQ,EAAE,CACvB,CAAC;IAEF,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,OAAO,EAAE,CAAC,aAEpC,MAAC,GAAG,IAAC,YAAY,EAAE,CAAC,aAClB,MAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAE,MAAM,CAAC,OAAO,aAC7B,QAAQ,CAAC,UAAU,2BACf,EACP,KAAC,IAAI,IAAC,QAAQ,oDAAqC,IAC/C,EAGN,KAAC,WAAW,IAAC,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,GAAI,EAGzE,MAAC,GAAG,IACF,aAAa,EAAC,QAAQ,EACtB,WAAW,EAAC,OAAO,EACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAC1B,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,CAAC,aAEX,KAAC,GAAG,IAAC,YAAY,EAAE,CAAC,YAClB,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,YAC5B,cAAc,CAAC,KAAK,GAChB,GACH,EACN,KAAC,YAAY,IAAC,SAAS,EAAE,cAAc,CAAC,SAAS,GAAI,IACjD,EAGN,KAAC,UAAU,KAAG,IACV,CACP,CAAC;AACJ,CAAC;AAED,eAAe,WAAW,CAAC"}
|
package/dist/Layout.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Layout component that provides the split-pane layout for the app.
|
|
3
|
+
* Combines terminal size tracking, layout state management, and the SplitPane component.
|
|
4
|
+
*/
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { useTerminalSize } from "./use-terminal-size.js";
|
|
7
|
+
import { useLayoutState, type UseLayoutStateOptions } from "./use-layout-state.js";
|
|
8
|
+
export interface LayoutProps {
|
|
9
|
+
/** Content for the game area (left/top pane) */
|
|
10
|
+
gameArea: ReactNode;
|
|
11
|
+
/** Content for the management area (right/bottom pane) */
|
|
12
|
+
managementArea: ReactNode;
|
|
13
|
+
/** Title for the game pane */
|
|
14
|
+
gameTitle?: string;
|
|
15
|
+
/** Title for the management pane */
|
|
16
|
+
managementTitle?: string;
|
|
17
|
+
/** Layout configuration options */
|
|
18
|
+
layoutOptions?: UseLayoutStateOptions;
|
|
19
|
+
/** Custom header content */
|
|
20
|
+
header?: ReactNode;
|
|
21
|
+
/** Custom footer content */
|
|
22
|
+
footer?: ReactNode;
|
|
23
|
+
/** Whether the layout handles keyboard input */
|
|
24
|
+
handleInput?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Main layout component that provides a resizable split-pane interface.
|
|
28
|
+
*
|
|
29
|
+
* Features:
|
|
30
|
+
* - Responsive to terminal size changes
|
|
31
|
+
* - Graceful handling of small terminal windows
|
|
32
|
+
* - Keyboard shortcuts for resizing and layout control
|
|
33
|
+
* - Persisted layout state during session
|
|
34
|
+
*
|
|
35
|
+
* Keyboard shortcuts:
|
|
36
|
+
* - Tab: Toggle focus between panes
|
|
37
|
+
* - Alt+Arrow: Resize panes
|
|
38
|
+
* - D: Toggle split direction
|
|
39
|
+
* - H: Hide/show secondary pane
|
|
40
|
+
* - R: Reset layout to defaults
|
|
41
|
+
*/
|
|
42
|
+
export declare function Layout({ gameArea, managementArea, gameTitle, managementTitle, layoutOptions, header, footer, handleInput, }: LayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
/**
|
|
44
|
+
* Export the layout state hook for external access to layout controls
|
|
45
|
+
*/
|
|
46
|
+
export { useLayoutState, useTerminalSize };
|
|
47
|
+
export type { UseLayoutStateOptions, UseLayoutStateResult } from "./use-layout-state.js";
|
|
48
|
+
export type { UseTerminalSizeResult } from "./use-terminal-size.js";
|
|
49
|
+
//# sourceMappingURL=Layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../src/Layout.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EACL,eAAe,EAGhB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAInF,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,QAAQ,EAAE,SAAS,CAAC;IACpB,0DAA0D;IAC1D,cAAc,EAAE,SAAS,CAAC;IAC1B,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mCAAmC;IACnC,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,4BAA4B;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA+DD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,cAAc,EACd,SAAkB,EAClB,eAA8B,EAC9B,aAAa,EACb,MAAM,EACN,MAAM,EACN,WAAkB,GACnB,EAAE,WAAW,2CAqIb;AAED;;GAEG;AACH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC;AAC3C,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACzF,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/Layout.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Main Layout component that provides the split-pane layout for the app.
|
|
4
|
+
* Combines terminal size tracking, layout state management, and the SplitPane component.
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text, useInput } from "ink";
|
|
7
|
+
import { SplitPane } from "./SplitPane.js";
|
|
8
|
+
import { useTerminalSize, MIN_WIDTH, MIN_HEIGHT, } from "./use-terminal-size.js";
|
|
9
|
+
import { useLayoutState } from "./use-layout-state.js";
|
|
10
|
+
import { navIcons } from "./theme.js";
|
|
11
|
+
import { useThemeColors } from "./useTheme.js";
|
|
12
|
+
function TooSmallWarning({ width, height, minWidth, minHeight, }) {
|
|
13
|
+
const colors = useThemeColors();
|
|
14
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 1, children: [_jsx(Text, { color: colors.warning, bold: true, children: "Terminal too small" }), _jsxs(Text, { color: colors.textMuted, children: ["Current: ", width, "x", height] }), _jsxs(Text, { color: colors.textMuted, children: ["Required: ", minWidth, "x", minHeight] }), _jsx(Text, { color: colors.warning, children: "Please resize your terminal" })] }));
|
|
15
|
+
}
|
|
16
|
+
function LayoutHelp({ direction, showSecondary, isSmall }) {
|
|
17
|
+
if (isSmall) {
|
|
18
|
+
return (_jsx(Text, { dimColor: true, children: "Tab: Focus | H: Toggle pane | ?: Help" }));
|
|
19
|
+
}
|
|
20
|
+
const resizeKeys = direction === "horizontal" ? "Alt+←/→: Resize" : "Alt+↑/↓: Resize";
|
|
21
|
+
return (_jsxs(Text, { dimColor: true, children: ["Tab: Focus | ", resizeKeys, " | D: Direction | H: ", showSecondary ? "Hide" : "Show", " ", "pane | R: Reset | ?: Help"] }));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Main layout component that provides a resizable split-pane interface.
|
|
25
|
+
*
|
|
26
|
+
* Features:
|
|
27
|
+
* - Responsive to terminal size changes
|
|
28
|
+
* - Graceful handling of small terminal windows
|
|
29
|
+
* - Keyboard shortcuts for resizing and layout control
|
|
30
|
+
* - Persisted layout state during session
|
|
31
|
+
*
|
|
32
|
+
* Keyboard shortcuts:
|
|
33
|
+
* - Tab: Toggle focus between panes
|
|
34
|
+
* - Alt+Arrow: Resize panes
|
|
35
|
+
* - D: Toggle split direction
|
|
36
|
+
* - H: Hide/show secondary pane
|
|
37
|
+
* - R: Reset layout to defaults
|
|
38
|
+
*/
|
|
39
|
+
export function Layout({ gameArea, managementArea, gameTitle = "Game", managementTitle = "Management", layoutOptions, header, footer, handleInput = true, }) {
|
|
40
|
+
const terminalSize = useTerminalSize();
|
|
41
|
+
const layout = useLayoutState(layoutOptions);
|
|
42
|
+
const colors = useThemeColors();
|
|
43
|
+
// Handle layout-specific keyboard shortcuts
|
|
44
|
+
useInput((input) => {
|
|
45
|
+
if (!handleInput)
|
|
46
|
+
return;
|
|
47
|
+
// 'd' to toggle direction
|
|
48
|
+
if (input === "d" || input === "D") {
|
|
49
|
+
layout.toggleDirection();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// 'h' to toggle secondary pane
|
|
53
|
+
if (input === "h" || input === "H") {
|
|
54
|
+
layout.toggleSecondary();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// 'r' to reset layout
|
|
58
|
+
if (input === "r" || input === "R") {
|
|
59
|
+
layout.resetLayout();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}, { isActive: handleInput });
|
|
63
|
+
// Show warning if terminal is too small
|
|
64
|
+
if (terminalSize.isTooSmall) {
|
|
65
|
+
return (_jsx(TooSmallWarning, { width: terminalSize.width, height: terminalSize.height, minWidth: MIN_WIDTH, minHeight: MIN_HEIGHT }));
|
|
66
|
+
}
|
|
67
|
+
// Calculate available space for the split pane
|
|
68
|
+
// Account for header (3 rows), footer (2 rows), and help text (1 row)
|
|
69
|
+
const headerHeight = header ? 3 : 0;
|
|
70
|
+
const footerHeight = footer ? 2 : 0;
|
|
71
|
+
const helpHeight = 1;
|
|
72
|
+
const availableHeight = Math.max(1, terminalSize.height - headerHeight - footerHeight - helpHeight);
|
|
73
|
+
const availableWidth = terminalSize.width;
|
|
74
|
+
// Create pane content with titles
|
|
75
|
+
const firstPaneContent = (_jsxs(Box, { flexDirection: "column", width: "100%", height: "100%", children: [_jsx(Box, { children: _jsxs(Text, { color: layout.state.focusedPane === 0 ? colors.primary : colors.textMuted, bold: layout.state.focusedPane === 0, children: [layout.state.focusedPane === 0 ? `${navIcons.arrowRight} ` : " ", gameTitle] }) }), _jsx(Box, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: gameArea })] }));
|
|
76
|
+
const secondPaneContent = (_jsxs(Box, { flexDirection: "column", width: "100%", height: "100%", children: [_jsx(Box, { children: _jsxs(Text, { color: layout.state.focusedPane === 1 ? colors.primary : colors.textMuted, bold: layout.state.focusedPane === 1, children: [layout.state.focusedPane === 1 ? `${navIcons.arrowRight} ` : " ", managementTitle] }) }), _jsx(Box, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: managementArea })] }));
|
|
77
|
+
return (_jsxs(Box, { flexDirection: "column", width: terminalSize.width, height: terminalSize.height, children: [header && (_jsx(Box, { height: headerHeight, flexShrink: 0, children: header })), _jsx(Box, { flexGrow: 1, height: availableHeight, children: _jsx(SplitPane, { first: firstPaneContent, second: secondPaneContent, direction: layout.state.direction, splitRatio: layout.state.splitRatio, width: availableWidth, height: availableHeight, isResizing: layout.state.isResizing, showSecondary: layout.state.showSecondary, focusedPane: layout.state.focusedPane, onResize: layout.adjustSplitRatio, onToggleFocus: layout.toggleFocus, handleInput: handleInput }) }), footer && (_jsx(Box, { height: footerHeight, flexShrink: 0, children: footer })), _jsx(Box, { height: helpHeight, flexShrink: 0, children: _jsx(LayoutHelp, { direction: layout.state.direction, showSecondary: layout.state.showSecondary, isSmall: terminalSize.isSmall }) })] }));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Export the layout state hook for external access to layout controls
|
|
81
|
+
*/
|
|
82
|
+
export { useLayoutState, useTerminalSize };
|
|
83
|
+
//# sourceMappingURL=Layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Layout.js","sourceRoot":"","sources":["../src/Layout.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaderboard Component
|
|
3
|
+
*
|
|
4
|
+
* Displays top 10 scores for a game with timestamps.
|
|
5
|
+
*/
|
|
6
|
+
import { type ScoreEntry } from "./high-scores.js";
|
|
7
|
+
interface LeaderboardProps {
|
|
8
|
+
/** Game title to display */
|
|
9
|
+
title: string;
|
|
10
|
+
/** List of scores to display */
|
|
11
|
+
scores: ScoreEntry[];
|
|
12
|
+
/** Whether lower scores are better (for time-based games) */
|
|
13
|
+
lowerIsBetter?: boolean;
|
|
14
|
+
/** Format function for the score value */
|
|
15
|
+
formatScore?: (score: number) => string;
|
|
16
|
+
/** Highlight this score position (1-indexed) */
|
|
17
|
+
highlightPosition?: number;
|
|
18
|
+
/** Maximum entries to show */
|
|
19
|
+
maxEntries?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format time in mm:ss format
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatTime(seconds: number): string;
|
|
25
|
+
/**
|
|
26
|
+
* Leaderboard display component
|
|
27
|
+
*/
|
|
28
|
+
export declare function Leaderboard({ title, scores, lowerIsBetter: _lowerIsBetter, formatScore, highlightPosition, maxEntries, }: LeaderboardProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
/**
|
|
30
|
+
* Compact leaderboard for displaying in game HUD
|
|
31
|
+
*/
|
|
32
|
+
export declare function CompactLeaderboard({ scores, formatScore, maxEntries, }: {
|
|
33
|
+
scores: ScoreEntry[];
|
|
34
|
+
formatScore?: (score: number) => string;
|
|
35
|
+
maxEntries?: number;
|
|
36
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
37
|
+
/**
|
|
38
|
+
* New high score notification banner
|
|
39
|
+
*/
|
|
40
|
+
export declare function NewHighScoreBanner({ position, score, formatScore, }: {
|
|
41
|
+
position: number;
|
|
42
|
+
score: number;
|
|
43
|
+
formatScore?: (score: number) => string;
|
|
44
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
export default Leaderboard;
|
|
46
|
+
//# sourceMappingURL=Leaderboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Leaderboard.d.ts","sourceRoot":"","sources":["../src/Leaderboard.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,kBAAkB,CAAC;AAGpE,UAAU,gBAAgB;IACxB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AASD;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,MAAM,EACN,aAAa,EAAE,cAAsB,EACrC,WAAgC,EAChC,iBAAiB,EACjB,UAAe,GAChB,EAAE,gBAAgB,2CA6DlB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,WAAgC,EAChC,UAAc,GACf,EAAE;IACD,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,kDAmBA;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,KAAK,EACL,WAAgC,GACjC,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACzC,2CAiCA;AAED,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Leaderboard Component
|
|
4
|
+
*
|
|
5
|
+
* Displays top 10 scores for a game with timestamps.
|
|
6
|
+
*/
|
|
7
|
+
import { Box, Text } from "ink";
|
|
8
|
+
import { formatScoreDate } from "./high-scores.js";
|
|
9
|
+
import { useThemeColors, getThemedRankColor, useTheme } from "./useTheme.js";
|
|
10
|
+
/**
|
|
11
|
+
* Default score formatter
|
|
12
|
+
*/
|
|
13
|
+
function defaultFormatScore(score) {
|
|
14
|
+
return score.toLocaleString();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Format time in mm:ss format
|
|
18
|
+
*/
|
|
19
|
+
export function formatTime(seconds) {
|
|
20
|
+
if (seconds >= 999)
|
|
21
|
+
return "--:--";
|
|
22
|
+
const mins = Math.floor(seconds / 60);
|
|
23
|
+
const secs = seconds % 60;
|
|
24
|
+
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Leaderboard display component
|
|
28
|
+
*/
|
|
29
|
+
export function Leaderboard({ title, scores, lowerIsBetter: _lowerIsBetter = false, formatScore = defaultFormatScore, highlightPosition, maxEntries = 10, }) {
|
|
30
|
+
const colors = useThemeColors();
|
|
31
|
+
const { theme } = useTheme();
|
|
32
|
+
const displayScores = scores.slice(0, maxEntries);
|
|
33
|
+
const hasScores = displayScores.length > 0;
|
|
34
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsx(Text, { bold: true, color: colors.primary, children: title }) }), !hasScores ? (_jsx(Box, { justifyContent: "center", paddingY: 1, children: _jsx(Text, { dimColor: true, children: "No scores yet" }) })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: ["#".padStart(2), " ", "Score".padStart(8), " ", "Date".padStart(10)] }) }), displayScores.map((entry, index) => {
|
|
35
|
+
const position = index + 1;
|
|
36
|
+
const isHighlighted = highlightPosition === position;
|
|
37
|
+
const positionStr = position.toString().padStart(2);
|
|
38
|
+
const scoreStr = formatScore(entry.score).padStart(8);
|
|
39
|
+
const dateStr = formatScoreDate(entry.timestamp).padStart(10);
|
|
40
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: getThemedRankColor(theme, position, isHighlighted), bold: isHighlighted || position === 1, children: [positionStr, " ", scoreStr, " ", dateStr, isHighlighted ? " NEW!" : ""] }) }, index));
|
|
41
|
+
}), displayScores.length < maxEntries && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [maxEntries - displayScores.length, " more slot", maxEntries - displayScores.length !== 1 ? "s" : "", " available"] }) }))] }))] }));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compact leaderboard for displaying in game HUD
|
|
45
|
+
*/
|
|
46
|
+
export function CompactLeaderboard({ scores, formatScore = defaultFormatScore, maxEntries = 5, }) {
|
|
47
|
+
const displayScores = scores.slice(0, maxEntries);
|
|
48
|
+
if (displayScores.length === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, bold: true, children: "Top Scores" }), displayScores.map((entry, index) => (_jsxs(Text, { dimColor: true, children: [(index + 1).toString(), ". ", formatScore(entry.score)] }, index)))] }));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* New high score notification banner
|
|
55
|
+
*/
|
|
56
|
+
export function NewHighScoreBanner({ position, score, formatScore = defaultFormatScore, }) {
|
|
57
|
+
const colors = useThemeColors();
|
|
58
|
+
const positionText = position === 1
|
|
59
|
+
? "1st"
|
|
60
|
+
: position === 2
|
|
61
|
+
? "2nd"
|
|
62
|
+
: position === 3
|
|
63
|
+
? "3rd"
|
|
64
|
+
: `${position}th`;
|
|
65
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", borderStyle: "double", borderColor: colors.success, paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: colors.success, children: "NEW HIGH SCORE!" }), _jsx(Text, { children: _jsx(Text, { color: colors.accent, bold: true, children: formatScore(score) }) }), _jsxs(Text, { children: ["You ranked ", _jsx(Text, { color: colors.primary, children: positionText }), " place!"] })] }));
|
|
66
|
+
}
|
|
67
|
+
export default Leaderboard;
|
|
68
|
+
//# sourceMappingURL=Leaderboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Leaderboard.js","sourceRoot":"","sources":["../src/Leaderboard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogViewer Component
|
|
3
|
+
*
|
|
4
|
+
* Real-time log streaming component with scrollable view,
|
|
5
|
+
* condensed/full view toggle, and formatted output.
|
|
6
|
+
*/
|
|
7
|
+
import type { ClaudeCodeOutput } from "./use-claude-code.js";
|
|
8
|
+
export interface LogViewerProps {
|
|
9
|
+
/** Array of log outputs from Claude Code */
|
|
10
|
+
logs: ClaudeCodeOutput[];
|
|
11
|
+
/** Maximum number of lines to display in the viewport */
|
|
12
|
+
maxVisibleLines?: number;
|
|
13
|
+
/** Whether the component has focus for keyboard input */
|
|
14
|
+
hasFocus?: boolean;
|
|
15
|
+
/** Height of the log viewer area */
|
|
16
|
+
height?: number;
|
|
17
|
+
/** Initial view mode */
|
|
18
|
+
initialViewMode?: "condensed" | "full";
|
|
19
|
+
}
|
|
20
|
+
export interface LogLine {
|
|
21
|
+
id: number;
|
|
22
|
+
timestamp: Date;
|
|
23
|
+
type: "stdout" | "stderr";
|
|
24
|
+
content: string;
|
|
25
|
+
/** Original log entry index for tracking */
|
|
26
|
+
sourceIndex: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Main LogViewer component
|
|
30
|
+
*/
|
|
31
|
+
export declare function LogViewer({ logs, maxVisibleLines, hasFocus, height, initialViewMode, }: LogViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export default LogViewer;
|
|
33
|
+
//# sourceMappingURL=LogViewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogViewer.d.ts","sourceRoot":"","sources":["../src/LogViewer.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACzB,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,eAAe,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;CACrB;AAsKD;;GAEG;AACH,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,eAAoB,EACpB,QAAgB,EAChB,MAAM,EACN,eAA6B,GAC9B,EAAE,cAAc,2CA0JhB;AAED,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* LogViewer Component
|
|
4
|
+
*
|
|
5
|
+
* Real-time log streaming component with scrollable view,
|
|
6
|
+
* condensed/full view toggle, and formatted output.
|
|
7
|
+
*/
|
|
8
|
+
import { Box, Text, useInput } from "ink";
|
|
9
|
+
import { useState, useEffect, useRef, useMemo } from "react";
|
|
10
|
+
import { useThemeColors } from "./useTheme.js";
|
|
11
|
+
// ANSI escape code regex for stripping colors in condensed view
|
|
12
|
+
const ANSI_REGEX =
|
|
13
|
+
// eslint-disable-next-line no-control-regex
|
|
14
|
+
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
|
15
|
+
/**
|
|
16
|
+
* Strip ANSI escape codes from text
|
|
17
|
+
*/
|
|
18
|
+
function stripAnsi(text) {
|
|
19
|
+
return text.replace(ANSI_REGEX, "");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format timestamp for log display
|
|
23
|
+
*/
|
|
24
|
+
function formatTimestamp(date) {
|
|
25
|
+
return date.toLocaleTimeString("en-US", {
|
|
26
|
+
hour12: false,
|
|
27
|
+
hour: "2-digit",
|
|
28
|
+
minute: "2-digit",
|
|
29
|
+
second: "2-digit",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Process logs into display lines
|
|
34
|
+
*/
|
|
35
|
+
function processLogs(logs, viewMode) {
|
|
36
|
+
const lines = [];
|
|
37
|
+
let lineId = 0;
|
|
38
|
+
for (let i = 0; i < logs.length; i++) {
|
|
39
|
+
const log = logs[i];
|
|
40
|
+
const content = viewMode === "condensed" ? stripAnsi(log.content) : log.content;
|
|
41
|
+
// Split multi-line content into separate lines
|
|
42
|
+
const contentLines = content.split("\n");
|
|
43
|
+
for (const line of contentLines) {
|
|
44
|
+
// In condensed mode, skip empty lines and whitespace-only lines
|
|
45
|
+
if (viewMode === "condensed" && line.trim() === "") {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
lines.push({
|
|
49
|
+
id: lineId++,
|
|
50
|
+
timestamp: log.timestamp,
|
|
51
|
+
type: log.type,
|
|
52
|
+
content: line,
|
|
53
|
+
sourceIndex: i,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return lines;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Single log line component
|
|
61
|
+
*/
|
|
62
|
+
function LogLineDisplay({ line, showTimestamp, viewMode, }) {
|
|
63
|
+
const colors = useThemeColors();
|
|
64
|
+
const textColor = line.type === "stderr" ? colors.error : colors.text;
|
|
65
|
+
return (_jsxs(Box, { children: [showTimestamp && (_jsxs(Text, { dimColor: true, children: ["[", formatTimestamp(line.timestamp), "] "] })), viewMode === "full" && (_jsx(Text, { color: line.type === "stderr" ? colors.error : colors.textMuted, children: line.type === "stderr" ? "ERR " : "OUT " })), _jsx(Text, { color: textColor, wrap: "truncate", children: line.content })] }));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Scrollbar indicator component
|
|
69
|
+
*/
|
|
70
|
+
function ScrollIndicator({ currentPosition, totalLines, visibleLines, }) {
|
|
71
|
+
if (totalLines <= visibleLines) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const scrollPercentage = totalLines > visibleLines
|
|
75
|
+
? (currentPosition / (totalLines - visibleLines)) * 100
|
|
76
|
+
: 0;
|
|
77
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsx(Text, { dimColor: true, children: scrollPercentage < 100 ? "^" : " " }), _jsxs(Text, { dimColor: true, children: [Math.round(scrollPercentage), "%"] }), _jsx(Text, { dimColor: true, children: scrollPercentage > 0 ? "v" : " " })] }));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Log viewer header with controls
|
|
81
|
+
*/
|
|
82
|
+
function LogViewerHeader({ viewMode, totalLines, autoScroll, hasFocus, }) {
|
|
83
|
+
const colors = useThemeColors();
|
|
84
|
+
return (_jsxs(Box, { justifyContent: "space-between", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: colors.primary, children: "Logs" }), _jsxs(Text, { dimColor: true, children: [" (", totalLines, " lines)"] }), autoScroll && _jsx(Text, { color: colors.success, children: " [AUTO]" })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "View: " }), _jsx(Text, { color: viewMode === "full" ? colors.primary : colors.textMuted, children: viewMode.toUpperCase() }), hasFocus && (_jsx(Text, { dimColor: true, children: " | V: toggle view | J/K: scroll" }))] })] }));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Main LogViewer component
|
|
88
|
+
*/
|
|
89
|
+
export function LogViewer({ logs, maxVisibleLines = 20, hasFocus = false, height, initialViewMode = "condensed", }) {
|
|
90
|
+
const [viewMode, setViewMode] = useState(initialViewMode);
|
|
91
|
+
const [scrollPosition, setScrollPosition] = useState(0);
|
|
92
|
+
const [autoScroll, setAutoScroll] = useState(true);
|
|
93
|
+
const lastLogCountRef = useRef(logs.length);
|
|
94
|
+
// Process logs into display lines
|
|
95
|
+
const displayLines = useMemo(() => processLogs(logs, viewMode), [logs, viewMode]);
|
|
96
|
+
// Calculate visible area
|
|
97
|
+
const effectiveHeight = height ? Math.max(height - 4, 5) : maxVisibleLines;
|
|
98
|
+
const totalLines = displayLines.length;
|
|
99
|
+
const maxScroll = Math.max(0, totalLines - effectiveHeight);
|
|
100
|
+
// Auto-scroll when new logs arrive
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (logs.length > lastLogCountRef.current && autoScroll) {
|
|
103
|
+
setScrollPosition(maxScroll);
|
|
104
|
+
}
|
|
105
|
+
lastLogCountRef.current = logs.length;
|
|
106
|
+
}, [logs.length, maxScroll, autoScroll]);
|
|
107
|
+
// Handle keyboard input when focused
|
|
108
|
+
useInput((input, key) => {
|
|
109
|
+
if (!hasFocus)
|
|
110
|
+
return;
|
|
111
|
+
// Toggle view mode with 'v'
|
|
112
|
+
if (input === "v" || input === "V") {
|
|
113
|
+
setViewMode((current) => (current === "condensed" ? "full" : "condensed"));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Scroll up with 'k' or arrow up
|
|
117
|
+
if (input === "k" || key.upArrow) {
|
|
118
|
+
setScrollPosition((pos) => Math.max(0, pos - 1));
|
|
119
|
+
setAutoScroll(false);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Scroll down with 'j' or arrow down
|
|
123
|
+
if (input === "j" || key.downArrow) {
|
|
124
|
+
setScrollPosition((pos) => {
|
|
125
|
+
const newPos = Math.min(maxScroll, pos + 1);
|
|
126
|
+
// Re-enable auto-scroll if we're at the bottom
|
|
127
|
+
if (newPos === maxScroll) {
|
|
128
|
+
setAutoScroll(true);
|
|
129
|
+
}
|
|
130
|
+
return newPos;
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Page up with 'u' or page up
|
|
135
|
+
if (input === "u" || key.pageUp) {
|
|
136
|
+
setScrollPosition((pos) => Math.max(0, pos - effectiveHeight));
|
|
137
|
+
setAutoScroll(false);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Page down with 'd' or page down
|
|
141
|
+
if (input === "d" || key.pageDown) {
|
|
142
|
+
setScrollPosition((pos) => {
|
|
143
|
+
const newPos = Math.min(maxScroll, pos + effectiveHeight);
|
|
144
|
+
if (newPos === maxScroll) {
|
|
145
|
+
setAutoScroll(true);
|
|
146
|
+
}
|
|
147
|
+
return newPos;
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Jump to top with 'g'
|
|
152
|
+
if (input === "g") {
|
|
153
|
+
setScrollPosition(0);
|
|
154
|
+
setAutoScroll(false);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Jump to bottom with 'G'
|
|
158
|
+
if (input === "G") {
|
|
159
|
+
setScrollPosition(maxScroll);
|
|
160
|
+
setAutoScroll(true);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Toggle auto-scroll with 'a'
|
|
164
|
+
if (input === "a" || input === "A") {
|
|
165
|
+
setAutoScroll((current) => !current);
|
|
166
|
+
if (!autoScroll) {
|
|
167
|
+
setScrollPosition(maxScroll);
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}, { isActive: hasFocus });
|
|
172
|
+
// Get visible lines based on scroll position
|
|
173
|
+
const visibleLines = displayLines.slice(scrollPosition, scrollPosition + effectiveHeight);
|
|
174
|
+
// Show timestamps only in full mode or every few lines in condensed
|
|
175
|
+
const showTimestamps = viewMode === "full";
|
|
176
|
+
return (_jsxs(Box, { flexDirection: "column", height: height, padding: 1, children: [_jsx(LogViewerHeader, { viewMode: viewMode, totalLines: totalLines, autoScroll: autoScroll, hasFocus: hasFocus }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, children: visibleLines.length === 0 ? (_jsx(Text, { dimColor: true, children: "No logs yet. Start Claude Code to see output." })) : (visibleLines.map((line) => (_jsx(LogLineDisplay, { line: line, showTimestamp: showTimestamps, viewMode: viewMode }, line.id)))) }), _jsx(ScrollIndicator, { currentPosition: scrollPosition, totalLines: totalLines, visibleLines: effectiveHeight })] }), totalLines > effectiveHeight && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Lines ", scrollPosition + 1, "-", Math.min(scrollPosition + effectiveHeight, totalLines), " of", " ", totalLines] }) }))] }));
|
|
177
|
+
}
|
|
178
|
+
export default LogViewer;
|
|
179
|
+
//# sourceMappingURL=LogViewer.js.map
|