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,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for managing and persisting layout state during a session.
|
|
3
|
+
* Handles split ratios, pane visibility, and layout preferences.
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useCallback, useMemo } from "react";
|
|
6
|
+
const DEFAULT_OPTIONS = {
|
|
7
|
+
initialDirection: "horizontal",
|
|
8
|
+
initialSplitRatio: 0.5,
|
|
9
|
+
minRatio: 0.2,
|
|
10
|
+
maxRatio: 0.8,
|
|
11
|
+
resizeStep: 0.05,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* React hook for managing layout state with resizable panes.
|
|
15
|
+
*
|
|
16
|
+
* @param options - Configuration options for the layout
|
|
17
|
+
* @returns Layout state and control functions
|
|
18
|
+
*/
|
|
19
|
+
export function useLayoutState(options = {}) {
|
|
20
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
21
|
+
const [state, setState] = useState(() => ({
|
|
22
|
+
direction: opts.initialDirection,
|
|
23
|
+
splitRatio: opts.initialSplitRatio,
|
|
24
|
+
isResizing: false,
|
|
25
|
+
showSecondary: true,
|
|
26
|
+
focusedPane: 0,
|
|
27
|
+
}));
|
|
28
|
+
const clampRatio = useCallback((ratio) => {
|
|
29
|
+
return Math.max(opts.minRatio, Math.min(opts.maxRatio, ratio));
|
|
30
|
+
}, [opts.minRatio, opts.maxRatio]);
|
|
31
|
+
const setSplitRatio = useCallback((ratio) => {
|
|
32
|
+
setState((prev) => ({
|
|
33
|
+
...prev,
|
|
34
|
+
splitRatio: clampRatio(ratio),
|
|
35
|
+
}));
|
|
36
|
+
}, [clampRatio]);
|
|
37
|
+
const adjustSplitRatio = useCallback((delta) => {
|
|
38
|
+
setState((prev) => ({
|
|
39
|
+
...prev,
|
|
40
|
+
splitRatio: clampRatio(prev.splitRatio + delta),
|
|
41
|
+
}));
|
|
42
|
+
}, [clampRatio]);
|
|
43
|
+
const increaseRatio = useCallback(() => {
|
|
44
|
+
adjustSplitRatio(opts.resizeStep);
|
|
45
|
+
}, [adjustSplitRatio, opts.resizeStep]);
|
|
46
|
+
const decreaseRatio = useCallback(() => {
|
|
47
|
+
adjustSplitRatio(-opts.resizeStep);
|
|
48
|
+
}, [adjustSplitRatio, opts.resizeStep]);
|
|
49
|
+
const toggleDirection = useCallback(() => {
|
|
50
|
+
setState((prev) => ({
|
|
51
|
+
...prev,
|
|
52
|
+
direction: prev.direction === "horizontal" ? "vertical" : "horizontal",
|
|
53
|
+
}));
|
|
54
|
+
}, []);
|
|
55
|
+
const setDirection = useCallback((direction) => {
|
|
56
|
+
setState((prev) => ({
|
|
57
|
+
...prev,
|
|
58
|
+
direction,
|
|
59
|
+
}));
|
|
60
|
+
}, []);
|
|
61
|
+
const toggleSecondary = useCallback(() => {
|
|
62
|
+
setState((prev) => ({
|
|
63
|
+
...prev,
|
|
64
|
+
showSecondary: !prev.showSecondary,
|
|
65
|
+
}));
|
|
66
|
+
}, []);
|
|
67
|
+
const setShowSecondary = useCallback((show) => {
|
|
68
|
+
setState((prev) => ({
|
|
69
|
+
...prev,
|
|
70
|
+
showSecondary: show,
|
|
71
|
+
}));
|
|
72
|
+
}, []);
|
|
73
|
+
const setResizing = useCallback((isResizing) => {
|
|
74
|
+
setState((prev) => ({
|
|
75
|
+
...prev,
|
|
76
|
+
isResizing,
|
|
77
|
+
}));
|
|
78
|
+
}, []);
|
|
79
|
+
const focusPane = useCallback((pane) => {
|
|
80
|
+
setState((prev) => ({
|
|
81
|
+
...prev,
|
|
82
|
+
focusedPane: pane,
|
|
83
|
+
}));
|
|
84
|
+
}, []);
|
|
85
|
+
const toggleFocus = useCallback(() => {
|
|
86
|
+
setState((prev) => ({
|
|
87
|
+
...prev,
|
|
88
|
+
focusedPane: prev.focusedPane === 0 ? 1 : 0,
|
|
89
|
+
}));
|
|
90
|
+
}, []);
|
|
91
|
+
const resetLayout = useCallback(() => {
|
|
92
|
+
setState({
|
|
93
|
+
direction: opts.initialDirection,
|
|
94
|
+
splitRatio: opts.initialSplitRatio,
|
|
95
|
+
isResizing: false,
|
|
96
|
+
showSecondary: true,
|
|
97
|
+
focusedPane: 0,
|
|
98
|
+
});
|
|
99
|
+
}, [opts.initialDirection, opts.initialSplitRatio]);
|
|
100
|
+
const calculateDimensions = useCallback((availableWidth, availableHeight) => {
|
|
101
|
+
if (!state.showSecondary) {
|
|
102
|
+
return {
|
|
103
|
+
firstPane: { width: availableWidth, height: availableHeight },
|
|
104
|
+
secondPane: { width: 0, height: 0 },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (state.direction === "horizontal") {
|
|
108
|
+
// Subtract 1 for the divider
|
|
109
|
+
const usableWidth = availableWidth - 1;
|
|
110
|
+
const firstWidth = Math.floor(usableWidth * state.splitRatio);
|
|
111
|
+
const secondWidth = usableWidth - firstWidth;
|
|
112
|
+
return {
|
|
113
|
+
firstPane: { width: firstWidth, height: availableHeight },
|
|
114
|
+
secondPane: { width: secondWidth, height: availableHeight },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Vertical split - subtract 1 for the divider
|
|
119
|
+
const usableHeight = availableHeight - 1;
|
|
120
|
+
const firstHeight = Math.floor(usableHeight * state.splitRatio);
|
|
121
|
+
const secondHeight = usableHeight - firstHeight;
|
|
122
|
+
return {
|
|
123
|
+
firstPane: { width: availableWidth, height: firstHeight },
|
|
124
|
+
secondPane: { width: availableWidth, height: secondHeight },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}, [state.direction, state.splitRatio, state.showSecondary]);
|
|
128
|
+
return useMemo(() => ({
|
|
129
|
+
state,
|
|
130
|
+
setSplitRatio,
|
|
131
|
+
adjustSplitRatio,
|
|
132
|
+
increaseRatio,
|
|
133
|
+
decreaseRatio,
|
|
134
|
+
toggleDirection,
|
|
135
|
+
setDirection,
|
|
136
|
+
toggleSecondary,
|
|
137
|
+
setShowSecondary,
|
|
138
|
+
setResizing,
|
|
139
|
+
focusPane,
|
|
140
|
+
toggleFocus,
|
|
141
|
+
resetLayout,
|
|
142
|
+
calculateDimensions,
|
|
143
|
+
}), [
|
|
144
|
+
state,
|
|
145
|
+
setSplitRatio,
|
|
146
|
+
adjustSplitRatio,
|
|
147
|
+
increaseRatio,
|
|
148
|
+
decreaseRatio,
|
|
149
|
+
toggleDirection,
|
|
150
|
+
setDirection,
|
|
151
|
+
toggleSecondary,
|
|
152
|
+
setShowSecondary,
|
|
153
|
+
setResizing,
|
|
154
|
+
focusPane,
|
|
155
|
+
toggleFocus,
|
|
156
|
+
resetLayout,
|
|
157
|
+
calculateDimensions,
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=use-layout-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-layout-state.js","sourceRoot":"","sources":["../src/use-layout-state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAqEvD,MAAM,eAAe,GAAoC;IACvD,gBAAgB,EAAE,YAAY;IAC9B,iBAAiB,EAAE,GAAG;IACtB,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,GAAG;IACb,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAiC,EAAE;IAEnC,MAAM,IAAI,GAAG,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,EAAE,CAAC;IAEhD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAc,GAAG,EAAE,CAAC,CAAC;QACrD,SAAS,EAAE,IAAI,CAAC,gBAAgB;QAChC,UAAU,EAAE,IAAI,CAAC,iBAAiB;QAClC,UAAU,EAAE,KAAK;QACjB,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,CAAC;KACf,CAAC,CAAC,CAAC;IAEJ,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,KAAa,EAAU,EAAE;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IACjE,CAAC,EACD,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAC/B,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,KAAa,EAAE,EAAE;QAChB,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC;SAC9B,CAAC,CAAC,CAAC;IACN,CAAC,EACD,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,KAAa,EAAE,EAAE;QAChB,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,UAAU,EAAE,UAAU,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;SAChD,CAAC,CAAC,CAAC;IACN,CAAC,EACD,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAExC,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,gBAAgB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrC,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAExC,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,SAAS,EAAE,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY;SACvE,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,SAAyB,EAAE,EAAE;QAC7D,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,SAAS;SACV,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,aAAa,EAAE,CAAC,IAAI,CAAC,aAAa;SACnC,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,IAAa,EAAE,EAAE;QACrD,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,UAAmB,EAAE,EAAE;QACtD,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,UAAU;SACX,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,IAAW,EAAE,EAAE;QAC5C,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,WAAW,EAAE,IAAI,CAAC,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC5C,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,QAAQ,CAAC;YACP,SAAS,EAAE,IAAI,CAAC,gBAAgB;YAChC,UAAU,EAAE,IAAI,CAAC,iBAAiB;YAClC,UAAU,EAAE,KAAK;YACjB,aAAa,EAAE,IAAI;YACnB,WAAW,EAAE,CAAC;SACf,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEpD,MAAM,mBAAmB,GAAG,WAAW,CACrC,CAAC,cAAsB,EAAE,eAAuB,EAAkB,EAAE;QAClE,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;YACzB,OAAO;gBACL,SAAS,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE;gBAC7D,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;aACpC,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,SAAS,KAAK,YAAY,EAAE,CAAC;YACrC,6BAA6B;YAC7B,MAAM,WAAW,GAAG,cAAc,GAAG,CAAC,CAAC;YACvC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;YAC9D,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;YAE7C,OAAO;gBACL,SAAS,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE;gBACzD,UAAU,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE;aAC5D,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,8CAA8C;YAC9C,MAAM,YAAY,GAAG,eAAe,GAAG,CAAC,CAAC;YACzC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;YAChE,MAAM,YAAY,GAAG,YAAY,GAAG,WAAW,CAAC;YAEhD,OAAO;gBACL,SAAS,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE;gBACzD,UAAU,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE;aAC5D,CAAC;QACJ,CAAC;IACH,CAAC,EACD,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,CACzD,CAAC;IAEF,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC;QACL,KAAK;QACL,aAAa;QACb,gBAAgB;QAChB,aAAa;QACb,aAAa;QACb,eAAe;QACf,YAAY;QACZ,eAAe;QACf,gBAAgB;QAChB,WAAW;QACX,SAAS;QACT,WAAW;QACX,WAAW;QACX,mBAAmB;KACpB,CAAC,EACF;QACE,KAAK;QACL,aAAa;QACb,gBAAgB;QAChB,aAAa;QACb,aAAa;QACb,eAAe;QACf,YAAY;QACZ,eAAe;QACf,gBAAgB;QAChB,WAAW;QACX,SAAS;QACT,WAAW;QACX,WAAW;QACX,mBAAmB;KACpB,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hook for Ralph Loop status parsing
|
|
3
|
+
*
|
|
4
|
+
* Integrates with useClaudeCode to parse output and provide
|
|
5
|
+
* structured Ralph loop state for UI consumption.
|
|
6
|
+
*/
|
|
7
|
+
import type { ClaudeCodeOutput } from "./use-claude-code.js";
|
|
8
|
+
import { type RalphLoopState, type RalphLoopStatus } from "./ralph-loop-parser.js";
|
|
9
|
+
export interface UseRalphLoopOptions {
|
|
10
|
+
/** Maximum number of recent outputs to keep in history */
|
|
11
|
+
maxHistorySize?: number;
|
|
12
|
+
/** Whether to auto-detect loop start from output */
|
|
13
|
+
autoDetectStart?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface UseRalphLoopResult {
|
|
16
|
+
/** Current Ralph loop state */
|
|
17
|
+
state: RalphLoopState;
|
|
18
|
+
/** Whether user attention is needed */
|
|
19
|
+
needsAttention: boolean;
|
|
20
|
+
/** Human-readable status message */
|
|
21
|
+
statusMessage: string;
|
|
22
|
+
/** Formatted progress string (e.g., "3/10" or "75%") */
|
|
23
|
+
progressString: string | null;
|
|
24
|
+
/** Process new output from Claude Code */
|
|
25
|
+
processOutput: (output: ClaudeCodeOutput) => void;
|
|
26
|
+
/** Process multiple outputs at once */
|
|
27
|
+
processOutputs: (outputs: ClaudeCodeOutput[]) => void;
|
|
28
|
+
/** Reset the loop state */
|
|
29
|
+
reset: () => void;
|
|
30
|
+
/** Manually set the loop status */
|
|
31
|
+
setStatus: (status: RalphLoopStatus) => void;
|
|
32
|
+
/** Clear user attention state */
|
|
33
|
+
clearAttention: () => void;
|
|
34
|
+
}
|
|
35
|
+
export declare function useRalphLoop(options?: UseRalphLoopOptions): UseRalphLoopResult;
|
|
36
|
+
/**
|
|
37
|
+
* Hook that automatically processes Claude Code output
|
|
38
|
+
* and maintains Ralph loop state
|
|
39
|
+
*/
|
|
40
|
+
export declare function useRalphLoopWithClaudeOutput(outputs: ClaudeCodeOutput[], options?: UseRalphLoopOptions): UseRalphLoopResult;
|
|
41
|
+
//# sourceMappingURL=use-ralph-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-ralph-loop.d.ts","sourceRoot":"","sources":["../src/use-ralph-loop.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAMrB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,+BAA+B;IAC/B,KAAK,EAAE,cAAc,CAAC;IACtB,uCAAuC;IACvC,cAAc,EAAE,OAAO,CAAC;IACxB,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0CAA0C;IAC1C,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,uCAAuC;IACvC,cAAc,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACtD,2BAA2B;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mCAAmC;IACnC,SAAS,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C,iCAAiC;IACjC,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAiGpB;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,gBAAgB,EAAE,EAC3B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAepB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hook for Ralph Loop status parsing
|
|
3
|
+
*
|
|
4
|
+
* Integrates with useClaudeCode to parse output and provide
|
|
5
|
+
* structured Ralph loop state for UI consumption.
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
8
|
+
import { createInitialState, parseOutput, needsUserAttention, getStatusMessage, getProgressString, } from "./ralph-loop-parser.js";
|
|
9
|
+
export function useRalphLoop(options = {}) {
|
|
10
|
+
const { autoDetectStart = true } = options;
|
|
11
|
+
const [state, setState] = useState(createInitialState);
|
|
12
|
+
// Process a single output
|
|
13
|
+
const processOutput = useCallback((output) => {
|
|
14
|
+
setState((currentState) => {
|
|
15
|
+
// Only process stdout for status parsing (stderr is for errors)
|
|
16
|
+
if (output.type === "stderr") {
|
|
17
|
+
// For stderr, just mark as errored if we detect error patterns
|
|
18
|
+
const errorState = parseOutput(output.content, currentState);
|
|
19
|
+
if (errorState.userAttention.type === "error") {
|
|
20
|
+
return {
|
|
21
|
+
...errorState,
|
|
22
|
+
status: "errored",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return currentState;
|
|
26
|
+
}
|
|
27
|
+
const newState = parseOutput(output.content, currentState);
|
|
28
|
+
// Auto-detect loop start if enabled
|
|
29
|
+
if (autoDetectStart && currentState.status === "idle") {
|
|
30
|
+
if (newState.agentActivity.isActive ||
|
|
31
|
+
newState.progress.currentStep > 0) {
|
|
32
|
+
return {
|
|
33
|
+
...newState,
|
|
34
|
+
status: "running",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return newState;
|
|
39
|
+
});
|
|
40
|
+
}, [autoDetectStart]);
|
|
41
|
+
// Process multiple outputs
|
|
42
|
+
const processOutputs = useCallback((outputs) => {
|
|
43
|
+
for (const output of outputs) {
|
|
44
|
+
processOutput(output);
|
|
45
|
+
}
|
|
46
|
+
}, [processOutput]);
|
|
47
|
+
// Reset to initial state
|
|
48
|
+
const reset = useCallback(() => {
|
|
49
|
+
setState(createInitialState());
|
|
50
|
+
}, []);
|
|
51
|
+
// Manually set status
|
|
52
|
+
const setStatus = useCallback((status) => {
|
|
53
|
+
setState((current) => ({
|
|
54
|
+
...current,
|
|
55
|
+
status,
|
|
56
|
+
lastUpdated: new Date(),
|
|
57
|
+
}));
|
|
58
|
+
}, []);
|
|
59
|
+
// Clear user attention
|
|
60
|
+
const clearAttention = useCallback(() => {
|
|
61
|
+
setState((current) => ({
|
|
62
|
+
...current,
|
|
63
|
+
userAttention: {
|
|
64
|
+
needed: false,
|
|
65
|
+
reason: null,
|
|
66
|
+
type: null,
|
|
67
|
+
prompt: null,
|
|
68
|
+
},
|
|
69
|
+
lastUpdated: new Date(),
|
|
70
|
+
}));
|
|
71
|
+
}, []);
|
|
72
|
+
// Computed values
|
|
73
|
+
const needsAttention = useMemo(() => needsUserAttention(state), [state]);
|
|
74
|
+
const statusMessage = useMemo(() => getStatusMessage(state), [state]);
|
|
75
|
+
const progressString = useMemo(() => getProgressString(state), [state]);
|
|
76
|
+
return {
|
|
77
|
+
state,
|
|
78
|
+
needsAttention,
|
|
79
|
+
statusMessage,
|
|
80
|
+
progressString,
|
|
81
|
+
processOutput,
|
|
82
|
+
processOutputs,
|
|
83
|
+
reset,
|
|
84
|
+
setStatus,
|
|
85
|
+
clearAttention,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Hook that automatically processes Claude Code output
|
|
90
|
+
* and maintains Ralph loop state
|
|
91
|
+
*/
|
|
92
|
+
export function useRalphLoopWithClaudeOutput(outputs, options = {}) {
|
|
93
|
+
const ralphLoop = useRalphLoop(options);
|
|
94
|
+
// Process new outputs when they arrive
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (outputs.length > 0) {
|
|
97
|
+
// Process only the most recent output to avoid reprocessing
|
|
98
|
+
const latestOutput = outputs[outputs.length - 1];
|
|
99
|
+
ralphLoop.processOutput(latestOutput);
|
|
100
|
+
}
|
|
101
|
+
// We only want to run when outputs array length changes
|
|
102
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
103
|
+
}, [outputs.length]);
|
|
104
|
+
return ralphLoop;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=use-ralph-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-ralph-loop.js","sourceRoot":"","sources":["../src/use-ralph-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSpinner Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides animated spinner functionality for visual feedback
|
|
5
|
+
* during loading states or background operations.
|
|
6
|
+
*/
|
|
7
|
+
export type SpinnerStyle = "spinner" | "dots" | "braille";
|
|
8
|
+
export interface UseSpinnerOptions {
|
|
9
|
+
/** Whether the spinner is active */
|
|
10
|
+
isActive?: boolean;
|
|
11
|
+
/** Animation speed in milliseconds */
|
|
12
|
+
interval?: number;
|
|
13
|
+
/** Spinner style */
|
|
14
|
+
style?: SpinnerStyle;
|
|
15
|
+
}
|
|
16
|
+
export interface UseSpinnerResult {
|
|
17
|
+
/** Current spinner frame character */
|
|
18
|
+
frame: string;
|
|
19
|
+
/** Elapsed time in milliseconds since start */
|
|
20
|
+
elapsedMs: number;
|
|
21
|
+
/** Whether the spinner is currently running */
|
|
22
|
+
isRunning: boolean;
|
|
23
|
+
/** Start the spinner */
|
|
24
|
+
start: () => void;
|
|
25
|
+
/** Stop the spinner */
|
|
26
|
+
stop: () => void;
|
|
27
|
+
/** Reset elapsed time to 0 */
|
|
28
|
+
reset: () => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Hook for animated spinners
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* const spinner = useSpinner({ isActive: isLoading });
|
|
36
|
+
* return <Text color="cyan">{spinner.frame} Loading...</Text>;
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function useSpinner({ isActive, interval, style, }?: UseSpinnerOptions): UseSpinnerResult;
|
|
40
|
+
/**
|
|
41
|
+
* Simple hook that just returns a spinner frame based on elapsed time
|
|
42
|
+
* Useful when you already track elapsed time elsewhere
|
|
43
|
+
*/
|
|
44
|
+
export declare function useSpinnerFrame(elapsedMs: number, style?: SpinnerStyle): string;
|
|
45
|
+
export default useSpinner;
|
|
46
|
+
//# sourceMappingURL=use-spinner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-spinner.d.ts","sourceRoot":"","sources":["../src/use-spinner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAe,EACf,QAAa,EACb,KAAiB,GAClB,GAAE,iBAAsB,GAAG,gBAAgB,CAsD3C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,YAAwB,GAC9B,MAAM,CAER;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSpinner Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides animated spinner functionality for visual feedback
|
|
5
|
+
* during loading states or background operations.
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useEffect, useCallback } from "react";
|
|
8
|
+
import { getSpinnerFrame } from "./theme.js";
|
|
9
|
+
/**
|
|
10
|
+
* Hook for animated spinners
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const spinner = useSpinner({ isActive: isLoading });
|
|
15
|
+
* return <Text color="cyan">{spinner.frame} Loading...</Text>;
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function useSpinner({ isActive = true, interval = 80, style = "spinner", } = {}) {
|
|
19
|
+
const [elapsedMs, setElapsedMs] = useState(0);
|
|
20
|
+
const [isRunning, setIsRunning] = useState(isActive);
|
|
21
|
+
// Sync with isActive prop
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setIsRunning(isActive);
|
|
24
|
+
if (isActive) {
|
|
25
|
+
setElapsedMs(0);
|
|
26
|
+
}
|
|
27
|
+
}, [isActive]);
|
|
28
|
+
// Animation loop
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!isRunning)
|
|
31
|
+
return;
|
|
32
|
+
const startTime = Date.now() - elapsedMs;
|
|
33
|
+
let animationFrame;
|
|
34
|
+
const tick = () => {
|
|
35
|
+
setElapsedMs(Date.now() - startTime);
|
|
36
|
+
animationFrame = setTimeout(tick, interval);
|
|
37
|
+
};
|
|
38
|
+
tick();
|
|
39
|
+
return () => {
|
|
40
|
+
clearTimeout(animationFrame);
|
|
41
|
+
};
|
|
42
|
+
}, [isRunning, interval]);
|
|
43
|
+
const start = useCallback(() => {
|
|
44
|
+
setIsRunning(true);
|
|
45
|
+
setElapsedMs(0);
|
|
46
|
+
}, []);
|
|
47
|
+
const stop = useCallback(() => {
|
|
48
|
+
setIsRunning(false);
|
|
49
|
+
}, []);
|
|
50
|
+
const reset = useCallback(() => {
|
|
51
|
+
setElapsedMs(0);
|
|
52
|
+
}, []);
|
|
53
|
+
const frame = getSpinnerFrame(elapsedMs, style);
|
|
54
|
+
return {
|
|
55
|
+
frame,
|
|
56
|
+
elapsedMs,
|
|
57
|
+
isRunning,
|
|
58
|
+
start,
|
|
59
|
+
stop,
|
|
60
|
+
reset,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Simple hook that just returns a spinner frame based on elapsed time
|
|
65
|
+
* Useful when you already track elapsed time elsewhere
|
|
66
|
+
*/
|
|
67
|
+
export function useSpinnerFrame(elapsedMs, style = "spinner") {
|
|
68
|
+
return getSpinnerFrame(elapsedMs, style);
|
|
69
|
+
}
|
|
70
|
+
export default useSpinner;
|
|
71
|
+
//# sourceMappingURL=use-spinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-spinner.js","sourceRoot":"","sources":["../src/use-spinner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA4B7C;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,GAAG,IAAI,EACf,QAAQ,GAAG,EAAE,EACb,KAAK,GAAG,SAAS,MACI,EAAE;IACvB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAErD,0BAA0B;IAC1B,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,QAAQ,EAAE,CAAC;YACb,YAAY,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,iBAAiB;IACjB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS;YAAE,OAAO;QAEvB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,cAA6C,CAAC;QAElD,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;YACrC,cAAc,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC;QAEF,IAAI,EAAE,CAAC;QAEP,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,cAAc,CAAC,CAAC;QAC/B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,YAAY,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,YAAY,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAEhD,OAAO;QACL,KAAK;QACL,SAAS;QACT,SAAS;QACT,KAAK;QACL,IAAI;QACJ,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAiB,EACjB,QAAsB,SAAS;IAE/B,OAAO,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for tracking game stats and achievements.
|
|
5
|
+
* Provides easy integration with game components.
|
|
6
|
+
*/
|
|
7
|
+
import { type GameStats, type GlobalStats, type Achievement } from "./stats.js";
|
|
8
|
+
export interface UseStatsResult {
|
|
9
|
+
/** Stats for the specific game */
|
|
10
|
+
gameStats: GameStats;
|
|
11
|
+
/** Global stats across all games */
|
|
12
|
+
globalStats: GlobalStats;
|
|
13
|
+
/** Whether stats are still loading */
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
/** Record end of a game session */
|
|
16
|
+
recordSession: (score: number, duration: number, won?: boolean, customStats?: Record<string, number>) => Promise<string[]>;
|
|
17
|
+
/** Reload stats from disk */
|
|
18
|
+
refreshStats: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hook for tracking stats for a specific game
|
|
22
|
+
*/
|
|
23
|
+
export declare function useStats(gameId: string): UseStatsResult;
|
|
24
|
+
export interface UseAchievementsResult {
|
|
25
|
+
/** All achievements with unlock status */
|
|
26
|
+
achievements: Array<Achievement & {
|
|
27
|
+
unlocked: boolean;
|
|
28
|
+
unlockedAt: string | null;
|
|
29
|
+
}>;
|
|
30
|
+
/** Total number of achievements */
|
|
31
|
+
total: number;
|
|
32
|
+
/** Number of unlocked achievements */
|
|
33
|
+
unlocked: number;
|
|
34
|
+
/** Whether achievements are still loading */
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
/** Get achievement details by ID */
|
|
37
|
+
getAchievement: (id: string) => Achievement | undefined;
|
|
38
|
+
/** Reload achievements from disk */
|
|
39
|
+
refreshAchievements: () => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Hook for accessing achievements
|
|
43
|
+
*/
|
|
44
|
+
export declare function useAchievements(): UseAchievementsResult;
|
|
45
|
+
export interface UseGameSessionResult {
|
|
46
|
+
/** Start tracking a game session */
|
|
47
|
+
startSession: () => void;
|
|
48
|
+
/** End the session and record stats */
|
|
49
|
+
endSession: (score: number, won?: boolean, customStats?: Record<string, number>) => Promise<string[]>;
|
|
50
|
+
/** Current session duration in seconds */
|
|
51
|
+
sessionDuration: number;
|
|
52
|
+
/** Whether a session is active */
|
|
53
|
+
isSessionActive: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Hook for tracking a single game session's duration
|
|
57
|
+
*/
|
|
58
|
+
export declare function useGameSession(gameId: string): UseGameSessionResult;
|
|
59
|
+
//# sourceMappingURL=use-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-stats.d.ts","sourceRoot":"","sources":["../src/use-stats.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,WAAW,EAOjB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,SAAS,EAAE,SAAS,CAAC;IACrB,oCAAoC;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,aAAa,EAAE,CACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,OAAO,EACb,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvB,6BAA6B;IAC7B,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAqEvD;AAED,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,YAAY,EAAE,KAAK,CAAC,WAAW,GAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACpF,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;IACxD,oCAAoC;IACpC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,qBAAqB,CAmCvD;AAED,MAAM,WAAW,oBAAoB;IACnC,oCAAoC;IACpC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,uCAAuC;IACvC,UAAU,EAAE,CACV,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,OAAO,EACb,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,CA6DnE"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for tracking game stats and achievements.
|
|
5
|
+
* Provides easy integration with game components.
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
8
|
+
import { getGameStats, getGlobalStats, recordGameSession, getAchievements, getAchievementById, getAchievementCount, } from "./stats.js";
|
|
9
|
+
/**
|
|
10
|
+
* Hook for tracking stats for a specific game
|
|
11
|
+
*/
|
|
12
|
+
export function useStats(gameId) {
|
|
13
|
+
const [gameStats, setGameStats] = useState({
|
|
14
|
+
gamesPlayed: 0,
|
|
15
|
+
timePlayed: 0,
|
|
16
|
+
highestScore: 0,
|
|
17
|
+
totalScore: 0,
|
|
18
|
+
wins: 0,
|
|
19
|
+
losses: 0,
|
|
20
|
+
lastPlayed: null,
|
|
21
|
+
});
|
|
22
|
+
const [globalStats, setGlobalStats] = useState({
|
|
23
|
+
totalGamesPlayed: 0,
|
|
24
|
+
totalTimePlayed: 0,
|
|
25
|
+
achievementsUnlocked: 0,
|
|
26
|
+
firstPlayed: null,
|
|
27
|
+
lastPlayed: null,
|
|
28
|
+
sessionCount: 0,
|
|
29
|
+
currentStreak: 0,
|
|
30
|
+
bestStreak: 0,
|
|
31
|
+
lastStreakDate: null,
|
|
32
|
+
});
|
|
33
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
34
|
+
const loadStats = useCallback(async () => {
|
|
35
|
+
setIsLoading(true);
|
|
36
|
+
try {
|
|
37
|
+
const [game, global] = await Promise.all([
|
|
38
|
+
getGameStats(gameId),
|
|
39
|
+
getGlobalStats(),
|
|
40
|
+
]);
|
|
41
|
+
setGameStats(game);
|
|
42
|
+
setGlobalStats(global);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
setIsLoading(false);
|
|
46
|
+
}
|
|
47
|
+
}, [gameId]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
void loadStats();
|
|
50
|
+
}, [loadStats]);
|
|
51
|
+
const recordSession = useCallback(async (score, duration, won, customStats) => {
|
|
52
|
+
const newAchievements = await recordGameSession(gameId, score, duration, won, customStats);
|
|
53
|
+
// Reload stats after recording
|
|
54
|
+
await loadStats();
|
|
55
|
+
return newAchievements;
|
|
56
|
+
}, [gameId, loadStats]);
|
|
57
|
+
return {
|
|
58
|
+
gameStats,
|
|
59
|
+
globalStats,
|
|
60
|
+
isLoading,
|
|
61
|
+
recordSession,
|
|
62
|
+
refreshStats: loadStats,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Hook for accessing achievements
|
|
67
|
+
*/
|
|
68
|
+
export function useAchievements() {
|
|
69
|
+
const [achievements, setAchievements] = useState([]);
|
|
70
|
+
const [total, setTotal] = useState(0);
|
|
71
|
+
const [unlocked, setUnlocked] = useState(0);
|
|
72
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
73
|
+
const loadAchievements = useCallback(async () => {
|
|
74
|
+
setIsLoading(true);
|
|
75
|
+
try {
|
|
76
|
+
const [achievementsList, counts] = await Promise.all([
|
|
77
|
+
getAchievements(),
|
|
78
|
+
getAchievementCount(),
|
|
79
|
+
]);
|
|
80
|
+
setAchievements(achievementsList);
|
|
81
|
+
setTotal(counts.total);
|
|
82
|
+
setUnlocked(counts.unlocked);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setIsLoading(false);
|
|
86
|
+
}
|
|
87
|
+
}, []);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
void loadAchievements();
|
|
90
|
+
}, [loadAchievements]);
|
|
91
|
+
return {
|
|
92
|
+
achievements,
|
|
93
|
+
total,
|
|
94
|
+
unlocked,
|
|
95
|
+
isLoading,
|
|
96
|
+
getAchievement: getAchievementById,
|
|
97
|
+
refreshAchievements: loadAchievements,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Hook for tracking a single game session's duration
|
|
102
|
+
*/
|
|
103
|
+
export function useGameSession(gameId) {
|
|
104
|
+
const [isActive, setIsActive] = useState(false);
|
|
105
|
+
const [duration, setDuration] = useState(0);
|
|
106
|
+
const startTimeRef = useRef(null);
|
|
107
|
+
const intervalRef = useRef(null);
|
|
108
|
+
const startSession = useCallback(() => {
|
|
109
|
+
startTimeRef.current = Date.now();
|
|
110
|
+
setIsActive(true);
|
|
111
|
+
setDuration(0);
|
|
112
|
+
// Update duration every second
|
|
113
|
+
intervalRef.current = setInterval(() => {
|
|
114
|
+
if (startTimeRef.current) {
|
|
115
|
+
setDuration(Math.floor((Date.now() - startTimeRef.current) / 1000));
|
|
116
|
+
}
|
|
117
|
+
}, 1000);
|
|
118
|
+
}, []);
|
|
119
|
+
const endSession = useCallback(async (score, won, customStats) => {
|
|
120
|
+
if (intervalRef.current) {
|
|
121
|
+
clearInterval(intervalRef.current);
|
|
122
|
+
intervalRef.current = null;
|
|
123
|
+
}
|
|
124
|
+
const finalDuration = startTimeRef.current
|
|
125
|
+
? Math.floor((Date.now() - startTimeRef.current) / 1000)
|
|
126
|
+
: 0;
|
|
127
|
+
setIsActive(false);
|
|
128
|
+
startTimeRef.current = null;
|
|
129
|
+
// Record the session
|
|
130
|
+
if (finalDuration > 0) {
|
|
131
|
+
return recordGameSession(gameId, score, finalDuration, won, customStats);
|
|
132
|
+
}
|
|
133
|
+
return [];
|
|
134
|
+
}, [gameId]);
|
|
135
|
+
// Cleanup on unmount
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
return () => {
|
|
138
|
+
if (intervalRef.current) {
|
|
139
|
+
clearInterval(intervalRef.current);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}, []);
|
|
143
|
+
return {
|
|
144
|
+
startSession,
|
|
145
|
+
endSession,
|
|
146
|
+
sessionDuration: duration,
|
|
147
|
+
isSessionActive: isActive,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=use-stats.js.map
|