@involvex/youtube-music-cli 0.0.47 → 0.0.49
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/CHANGELOG.md +8 -0
- package/dist/cli.js.map +6 -6
- package/dist/youtube-music-cli +0 -0
- package/package.json +1 -1
- package/dist/eslint.config.js +0 -55
- package/dist/package.json +0 -120
- package/dist/scripts/build-cli.js +0 -46
- package/dist/source/app.js +0 -17
- package/dist/source/cli.js +0 -504
- package/dist/source/components/common/ErrorBoundary.js +0 -22
- package/dist/source/components/common/Help.js +0 -18
- package/dist/source/components/common/ShortcutsBar.js +0 -89
- package/dist/source/components/config/ConfigLayout.js +0 -84
- package/dist/source/components/config/KeybindingsLayout.js +0 -107
- package/dist/source/components/export/ExportLayout.js +0 -111
- package/dist/source/components/import/ImportLayout.js +0 -119
- package/dist/source/components/import/ImportProgress.js +0 -73
- package/dist/source/components/layouts/ExploreLayout.js +0 -72
- package/dist/source/components/layouts/HistoryLayout.js +0 -37
- package/dist/source/components/layouts/LyricsLayout.js +0 -89
- package/dist/source/components/layouts/MainLayout.js +0 -190
- package/dist/source/components/layouts/MiniPlayerLayout.js +0 -20
- package/dist/source/components/layouts/PlayerLayout.js +0 -9
- package/dist/source/components/layouts/PluginsLayout.js +0 -77
- package/dist/source/components/layouts/SearchLayout.js +0 -193
- package/dist/source/components/layouts/TrendingLayout.js +0 -59
- package/dist/source/components/player/NowPlaying.js +0 -45
- package/dist/source/components/player/PlayerControls.js +0 -83
- package/dist/source/components/player/ProgressBar.js +0 -19
- package/dist/source/components/player/QueueList.js +0 -36
- package/dist/source/components/player/Suggestions.js +0 -50
- package/dist/source/components/playlist/PlaylistList.js +0 -138
- package/dist/source/components/plugins/PluginInstallDialog.js +0 -41
- package/dist/source/components/plugins/PluginsAvailable.js +0 -55
- package/dist/source/components/plugins/PluginsList.js +0 -18
- package/dist/source/components/search/SearchBar.js +0 -55
- package/dist/source/components/search/SearchHistory.js +0 -35
- package/dist/source/components/search/SearchResults.js +0 -280
- package/dist/source/components/settings/Settings.js +0 -211
- package/dist/source/components/theme/ThemeSwitcher.js +0 -11
- package/dist/source/config/themes.config.js +0 -123
- package/dist/source/contexts/theme.context.js +0 -29
- package/dist/source/hooks/useKeyboard.js +0 -188
- package/dist/source/hooks/useKeyboardBlocker.js +0 -45
- package/dist/source/hooks/useNavigation.js +0 -5
- package/dist/source/hooks/usePlayer.js +0 -43
- package/dist/source/hooks/usePlaylist.js +0 -65
- package/dist/source/hooks/useSearch.js +0 -76
- package/dist/source/hooks/useSleepTimer.js +0 -48
- package/dist/source/hooks/useTerminalSize.js +0 -24
- package/dist/source/hooks/useTheme.js +0 -5
- package/dist/source/hooks/useYouTubeMusic.js +0 -112
- package/dist/source/main.js +0 -127
- package/dist/source/services/cache/cache.service.js +0 -67
- package/dist/source/services/completions/completions.service.js +0 -313
- package/dist/source/services/config/config.service.js +0 -191
- package/dist/source/services/discord/discord-rpc.service.js +0 -95
- package/dist/source/services/download/download.service.js +0 -350
- package/dist/source/services/export/export.service.js +0 -131
- package/dist/source/services/history/history.service.js +0 -83
- package/dist/source/services/import/import.service.js +0 -272
- package/dist/source/services/import/spotify.service.js +0 -171
- package/dist/source/services/import/track-matcher.service.js +0 -271
- package/dist/source/services/import/youtube-import.service.js +0 -84
- package/dist/source/services/logger/logger.service.js +0 -52
- package/dist/source/services/lyrics/lyrics.service.js +0 -93
- package/dist/source/services/mpris/mpris.service.js +0 -78
- package/dist/source/services/notification/notification.service.js +0 -57
- package/dist/source/services/player/dependency-check.service.js +0 -140
- package/dist/source/services/player/player.service.js +0 -478
- package/dist/source/services/player-state/player-state.service.js +0 -123
- package/dist/source/services/plugin/plugin-audio-api.js +0 -36
- package/dist/source/services/plugin/plugin-context.js +0 -256
- package/dist/source/services/plugin/plugin-hooks.service.js +0 -135
- package/dist/source/services/plugin/plugin-installer.service.js +0 -248
- package/dist/source/services/plugin/plugin-loader.service.js +0 -161
- package/dist/source/services/plugin/plugin-permissions.service.js +0 -194
- package/dist/source/services/plugin/plugin-registry.service.js +0 -215
- package/dist/source/services/plugin/plugin-ui-api.js +0 -46
- package/dist/source/services/plugin/plugin-updater.service.js +0 -206
- package/dist/source/services/scrobbling/scrobbling.service.js +0 -115
- package/dist/source/services/sleep-timer/sleep-timer.service.js +0 -45
- package/dist/source/services/version-check/version-check.service.js +0 -121
- package/dist/source/services/web/static-file.service.js +0 -185
- package/dist/source/services/web/web-server-manager.js +0 -507
- package/dist/source/services/web/web-streaming.service.js +0 -292
- package/dist/source/services/web/websocket.server.js +0 -267
- package/dist/source/services/youtube-music/api.js +0 -649
- package/dist/source/services/youtube-music/search.service.js +0 -38
- package/dist/source/stores/history.store.js +0 -64
- package/dist/source/stores/navigation.store.js +0 -90
- package/dist/source/stores/player.store.js +0 -789
- package/dist/source/stores/plugins.store.js +0 -177
- package/dist/source/types/actions.js +0 -1
- package/dist/source/types/cli.types.js +0 -1
- package/dist/source/types/config.types.js +0 -1
- package/dist/source/types/history.types.js +0 -1
- package/dist/source/types/import.types.js +0 -2
- package/dist/source/types/keyboard.types.js +0 -1
- package/dist/source/types/navigation.types.js +0 -1
- package/dist/source/types/player.types.js +0 -1
- package/dist/source/types/playlist.types.js +0 -1
- package/dist/source/types/plugin.types.js +0 -1
- package/dist/source/types/theme.types.js +0 -1
- package/dist/source/types/web.types.js +0 -2
- package/dist/source/types/youtube-music.types.js +0 -1
- package/dist/source/types/youtubei.types.js +0 -3
- package/dist/source/utils/constants.js +0 -135
- package/dist/source/utils/format.js +0 -24
- package/dist/source/utils/icons.js +0 -28
- package/dist/source/utils/search-filters.js +0 -100
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Search results component
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { Box, Text } from 'ink';
|
|
5
|
-
import { useTheme } from "../../hooks/useTheme.js";
|
|
6
|
-
import { useNavigation } from "../../hooks/useNavigation.js";
|
|
7
|
-
import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
8
|
-
import { usePlayer } from "../../hooks/usePlayer.js";
|
|
9
|
-
import { usePlaylist } from "../../hooks/usePlaylist.js";
|
|
10
|
-
import { KEYBINDINGS } from "../../utils/constants.js";
|
|
11
|
-
import { truncate } from "../../utils/format.js";
|
|
12
|
-
import { useCallback, useRef, useEffect, useState } from 'react';
|
|
13
|
-
import { logger } from "../../services/logger/logger.service.js";
|
|
14
|
-
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
15
|
-
import { getMusicService } from "../../services/youtube-music/api.js";
|
|
16
|
-
import { getDownloadService } from "../../services/download/download.service.js";
|
|
17
|
-
// Generate unique component instance ID
|
|
18
|
-
let instanceCounter = 0;
|
|
19
|
-
function SearchResults({ results, selectedIndex, isActive = true, onMixCreated, onDownloadStatus, }) {
|
|
20
|
-
const { theme } = useTheme();
|
|
21
|
-
const { dispatch } = useNavigation();
|
|
22
|
-
const { play, dispatch: playerDispatch } = usePlayer();
|
|
23
|
-
const { columns } = useTerminalSize();
|
|
24
|
-
const musicService = getMusicService();
|
|
25
|
-
const downloadService = getDownloadService();
|
|
26
|
-
const { createPlaylist } = usePlaylist();
|
|
27
|
-
const [isDownloading, setIsDownloading] = useState(false);
|
|
28
|
-
const mixCreatedRef = useRef(onMixCreated);
|
|
29
|
-
mixCreatedRef.current = onMixCreated;
|
|
30
|
-
const downloadStatusRef = useRef(onDownloadStatus);
|
|
31
|
-
downloadStatusRef.current = onDownloadStatus;
|
|
32
|
-
// Track component instance and last action time for debouncing
|
|
33
|
-
const instanceIdRef = useRef(++instanceCounter);
|
|
34
|
-
const lastSelectTime = useRef(0);
|
|
35
|
-
const SELECT_DEBOUNCE_MS = 300; // Prevent duplicate triggers within 300ms
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const instanceId = instanceIdRef.current;
|
|
38
|
-
logger.debug('SearchResults', 'Component mounted', { instanceId });
|
|
39
|
-
return () => {
|
|
40
|
-
logger.debug('SearchResults', 'Component unmounted', { instanceId });
|
|
41
|
-
};
|
|
42
|
-
}, []);
|
|
43
|
-
// Navigate results with arrow keys
|
|
44
|
-
const navigateUp = useCallback(() => {
|
|
45
|
-
if (!isActive)
|
|
46
|
-
return;
|
|
47
|
-
if (selectedIndex > 0) {
|
|
48
|
-
dispatch({ category: 'SET_SELECTED_RESULT', index: selectedIndex - 1 });
|
|
49
|
-
}
|
|
50
|
-
}, [selectedIndex, dispatch, isActive]);
|
|
51
|
-
const navigateDown = useCallback(() => {
|
|
52
|
-
if (!isActive)
|
|
53
|
-
return;
|
|
54
|
-
if (selectedIndex < results.length - 1) {
|
|
55
|
-
dispatch({ category: 'SET_SELECTED_RESULT', index: selectedIndex + 1 });
|
|
56
|
-
}
|
|
57
|
-
}, [selectedIndex, results.length, dispatch, isActive]);
|
|
58
|
-
// Play selected result
|
|
59
|
-
const playSelected = useCallback(async () => {
|
|
60
|
-
logger.debug('SearchResults', 'playSelected called', {
|
|
61
|
-
isActive,
|
|
62
|
-
selectedIndex,
|
|
63
|
-
resultsLength: results.length,
|
|
64
|
-
});
|
|
65
|
-
if (!isActive)
|
|
66
|
-
return;
|
|
67
|
-
const selected = results[selectedIndex];
|
|
68
|
-
logger.info('SearchResults', 'Playing selected track', {
|
|
69
|
-
type: selected?.type,
|
|
70
|
-
title: selected?.type === 'song' ? selected.data.title : 'N/A',
|
|
71
|
-
});
|
|
72
|
-
if (selected && selected.type === 'song') {
|
|
73
|
-
// Clear queue when playing from search results to ensure indices match
|
|
74
|
-
play(selected.data, { clearQueue: true });
|
|
75
|
-
}
|
|
76
|
-
else if (selected && selected.type === 'artist') {
|
|
77
|
-
const artistName = 'name' in selected.data ? selected.data.name : '';
|
|
78
|
-
if (!artistName) {
|
|
79
|
-
logger.warn('SearchResults', 'Artist name missing, cannot search songs');
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
try {
|
|
83
|
-
const response = await musicService.search(artistName, {
|
|
84
|
-
type: 'songs',
|
|
85
|
-
limit: 20,
|
|
86
|
-
});
|
|
87
|
-
const tracks = response.results
|
|
88
|
-
.filter(result => result.type === 'song')
|
|
89
|
-
.map(result => result.data);
|
|
90
|
-
if (tracks.length === 0) {
|
|
91
|
-
logger.warn('SearchResults', 'No songs found for artist', {
|
|
92
|
-
artistName,
|
|
93
|
-
});
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
// Replace queue with artist songs and start playback
|
|
97
|
-
playerDispatch({ category: 'CLEAR_QUEUE' });
|
|
98
|
-
playerDispatch({ category: 'SET_QUEUE', queue: tracks });
|
|
99
|
-
playerDispatch({ category: 'PLAY', track: tracks[0] });
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
logger.error('SearchResults', 'Failed to play artist songs', {
|
|
103
|
-
error,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
logger.warn('SearchResults', 'Selected item is not playable', {
|
|
109
|
-
type: selected?.type,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}, [selectedIndex, results, play, isActive, musicService, playerDispatch]);
|
|
113
|
-
// Play selected result handler (memoized to prevent duplicate registrations)
|
|
114
|
-
const handleSelect = useCallback(() => {
|
|
115
|
-
const now = Date.now();
|
|
116
|
-
const timeSinceLastSelect = now - lastSelectTime.current;
|
|
117
|
-
const instanceId = instanceIdRef.current;
|
|
118
|
-
if (!isActive) {
|
|
119
|
-
logger.debug('SearchResults', 'SELECT ignored, not active', { instanceId });
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
// Debounce to prevent double-triggers
|
|
123
|
-
if (timeSinceLastSelect < SELECT_DEBOUNCE_MS) {
|
|
124
|
-
logger.warn('SearchResults', 'SELECT debounced (duplicate trigger)', {
|
|
125
|
-
instanceId,
|
|
126
|
-
timeSinceLastSelect,
|
|
127
|
-
debounceMs: SELECT_DEBOUNCE_MS,
|
|
128
|
-
});
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
lastSelectTime.current = now;
|
|
132
|
-
logger.debug('SearchResults', 'SELECT key pressed', { isActive, instanceId });
|
|
133
|
-
playSelected();
|
|
134
|
-
}, [isActive, playSelected]);
|
|
135
|
-
const createMixPlaylist = useCallback(async () => {
|
|
136
|
-
if (!isActive)
|
|
137
|
-
return;
|
|
138
|
-
const selected = results[selectedIndex];
|
|
139
|
-
if (!selected) {
|
|
140
|
-
logger.warn('SearchResults', 'No result selected for mix');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
let playlistName = 'Dynamic mix';
|
|
144
|
-
const collectedTracks = [];
|
|
145
|
-
if (selected.type === 'song') {
|
|
146
|
-
const selectedTrack = selected.data;
|
|
147
|
-
const title = selectedTrack.title || 'selected track';
|
|
148
|
-
playlistName = `Mix for ${title}`;
|
|
149
|
-
collectedTracks.push(selectedTrack);
|
|
150
|
-
try {
|
|
151
|
-
const suggestions = await musicService.getSuggestions(selectedTrack.videoId);
|
|
152
|
-
collectedTracks.push(...suggestions);
|
|
153
|
-
}
|
|
154
|
-
catch (error) {
|
|
155
|
-
logger.error('SearchResults', 'Failed to fetch song suggestions', {
|
|
156
|
-
error,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else if (selected.type === 'artist') {
|
|
161
|
-
const artistName = 'name' in selected.data ? selected.data.name : '';
|
|
162
|
-
if (!artistName) {
|
|
163
|
-
logger.warn('SearchResults', 'Artist name missing for mix');
|
|
164
|
-
mixCreatedRef.current?.('Artist information is missing, cannot create mix.');
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
playlistName = `${artistName} mix`;
|
|
168
|
-
try {
|
|
169
|
-
const response = await musicService.search(artistName, {
|
|
170
|
-
type: 'songs',
|
|
171
|
-
limit: 25,
|
|
172
|
-
});
|
|
173
|
-
const artistTracks = response.results
|
|
174
|
-
.filter(result => result.type === 'song')
|
|
175
|
-
.map(result => result.data);
|
|
176
|
-
collectedTracks.push(...artistTracks);
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
logger.error('SearchResults', 'Failed to fetch artist songs for mix', {
|
|
180
|
-
error,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
logger.warn('SearchResults', 'Mix creation unsupported result type', {
|
|
186
|
-
type: selected.type,
|
|
187
|
-
});
|
|
188
|
-
mixCreatedRef.current?.('Mix creation is only supported for songs and artists.');
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
const uniqueTracks = [];
|
|
192
|
-
const seenVideoIds = new Set();
|
|
193
|
-
for (const track of collectedTracks) {
|
|
194
|
-
if (!track?.videoId || seenVideoIds.has(track.videoId))
|
|
195
|
-
continue;
|
|
196
|
-
seenVideoIds.add(track.videoId);
|
|
197
|
-
uniqueTracks.push(track);
|
|
198
|
-
}
|
|
199
|
-
if (uniqueTracks.length === 0) {
|
|
200
|
-
mixCreatedRef.current?.('No similar tracks were found to create a mix.');
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const playlist = createPlaylist(playlistName, uniqueTracks);
|
|
204
|
-
logger.info('SearchResults', 'Mix playlist created', {
|
|
205
|
-
name: playlist.name,
|
|
206
|
-
trackCount: uniqueTracks.length,
|
|
207
|
-
});
|
|
208
|
-
// Queue the mix tracks and start playing the first one
|
|
209
|
-
playerDispatch({ category: 'SET_QUEUE', queue: uniqueTracks });
|
|
210
|
-
const firstTrack = uniqueTracks[0];
|
|
211
|
-
if (firstTrack) {
|
|
212
|
-
playerDispatch({ category: 'PLAY', track: firstTrack });
|
|
213
|
-
}
|
|
214
|
-
mixCreatedRef.current?.(`Created mix "${playlist.name}" with ${uniqueTracks.length} tracks — playing now (Esc to go back).`);
|
|
215
|
-
}, [
|
|
216
|
-
createPlaylist,
|
|
217
|
-
isActive,
|
|
218
|
-
musicService,
|
|
219
|
-
playerDispatch,
|
|
220
|
-
results,
|
|
221
|
-
selectedIndex,
|
|
222
|
-
]);
|
|
223
|
-
const downloadSelected = useCallback(async () => {
|
|
224
|
-
if (!isActive)
|
|
225
|
-
return;
|
|
226
|
-
if (isDownloading) {
|
|
227
|
-
downloadStatusRef.current?.('Download already in progress. Please wait.');
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
const selected = results[selectedIndex];
|
|
231
|
-
if (!selected)
|
|
232
|
-
return;
|
|
233
|
-
const config = downloadService.getConfig();
|
|
234
|
-
if (!config.enabled) {
|
|
235
|
-
downloadStatusRef.current?.('Downloads are disabled. Enable Download Feature in Settings.');
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
try {
|
|
239
|
-
setIsDownloading(true);
|
|
240
|
-
const target = await downloadService.resolveSearchTarget(selected);
|
|
241
|
-
if (target.tracks.length === 0) {
|
|
242
|
-
downloadStatusRef.current?.(`No tracks found for "${target.name}".`);
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
downloadStatusRef.current?.(`Downloading ${target.tracks.length} track(s) from "${target.name}"... this can take a few minutes.`);
|
|
246
|
-
const summary = await downloadService.downloadTracks(target.tracks);
|
|
247
|
-
downloadStatusRef.current?.(`Downloaded ${summary.downloaded}, skipped ${summary.skipped}, failed ${summary.failed}.`);
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
downloadStatusRef.current?.(error instanceof Error ? error.message : 'Download failed.');
|
|
251
|
-
}
|
|
252
|
-
finally {
|
|
253
|
-
setIsDownloading(false);
|
|
254
|
-
}
|
|
255
|
-
}, [downloadService, isActive, isDownloading, results, selectedIndex]);
|
|
256
|
-
useKeyBinding(KEYBINDINGS.UP, navigateUp);
|
|
257
|
-
useKeyBinding(KEYBINDINGS.DOWN, navigateDown);
|
|
258
|
-
useKeyBinding(KEYBINDINGS.SELECT, handleSelect);
|
|
259
|
-
useKeyBinding(KEYBINDINGS.CREATE_MIX, () => {
|
|
260
|
-
void createMixPlaylist();
|
|
261
|
-
});
|
|
262
|
-
useKeyBinding(KEYBINDINGS.DOWNLOAD, () => {
|
|
263
|
-
void downloadSelected();
|
|
264
|
-
});
|
|
265
|
-
// Note: Removed redundant useEffect that was syncing selectedIndex to dispatch
|
|
266
|
-
// This was causing unnecessary re-renders. The selectedIndex is already managed
|
|
267
|
-
// by the parent component (SearchLayout) and passed down as a prop.
|
|
268
|
-
if (results.length === 0) {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
// Calculate responsive truncation
|
|
272
|
-
const maxTitleWidth = Math.max(20, Math.floor(columns * 0.4));
|
|
273
|
-
return (_jsx(Box, { flexDirection: "column", children: results.map((result, index) => {
|
|
274
|
-
const isSelected = index === selectedIndex;
|
|
275
|
-
const data = result.data;
|
|
276
|
-
const title = 'title' in data ? data.title : 'name' in data ? data.name : 'Unknown';
|
|
277
|
-
return (_jsxs(Box, { paddingX: 1, backgroundColor: isSelected ? theme.colors.secondary : undefined, children: [_jsx(Text, { color: isSelected ? theme.colors.primary : theme.colors.dim, bold: isSelected, children: (isSelected ? '> ' : ' ') + (index + 1).toString().padEnd(4) }), _jsx(Text, { color: isSelected ? theme.colors.primary : theme.colors.dim, bold: isSelected, children: result.type.toUpperCase().padEnd(10) }), _jsx(Text, { color: isSelected ? theme.colors.primary : theme.colors.text, bold: isSelected, children: truncate(title, maxTitleWidth) })] }, index));
|
|
278
|
-
}) }));
|
|
279
|
-
}
|
|
280
|
-
export default React.memo(SearchResults);
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Settings component
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { Box, Text } from 'ink';
|
|
5
|
-
import TextInput from 'ink-text-input';
|
|
6
|
-
import { useTheme } from "../../hooks/useTheme.js";
|
|
7
|
-
import { useNavigation } from "../../hooks/useNavigation.js";
|
|
8
|
-
import { getConfigService } from "../../services/config/config.service.js";
|
|
9
|
-
import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
10
|
-
import { KEYBINDINGS, VIEW } from "../../utils/constants.js";
|
|
11
|
-
import { useSleepTimer } from "../../hooks/useSleepTimer.js";
|
|
12
|
-
import { formatTime } from "../../utils/format.js";
|
|
13
|
-
import { useKeyboardBlocker } from "../../hooks/useKeyboardBlocker.js";
|
|
14
|
-
const QUALITIES = ['low', 'medium', 'high'];
|
|
15
|
-
const DOWNLOAD_FORMATS = ['mp3', 'm4a'];
|
|
16
|
-
const CROSSFADE_PRESETS = [0, 1, 2, 3, 5];
|
|
17
|
-
const EQUALIZER_PRESETS = [
|
|
18
|
-
'flat',
|
|
19
|
-
'bass_boost',
|
|
20
|
-
'vocal',
|
|
21
|
-
'bright',
|
|
22
|
-
'warm',
|
|
23
|
-
];
|
|
24
|
-
const VOLUME_FADE_PRESETS = [0, 1, 2, 3, 5];
|
|
25
|
-
const SETTINGS_ITEMS = [
|
|
26
|
-
'Stream Quality',
|
|
27
|
-
'Audio Normalization',
|
|
28
|
-
'Gapless Playback',
|
|
29
|
-
'Crossfade Duration',
|
|
30
|
-
'Volume Fade Duration',
|
|
31
|
-
'Equalizer Preset',
|
|
32
|
-
'Notifications',
|
|
33
|
-
'Discord Rich Presence',
|
|
34
|
-
'Downloads Enabled',
|
|
35
|
-
'Download Folder',
|
|
36
|
-
'Download Format',
|
|
37
|
-
'Sleep Timer',
|
|
38
|
-
'Import Playlists',
|
|
39
|
-
'Export Playlists',
|
|
40
|
-
'Custom Keybindings',
|
|
41
|
-
'Manage Plugins',
|
|
42
|
-
];
|
|
43
|
-
export default function Settings() {
|
|
44
|
-
const { theme } = useTheme();
|
|
45
|
-
const { dispatch } = useNavigation();
|
|
46
|
-
const config = getConfigService();
|
|
47
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
48
|
-
const [quality, setQuality] = useState(config.get('streamQuality') || 'high');
|
|
49
|
-
const [audioNormalization, setAudioNormalization] = useState(config.get('audioNormalization') ?? false);
|
|
50
|
-
const [gaplessPlayback, setGaplessPlayback] = useState(config.get('gaplessPlayback') ?? true);
|
|
51
|
-
const [crossfadeDuration, setCrossfadeDuration] = useState(config.get('crossfadeDuration') ?? 0);
|
|
52
|
-
const [volumeFadeDuration, setVolumeFadeDuration] = useState(config.get('volumeFadeDuration') ?? 0);
|
|
53
|
-
const [equalizerPreset, setEqualizerPreset] = useState(config.get('equalizerPreset') ?? 'flat');
|
|
54
|
-
const [notifications, setNotifications] = useState(config.get('notifications') ?? false);
|
|
55
|
-
const [discordRpc, setDiscordRpc] = useState(config.get('discordRichPresence') ?? false);
|
|
56
|
-
const [downloadsEnabled, setDownloadsEnabled] = useState(config.get('downloadsEnabled') ?? false);
|
|
57
|
-
const [downloadDirectory, setDownloadDirectory] = useState(config.get('downloadDirectory') ?? '');
|
|
58
|
-
const [downloadFormat, setDownloadFormat] = useState(config.get('downloadFormat') ?? 'mp3');
|
|
59
|
-
const [isEditingDownloadDirectory, setIsEditingDownloadDirectory] = useState(false);
|
|
60
|
-
const { isActive, activeMinutes, remainingSeconds, startTimer, cancelTimer, presets, } = useSleepTimer();
|
|
61
|
-
useKeyboardBlocker(isEditingDownloadDirectory);
|
|
62
|
-
const navigateUp = () => {
|
|
63
|
-
setSelectedIndex(prev => Math.max(0, prev - 1));
|
|
64
|
-
};
|
|
65
|
-
const navigateDown = () => {
|
|
66
|
-
setSelectedIndex(prev => Math.min(SETTINGS_ITEMS.length - 1, prev + 1));
|
|
67
|
-
};
|
|
68
|
-
const toggleQuality = () => {
|
|
69
|
-
const currentIndex = QUALITIES.indexOf(quality);
|
|
70
|
-
const nextQuality = QUALITIES[(currentIndex + 1) % QUALITIES.length];
|
|
71
|
-
setQuality(nextQuality);
|
|
72
|
-
config.set('streamQuality', nextQuality);
|
|
73
|
-
};
|
|
74
|
-
const toggleNormalization = () => {
|
|
75
|
-
const next = !audioNormalization;
|
|
76
|
-
setAudioNormalization(next);
|
|
77
|
-
config.set('audioNormalization', next);
|
|
78
|
-
};
|
|
79
|
-
const toggleGaplessPlayback = () => {
|
|
80
|
-
const next = !gaplessPlayback;
|
|
81
|
-
setGaplessPlayback(next);
|
|
82
|
-
config.set('gaplessPlayback', next);
|
|
83
|
-
};
|
|
84
|
-
const cycleCrossfadeDuration = () => {
|
|
85
|
-
const currentIndex = CROSSFADE_PRESETS.indexOf(crossfadeDuration);
|
|
86
|
-
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % CROSSFADE_PRESETS.length;
|
|
87
|
-
const next = CROSSFADE_PRESETS[nextIndex] ?? 0;
|
|
88
|
-
setCrossfadeDuration(next);
|
|
89
|
-
config.set('crossfadeDuration', next);
|
|
90
|
-
};
|
|
91
|
-
const cycleVolumeFadeDuration = () => {
|
|
92
|
-
const currentIndex = VOLUME_FADE_PRESETS.indexOf(volumeFadeDuration);
|
|
93
|
-
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % VOLUME_FADE_PRESETS.length;
|
|
94
|
-
const next = VOLUME_FADE_PRESETS[nextIndex] ?? 0;
|
|
95
|
-
setVolumeFadeDuration(next);
|
|
96
|
-
config.set('volumeFadeDuration', next);
|
|
97
|
-
};
|
|
98
|
-
const cycleEqualizerPreset = () => {
|
|
99
|
-
const currentIndex = EQUALIZER_PRESETS.indexOf(equalizerPreset);
|
|
100
|
-
const nextPreset = EQUALIZER_PRESETS[(currentIndex + 1) % EQUALIZER_PRESETS.length];
|
|
101
|
-
setEqualizerPreset(nextPreset);
|
|
102
|
-
config.set('equalizerPreset', nextPreset);
|
|
103
|
-
};
|
|
104
|
-
const formatEqualizerLabel = (preset) => preset
|
|
105
|
-
.split('_')
|
|
106
|
-
.map(segment => `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`)
|
|
107
|
-
.join(' ');
|
|
108
|
-
const toggleNotifications = () => {
|
|
109
|
-
const next = !notifications;
|
|
110
|
-
setNotifications(next);
|
|
111
|
-
config.set('notifications', next);
|
|
112
|
-
};
|
|
113
|
-
const toggleDiscordRpc = () => {
|
|
114
|
-
const next = !discordRpc;
|
|
115
|
-
setDiscordRpc(next);
|
|
116
|
-
config.set('discordRichPresence', next);
|
|
117
|
-
};
|
|
118
|
-
const toggleDownloadsEnabled = () => {
|
|
119
|
-
const next = !downloadsEnabled;
|
|
120
|
-
setDownloadsEnabled(next);
|
|
121
|
-
config.set('downloadsEnabled', next);
|
|
122
|
-
};
|
|
123
|
-
const cycleDownloadFormat = () => {
|
|
124
|
-
const currentIndex = DOWNLOAD_FORMATS.indexOf(downloadFormat);
|
|
125
|
-
const nextFormat = DOWNLOAD_FORMATS[(currentIndex + 1) % DOWNLOAD_FORMATS.length];
|
|
126
|
-
setDownloadFormat(nextFormat);
|
|
127
|
-
config.set('downloadFormat', nextFormat);
|
|
128
|
-
};
|
|
129
|
-
const cycleSleepTimer = () => {
|
|
130
|
-
if (isActive) {
|
|
131
|
-
cancelTimer();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
// Find next preset (start from first if none active)
|
|
135
|
-
const currentPresetIndex = activeMinutes
|
|
136
|
-
? presets.indexOf(activeMinutes)
|
|
137
|
-
: -1;
|
|
138
|
-
const nextPreset = presets[(currentPresetIndex + 1) % presets.length];
|
|
139
|
-
startTimer(nextPreset);
|
|
140
|
-
};
|
|
141
|
-
const handleSelect = () => {
|
|
142
|
-
if (selectedIndex === 0) {
|
|
143
|
-
toggleQuality();
|
|
144
|
-
}
|
|
145
|
-
else if (selectedIndex === 1) {
|
|
146
|
-
toggleNormalization();
|
|
147
|
-
}
|
|
148
|
-
else if (selectedIndex === 2) {
|
|
149
|
-
toggleGaplessPlayback();
|
|
150
|
-
}
|
|
151
|
-
else if (selectedIndex === 3) {
|
|
152
|
-
cycleCrossfadeDuration();
|
|
153
|
-
}
|
|
154
|
-
else if (selectedIndex === 4) {
|
|
155
|
-
cycleVolumeFadeDuration();
|
|
156
|
-
}
|
|
157
|
-
else if (selectedIndex === 5) {
|
|
158
|
-
cycleEqualizerPreset();
|
|
159
|
-
}
|
|
160
|
-
else if (selectedIndex === 6) {
|
|
161
|
-
toggleNotifications();
|
|
162
|
-
}
|
|
163
|
-
else if (selectedIndex === 7) {
|
|
164
|
-
toggleDiscordRpc();
|
|
165
|
-
}
|
|
166
|
-
else if (selectedIndex === 8) {
|
|
167
|
-
toggleDownloadsEnabled();
|
|
168
|
-
}
|
|
169
|
-
else if (selectedIndex === 9) {
|
|
170
|
-
setIsEditingDownloadDirectory(true);
|
|
171
|
-
}
|
|
172
|
-
else if (selectedIndex === 10) {
|
|
173
|
-
cycleDownloadFormat();
|
|
174
|
-
}
|
|
175
|
-
else if (selectedIndex === 11) {
|
|
176
|
-
cycleSleepTimer();
|
|
177
|
-
}
|
|
178
|
-
else if (selectedIndex === 12) {
|
|
179
|
-
dispatch({ category: 'NAVIGATE', view: VIEW.IMPORT });
|
|
180
|
-
}
|
|
181
|
-
else if (selectedIndex === 13) {
|
|
182
|
-
dispatch({ category: 'NAVIGATE', view: VIEW.EXPORT_PLAYLISTS });
|
|
183
|
-
}
|
|
184
|
-
else if (selectedIndex === 14) {
|
|
185
|
-
dispatch({ category: 'NAVIGATE', view: VIEW.KEYBINDINGS });
|
|
186
|
-
}
|
|
187
|
-
else if (selectedIndex === 15) {
|
|
188
|
-
dispatch({ category: 'NAVIGATE', view: VIEW.PLUGINS });
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
useKeyBinding(KEYBINDINGS.UP, navigateUp);
|
|
192
|
-
useKeyBinding(KEYBINDINGS.DOWN, navigateDown);
|
|
193
|
-
useKeyBinding(KEYBINDINGS.SELECT, handleSelect);
|
|
194
|
-
const sleepTimerLabel = isActive && remainingSeconds !== null
|
|
195
|
-
? `Sleep Timer: ${formatTime(remainingSeconds)} remaining (Enter to cancel)`
|
|
196
|
-
: 'Sleep Timer: Off (Enter to set)';
|
|
197
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Box, { borderStyle: "double", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Settings" }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 0 ? theme.colors.primary : undefined, color: selectedIndex === 0 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 0, children: ["Stream Quality: ", quality.toUpperCase()] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 1 ? theme.colors.primary : undefined, color: selectedIndex === 1 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 1, children: ["Audio Normalization: ", audioNormalization ? 'ON' : 'OFF'] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 2 ? theme.colors.primary : undefined, color: selectedIndex === 2 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 2, children: ["Gapless Playback: ", gaplessPlayback ? 'ON' : 'OFF'] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 3 ? theme.colors.primary : undefined, color: selectedIndex === 3 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 3, children: ["Crossfade: ", crossfadeDuration === 0 ? 'Off' : `${crossfadeDuration}s`] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 4 ? theme.colors.primary : undefined, color: selectedIndex === 4 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 4, children: ["Volume Fade:", ' ', volumeFadeDuration === 0 ? 'Off' : `${volumeFadeDuration}s`] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 5 ? theme.colors.primary : undefined, color: selectedIndex === 5 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 5, children: ["Equalizer: ", formatEqualizerLabel(equalizerPreset)] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 6 ? theme.colors.primary : undefined, color: selectedIndex === 6 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 6, children: ["Desktop Notifications: ", notifications ? 'ON' : 'OFF'] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 7 ? theme.colors.primary : undefined, color: selectedIndex === 7 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 7, children: ["Discord Rich Presence: ", discordRpc ? 'ON' : 'OFF'] }) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 8 ? theme.colors.primary : undefined, color: selectedIndex === 8 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 8, children: ["Download Feature: ", downloadsEnabled ? 'ON' : 'OFF'] }) }), _jsx(Box, { paddingX: 1, children: isEditingDownloadDirectory && selectedIndex === 9 ? (_jsx(TextInput, { value: downloadDirectory, onChange: setDownloadDirectory, onSubmit: value => {
|
|
198
|
-
const normalized = value.trim();
|
|
199
|
-
if (!normalized) {
|
|
200
|
-
setIsEditingDownloadDirectory(false);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
setDownloadDirectory(normalized);
|
|
204
|
-
config.set('downloadDirectory', normalized);
|
|
205
|
-
setIsEditingDownloadDirectory(false);
|
|
206
|
-
}, placeholder: "Download directory", focus: true })) : (_jsxs(Text, { backgroundColor: selectedIndex === 9 ? theme.colors.primary : undefined, color: selectedIndex === 9 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 9, children: ["Download Folder: ", downloadDirectory] })) }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { backgroundColor: selectedIndex === 10 ? theme.colors.primary : undefined, color: selectedIndex === 10 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 10, children: ["Download Format: ", downloadFormat.toUpperCase()] }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { backgroundColor: selectedIndex === 11 ? theme.colors.primary : undefined, color: selectedIndex === 11
|
|
207
|
-
? theme.colors.background
|
|
208
|
-
: isActive
|
|
209
|
-
? theme.colors.accent
|
|
210
|
-
: theme.colors.text, bold: selectedIndex === 11, children: sleepTimerLabel }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { backgroundColor: selectedIndex === 12 ? theme.colors.primary : undefined, color: selectedIndex === 12 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 12, children: "Import Playlists \u2192" }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { backgroundColor: selectedIndex === 13 ? theme.colors.primary : undefined, color: selectedIndex === 13 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 13, children: "Export Playlists \u2192" }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { backgroundColor: selectedIndex === 14 ? theme.colors.primary : undefined, color: selectedIndex === 14 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 14, children: "Custom Keybindings \u2192" }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { backgroundColor: selectedIndex === 15 ? theme.colors.primary : undefined, color: selectedIndex === 15 ? theme.colors.background : theme.colors.text, bold: selectedIndex === 15, children: "Manage Plugins" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.dim, children: "Arrows to navigate, Enter to select, Esc/q to go back" }) })] }));
|
|
211
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
// Theme switcher component
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
import { useTheme } from "../../hooks/useTheme.js";
|
|
5
|
-
import { BUILTIN_THEMES } from "../../config/themes.config.js";
|
|
6
|
-
import { useState } from 'react';
|
|
7
|
-
export default function ThemeSwitcher() {
|
|
8
|
-
const { theme } = useTheme();
|
|
9
|
-
const [expanded, _setExpanded] = useState(false);
|
|
10
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { borderStyle: "double", borderColor: theme.colors.secondary, paddingX: 1, children: [_jsxs(Text, { bold: true, color: theme.colors.primary, children: ["Theme: ", theme.name] }), _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.dim, children: "(Enter to change, Esc to close)" })] }), expanded ? (_jsx(_Fragment, { children: Object.keys(BUILTIN_THEMES).map(themeName => (_jsxs(Box, { paddingX: 2, children: [_jsx(Text, { color: theme.colors.text, children: "\u2192 " }), _jsx(Text, { color: theme.colors.dim, children: themeName }), _jsx(Text, { children: " " }), _jsxs(Text, { color: theme.colors.dim, children: ["Press ", _jsx(Text, { color: theme.colors.text, children: "Enter" }), " to select"] })] }, themeName))) })) : (_jsxs(Box, { paddingX: 2, children: [_jsx(Text, { color: theme.colors.dim, children: "Press " }), _jsx(Text, { color: theme.colors.text, children: "Enter" }), _jsx(Text, { color: theme.colors.dim, children: " to browse themes" })] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.colors.dim, children: ["Current: ", _jsx(Text, { color: theme.colors.primary, children: theme.name })] }) })] }));
|
|
11
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
export const BUILTIN_THEMES = {
|
|
2
|
-
dark: {
|
|
3
|
-
name: 'Dark',
|
|
4
|
-
colors: {
|
|
5
|
-
primary: 'cyan',
|
|
6
|
-
secondary: 'blue',
|
|
7
|
-
background: 'black',
|
|
8
|
-
text: 'white',
|
|
9
|
-
accent: 'yellow',
|
|
10
|
-
dim: 'gray',
|
|
11
|
-
error: 'red',
|
|
12
|
-
success: 'green',
|
|
13
|
-
warning: 'yellow',
|
|
14
|
-
},
|
|
15
|
-
inverse: false,
|
|
16
|
-
},
|
|
17
|
-
light: {
|
|
18
|
-
name: 'Light',
|
|
19
|
-
colors: {
|
|
20
|
-
primary: 'blue',
|
|
21
|
-
secondary: 'cyan',
|
|
22
|
-
background: 'white',
|
|
23
|
-
text: 'black',
|
|
24
|
-
accent: 'magenta',
|
|
25
|
-
dim: 'gray',
|
|
26
|
-
error: 'red',
|
|
27
|
-
success: 'green',
|
|
28
|
-
warning: 'yellow',
|
|
29
|
-
},
|
|
30
|
-
inverse: false,
|
|
31
|
-
},
|
|
32
|
-
midnight: {
|
|
33
|
-
name: 'Midnight',
|
|
34
|
-
colors: {
|
|
35
|
-
primary: 'magenta',
|
|
36
|
-
secondary: 'purple',
|
|
37
|
-
background: 'black',
|
|
38
|
-
text: 'white',
|
|
39
|
-
accent: 'cyan',
|
|
40
|
-
dim: 'gray',
|
|
41
|
-
error: 'red',
|
|
42
|
-
success: 'greenBright',
|
|
43
|
-
warning: 'yellowBright',
|
|
44
|
-
},
|
|
45
|
-
inverse: false,
|
|
46
|
-
},
|
|
47
|
-
matrix: {
|
|
48
|
-
name: 'Matrix',
|
|
49
|
-
colors: {
|
|
50
|
-
primary: 'green',
|
|
51
|
-
secondary: 'greenBright',
|
|
52
|
-
background: 'black',
|
|
53
|
-
text: 'white',
|
|
54
|
-
accent: 'greenBright',
|
|
55
|
-
dim: 'green',
|
|
56
|
-
error: 'red',
|
|
57
|
-
success: 'greenBright',
|
|
58
|
-
warning: 'yellow',
|
|
59
|
-
},
|
|
60
|
-
inverse: false,
|
|
61
|
-
},
|
|
62
|
-
dracula: {
|
|
63
|
-
name: 'Dracula',
|
|
64
|
-
colors: {
|
|
65
|
-
primary: 'magenta',
|
|
66
|
-
secondary: 'cyan',
|
|
67
|
-
background: 'black',
|
|
68
|
-
text: 'white',
|
|
69
|
-
accent: 'yellow',
|
|
70
|
-
dim: 'gray',
|
|
71
|
-
error: 'red',
|
|
72
|
-
success: 'green',
|
|
73
|
-
warning: 'yellow',
|
|
74
|
-
},
|
|
75
|
-
inverse: false,
|
|
76
|
-
},
|
|
77
|
-
nord: {
|
|
78
|
-
name: 'Nord',
|
|
79
|
-
colors: {
|
|
80
|
-
primary: 'blue',
|
|
81
|
-
secondary: 'cyan',
|
|
82
|
-
background: 'black',
|
|
83
|
-
text: 'white',
|
|
84
|
-
accent: 'blueBright',
|
|
85
|
-
dim: 'gray',
|
|
86
|
-
error: 'red',
|
|
87
|
-
success: 'greenBright',
|
|
88
|
-
warning: 'yellow',
|
|
89
|
-
},
|
|
90
|
-
inverse: false,
|
|
91
|
-
},
|
|
92
|
-
solarized: {
|
|
93
|
-
name: 'Solarized',
|
|
94
|
-
colors: {
|
|
95
|
-
primary: 'cyan',
|
|
96
|
-
secondary: 'blue',
|
|
97
|
-
background: 'black',
|
|
98
|
-
text: 'white',
|
|
99
|
-
accent: 'yellow',
|
|
100
|
-
dim: 'gray',
|
|
101
|
-
error: 'red',
|
|
102
|
-
success: 'green',
|
|
103
|
-
warning: 'magenta',
|
|
104
|
-
},
|
|
105
|
-
inverse: false,
|
|
106
|
-
},
|
|
107
|
-
catppuccin: {
|
|
108
|
-
name: 'Catppuccin',
|
|
109
|
-
colors: {
|
|
110
|
-
primary: 'magenta',
|
|
111
|
-
secondary: 'blue',
|
|
112
|
-
background: 'black',
|
|
113
|
-
text: 'white',
|
|
114
|
-
accent: 'cyan',
|
|
115
|
-
dim: 'gray',
|
|
116
|
-
error: 'red',
|
|
117
|
-
success: 'green',
|
|
118
|
-
warning: 'yellow',
|
|
119
|
-
},
|
|
120
|
-
inverse: false,
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
export const DEFAULT_THEME = BUILTIN_THEMES['dark'];
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// Theme context and provider
|
|
3
|
-
import { createContext, useContext, useState, useCallback, } from 'react';
|
|
4
|
-
import { getConfigService } from "../services/config/config.service.js";
|
|
5
|
-
const ThemeContext = createContext(null);
|
|
6
|
-
export function ThemeProvider({ children }) {
|
|
7
|
-
const [theme, setThemeState] = useState(getConfigService().getTheme());
|
|
8
|
-
const [themeName, setThemeNameState] = useState(getConfigService().get('theme'));
|
|
9
|
-
const setTheme = useCallback((name) => {
|
|
10
|
-
const configService = getConfigService();
|
|
11
|
-
configService.updateTheme(name);
|
|
12
|
-
setThemeNameState(name);
|
|
13
|
-
setThemeState(configService.getTheme());
|
|
14
|
-
}, []);
|
|
15
|
-
const setCustomTheme = useCallback((themeValue) => {
|
|
16
|
-
const configService = getConfigService();
|
|
17
|
-
configService.setCustomTheme(themeValue);
|
|
18
|
-
setThemeNameState('custom');
|
|
19
|
-
setThemeState(themeValue);
|
|
20
|
-
}, []);
|
|
21
|
-
return (_jsx(ThemeContext.Provider, { value: { theme, themeName, setTheme, setCustomTheme }, children: children }));
|
|
22
|
-
}
|
|
23
|
-
export function useTheme() {
|
|
24
|
-
const context = useContext(ThemeContext);
|
|
25
|
-
if (!context) {
|
|
26
|
-
throw new Error('useTheme must be used within ThemeProvider');
|
|
27
|
-
}
|
|
28
|
-
return context;
|
|
29
|
-
}
|