keypress.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import {
  2. activeClientToggled,
  3. masterSet,
  4. playPaused,
  5. queuePushed,
  6. queueRemoved,
  7. stateSet,
  8. } from '../../../../actions';
  9. import { ActionKeyPressed, Keys } from '../../../../hooks/vim';
  10. import { getFilteredSongs } from '../selectors';
  11. import { CmusUIState, LibraryModeWindow, Overlay, View } from '../types';
  12. import { handleOrder } from './order';
  13. import { handleScroll } from './scroll';
  14. import { withGlobalAction } from './utils';
  15. const libraryModeWindows: LibraryModeWindow[] = Object.values(LibraryModeWindow);
  16. const switchLibraryMode = (state: CmusUIState): CmusUIState => ({
  17. ...state,
  18. library: {
  19. ...state.library,
  20. modeWindow:
  21. libraryModeWindows[
  22. (libraryModeWindows.indexOf(state.library.modeWindow) + 1) % libraryModeWindows.length
  23. ],
  24. },
  25. });
  26. function toggleExpandArtist(library: CmusUIState['library']): CmusUIState['library'] {
  27. if (library.activeArtist === null) {
  28. return library;
  29. }
  30. if (library.expandedArtists.includes(library.activeArtist)) {
  31. return {
  32. ...library,
  33. expandedArtists: library.expandedArtists.filter(
  34. (compare) => compare !== library.activeArtist,
  35. ),
  36. activeAlbum: null,
  37. };
  38. }
  39. return { ...library, expandedArtists: [...library.expandedArtists, library.activeArtist] };
  40. }
  41. const activateSong = (state: CmusUIState, songId: number | null): CmusUIState =>
  42. songId
  43. ? withGlobalAction(
  44. state,
  45. stateSet({
  46. playing: true,
  47. songId,
  48. currentTime: 0,
  49. seekTime: 0,
  50. }),
  51. )
  52. : state;
  53. function handleActivate(state: CmusUIState): CmusUIState {
  54. switch (state.view) {
  55. case View.Library:
  56. switch (state.library.modeWindow) {
  57. case LibraryModeWindow.SongList:
  58. return activateSong(state, state.library.activeSongId);
  59. case LibraryModeWindow.ArtistList:
  60. return activateSong(state, getFilteredSongs(state)[0]?.id ?? null);
  61. default:
  62. return state;
  63. }
  64. case View.Queue:
  65. if (!state.queue.active) {
  66. return state;
  67. }
  68. return activateSong(state, state.queue.active);
  69. case View.ClientList:
  70. if (!state.clientList.active) {
  71. return state;
  72. }
  73. return withGlobalAction(state, masterSet(state.clientList.active));
  74. default:
  75. return state;
  76. }
  77. }
  78. function handleToggle(state: CmusUIState): CmusUIState {
  79. switch (state.view) {
  80. case View.Library:
  81. if (state.library.modeWindow === LibraryModeWindow.ArtistList) {
  82. return { ...state, library: toggleExpandArtist(state.library) };
  83. }
  84. return state;
  85. case View.ClientList:
  86. if (!state.clientList.active) {
  87. return state;
  88. }
  89. return withGlobalAction(state, activeClientToggled(state.clientList.active));
  90. default:
  91. return state;
  92. }
  93. }
  94. function addSelectedToQueue(state: CmusUIState): CmusUIState {
  95. if (state.view !== View.Library) {
  96. return state;
  97. }
  98. switch (state.library.modeWindow) {
  99. case LibraryModeWindow.ArtistList:
  100. return withGlobalAction(state, queuePushed(getFilteredSongs(state).map(({ id }) => id)));
  101. case LibraryModeWindow.SongList:
  102. if (!state.library.activeSongId) {
  103. return state;
  104. }
  105. return withGlobalAction(state, queuePushed([state.library.activeSongId]));
  106. default:
  107. return state;
  108. }
  109. }
  110. export function handleKeyPress(state: CmusUIState, action: ActionKeyPressed): CmusUIState {
  111. switch (action.key) {
  112. case Keys.colon:
  113. return { ...state, commandMode: true };
  114. case Keys['1']:
  115. return { ...state, view: View.Library };
  116. case Keys['2']:
  117. return { ...state, view: View.ClientList };
  118. case Keys['3']:
  119. return { ...state, view: View.Queue };
  120. case Keys.tab:
  121. if (state.view === View.Library) {
  122. return switchLibraryMode(state);
  123. }
  124. return state;
  125. case Keys.space:
  126. return handleToggle(state);
  127. case Keys.enter:
  128. return handleActivate(state);
  129. case Keys.esc:
  130. return { ...state, overlay: null };
  131. case Keys.slash:
  132. return { ...state, searchMode: true };
  133. case Keys.question:
  134. return { ...state, overlay: Overlay.Help };
  135. case Keys.B:
  136. return { ...state, skipSong: { delta: 1, serialNumber: state.skipSong.serialNumber + 1 } };
  137. case Keys.Z:
  138. return { ...state, skipSong: { delta: -1, serialNumber: state.skipSong.serialNumber + 1 } };
  139. case Keys.S:
  140. return withGlobalAction(
  141. state,
  142. stateSet((last) => ({ shuffleMode: !last.shuffleMode })),
  143. );
  144. case Keys.C:
  145. return withGlobalAction(state, playPaused());
  146. case Keys.D:
  147. if (state.view === View.Queue && state.queue.active) {
  148. return withGlobalAction(state, queueRemoved(state.queue.active));
  149. }
  150. return state;
  151. case Keys.E:
  152. return addSelectedToQueue(state);
  153. case Keys.J:
  154. return handleScroll(state, 1);
  155. case Keys.K:
  156. return handleScroll(state, -1);
  157. case Keys.p:
  158. return handleOrder(state, 1);
  159. case Keys.P:
  160. return handleOrder(state, -1);
  161. case Keys.pageDown:
  162. return handleScroll(state, 20);
  163. case Keys.pageUp:
  164. return handleScroll(state, -20);
  165. default:
  166. return state;
  167. }
  168. }