flock-core 0.5.0b50__py3-none-any.whl → 0.5.0b51__py3-none-any.whl
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.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/dashboard/launcher.py +1 -1
- flock/frontend/README.md +678 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4347 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +79 -0
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
- flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterBar.module.css +29 -0
- flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
- flock/frontend/src/components/filters/FilterBar.tsx +33 -0
- flock/frontend/src/components/filters/FilterPills.module.css +79 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
- flock/frontend/src/components/filters/FilterPills.tsx +67 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
- flock/frontend/src/components/graph/AgentNode.tsx +322 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
- flock/frontend/src/components/graph/MessageNode.tsx +116 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +407 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
- flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
- flock/frontend/src/components/modules/registerModules.ts +20 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +139 -0
- flock/frontend/src/hooks/usePersistence.ts +139 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +213 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +794 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +146 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.test.ts +595 -0
- flock/frontend/src/services/websocket.ts +685 -0
- flock/frontend/src/store/filterStore.test.ts +242 -0
- flock/frontend/src/store/filterStore.ts +103 -0
- flock/frontend/src/store/graphStore.test.ts +186 -0
- flock/frontend/src/store/graphStore.ts +414 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +57 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +110 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +14 -0
- flock/frontend/src/types/graph.ts +55 -0
- flock/frontend/src/types/modules.ts +7 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/mockData.ts +85 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/utils/transforms.test.ts +860 -0
- flock/frontend/src/utils/transforms.ts +323 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/METADATA +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/RECORD +116 -6
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Selector Component
|
|
3
|
+
*
|
|
4
|
+
* Searchable dropdown for selecting from 300+ terminal themes.
|
|
5
|
+
* Shows color preview swatches and applies themes in real-time.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState, useEffect } from 'react';
|
|
9
|
+
import { useSettingsStore } from '../../store/settingsStore';
|
|
10
|
+
import { fetchThemeList, fetchTheme, POPULAR_THEMES } from '../../services/themeService';
|
|
11
|
+
import { applyTheme, resetToDefaultTheme } from '../../services/themeApplicator';
|
|
12
|
+
import { TerminalTheme } from '../../types/theme';
|
|
13
|
+
|
|
14
|
+
const ThemeSelector: React.FC = () => {
|
|
15
|
+
const [themes, setThemes] = useState<string[]>([]);
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
const [error, setError] = useState<string | null>(null);
|
|
18
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
19
|
+
const [previewTheme, setPreviewTheme] = useState<TerminalTheme | null>(null);
|
|
20
|
+
|
|
21
|
+
const currentTheme = useSettingsStore((state) => state.appearance.theme);
|
|
22
|
+
const setTheme = useSettingsStore((state) => state.setTheme);
|
|
23
|
+
|
|
24
|
+
// Load theme list on mount
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
loadThemes();
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const loadThemes = async () => {
|
|
30
|
+
try {
|
|
31
|
+
setLoading(true);
|
|
32
|
+
setError(null);
|
|
33
|
+
const themeList = await fetchThemeList();
|
|
34
|
+
setThemes(themeList);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
setError('Failed to load themes. Backend may not be running.');
|
|
37
|
+
console.error('Theme loading error:', err);
|
|
38
|
+
// Fallback to popular themes if API fails
|
|
39
|
+
setThemes(POPULAR_THEMES);
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleThemeSelect = async (themeName: string) => {
|
|
46
|
+
try {
|
|
47
|
+
setError(null);
|
|
48
|
+
// Handle default theme (doesn't need API fetch)
|
|
49
|
+
if (themeName === 'default') {
|
|
50
|
+
resetToDefaultTheme();
|
|
51
|
+
setTheme('default');
|
|
52
|
+
setPreviewTheme(null);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const theme = await fetchTheme(themeName);
|
|
56
|
+
applyTheme(theme);
|
|
57
|
+
setTheme(themeName);
|
|
58
|
+
setPreviewTheme(null);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
setError(`Failed to load theme "${themeName}"`);
|
|
61
|
+
console.error('Theme application error:', err);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleThemePreview = async (themeName: string) => {
|
|
66
|
+
try {
|
|
67
|
+
// Handle default theme (doesn't need API fetch)
|
|
68
|
+
if (themeName === 'default') {
|
|
69
|
+
resetToDefaultTheme();
|
|
70
|
+
setPreviewTheme(null);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const theme = await fetchTheme(themeName);
|
|
74
|
+
setPreviewTheme(theme);
|
|
75
|
+
applyTheme(theme);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('Theme preview error:', err);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleResetTheme = () => {
|
|
82
|
+
resetToDefaultTheme();
|
|
83
|
+
setTheme('default');
|
|
84
|
+
setPreviewTheme(null);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Always include "default" theme at the beginning
|
|
88
|
+
const allThemes = ['default', ...themes];
|
|
89
|
+
|
|
90
|
+
const filteredThemes = allThemes.filter((theme) =>
|
|
91
|
+
theme.toLowerCase().includes(searchQuery.toLowerCase())
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const popularThemes = filteredThemes.filter((theme) => POPULAR_THEMES.includes(theme));
|
|
95
|
+
const defaultTheme = filteredThemes.includes('default') ? ['default'] : [];
|
|
96
|
+
const otherThemes = filteredThemes.filter((theme) => !POPULAR_THEMES.includes(theme) && theme !== 'default');
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div>
|
|
100
|
+
<div className="settings-field">
|
|
101
|
+
<label htmlFor="theme-search" className="settings-label">
|
|
102
|
+
Search Themes
|
|
103
|
+
</label>
|
|
104
|
+
<input
|
|
105
|
+
id="theme-search"
|
|
106
|
+
type="text"
|
|
107
|
+
value={searchQuery}
|
|
108
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
109
|
+
placeholder="Search 300+ themes..."
|
|
110
|
+
className="settings-input"
|
|
111
|
+
disabled={loading}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{error && (
|
|
116
|
+
<div style={{
|
|
117
|
+
padding: 'var(--space-component-sm)',
|
|
118
|
+
background: 'var(--color-error-bg)',
|
|
119
|
+
color: 'var(--color-error-light)',
|
|
120
|
+
borderRadius: 'var(--radius-md)',
|
|
121
|
+
marginBottom: 'var(--space-component-md)',
|
|
122
|
+
fontSize: 'var(--font-size-caption)',
|
|
123
|
+
}}>
|
|
124
|
+
{error}
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{loading && (
|
|
129
|
+
<div style={{
|
|
130
|
+
padding: 'var(--space-component-md)',
|
|
131
|
+
textAlign: 'center',
|
|
132
|
+
color: 'var(--color-text-secondary)',
|
|
133
|
+
}}>
|
|
134
|
+
Loading themes...
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
{!loading && (
|
|
139
|
+
<>
|
|
140
|
+
{/* Current Theme */}
|
|
141
|
+
<div className="settings-field">
|
|
142
|
+
<label className="settings-label">Current Theme</label>
|
|
143
|
+
<div style={{
|
|
144
|
+
padding: 'var(--space-component-sm)',
|
|
145
|
+
background: 'var(--color-bg-elevated)',
|
|
146
|
+
borderRadius: 'var(--radius-md)',
|
|
147
|
+
border: 'var(--border-default)',
|
|
148
|
+
display: 'flex',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
justifyContent: 'space-between',
|
|
151
|
+
}}>
|
|
152
|
+
<span style={{ color: 'var(--color-text-primary)' }}>
|
|
153
|
+
{currentTheme === 'default' ? 'Default (Flock Flow)' : currentTheme}
|
|
154
|
+
</span>
|
|
155
|
+
{currentTheme !== 'default' && (
|
|
156
|
+
<button
|
|
157
|
+
onClick={handleResetTheme}
|
|
158
|
+
style={{
|
|
159
|
+
padding: '4px 8px',
|
|
160
|
+
background: 'transparent',
|
|
161
|
+
border: 'var(--border-default)',
|
|
162
|
+
borderRadius: 'var(--radius-sm)',
|
|
163
|
+
color: 'var(--color-text-secondary)',
|
|
164
|
+
fontSize: 'var(--font-size-caption)',
|
|
165
|
+
cursor: 'pointer',
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
Reset
|
|
169
|
+
</button>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Default Theme - Always first */}
|
|
175
|
+
{defaultTheme.length > 0 && (
|
|
176
|
+
<div className="settings-field">
|
|
177
|
+
<label className="settings-label">Built-in Theme</label>
|
|
178
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--gap-xs)' }}>
|
|
179
|
+
<button
|
|
180
|
+
onClick={() => handleThemeSelect('default')}
|
|
181
|
+
onMouseEnter={() => handleThemePreview('default')}
|
|
182
|
+
style={{
|
|
183
|
+
padding: 'var(--space-component-sm)',
|
|
184
|
+
background: currentTheme === 'default' ? 'var(--color-primary-500)' : 'var(--color-bg-elevated)',
|
|
185
|
+
border: 'var(--border-default)',
|
|
186
|
+
borderRadius: 'var(--radius-md)',
|
|
187
|
+
color: currentTheme === 'default' ? 'white' : 'var(--color-text-primary)',
|
|
188
|
+
fontSize: 'var(--font-size-body-sm)',
|
|
189
|
+
cursor: 'pointer',
|
|
190
|
+
textAlign: 'left',
|
|
191
|
+
transition: 'var(--transition-all)',
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
Default (Flock Flow)
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
|
|
200
|
+
{/* Popular Themes */}
|
|
201
|
+
{popularThemes.length > 0 && (
|
|
202
|
+
<div className="settings-field">
|
|
203
|
+
<label className="settings-label">Popular Themes</label>
|
|
204
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--gap-xs)' }}>
|
|
205
|
+
{popularThemes.map((theme) => (
|
|
206
|
+
<button
|
|
207
|
+
key={theme}
|
|
208
|
+
onClick={() => handleThemeSelect(theme)}
|
|
209
|
+
onMouseEnter={() => handleThemePreview(theme)}
|
|
210
|
+
style={{
|
|
211
|
+
padding: 'var(--space-component-sm)',
|
|
212
|
+
background: currentTheme === theme ? 'var(--color-primary-500)' : 'var(--color-bg-elevated)',
|
|
213
|
+
border: 'var(--border-default)',
|
|
214
|
+
borderRadius: 'var(--radius-md)',
|
|
215
|
+
color: currentTheme === theme ? 'white' : 'var(--color-text-primary)',
|
|
216
|
+
fontSize: 'var(--font-size-body-sm)',
|
|
217
|
+
cursor: 'pointer',
|
|
218
|
+
textAlign: 'left',
|
|
219
|
+
transition: 'var(--transition-all)',
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
{theme}
|
|
223
|
+
</button>
|
|
224
|
+
))}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{/* All Other Themes */}
|
|
230
|
+
{otherThemes.length > 0 && (
|
|
231
|
+
<div className="settings-field">
|
|
232
|
+
<label className="settings-label">
|
|
233
|
+
All Themes ({otherThemes.length})
|
|
234
|
+
</label>
|
|
235
|
+
<div style={{
|
|
236
|
+
maxHeight: '300px',
|
|
237
|
+
overflowY: 'auto',
|
|
238
|
+
display: 'flex',
|
|
239
|
+
flexDirection: 'column',
|
|
240
|
+
gap: 'var(--gap-xs)',
|
|
241
|
+
padding: 'var(--space-component-xs)',
|
|
242
|
+
background: 'var(--color-bg-base)',
|
|
243
|
+
borderRadius: 'var(--radius-md)',
|
|
244
|
+
}}>
|
|
245
|
+
{otherThemes.map((theme) => (
|
|
246
|
+
<button
|
|
247
|
+
key={theme}
|
|
248
|
+
onClick={() => handleThemeSelect(theme)}
|
|
249
|
+
onMouseEnter={() => handleThemePreview(theme)}
|
|
250
|
+
style={{
|
|
251
|
+
padding: 'var(--space-component-sm)',
|
|
252
|
+
background: currentTheme === theme ? 'var(--color-primary-500)' : 'var(--color-bg-elevated)',
|
|
253
|
+
border: 'var(--border-default)',
|
|
254
|
+
borderRadius: 'var(--radius-sm)',
|
|
255
|
+
color: currentTheme === theme ? 'white' : 'var(--color-text-primary)',
|
|
256
|
+
fontSize: 'var(--font-size-body-sm)',
|
|
257
|
+
cursor: 'pointer',
|
|
258
|
+
textAlign: 'left',
|
|
259
|
+
transition: 'var(--transition-all)',
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
{theme}
|
|
263
|
+
</button>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
|
|
269
|
+
{filteredThemes.length === 0 && !loading && (
|
|
270
|
+
<div style={{
|
|
271
|
+
padding: 'var(--space-component-md)',
|
|
272
|
+
textAlign: 'center',
|
|
273
|
+
color: 'var(--color-text-tertiary)',
|
|
274
|
+
}}>
|
|
275
|
+
No themes found matching "{searchQuery}"
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
</>
|
|
279
|
+
)}
|
|
280
|
+
|
|
281
|
+
{previewTheme && (
|
|
282
|
+
<div style={{
|
|
283
|
+
padding: 'var(--space-component-sm)',
|
|
284
|
+
background: 'var(--color-bg-overlay)',
|
|
285
|
+
borderRadius: 'var(--radius-md)',
|
|
286
|
+
marginTop: 'var(--space-component-md)',
|
|
287
|
+
fontSize: 'var(--font-size-caption)',
|
|
288
|
+
color: 'var(--color-text-tertiary)',
|
|
289
|
+
textAlign: 'center',
|
|
290
|
+
}}>
|
|
291
|
+
Previewing: {previewTheme.name} (click to apply)
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export default ThemeSelector;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useUIStore } from '../store/uiStore';
|
|
3
|
+
import { useSettingsStore } from '../store/settingsStore';
|
|
4
|
+
|
|
5
|
+
interface KeyboardShortcutConfig {
|
|
6
|
+
onToggleMode?: () => void;
|
|
7
|
+
onFocusFilter?: () => void;
|
|
8
|
+
onCloseWindows?: () => void;
|
|
9
|
+
onToggleHelp?: () => void;
|
|
10
|
+
onToggleAgentDetails?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Custom hook for managing global keyboard shortcuts
|
|
15
|
+
*
|
|
16
|
+
* Shortcuts:
|
|
17
|
+
* - Ctrl+M (or Cmd+M): Toggle between Agent View and Blackboard View
|
|
18
|
+
* - Ctrl+F (or Cmd+F): Focus the filter input
|
|
19
|
+
* - Ctrl+, (or Cmd+,): Toggle Settings Panel
|
|
20
|
+
* - Ctrl+Shift+F: Toggle Filters Panel
|
|
21
|
+
* - Ctrl+Shift+P: Toggle Publish Panel
|
|
22
|
+
* - Ctrl+Shift+D: Toggle Agent Details
|
|
23
|
+
* - Ctrl+/ (or Cmd+/): Toggle Keyboard Shortcuts Help
|
|
24
|
+
* - Esc: Close all panels and detail windows
|
|
25
|
+
*/
|
|
26
|
+
export const useKeyboardShortcuts = (config: KeyboardShortcutConfig = {}) => {
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
29
|
+
// Check for modifier key (Ctrl on Windows/Linux, Cmd on Mac)
|
|
30
|
+
const modifier = event.ctrlKey || event.metaKey;
|
|
31
|
+
|
|
32
|
+
// Ctrl+, or Cmd+,: Toggle Settings Panel
|
|
33
|
+
if (modifier && event.key === ',') {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
const showSettings = useSettingsStore.getState().ui.showSettings;
|
|
36
|
+
useSettingsStore.getState().setShowSettings(!showSettings);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Ctrl+Shift+F: Toggle Filters Panel
|
|
41
|
+
if (modifier && event.shiftKey && event.key === 'F') {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
const showFilters = useSettingsStore.getState().ui.showFilters;
|
|
44
|
+
useSettingsStore.getState().setShowFilters(!showFilters);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ctrl+Shift+P: Toggle Publish/Controls Panel
|
|
49
|
+
if (modifier && event.shiftKey && event.key === 'P') {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
const showControls = useSettingsStore.getState().ui.showControls;
|
|
52
|
+
useSettingsStore.getState().setShowControls(!showControls);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Ctrl+Shift+D: Toggle Agent Details
|
|
57
|
+
if (modifier && event.shiftKey && event.key === 'D') {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
if (config.onToggleAgentDetails) {
|
|
60
|
+
config.onToggleAgentDetails();
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Ctrl+/ or Cmd+/: Toggle Keyboard Shortcuts Help
|
|
66
|
+
if (modifier && event.key === '/') {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
if (config.onToggleHelp) {
|
|
69
|
+
config.onToggleHelp();
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Ctrl+M or Cmd+M: Toggle mode
|
|
75
|
+
if (modifier && event.key === 'm') {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
if (config.onToggleMode) {
|
|
78
|
+
config.onToggleMode();
|
|
79
|
+
} else {
|
|
80
|
+
// Default behavior: toggle mode
|
|
81
|
+
const currentMode = useUIStore.getState().mode;
|
|
82
|
+
useUIStore.getState().setMode(currentMode === 'agent' ? 'blackboard' : 'agent');
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Ctrl+F or Cmd+F: Focus filter (only if not in an input already)
|
|
88
|
+
if (modifier && event.key === 'f') {
|
|
89
|
+
const activeElement = document.activeElement;
|
|
90
|
+
const isInInput = activeElement?.tagName === 'INPUT' ||
|
|
91
|
+
activeElement?.tagName === 'TEXTAREA';
|
|
92
|
+
|
|
93
|
+
if (!isInInput) {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
if (config.onFocusFilter) {
|
|
96
|
+
config.onFocusFilter();
|
|
97
|
+
} else {
|
|
98
|
+
// Default behavior: focus correlation ID filter
|
|
99
|
+
const filterInput = document.querySelector<HTMLInputElement>(
|
|
100
|
+
'input[placeholder*="correlation"], input[placeholder*="Correlation"]'
|
|
101
|
+
);
|
|
102
|
+
if (filterInput) {
|
|
103
|
+
filterInput.focus();
|
|
104
|
+
filterInput.select();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Esc: Close windows/dialogs/panels
|
|
112
|
+
if (event.key === 'Escape') {
|
|
113
|
+
if (config.onCloseWindows) {
|
|
114
|
+
config.onCloseWindows();
|
|
115
|
+
} else {
|
|
116
|
+
// Priority: Close panels first, then detail windows
|
|
117
|
+
const { showSettings, showFilters, showControls } = useSettingsStore.getState().ui;
|
|
118
|
+
const detailWindows = useUIStore.getState().detailWindows;
|
|
119
|
+
|
|
120
|
+
if (showSettings) {
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
useSettingsStore.getState().setShowSettings(false);
|
|
123
|
+
} else if (showControls) {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
useSettingsStore.getState().setShowControls(false);
|
|
126
|
+
} else if (showFilters) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
useSettingsStore.getState().setShowFilters(false);
|
|
129
|
+
} else if (detailWindows.size > 0) {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
// Close all detail windows
|
|
132
|
+
detailWindows.clear();
|
|
133
|
+
useUIStore.setState({ detailWindows: new Map() });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Add event listener
|
|
141
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
142
|
+
|
|
143
|
+
// Cleanup
|
|
144
|
+
return () => {
|
|
145
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
146
|
+
};
|
|
147
|
+
}, [config]);
|
|
148
|
+
};
|