claw-dashboard 1.8.4 → 2.0.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/.c8rc.json +38 -0
- package/.dockerignore +68 -0
- package/.github/dependabot.yml +45 -0
- package/.github/pull_request_template.md +39 -0
- package/.github/workflows/ci.yml +147 -0
- package/.github/workflows/release.yml +40 -0
- package/.github/workflows/security.yml +84 -0
- package/.husky/pre-commit +1 -0
- package/.planning/codebase/ARCHITECTURE.md +206 -0
- package/.planning/codebase/INTEGRATIONS.md +150 -0
- package/.planning/codebase/STACK.md +122 -0
- package/.planning/codebase/STRUCTURE.md +201 -0
- package/CHANGELOG.md +293 -0
- package/CONTRIBUTING.md +378 -0
- package/Dockerfile +54 -0
- package/FEATURES.md +195 -21
- package/README.md +83 -6
- package/TODO.md +28 -0
- package/build-cjs.js +127 -0
- package/cjs-shim.js +13 -0
- package/dist/clawdash +1729 -0
- package/dist/clawdash.meta.json +2236 -0
- package/dist/widgets.cjs +6511 -0
- package/docker-compose.yml +67 -0
- package/docs/API.md +1050 -0
- package/docs/PLUGINS.md +1504 -0
- package/esbuild.config.js +158 -0
- package/eslint.config.js +56 -0
- package/examples/plugins/README.md +122 -0
- package/examples/plugins/api-status/index.js +294 -0
- package/examples/plugins/api-status/plugin.json +19 -0
- package/examples/plugins/hello-world/index.js +104 -0
- package/examples/plugins/hello-world/plugin.json +15 -0
- package/examples/plugins/system-metrics-chart/index.js +339 -0
- package/examples/plugins/system-metrics-chart/plugin.json +18 -0
- package/examples/plugins/weather-widget/index.js +136 -0
- package/examples/plugins/weather-widget/plugin.json +19 -0
- package/index.cjs +23005 -0
- package/index.js +5331 -512
- package/jest.config.js +11 -0
- package/man/clawdash.1 +276 -0
- package/package.json +54 -5
- package/schemas/plugin-manifest.json +128 -0
- package/scripts/release.js +595 -0
- package/src/alerts.js +693 -0
- package/src/auto-save.js +584 -0
- package/src/cache.js +390 -0
- package/src/checksum.js +146 -0
- package/src/cli/args.js +110 -0
- package/src/cli/export-schedule.js +423 -0
- package/src/cli/export-snapshot.js +138 -0
- package/src/cli/help.js +69 -0
- package/src/cli/import-snapshot.js +230 -0
- package/src/cli/index.js +14 -0
- package/src/cli/list-templates.js +69 -0
- package/src/cli/validate-config.js +76 -0
- package/src/cli/validate-plugin.js +187 -0
- package/src/cli/version.js +15 -0
- package/src/config-validator.js +586 -0
- package/src/config-watcher.js +401 -0
- package/src/config.js +684 -0
- package/src/container-detector.js +499 -0
- package/src/database.js +734 -0
- package/src/differential-render.js +327 -0
- package/src/errors.js +169 -0
- package/src/export-scheduler.js +581 -0
- package/src/gateway-manager.js +685 -0
- package/src/hints.js +371 -0
- package/src/loading-states.js +509 -0
- package/src/logger.js +185 -0
- package/src/memory-pressure.js +467 -0
- package/src/performance-monitor.js +374 -0
- package/src/plugin-errors.js +586 -0
- package/src/plugin-manifest-validator.js +219 -0
- package/src/plugin-reload.js +481 -0
- package/src/plugin-scaffold.js +1941 -0
- package/src/plugin-validator.js +284 -0
- package/src/retry.js +218 -0
- package/src/security.js +860 -0
- package/src/snapshot.js +372 -0
- package/src/splash.js +115 -0
- package/src/theme-selector.js +411 -0
- package/src/themes.js +874 -0
- package/src/transitions.js +534 -0
- package/src/types.d.ts +174 -0
- package/src/validation.js +926 -0
- package/src/web-server.js +885 -0
- package/src/widgets/builtin-widgets.js +1057 -0
- package/src/widgets/config-processor.js +401 -0
- package/src/widgets/dependency-resolver.js +596 -0
- package/src/widgets/index.js +79 -0
- package/src/widgets/plugin-api.js +845 -0
- package/src/widgets/widget-error-boundary.js +773 -0
- package/src/widgets/widget-error-isolation.js +551 -0
- package/src/widgets/widget-loader.js +1437 -0
- package/src/workers/system-worker.js +130 -0
- package/src/workers/worker-pool.js +633 -0
- package/tests/alerts.test.js +437 -0
- package/tests/auto-save.test.js +529 -0
- package/tests/cache.test.js +317 -0
- package/tests/cli.test.js +351 -0
- package/tests/command-palette.test.js +320 -0
- package/tests/config-processor.test.js +452 -0
- package/tests/config-validator.test.js +710 -0
- package/tests/config-watcher.test.js +594 -0
- package/tests/config.test.js +755 -0
- package/tests/database.test.js +438 -0
- package/tests/errors.test.js +189 -0
- package/tests/example-plugins.test.js +624 -0
- package/tests/integration.test.js +1321 -0
- package/tests/loading-states.test.js +300 -0
- package/tests/manifest-validation-on-load.test.js +671 -0
- package/tests/performance-monitor.test.js +190 -0
- package/tests/plugin-api-rate-limit.test.js +302 -0
- package/tests/plugin-errors.test.js +311 -0
- package/tests/plugin-lifecycle-e2e.test.js +1036 -0
- package/tests/plugin-reload.test.js +764 -0
- package/tests/rate-limiter.test.js +539 -0
- package/tests/retry.test.js +308 -0
- package/tests/security.test.js +411 -0
- package/tests/settings-modal.test.js +226 -0
- package/tests/theme-selector.test.js +57 -0
- package/tests/utils.js +242 -0
- package/tests/utils.test.js +317 -0
- package/tests/validate-plugin-cli.test.js +359 -0
- package/tests/validation.test.js +837 -0
- package/tests/web-server.test.js +646 -0
- package/tests/widget-arrange-mode.test.js +183 -0
- package/tests/widget-config-hot-reload.test.js +465 -0
- package/tests/widget-dependency.test.js +591 -0
- package/tests/widget-error-boundary.test.js +749 -0
- package/tests/widget-error-isolation.test.js +469 -0
- package/tests/widget-integration.test.js +1147 -0
- package/tests/widget-refresh-intervals.test.js +284 -0
- package/tests/worker-pool.test.js +522 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Selector UI Module
|
|
3
|
+
* Provides an interactive theme selection interface with live preview
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import logger from './logger.js';
|
|
7
|
+
import {
|
|
8
|
+
getThemeNames,
|
|
9
|
+
getTheme,
|
|
10
|
+
getCurrentTheme,
|
|
11
|
+
getThemeName,
|
|
12
|
+
setTheme,
|
|
13
|
+
saveTheme,
|
|
14
|
+
onThemeChange
|
|
15
|
+
} from './themes.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Theme preview card dimensions and layout
|
|
19
|
+
*/
|
|
20
|
+
const PREVIEW_CARD = {
|
|
21
|
+
width: 28,
|
|
22
|
+
height: 14,
|
|
23
|
+
margin: 2
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sample content for theme preview
|
|
28
|
+
* Shows various UI elements that demonstrate the theme
|
|
29
|
+
*/
|
|
30
|
+
const PREVIEW_SAMPLES = {
|
|
31
|
+
header: 'Theme Preview',
|
|
32
|
+
border: '─'.repeat(24),
|
|
33
|
+
textSample: 'Aa Bb Cc 123',
|
|
34
|
+
statusActive: '● Active',
|
|
35
|
+
statusIdle: '○ Idle',
|
|
36
|
+
gauge: '████░░░░░░ 40%',
|
|
37
|
+
chart: '▁▂▃▄▅▆▇█'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create theme selector modal
|
|
42
|
+
* @param {blessed.Screen} screen - Blessed screen instance
|
|
43
|
+
* @param {Object} blessed - Blessed library
|
|
44
|
+
* @param {Function} onClose - Callback when modal closes
|
|
45
|
+
* @returns {Object} Theme selector controller
|
|
46
|
+
*/
|
|
47
|
+
export function createThemeSelector(screen, blessed, onClose) {
|
|
48
|
+
const themeNames = getThemeNames();
|
|
49
|
+
const currentThemeName = getThemeName();
|
|
50
|
+
let selectedIndex = themeNames.indexOf(currentThemeName);
|
|
51
|
+
if (selectedIndex === -1) selectedIndex = 0;
|
|
52
|
+
|
|
53
|
+
let modalBox = null;
|
|
54
|
+
let previewBoxes = [];
|
|
55
|
+
let infoText = null;
|
|
56
|
+
let helpText = null;
|
|
57
|
+
let unsubscribeThemeChange = null;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Apply color to blessed element from theme
|
|
61
|
+
*/
|
|
62
|
+
function applyThemeColor(element, color, isBg = false) {
|
|
63
|
+
if (isBg) {
|
|
64
|
+
element.style.bg = color;
|
|
65
|
+
} else {
|
|
66
|
+
element.style.fg = color;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Render theme preview content with colors
|
|
72
|
+
*/
|
|
73
|
+
function renderPreviewContent(theme, blessed) {
|
|
74
|
+
const colors = theme.colors;
|
|
75
|
+
return [
|
|
76
|
+
`{${colors.branding.title}-fg}{bold}${PREVIEW_SAMPLES.header}{/bold}{/${colors.branding.title}-fg}`,
|
|
77
|
+
`{${colors.border.cpu}-fg}${PREVIEW_SAMPLES.border}{/${colors.border.cpu}-fg}`,
|
|
78
|
+
'',
|
|
79
|
+
` {${colors.text.primary}-fg}${PREVIEW_SAMPLES.textSample}{/${colors.text.primary}-fg}`,
|
|
80
|
+
'',
|
|
81
|
+
` {${colors.status.active}-fg}${PREVIEW_SAMPLES.statusActive}{/${colors.status.active}-fg}`,
|
|
82
|
+
` {${colors.status.idle}-fg}${PREVIEW_SAMPLES.statusIdle}{/${colors.status.idle}-fg}`,
|
|
83
|
+
'',
|
|
84
|
+
` {${colors.gauge.medium}-fg}${PREVIEW_SAMPLES.gauge}{/${colors.gauge.medium}-fg}`,
|
|
85
|
+
'',
|
|
86
|
+
` {${colors.chart.line}-fg}${PREVIEW_SAMPLES.chart}{/${colors.chart.line}-fg}`,
|
|
87
|
+
'',
|
|
88
|
+
`{center}{${colors.text.secondary}-fg}${theme.name}{/${colors.text.secondary}-fg}{/center}`
|
|
89
|
+
].join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create the theme selector modal
|
|
94
|
+
*/
|
|
95
|
+
function createModal() {
|
|
96
|
+
const screenWidth = screen.width;
|
|
97
|
+
const screenHeight = screen.height;
|
|
98
|
+
|
|
99
|
+
// Calculate layout
|
|
100
|
+
const cardsPerRow = Math.min(3, themeNames.length);
|
|
101
|
+
const modalWidth = (PREVIEW_CARD.width + PREVIEW_CARD.margin) * cardsPerRow + 4;
|
|
102
|
+
const rows = Math.ceil(themeNames.length / cardsPerRow);
|
|
103
|
+
const modalHeight = PREVIEW_CARD.height * rows + 8;
|
|
104
|
+
|
|
105
|
+
// Main modal container
|
|
106
|
+
modalBox = blessed.box({
|
|
107
|
+
parent: screen,
|
|
108
|
+
top: 'center',
|
|
109
|
+
left: 'center',
|
|
110
|
+
width: Math.min(modalWidth, screenWidth - 4),
|
|
111
|
+
height: Math.min(modalHeight, screenHeight - 4),
|
|
112
|
+
border: { type: 'line' },
|
|
113
|
+
style: {
|
|
114
|
+
border: { fg: 'cyan' },
|
|
115
|
+
bg: 'black'
|
|
116
|
+
},
|
|
117
|
+
tags: true,
|
|
118
|
+
label: ' {bold}Theme Selector{/bold} ',
|
|
119
|
+
scrollable: true,
|
|
120
|
+
alwaysScroll: true,
|
|
121
|
+
scrollbar: {
|
|
122
|
+
ch: ' ',
|
|
123
|
+
style: { bg: 'cyan' }
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Title
|
|
128
|
+
blessed.text({
|
|
129
|
+
parent: modalBox,
|
|
130
|
+
top: 1,
|
|
131
|
+
left: 'center',
|
|
132
|
+
width: modalWidth - 4,
|
|
133
|
+
content: '{center}Select a theme with arrow keys, press Enter to apply{/center}',
|
|
134
|
+
style: { fg: 'white' },
|
|
135
|
+
tags: true
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Create preview cards for each theme
|
|
139
|
+
themeNames.forEach((themeName, index) => {
|
|
140
|
+
const row = Math.floor(index / cardsPerRow);
|
|
141
|
+
const col = index % cardsPerRow;
|
|
142
|
+
const theme = themeName === 'auto' ? getCurrentTheme() : getTheme(themeName);
|
|
143
|
+
const isSelected = index === selectedIndex;
|
|
144
|
+
const isCurrent = themeName === currentThemeName;
|
|
145
|
+
|
|
146
|
+
const left = 2 + col * (PREVIEW_CARD.width + PREVIEW_CARD.margin);
|
|
147
|
+
const top = 3 + row * PREVIEW_CARD.height;
|
|
148
|
+
|
|
149
|
+
// Card container
|
|
150
|
+
const card = blessed.box({
|
|
151
|
+
parent: modalBox,
|
|
152
|
+
top,
|
|
153
|
+
left,
|
|
154
|
+
width: PREVIEW_CARD.width,
|
|
155
|
+
height: PREVIEW_CARD.height,
|
|
156
|
+
border: {
|
|
157
|
+
type: 'line',
|
|
158
|
+
fg: isSelected ? 'brightCyan' : 'gray'
|
|
159
|
+
},
|
|
160
|
+
style: {
|
|
161
|
+
bg: isSelected ? 'brightBlack' : 'black',
|
|
162
|
+
border: {
|
|
163
|
+
fg: isSelected ? 'brightCyan' : 'gray'
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
tags: true,
|
|
167
|
+
content: renderPreviewContent(theme, blessed)
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Selection indicator
|
|
171
|
+
if (isCurrent) {
|
|
172
|
+
blessed.text({
|
|
173
|
+
parent: card,
|
|
174
|
+
top: 0,
|
|
175
|
+
right: 0,
|
|
176
|
+
content: '●',
|
|
177
|
+
style: { fg: 'green' }
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
previewBoxes.push({ box: card, themeName, index });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Info text
|
|
185
|
+
infoText = blessed.text({
|
|
186
|
+
parent: modalBox,
|
|
187
|
+
bottom: 2,
|
|
188
|
+
left: 'center',
|
|
189
|
+
width: modalWidth - 4,
|
|
190
|
+
content: getInfoText(),
|
|
191
|
+
style: { fg: 'gray' },
|
|
192
|
+
tags: true
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Help text
|
|
196
|
+
helpText = blessed.text({
|
|
197
|
+
parent: modalBox,
|
|
198
|
+
bottom: 1,
|
|
199
|
+
left: 'center',
|
|
200
|
+
width: modalWidth - 4,
|
|
201
|
+
content: '{center}↑/↓/←/→: Navigate Enter: Apply t: Cycle themes q/Esc: Close{/center}',
|
|
202
|
+
style: { fg: 'gray' },
|
|
203
|
+
tags: true
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
screen.render();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get info text for current selection
|
|
211
|
+
*/
|
|
212
|
+
function getInfoText() {
|
|
213
|
+
const themeName = themeNames[selectedIndex];
|
|
214
|
+
const theme = getTheme(themeName);
|
|
215
|
+
if (themeName === 'auto') {
|
|
216
|
+
const detected = getCurrentTheme();
|
|
217
|
+
return `{center}Auto-detect → ${detected.name} (currently selected: ${currentThemeName}){/center}`;
|
|
218
|
+
}
|
|
219
|
+
return `{center}${theme.name}${themeName === currentThemeName ? ' (current)' : ''}{/center}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Update selection highlighting
|
|
224
|
+
*/
|
|
225
|
+
function updateSelection() {
|
|
226
|
+
previewBoxes.forEach(({ box, index }) => {
|
|
227
|
+
const isSelected = index === selectedIndex;
|
|
228
|
+
const isCurrent = themeNames[index] === currentThemeName;
|
|
229
|
+
|
|
230
|
+
box.style.bg = isSelected ? 'brightBlack' : 'black';
|
|
231
|
+
box.style.border.fg = isSelected ? 'brightCyan' : 'gray';
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (infoText) {
|
|
235
|
+
infoText.setContent(getInfoText());
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
screen.render();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Navigate selection
|
|
243
|
+
*/
|
|
244
|
+
function navigate(delta) {
|
|
245
|
+
const cardsPerRow = Math.min(3, themeNames.length);
|
|
246
|
+
const rows = Math.ceil(themeNames.length / cardsPerRow);
|
|
247
|
+
|
|
248
|
+
if (delta === -cardsPerRow && selectedIndex - cardsPerRow >= 0) {
|
|
249
|
+
// Move up
|
|
250
|
+
selectedIndex -= cardsPerRow;
|
|
251
|
+
} else if (delta === cardsPerRow && selectedIndex + cardsPerRow < themeNames.length) {
|
|
252
|
+
// Move down
|
|
253
|
+
selectedIndex += cardsPerRow;
|
|
254
|
+
} else if (delta === -1 && selectedIndex % cardsPerRow > 0) {
|
|
255
|
+
// Move left
|
|
256
|
+
selectedIndex--;
|
|
257
|
+
} else if (delta === 1 && (selectedIndex + 1) % cardsPerRow !== 0 && selectedIndex + 1 < themeNames.length) {
|
|
258
|
+
// Move right
|
|
259
|
+
selectedIndex++;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
updateSelection();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Apply selected theme
|
|
267
|
+
*/
|
|
268
|
+
function applySelectedTheme() {
|
|
269
|
+
const themeName = themeNames[selectedIndex];
|
|
270
|
+
if (themeName !== currentThemeName) {
|
|
271
|
+
setTheme(themeName);
|
|
272
|
+
saveTheme();
|
|
273
|
+
logger.info(`Theme changed to: ${themeName}`);
|
|
274
|
+
}
|
|
275
|
+
close();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Cycle to next theme (quick switch)
|
|
280
|
+
*/
|
|
281
|
+
function cycleThemeQuick() {
|
|
282
|
+
selectedIndex = (selectedIndex + 1) % themeNames.length;
|
|
283
|
+
const themeName = themeNames[selectedIndex];
|
|
284
|
+
setTheme(themeName);
|
|
285
|
+
saveTheme();
|
|
286
|
+
updateSelection();
|
|
287
|
+
|
|
288
|
+
// Update all preview cards to show current selection
|
|
289
|
+
previewBoxes.forEach(({ box, index }) => {
|
|
290
|
+
const isCurrent = themeNames[index] === themeName;
|
|
291
|
+
// Re-render with updated selection
|
|
292
|
+
const theme = themeNames[index] === 'auto' ? getCurrentTheme() : getTheme(themeNames[index]);
|
|
293
|
+
box.setContent(renderPreviewContent(theme, blessed));
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
screen.render();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Close the modal
|
|
301
|
+
*/
|
|
302
|
+
function close() {
|
|
303
|
+
if (unsubscribeThemeChange) {
|
|
304
|
+
unsubscribeThemeChange();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (modalBox) {
|
|
308
|
+
modalBox.destroy();
|
|
309
|
+
modalBox = null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
previewBoxes = [];
|
|
313
|
+
|
|
314
|
+
if (onClose) {
|
|
315
|
+
onClose();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
screen.render();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Handle key events
|
|
323
|
+
*/
|
|
324
|
+
function handleKey(ch, key) {
|
|
325
|
+
// Navigation
|
|
326
|
+
if (key.name === 'up') navigate(-3);
|
|
327
|
+
else if (key.name === 'down') navigate(3);
|
|
328
|
+
else if (key.name === 'left') navigate(-1);
|
|
329
|
+
else if (key.name === 'right') navigate(1);
|
|
330
|
+
else if (key.name === 'return') applySelectedTheme();
|
|
331
|
+
else if (ch === 't') cycleThemeQuick();
|
|
332
|
+
else if (ch === 'q' || key.name === 'escape') close();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Create the modal
|
|
336
|
+
createModal();
|
|
337
|
+
|
|
338
|
+
// Listen for theme changes from elsewhere
|
|
339
|
+
unsubscribeThemeChange = onThemeChange(() => {
|
|
340
|
+
// Refresh previews if theme changes externally
|
|
341
|
+
previewBoxes.forEach(({ box, index }) => {
|
|
342
|
+
const theme = themeNames[index] === 'auto' ? getCurrentTheme() : getTheme(themeNames[index]);
|
|
343
|
+
box.setContent(renderPreviewContent(theme, blessed));
|
|
344
|
+
});
|
|
345
|
+
screen.render();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
handleKey,
|
|
350
|
+
close,
|
|
351
|
+
isActive: () => modalBox !== null
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Show theme selector as a modal overlay
|
|
357
|
+
* @param {blessed.Screen} screen - Blessed screen instance
|
|
358
|
+
* @param {Object} blessed - Blessed library
|
|
359
|
+
* @param {Function} onThemeApplied - Callback when theme is applied
|
|
360
|
+
* @returns {Promise<void>}
|
|
361
|
+
*/
|
|
362
|
+
export async function showThemeSelector(screen, blessed, onThemeApplied) {
|
|
363
|
+
return new Promise((resolve) => {
|
|
364
|
+
const selector = createThemeSelector(screen, blessed, () => {
|
|
365
|
+
screen.removeListener('keypress', keyHandler);
|
|
366
|
+
if (onThemeApplied) {
|
|
367
|
+
onThemeApplied();
|
|
368
|
+
}
|
|
369
|
+
resolve();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
function keyHandler(ch, key) {
|
|
373
|
+
selector.handleKey(ch, key);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
screen.on('keypress', keyHandler);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get theme info for display
|
|
382
|
+
* @param {string} themeName - Theme name
|
|
383
|
+
* @returns {Object} Theme info object
|
|
384
|
+
*/
|
|
385
|
+
export function getThemeInfo(themeName) {
|
|
386
|
+
const theme = getTheme(themeName);
|
|
387
|
+
const current = getThemeName();
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
name: themeName,
|
|
391
|
+
displayName: theme?.name || themeName,
|
|
392
|
+
isCurrent: themeName === current,
|
|
393
|
+
isAuto: themeName === 'auto',
|
|
394
|
+
colors: theme?.colors ? Object.keys(theme.colors) : []
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get all themes info
|
|
400
|
+
* @returns {Array} Array of theme info objects
|
|
401
|
+
*/
|
|
402
|
+
export function getAllThemesInfo() {
|
|
403
|
+
return getThemeNames().map(name => getThemeInfo(name));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export default {
|
|
407
|
+
createThemeSelector,
|
|
408
|
+
showThemeSelector,
|
|
409
|
+
getThemeInfo,
|
|
410
|
+
getAllThemesInfo
|
|
411
|
+
};
|