@involvex/youtube-music-cli 0.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.
Files changed (136) hide show
  1. package/README.md +352 -0
  2. package/dist/eslint.config.d.ts +2 -0
  3. package/dist/eslint.config.js +55 -0
  4. package/dist/source/app.d.ts +4 -0
  5. package/dist/source/app.js +17 -0
  6. package/dist/source/cli.d.ts +2 -0
  7. package/dist/source/cli.js +241 -0
  8. package/dist/source/components/common/ErrorBoundary.d.ts +15 -0
  9. package/dist/source/components/common/ErrorBoundary.js +22 -0
  10. package/dist/source/components/common/Help.d.ts +1 -0
  11. package/dist/source/components/common/Help.js +10 -0
  12. package/dist/source/components/common/ShortcutsBar.d.ts +1 -0
  13. package/dist/source/components/common/ShortcutsBar.js +33 -0
  14. package/dist/source/components/config/ConfigLayout.d.ts +1 -0
  15. package/dist/source/components/config/ConfigLayout.js +84 -0
  16. package/dist/source/components/layouts/MainLayout.d.ts +4 -0
  17. package/dist/source/components/layouts/MainLayout.js +83 -0
  18. package/dist/source/components/layouts/PlayerLayout.d.ts +1 -0
  19. package/dist/source/components/layouts/PlayerLayout.js +10 -0
  20. package/dist/source/components/layouts/PluginsLayout.d.ts +1 -0
  21. package/dist/source/components/layouts/PluginsLayout.js +77 -0
  22. package/dist/source/components/layouts/SearchLayout.d.ts +4 -0
  23. package/dist/source/components/layouts/SearchLayout.js +81 -0
  24. package/dist/source/components/player/NowPlaying.d.ts +1 -0
  25. package/dist/source/components/player/NowPlaying.js +21 -0
  26. package/dist/source/components/player/PlayerControls.d.ts +1 -0
  27. package/dist/source/components/player/PlayerControls.js +41 -0
  28. package/dist/source/components/player/ProgressBar.d.ts +1 -0
  29. package/dist/source/components/player/ProgressBar.js +18 -0
  30. package/dist/source/components/player/QueueList.d.ts +4 -0
  31. package/dist/source/components/player/QueueList.js +30 -0
  32. package/dist/source/components/player/Suggestions.d.ts +1 -0
  33. package/dist/source/components/player/Suggestions.js +47 -0
  34. package/dist/source/components/playlist/PlaylistList.d.ts +1 -0
  35. package/dist/source/components/playlist/PlaylistList.js +11 -0
  36. package/dist/source/components/plugins/PluginInstallDialog.d.ts +5 -0
  37. package/dist/source/components/plugins/PluginInstallDialog.js +41 -0
  38. package/dist/source/components/plugins/PluginsAvailable.d.ts +5 -0
  39. package/dist/source/components/plugins/PluginsAvailable.js +55 -0
  40. package/dist/source/components/plugins/PluginsList.d.ts +8 -0
  41. package/dist/source/components/plugins/PluginsList.js +18 -0
  42. package/dist/source/components/search/SearchBar.d.ts +8 -0
  43. package/dist/source/components/search/SearchBar.js +50 -0
  44. package/dist/source/components/search/SearchResults.d.ts +10 -0
  45. package/dist/source/components/search/SearchResults.js +111 -0
  46. package/dist/source/components/settings/Settings.d.ts +1 -0
  47. package/dist/source/components/settings/Settings.js +42 -0
  48. package/dist/source/components/theme/ThemeSwitcher.d.ts +1 -0
  49. package/dist/source/components/theme/ThemeSwitcher.js +11 -0
  50. package/dist/source/config/themes.config.d.ts +3 -0
  51. package/dist/source/config/themes.config.js +63 -0
  52. package/dist/source/contexts/theme.context.d.ts +13 -0
  53. package/dist/source/contexts/theme.context.js +29 -0
  54. package/dist/source/hooks/useKeyboard.d.ts +10 -0
  55. package/dist/source/hooks/useKeyboard.js +104 -0
  56. package/dist/source/hooks/useNavigation.d.ts +1 -0
  57. package/dist/source/hooks/useNavigation.js +5 -0
  58. package/dist/source/hooks/usePlayer.d.ts +23 -0
  59. package/dist/source/hooks/usePlayer.js +35 -0
  60. package/dist/source/hooks/usePlaylist.d.ts +8 -0
  61. package/dist/source/hooks/usePlaylist.js +50 -0
  62. package/dist/source/hooks/useSearch.d.ts +8 -0
  63. package/dist/source/hooks/useSearch.js +76 -0
  64. package/dist/source/hooks/useTerminalSize.d.ts +4 -0
  65. package/dist/source/hooks/useTerminalSize.js +24 -0
  66. package/dist/source/hooks/useTheme.d.ts +6 -0
  67. package/dist/source/hooks/useTheme.js +5 -0
  68. package/dist/source/hooks/useYouTubeMusic.d.ts +11 -0
  69. package/dist/source/hooks/useYouTubeMusic.js +112 -0
  70. package/dist/source/main.d.ts +4 -0
  71. package/dist/source/main.js +69 -0
  72. package/dist/source/services/config/config.service.d.ts +26 -0
  73. package/dist/source/services/config/config.service.js +125 -0
  74. package/dist/source/services/logger/logger.service.d.ts +10 -0
  75. package/dist/source/services/logger/logger.service.js +52 -0
  76. package/dist/source/services/player/player.service.d.ts +58 -0
  77. package/dist/source/services/player/player.service.js +349 -0
  78. package/dist/source/services/player-state/player-state.service.d.ts +24 -0
  79. package/dist/source/services/player-state/player-state.service.js +122 -0
  80. package/dist/source/services/plugin/plugin-audio-api.d.ts +17 -0
  81. package/dist/source/services/plugin/plugin-audio-api.js +36 -0
  82. package/dist/source/services/plugin/plugin-context.d.ts +5 -0
  83. package/dist/source/services/plugin/plugin-context.js +256 -0
  84. package/dist/source/services/plugin/plugin-hooks.service.d.ts +62 -0
  85. package/dist/source/services/plugin/plugin-hooks.service.js +135 -0
  86. package/dist/source/services/plugin/plugin-installer.service.d.ts +27 -0
  87. package/dist/source/services/plugin/plugin-installer.service.js +247 -0
  88. package/dist/source/services/plugin/plugin-loader.service.d.ts +33 -0
  89. package/dist/source/services/plugin/plugin-loader.service.js +161 -0
  90. package/dist/source/services/plugin/plugin-permissions.service.d.ts +72 -0
  91. package/dist/source/services/plugin/plugin-permissions.service.js +194 -0
  92. package/dist/source/services/plugin/plugin-registry.service.d.ts +76 -0
  93. package/dist/source/services/plugin/plugin-registry.service.js +215 -0
  94. package/dist/source/services/plugin/plugin-ui-api.d.ts +25 -0
  95. package/dist/source/services/plugin/plugin-ui-api.js +46 -0
  96. package/dist/source/services/plugin/plugin-updater.service.d.ts +23 -0
  97. package/dist/source/services/plugin/plugin-updater.service.js +206 -0
  98. package/dist/source/services/youtube-music/api.d.ts +13 -0
  99. package/dist/source/services/youtube-music/api.js +371 -0
  100. package/dist/source/services/youtube-music/search.service.d.ts +11 -0
  101. package/dist/source/services/youtube-music/search.service.js +38 -0
  102. package/dist/source/stores/navigation.store.d.ts +10 -0
  103. package/dist/source/stores/navigation.store.js +67 -0
  104. package/dist/source/stores/player.store.d.ts +28 -0
  105. package/dist/source/stores/player.store.js +458 -0
  106. package/dist/source/stores/plugins.store.d.ts +46 -0
  107. package/dist/source/stores/plugins.store.js +177 -0
  108. package/dist/source/types/actions.d.ts +119 -0
  109. package/dist/source/types/actions.js +1 -0
  110. package/dist/source/types/cli.types.d.ts +14 -0
  111. package/dist/source/types/cli.types.js +1 -0
  112. package/dist/source/types/config.types.d.ts +19 -0
  113. package/dist/source/types/config.types.js +1 -0
  114. package/dist/source/types/keyboard.types.d.ts +5 -0
  115. package/dist/source/types/keyboard.types.js +1 -0
  116. package/dist/source/types/navigation.types.d.ts +14 -0
  117. package/dist/source/types/navigation.types.js +1 -0
  118. package/dist/source/types/player.types.d.ts +16 -0
  119. package/dist/source/types/player.types.js +1 -0
  120. package/dist/source/types/playlist.types.d.ts +12 -0
  121. package/dist/source/types/playlist.types.js +1 -0
  122. package/dist/source/types/plugin.types.d.ts +239 -0
  123. package/dist/source/types/plugin.types.js +1 -0
  124. package/dist/source/types/theme.types.d.ts +18 -0
  125. package/dist/source/types/theme.types.js +1 -0
  126. package/dist/source/types/youtube-music.types.d.ts +35 -0
  127. package/dist/source/types/youtube-music.types.js +1 -0
  128. package/dist/source/types/youtubei.types.d.ts +60 -0
  129. package/dist/source/types/youtubei.types.js +3 -0
  130. package/dist/source/utils/constants.d.ts +65 -0
  131. package/dist/source/utils/constants.js +82 -0
  132. package/dist/source/utils/format.d.ts +3 -0
  133. package/dist/source/utils/format.js +24 -0
  134. package/dist/test.d.ts +1 -0
  135. package/dist/test.js +13 -0
  136. package/package.json +100 -0
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Error boundary component for robust error handling
3
+ import { Component } from 'react';
4
+ import { Box, Text } from 'ink';
5
+ export class ErrorBoundary extends Component {
6
+ state = {
7
+ hasError: false,
8
+ error: null,
9
+ };
10
+ static getDerivedStateFromError(error) {
11
+ return { hasError: true, error };
12
+ }
13
+ componentDidCatch(error, errorInfo) {
14
+ console.error('Uncaught error:', error, errorInfo);
15
+ }
16
+ render() {
17
+ if (this.state.hasError) {
18
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "red", children: [_jsx(Text, { color: "red", bold: true, children: "Something went wrong!" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "white", children: this.state.error?.message }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "dim", children: "Press Ctrl+C to exit and restart the CLI." }) })] }));
19
+ }
20
+ return this.props.children;
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ export default function Help(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Help component for keyboard shortcuts
3
+ import { Box, Text } from 'ink';
4
+ import { useTheme } from "../../hooks/useTheme.js";
5
+ import { useNavigation } from "../../hooks/useNavigation.js";
6
+ export default function Help() {
7
+ const { theme } = useTheme();
8
+ const { dispatch: _dispatch } = useNavigation();
9
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, padding: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Keyboard Shortcuts" }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Global" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "q / Esc" }), " - Quit", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "?" }), " - Help", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "/" }), " - Search", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "p" }), " - Playlists", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "g" }), " - Suggestions", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "," }), " - Settings"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Player" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Space" }), " - Play/Pause", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "n" }), " - Next", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "b" }), " - Previous", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "=" }), " - Volume Up", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "-" }), " - Volume Down", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "s" }), " - Toggle Shuffle", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "r" }), " - Toggle Repeat"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Navigation" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Up" }), " /", _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: "k" }), " - Move Up", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Down" }), " /", _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: "j" }), " - Move Down", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Enter" }), " - Select", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " - Go Back"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Search" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Tab" }), " - Switch Search Type", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " - Clear Search", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "[ / ]" }), " - Results Limit"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Playlist" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "a" }), " - Add to Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "d" }), " - Remove from Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "c" }), " - Create Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "D" }), " - Delete Playlist"] }) }), _jsxs(Text, { color: theme.colors.dim, children: ["Press ", _jsx(Text, { color: theme.colors.text, children: "Esc" }), " or", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " to close"] })] })] }));
10
+ }
@@ -0,0 +1 @@
1
+ export default function ShortcutsBar(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Shortcuts bar component
3
+ import { Box, Text } from 'ink';
4
+ import { useCallback } from 'react';
5
+ import { usePlayer } from "../../hooks/usePlayer.js";
6
+ import { useNavigation } from "../../hooks/useNavigation.js";
7
+ import { useTheme } from "../../hooks/useTheme.js";
8
+ import { useKeyBinding } from "../../hooks/useKeyboard.js";
9
+ import { KEYBINDINGS } from "../../utils/constants.js";
10
+ export default function ShortcutsBar() {
11
+ const { theme } = useTheme();
12
+ const { dispatch: navDispatch } = useNavigation();
13
+ const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, } = usePlayer();
14
+ // Register key bindings globally
15
+ const handlePlayPause = () => {
16
+ if (playerState.isPlaying) {
17
+ pause();
18
+ }
19
+ else {
20
+ resume();
21
+ }
22
+ };
23
+ const goConfig = useCallback(() => {
24
+ navDispatch({ category: 'NAVIGATE', view: 'config' });
25
+ }, [navDispatch]);
26
+ useKeyBinding(KEYBINDINGS.PLAY_PAUSE, handlePlayPause);
27
+ useKeyBinding(KEYBINDINGS.NEXT, next);
28
+ useKeyBinding(KEYBINDINGS.PREVIOUS, previous);
29
+ useKeyBinding(KEYBINDINGS.VOLUME_UP, volumeUp);
30
+ useKeyBinding(KEYBINDINGS.VOLUME_DOWN, volumeDown);
31
+ useKeyBinding(KEYBINDINGS.SETTINGS, goConfig);
32
+ return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: ["Shortcuts: ", _jsx(Text, { color: theme.colors.text, children: "Space" }), " Play/Pause |", ' ', _jsx(Text, { color: theme.colors.text, children: "n" }), " Next |", ' ', _jsx(Text, { color: theme.colors.text, children: "p" }), " Previous |", ' ', _jsx(Text, { color: theme.colors.text, children: "/" }), " Search |", ' ', _jsx(Text, { color: theme.colors.text, children: "," }), " Settings |", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " Help |", ' ', _jsx(Text, { color: theme.colors.text, children: "q" }), " Quit"] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.dim, children: "[=/" }), "-", _jsx(Text, { color: theme.colors.dim, children: "]" }), " Vol:", ' ', _jsxs(Text, { color: theme.colors.primary, children: [playerState.volume, "%"] })] })] }));
33
+ }
@@ -0,0 +1 @@
1
+ export default function ConfigLayout(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Config screen layout
3
+ import { Box, Text } from 'ink';
4
+ import { useState, useCallback } from 'react';
5
+ import { useTheme } from "../../hooks/useTheme.js";
6
+ import { useKeyBinding } from "../../hooks/useKeyboard.js";
7
+ import { useNavigation } from "../../hooks/useNavigation.js";
8
+ import { KEYBINDINGS } from "../../utils/constants.js";
9
+ import { getConfigService } from "../../services/config/config.service.js";
10
+ export default function ConfigLayout() {
11
+ const { theme, setTheme } = useTheme();
12
+ const { dispatch } = useNavigation();
13
+ const [selectedSection, setSelectedSection] = useState('theme');
14
+ const config = getConfigService();
15
+ // Navigate sections
16
+ const goUp = useCallback(() => {
17
+ const sections = ['theme', 'quality', 'volumeStep'];
18
+ const currentIndex = sections.indexOf(selectedSection);
19
+ if (currentIndex > 0) {
20
+ setSelectedSection(sections[currentIndex - 1]);
21
+ }
22
+ }, [selectedSection]);
23
+ const goDown = useCallback(() => {
24
+ const sections = ['theme', 'quality', 'volumeStep'];
25
+ const currentIndex = sections.indexOf(selectedSection);
26
+ if (currentIndex < sections.length - 1) {
27
+ setSelectedSection(sections[currentIndex + 1]);
28
+ }
29
+ }, [selectedSection]);
30
+ // Handle Enter key based on selected section
31
+ const handleSelect = useCallback(() => {
32
+ if (selectedSection === 'theme') {
33
+ const themes = ['dark', 'light', 'midnight', 'matrix'];
34
+ const currentTheme = theme.name;
35
+ const currentIndex = themes.indexOf(currentTheme);
36
+ const nextIndex = (currentIndex + 1) % themes.length;
37
+ const nextTheme = themes[nextIndex];
38
+ setTheme(nextTheme);
39
+ config.set('theme', nextTheme);
40
+ }
41
+ else if (selectedSection === 'quality') {
42
+ const qualities = ['low', 'medium', 'high'];
43
+ const currentQuality = config.get('streamQuality');
44
+ const currentIndex = qualities.indexOf(currentQuality);
45
+ const nextIndex = (currentIndex + 1) % qualities.length;
46
+ config.set('streamQuality', qualities[nextIndex]);
47
+ }
48
+ }, [selectedSection, config, theme, setTheme]);
49
+ // Change volume step
50
+ const increaseVolumeStep = useCallback(() => {
51
+ if (selectedSection === 'volumeStep') {
52
+ const current = config.get('volume');
53
+ if (current < 100) {
54
+ config.set('volume', Math.min(100, current + 10));
55
+ }
56
+ }
57
+ }, [selectedSection, config]);
58
+ const decreaseVolumeStep = useCallback(() => {
59
+ if (selectedSection === 'volumeStep') {
60
+ const current = config.get('volume');
61
+ if (current > 0) {
62
+ config.set('volume', Math.max(0, current - 10));
63
+ }
64
+ }
65
+ }, [selectedSection, config]);
66
+ // Go back
67
+ const goBack = useCallback(() => {
68
+ dispatch({ category: 'GO_BACK' });
69
+ }, [dispatch]);
70
+ useKeyBinding(KEYBINDINGS.UP, goUp);
71
+ useKeyBinding(KEYBINDINGS.DOWN, goDown);
72
+ useKeyBinding(KEYBINDINGS.SELECT, handleSelect);
73
+ useKeyBinding(KEYBINDINGS.VOLUME_UP, increaseVolumeStep);
74
+ useKeyBinding(KEYBINDINGS.VOLUME_DOWN, decreaseVolumeStep);
75
+ useKeyBinding(KEYBINDINGS.BACK, goBack);
76
+ const currentTheme = theme.name;
77
+ const currentQuality = config.get('streamQuality') || 'high';
78
+ const currentVolume = config.get('volume') || 70;
79
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Settings" }) }), _jsxs(Box, { paddingX: 1, borderStyle: "single", borderColor: selectedSection === 'theme' ? theme.colors.primary : theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: ["Theme: ", _jsx(Text, { color: theme.colors.primary, children: currentTheme })] }), selectedSection === 'theme' && (_jsx(Text, { color: theme.colors.dim, children: " (Press Enter to cycle)" }))] }), _jsxs(Box, { paddingX: 1, borderStyle: "single", borderColor: selectedSection === 'quality'
80
+ ? theme.colors.primary
81
+ : theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: ["Stream Quality:", ' ', _jsx(Text, { color: theme.colors.primary, children: currentQuality })] }), selectedSection === 'quality' && (_jsx(Text, { color: theme.colors.dim, children: " (Press Enter to cycle)" }))] }), _jsxs(Box, { paddingX: 1, borderStyle: "single", borderColor: selectedSection === 'volumeStep'
82
+ ? theme.colors.primary
83
+ : theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: ["Default Volume:", ' ', _jsxs(Text, { color: theme.colors.primary, children: [currentVolume, "%"] })] }), selectedSection === 'volumeStep' && (_jsx(Text, { color: theme.colors.dim, children: " (Press =/- to adjust)" }))] })] }));
84
+ }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ declare function MainLayout(): import("react/jsx-runtime").JSX.Element;
3
+ declare const _default: React.MemoExoticComponent<typeof MainLayout>;
4
+ export default _default;
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Main layout shell
3
+ import { useCallback, useMemo } from 'react';
4
+ import React from 'react';
5
+ import { useNavigation } from "../../hooks/useNavigation.js";
6
+ import PlaylistList from "../playlist/PlaylistList.js";
7
+ import Help from "../common/Help.js";
8
+ import { useTheme } from "../../hooks/useTheme.js";
9
+ import { useKeyBinding } from "../../hooks/useKeyboard.js";
10
+ import SearchLayout from "./SearchLayout.js";
11
+ import PlayerLayout from "./PlayerLayout.js";
12
+ import PluginsLayout from "./PluginsLayout.js";
13
+ import Suggestions from "../player/Suggestions.js";
14
+ import Settings from "../settings/Settings.js";
15
+ import ConfigLayout from "../config/ConfigLayout.js";
16
+ import ShortcutsBar from "../common/ShortcutsBar.js";
17
+ import { KEYBINDINGS, VIEW } from "../../utils/constants.js";
18
+ import { Box } from 'ink';
19
+ import { useTerminalSize } from "../../hooks/useTerminalSize.js";
20
+ function MainLayout() {
21
+ const { theme } = useTheme();
22
+ const { state: navState, dispatch } = useNavigation();
23
+ const { columns } = useTerminalSize();
24
+ // Responsive padding based on terminal size
25
+ const getPadding = () => (columns < 100 ? 0 : 1);
26
+ // Navigate to different views
27
+ const goToSearch = useCallback(() => {
28
+ dispatch({ category: 'NAVIGATE', view: VIEW.SEARCH });
29
+ }, [dispatch]);
30
+ const goToPlaylists = useCallback(() => {
31
+ dispatch({ category: 'NAVIGATE', view: VIEW.PLAYLISTS });
32
+ }, [dispatch]);
33
+ const goToSuggestions = useCallback(() => {
34
+ dispatch({ category: 'NAVIGATE', view: VIEW.SUGGESTIONS });
35
+ }, [dispatch]);
36
+ const goToSettings = useCallback(() => {
37
+ dispatch({ category: 'NAVIGATE', view: VIEW.SETTINGS });
38
+ }, [dispatch]);
39
+ const goToHelp = useCallback(() => {
40
+ dispatch({ category: 'NAVIGATE', view: VIEW.HELP });
41
+ }, [dispatch]);
42
+ const handleQuit = useCallback(() => {
43
+ // From player view, quit the app
44
+ if (navState.currentView === VIEW.PLAYER) {
45
+ process.exit(0);
46
+ }
47
+ // From other views, go back
48
+ dispatch({ category: 'GO_BACK' });
49
+ }, [navState.currentView, dispatch]);
50
+ // Global keyboard bindings
51
+ useKeyBinding(KEYBINDINGS.QUIT, handleQuit);
52
+ useKeyBinding(KEYBINDINGS.SEARCH, goToSearch);
53
+ useKeyBinding(KEYBINDINGS.PLAYLISTS, goToPlaylists);
54
+ useKeyBinding(KEYBINDINGS.SUGGESTIONS, goToSuggestions);
55
+ useKeyBinding(KEYBINDINGS.SETTINGS, goToSettings);
56
+ useKeyBinding(KEYBINDINGS.HELP, goToHelp);
57
+ // Memoize the view component to prevent unnecessary remounts
58
+ // Only recreate when currentView actually changes
59
+ const currentView = useMemo(() => {
60
+ switch (navState.currentView) {
61
+ case 'player':
62
+ return _jsx(PlayerLayout, {}, "player");
63
+ case 'search':
64
+ return _jsx(SearchLayout, {}, "search");
65
+ case 'playlists':
66
+ return _jsx(PlaylistList, {}, "playlists");
67
+ case 'suggestions':
68
+ return _jsx(Suggestions, {}, "suggestions");
69
+ case 'settings':
70
+ return _jsx(Settings, {}, "settings");
71
+ case 'plugins':
72
+ return _jsx(PluginsLayout, {}, "plugins");
73
+ case 'config':
74
+ return _jsx(ConfigLayout, {}, "config");
75
+ case 'help':
76
+ return _jsx(Help, {}, "help");
77
+ default:
78
+ return _jsx(PlayerLayout, {}, "player-default");
79
+ }
80
+ }, [navState.currentView]);
81
+ return (_jsxs(Box, { flexDirection: "column", paddingX: getPadding(), borderStyle: "single", borderColor: theme.colors.primary, children: [currentView, _jsx(ShortcutsBar, {})] }));
82
+ }
83
+ export default React.memo(MainLayout);
@@ -0,0 +1 @@
1
+ export default function PlayerLayout(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { usePlayer } from "../../hooks/usePlayer.js";
3
+ import NowPlaying from "../player/NowPlaying.js";
4
+ import ProgressBar from "../player/ProgressBar.js";
5
+ import QueueList from "../player/QueueList.js";
6
+ import { Box } from 'ink';
7
+ export default function PlayerLayout() {
8
+ const { state: playerState } = usePlayer();
9
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(NowPlaying, {}), _jsx(ProgressBar, {}), playerState.queue.length > 0 && _jsx(QueueList, {})] }));
10
+ }
@@ -0,0 +1 @@
1
+ export default function PluginsLayout(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Plugins layout - main plugin management view
3
+ import { useState, useCallback } from 'react';
4
+ import { Box, Text } from 'ink';
5
+ import { useTheme } from "../../hooks/useTheme.js";
6
+ import { usePlugins } from "../../stores/plugins.store.js";
7
+ import { useKeyBinding } from "../../hooks/useKeyboard.js";
8
+ import { KEYBINDINGS } from "../../utils/constants.js";
9
+ import PluginsList from "../plugins/PluginsList.js";
10
+ import PluginInstallDialog from "../plugins/PluginInstallDialog.js";
11
+ export default function PluginsLayout() {
12
+ const { theme } = useTheme();
13
+ const { state, dispatch, enablePlugin, disablePlugin, uninstallPlugin, updatePlugin, } = usePlugins();
14
+ const [viewMode, setViewMode] = useState('list');
15
+ const { installedPlugins, selectedIndex, isLoading, error, lastAction } = state;
16
+ // Navigation
17
+ const navigateUp = useCallback(() => {
18
+ if (viewMode === 'list') {
19
+ dispatch({
20
+ type: 'SET_SELECTED',
21
+ index: Math.max(0, selectedIndex - 1),
22
+ });
23
+ }
24
+ }, [viewMode, selectedIndex, dispatch]);
25
+ const navigateDown = useCallback(() => {
26
+ if (viewMode === 'list') {
27
+ dispatch({
28
+ type: 'SET_SELECTED',
29
+ index: Math.min(installedPlugins.length - 1, selectedIndex + 1),
30
+ });
31
+ }
32
+ }, [viewMode, selectedIndex, installedPlugins.length, dispatch]);
33
+ // Actions
34
+ const togglePlugin = useCallback(async () => {
35
+ const plugin = installedPlugins[selectedIndex];
36
+ if (!plugin)
37
+ return;
38
+ if (plugin.enabled) {
39
+ await disablePlugin(plugin.manifest.id);
40
+ }
41
+ else {
42
+ await enablePlugin(plugin.manifest.id);
43
+ }
44
+ }, [installedPlugins, selectedIndex, enablePlugin, disablePlugin]);
45
+ const removePlugin = useCallback(async () => {
46
+ const plugin = installedPlugins[selectedIndex];
47
+ if (!plugin)
48
+ return;
49
+ await uninstallPlugin(plugin.manifest.id);
50
+ }, [installedPlugins, selectedIndex, uninstallPlugin]);
51
+ const handleUpdate = useCallback(async () => {
52
+ const plugin = installedPlugins[selectedIndex];
53
+ if (!plugin)
54
+ return;
55
+ await updatePlugin(plugin.manifest.id);
56
+ }, [installedPlugins, selectedIndex, updatePlugin]);
57
+ const openInstall = useCallback(() => {
58
+ setViewMode('install');
59
+ }, []);
60
+ const closeInstall = useCallback(() => {
61
+ setViewMode('list');
62
+ }, []);
63
+ // Key bindings
64
+ useKeyBinding(KEYBINDINGS.UP, navigateUp);
65
+ useKeyBinding(KEYBINDINGS.DOWN, navigateDown);
66
+ useKeyBinding(['e'], togglePlugin);
67
+ useKeyBinding(['r'], removePlugin);
68
+ useKeyBinding(['u'], handleUpdate);
69
+ useKeyBinding(['i'], openInstall);
70
+ // Show install dialog
71
+ if (viewMode === 'install') {
72
+ return _jsx(PluginInstallDialog, { onClose: closeInstall });
73
+ }
74
+ // Get selected plugin details
75
+ const selectedPlugin = installedPlugins[selectedIndex];
76
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Box, { borderStyle: "double", borderColor: theme.colors.secondary, paddingX: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Plugin Manager" }) }), isLoading && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: theme.colors.warning, children: "Loading..." }) })), error && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.colors.error, children: ["Error: ", error] }) })), lastAction && !error && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.colors.success, children: ["\u2713 ", lastAction] }) })), _jsx(PluginsList, { plugins: installedPlugins, selectedIndex: selectedIndex }), selectedPlugin && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: selectedPlugin.manifest.name }), _jsx(Text, { color: theme.colors.dim, children: selectedPlugin.manifest.description }), _jsxs(Text, { color: theme.colors.dim, children: ["Author: ", selectedPlugin.manifest.author] }), _jsxs(Text, { color: theme.colors.dim, children: ["Permissions: ", selectedPlugin.manifest.permissions.join(', ')] })] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.colors.dim, children: [_jsx(Text, { color: theme.colors.text, children: "i" }), "=Install", ' ', _jsx(Text, { color: theme.colors.text, children: "e" }), "=Enable/Disable", ' ', _jsx(Text, { color: theme.colors.text, children: "r" }), "=Remove", ' ', _jsx(Text, { color: theme.colors.text, children: "u" }), "=Update", ' ', _jsx(Text, { color: theme.colors.text, children: "Esc" }), "=Back"] }) })] }));
77
+ }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ declare function SearchLayout(): import("react/jsx-runtime").JSX.Element;
3
+ declare const _default: React.MemoExoticComponent<typeof SearchLayout>;
4
+ export default _default;
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Search view layout
3
+ import { useNavigation } from "../../hooks/useNavigation.js";
4
+ import { useYouTubeMusic } from "../../hooks/useYouTubeMusic.js";
5
+ import SearchResults from "../search/SearchResults.js";
6
+ import { useState, useCallback, useEffect } from 'react';
7
+ import React from 'react';
8
+ import { useTheme } from "../../hooks/useTheme.js";
9
+ import SearchBar from "../search/SearchBar.js";
10
+ import { useKeyBinding } from "../../hooks/useKeyboard.js";
11
+ import { KEYBINDINGS } from "../../utils/constants.js";
12
+ import { Box, Text } from 'ink';
13
+ function SearchLayout() {
14
+ const { theme } = useTheme();
15
+ const { state: navState, dispatch } = useNavigation();
16
+ const { isLoading, error, search } = useYouTubeMusic();
17
+ const [results, setResults] = useState([]);
18
+ const [isTyping, setIsTyping] = useState(true);
19
+ const [isSearching, setIsSearching] = useState(false);
20
+ // Handle search action
21
+ const performSearch = useCallback(async (query) => {
22
+ if (!query || isSearching)
23
+ return;
24
+ setIsSearching(true);
25
+ const response = await search(query, {
26
+ type: navState.searchType,
27
+ limit: navState.searchLimit,
28
+ });
29
+ if (response) {
30
+ setResults(response.results);
31
+ dispatch({ category: 'SET_SELECTED_RESULT', index: 0 });
32
+ dispatch({ category: 'SET_HAS_SEARCHED', hasSearched: true });
33
+ // Defer focus switch to avoid consuming the same Enter key
34
+ // Use longer delay to ensure key event has been fully processed
35
+ setTimeout(() => setIsTyping(false), 100);
36
+ }
37
+ setIsSearching(false);
38
+ }, [search, navState.searchType, navState.searchLimit, dispatch, isSearching]);
39
+ // Adjust results limit
40
+ const increaseLimit = useCallback(() => {
41
+ dispatch({ category: 'SET_SEARCH_LIMIT', limit: navState.searchLimit + 5 });
42
+ }, [navState.searchLimit, dispatch]);
43
+ const decreaseLimit = useCallback(() => {
44
+ dispatch({ category: 'SET_SEARCH_LIMIT', limit: navState.searchLimit - 5 });
45
+ }, [navState.searchLimit, dispatch]);
46
+ useKeyBinding(KEYBINDINGS.INCREASE_RESULTS, increaseLimit);
47
+ useKeyBinding(KEYBINDINGS.DECREASE_RESULTS, decreaseLimit);
48
+ // Initial search if query is in state (usually from CLI flags)
49
+ useEffect(() => {
50
+ if (navState.searchQuery && !navState.hasSearched) {
51
+ void performSearch(navState.searchQuery);
52
+ }
53
+ // We only want this to run once on mount or when searchQuery changes initially
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, []);
56
+ // Handle going back
57
+ const goBack = useCallback(() => {
58
+ if (!isTyping) {
59
+ setIsTyping(true); // Back to typing if in results
60
+ dispatch({ category: 'SET_HAS_SEARCHED', hasSearched: false });
61
+ }
62
+ else {
63
+ dispatch({ category: 'GO_BACK' });
64
+ }
65
+ }, [isTyping, dispatch]);
66
+ useKeyBinding(KEYBINDINGS.BACK, goBack);
67
+ // Reset search state when leaving view
68
+ useEffect(() => {
69
+ return () => {
70
+ setResults([]);
71
+ dispatch({ category: 'SET_HAS_SEARCHED', hasSearched: false });
72
+ dispatch({ category: 'SET_SEARCH_QUERY', query: '' });
73
+ };
74
+ }, [dispatch]);
75
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: "Search" }), _jsxs(Text, { color: theme.colors.dim, children: [' ', "| Limit: ", navState.searchLimit, " (Use [ or ] to adjust)"] })] }), _jsx(SearchBar, { isActive: isTyping && !isSearching, onInput: input => {
76
+ void performSearch(input);
77
+ } }), (isLoading || isSearching) && (_jsx(Text, { color: theme.colors.accent, children: "Searching..." })), error && _jsx(Text, { color: theme.colors.error, children: error }), !isLoading && navState.hasSearched && (_jsx(SearchResults, { results: results, selectedIndex: navState.selectedResult, isActive: !isTyping })), !isLoading && navState.hasSearched && results.length === 0 && !error && (_jsx(Text, { color: theme.colors.dim, children: "No results found" })), _jsx(Text, { color: theme.colors.dim, children: isTyping
78
+ ? 'Type to search, Enter to start'
79
+ : 'Arrows to navigate, Enter to play, Esc to type again' })] }));
80
+ }
81
+ export default React.memo(SearchLayout);
@@ -0,0 +1 @@
1
+ export default function NowPlaying(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Now playing component
3
+ import { Box, Text } from 'ink';
4
+ import { usePlayer } from "../../hooks/usePlayer.js";
5
+ import { useTheme } from "../../hooks/useTheme.js";
6
+ import { formatTime } from "../../utils/format.js";
7
+ import { useTerminalSize } from "../../hooks/useTerminalSize.js";
8
+ export default function NowPlaying() {
9
+ const { theme } = useTheme();
10
+ const { state: playerState } = usePlayer();
11
+ const { columns } = useTerminalSize();
12
+ if (!playerState.currentTrack) {
13
+ return (_jsx(Box, { borderStyle: "round", borderColor: theme.colors.dim, padding: 1, marginY: 1, children: _jsx(Text, { color: theme.colors.dim, children: "No track playing" }) }));
14
+ }
15
+ const track = playerState.currentTrack;
16
+ const artists = track.artists?.map(a => a.name).join(', ') || 'Unknown Artist';
17
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.primary, padding: 1, marginY: 1, children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: track.title }), _jsx(Text, { color: theme.colors.secondary, children: artists }), track.album && _jsx(Text, { color: theme.colors.dim, children: track.album.name }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.text, children: formatTime(playerState.progress) }), _jsx(Text, { children: " " }), _jsxs(Text, { color: theme.colors.dim, children: ["[", Math.round((playerState.progress / (playerState.duration || 1)) * 100), "%]"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: formatTime(playerState.duration) })] }), playerState.duration > 0 && (_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: '■'.repeat(Math.floor((playerState.progress / playerState.duration) * (columns - 10))) }), _jsx(Text, { color: theme.colors.dim, children: '-'.repeat(Math.max(0, columns -
18
+ 10 -
19
+ Math.floor((playerState.progress / playerState.duration) *
20
+ (columns - 10)))) })] })), playerState.isLoading && (_jsx(Text, { color: theme.colors.accent, children: "Loading..." })), playerState.error && (_jsx(Text, { color: theme.colors.error, children: playerState.error }))] }));
21
+ }
@@ -0,0 +1 @@
1
+ export default function PlayerControls(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Player controls component
3
+ import { useKeyBinding } from "../../hooks/useKeyboard.js";
4
+ import { KEYBINDINGS } from "../../utils/constants.js";
5
+ import { usePlayer } from "../../hooks/usePlayer.js";
6
+ import { useTheme } from "../../hooks/useTheme.js";
7
+ import { Box, Text } from 'ink';
8
+ import { useEffect } from 'react';
9
+ import { logger } from "../../services/logger/logger.service.js";
10
+ let mountCount = 0;
11
+ export default function PlayerControls() {
12
+ const instanceId = ++mountCount;
13
+ useEffect(() => {
14
+ logger.debug('PlayerControls', 'Component mounted', { instanceId });
15
+ return () => {
16
+ logger.debug('PlayerControls', 'Component unmounted', { instanceId });
17
+ };
18
+ }, [instanceId]);
19
+ const { theme } = useTheme();
20
+ const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, } = usePlayer();
21
+ // DEBUG: Log when callbacks change (detect instability)
22
+ useEffect(() => {
23
+ // Temporarily output to stderr to debug without triggering Ink re-render
24
+ process.stderr.write(`[PlayerControls] volumeUp callback: ${typeof volumeUp}\n`);
25
+ }, [volumeUp, instanceId]);
26
+ const handlePlayPause = () => {
27
+ if (playerState.isPlaying) {
28
+ pause();
29
+ }
30
+ else {
31
+ resume();
32
+ }
33
+ };
34
+ // Keyboard bindings
35
+ useKeyBinding(KEYBINDINGS.PLAY_PAUSE, handlePlayPause);
36
+ useKeyBinding(KEYBINDINGS.NEXT, next);
37
+ useKeyBinding(KEYBINDINGS.PREVIOUS, previous);
38
+ useKeyBinding(KEYBINDINGS.VOLUME_UP, volumeUp);
39
+ useKeyBinding(KEYBINDINGS.VOLUME_DOWN, volumeDown);
40
+ return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 2, borderStyle: "classic", borderColor: theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "b" }), "] Prev"] }), _jsx(Text, { color: theme.colors.primary, children: playerState.isPlaying ? (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Pause"] })) : (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Play"] })) }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "n" }), "] Next"] }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "+/-" }), "] Vol: ", playerState.volume, "%"] })] }));
41
+ }
@@ -0,0 +1 @@
1
+ export default function ProgressBar(): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,18 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ // Progress bar component
3
+ import { Box, Text } from 'ink';
4
+ import { useTheme } from "../../hooks/useTheme.js";
5
+ import { usePlayer } from "../../hooks/usePlayer.js";
6
+ import { formatTime } from "../../utils/format.js";
7
+ export default function ProgressBar() {
8
+ const { theme } = useTheme();
9
+ const { state: playerState } = usePlayer();
10
+ if (!playerState.currentTrack || !playerState.duration) {
11
+ return null;
12
+ }
13
+ const progress = playerState.progress;
14
+ const duration = playerState.duration;
15
+ const percentage = duration > 0 ? Math.floor((progress / duration) * 100) : 0;
16
+ const barWidth = Math.max(0, Math.min(20, Math.floor(percentage / 5))); // 20 chars max, bounds checked
17
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.text, children: [formatTime(progress), " / ", formatTime(duration)] }), _jsxs(Text, { color: theme.colors.dim, children: [" ", percentage, "%"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: '■'.repeat(barWidth) }), _jsx(Text, { color: theme.colors.dim, children: '-'.repeat(20 - barWidth) })] })] }));
18
+ }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ declare function QueueList(): import("react/jsx-runtime").JSX.Element;
3
+ declare const _default: React.MemoExoticComponent<typeof QueueList>;
4
+ export default _default;
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Queue management component
3
+ import { useState } from 'react';
4
+ import React from 'react';
5
+ import { Box, Text } from 'ink';
6
+ import { useTheme } from "../../hooks/useTheme.js";
7
+ import { usePlayer } from "../../hooks/usePlayer.js";
8
+ import { truncate } from "../../utils/format.js";
9
+ import { useTerminalSize } from "../../hooks/useTerminalSize.js";
10
+ function QueueList() {
11
+ const { theme } = useTheme();
12
+ const { state: playerState } = usePlayer();
13
+ const { columns } = useTerminalSize();
14
+ const [selectedIndex, _setSelectedIndex] = useState(0);
15
+ // Calculate responsive truncation
16
+ const getTruncateLength = (baseLength) => {
17
+ const scale = Math.min(1, columns / 100);
18
+ return Math.max(20, Math.floor(baseLength * scale));
19
+ };
20
+ if (playerState.queue.length === 0) {
21
+ return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: theme.colors.dim, children: "Queue is empty" }) }));
22
+ }
23
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Box, { borderStyle: "double", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 1, children: _jsxs(Text, { bold: true, color: theme.colors.primary, children: ["Queue (", playerState.queue.length, " tracks)"] }) }), playerState.queue.map((track, index) => {
24
+ const isSelected = index === selectedIndex;
25
+ const artists = track.artists?.map(a => a.name).join(', ') || 'Unknown';
26
+ const title = truncate(track.title, getTruncateLength(50));
27
+ return (_jsxs(Box, { paddingX: 1, borderStyle: isSelected ? 'double' : undefined, borderColor: isSelected ? theme.colors.primary : undefined, children: [_jsxs(Text, { color: theme.colors.dim, children: [index + 1, "."] }), _jsx(Text, { color: isSelected ? theme.colors.primary : theme.colors.text, bold: isSelected, children: title }), _jsxs(Text, { color: theme.colors.dim, children: [' - ', artists] })] }, track.videoId));
28
+ })] }));
29
+ }
30
+ export default React.memo(QueueList);
@@ -0,0 +1 @@
1
+ export default function Suggestions(): import("react/jsx-runtime").JSX.Element;