Prechádzať zdrojové kódy

feat: don't drop back to login component on connection closure

Fela Maslen 4 rokov pred
rodič
commit
b922a133c1

+ 15 - 4
gmus-web/src/components/app.tsx

@@ -13,14 +13,22 @@ import { LoadingWrapper } from './identify';
 import { Interact, Props as InteractProps } from './interact';
 import { Player } from './player';
 import { uiProviders } from './ui';
-import { UIProvider } from './ui/types';
+import { UIProps, UIProvider } from './ui/types';
 
 export type Props = {
-  socket: WebSocket;
+  socket: WebSocket | null;
   interacted: boolean;
-} & InteractProps;
+} & InteractProps &
+  Pick<UIProps, 'connecting' | 'ready' | 'error'>;
 
-export const App: React.FC<Props> = ({ socket, interacted, setInteracted }) => {
+export const App: React.FC<Props> = ({
+  socket,
+  connecting,
+  ready,
+  error,
+  interacted,
+  setInteracted,
+}) => {
   useKeepalive(socket);
   useCurrentlyPlayingSongInfo();
 
@@ -59,6 +67,9 @@ export const App: React.FC<Props> = ({ socket, interacted, setInteracted }) => {
         {interacted && (
           <Suspense fallback={<LoadingWrapper />}>
             <UI
+              connecting={connecting}
+              ready={ready}
+              error={error}
               isMaster={isMaster(state)}
               currentSong={state.songInfo}
               nextSong={onNext}

+ 13 - 3
gmus-web/src/components/root.tsx

@@ -21,13 +21,16 @@ export const Root: React.FC = () => {
     [dispatch],
   );
 
-  const { name, onIdentify, socket, connecting, connected, error } = useSocket(onMessage, onLogin);
+  const { identified, onIdentify, ready, connecting, error, socket } = useSocket(
+    onMessage,
+    onLogin,
+  );
 
   const dispatchWithEffects = useDispatchWithEffects(state, dispatch, socket);
 
   const [interacted, setInteracted] = useState<boolean>(false);
 
-  if (!(socket && connected && name) || error) {
+  if (!identified) {
     return (
       <Identify connecting={connecting} onIdentify={onIdentify} setInteracted={setInteracted} />
     );
@@ -36,7 +39,14 @@ export const Root: React.FC = () => {
   return (
     <StateContext.Provider value={state}>
       <DispatchContext.Provider value={dispatchWithEffects}>
-        <App socket={socket} interacted={interacted} setInteracted={setInteracted} />
+        <App
+          socket={socket}
+          connecting={connecting}
+          ready={ready}
+          error={error}
+          interacted={interacted}
+          setInteracted={setInteracted}
+        />
       </DispatchContext.Provider>
     </StateContext.Provider>
   );

+ 4 - 0
gmus-web/src/components/ui/cmus/styled/spinner.tsx

@@ -1,4 +1,5 @@
 import { rem } from 'polished';
+import React from 'react';
 import styled, { keyframes } from 'styled-components';
 
 const spin = keyframes`
@@ -26,3 +27,6 @@ export const AsciiSpinner = styled.span`
     width: auto;
   }
 `;
+
+export const SpinnerOrEmpty: React.FC<{ loading: boolean }> = ({ loading }) =>
+  loading ? <AsciiSpinner /> : <span>&nbsp;&nbsp;</span>;

+ 2 - 2
gmus-web/src/components/ui/cmus/views/artists.tsx

@@ -5,7 +5,7 @@ import { FixedSizeList as List } from 'react-window';
 import { namedMemo } from '../../../../utils/component';
 import { CmusUIStateContext } from '../reducer';
 import { NoWrapFill } from '../styled/layout';
-import { AsciiSpinner } from '../styled/spinner';
+import { SpinnerOrEmpty } from '../styled/spinner';
 import { getArtistAlbumScrollIndex, lineHeight, useAutoJumpyScroll } from '../utils/scroll';
 
 import * as Styled from './artists.styles';
@@ -46,7 +46,7 @@ const Artist = namedMemo<{ row: ArtistData; style: CSSProperties }>(
       style={style}
       highlight={highlight}
     >
-      {loading ? <AsciiSpinner /> : <>&nbsp;&nbsp;</>}
+      <SpinnerOrEmpty loading={loading} />
       <NoWrapFill>{artist || 'Unknown Artist'}</NoWrapFill>
     </Styled.ArtistTitle>
   ),

+ 21 - 1
gmus-web/src/components/ui/cmus/views/status.tsx

@@ -4,11 +4,15 @@ import { isMaster } from '../../../../selectors';
 
 import { MusicPlayer, Song } from '../../../../types';
 import { formatTime } from '../../../../utils/time';
+import { AsciiSpinner } from '../styled/spinner';
 
 import * as Styled from './status.styles';
 
 export type Props = {
   song: Song | null;
+  connecting?: boolean;
+  ready?: boolean;
+  error?: boolean;
 };
 
 function getTrackMetadata(song: Song | null): string {
@@ -28,7 +32,21 @@ function getPlayPauseIcon(player: MusicPlayer): string {
   return '|';
 }
 
-export const PlayerStatus: React.FC<Props> = ({ song }) => {
+const StatusIcon: React.FC<Omit<Props, 'song'>> = ({
+  connecting = false,
+  ready = false,
+  error = false,
+}) => {
+  if (connecting) {
+    return <AsciiSpinner />;
+  }
+  if (error || !ready) {
+    return <span>!&nbsp;</span>;
+  }
+  return <span>✓&nbsp;</span>;
+};
+
+export const PlayerStatus: React.FC<Props> = ({ song, ...props }) => {
   const state = useContext(StateContext);
   return (
     <Styled.StatusContainer>
@@ -43,6 +61,8 @@ export const PlayerStatus: React.FC<Props> = ({ song }) => {
         <Styled.ClientName>
           {state.myClientName} [{isMaster(state) ? 'Master' : 'Slave'}]
         </Styled.ClientName>
+        &nbsp;
+        <StatusIcon {...props} />
       </Styled.PlayStatus>
     </Styled.StatusContainer>
   );

+ 10 - 3
gmus-web/src/components/ui/cmus/wrapper.tsx

@@ -28,7 +28,14 @@ import * as Styled from './wrapper.styles';
 
 const viewTitles = Object.values(View);
 
-export const CmusUIProvider: UIProviderComponent = ({ currentSong, nextSong, prevSong }) => {
+export const CmusUIProvider: UIProviderComponent = ({
+  connecting,
+  ready,
+  error,
+  currentSong,
+  nextSong,
+  prevSong,
+}) => {
   useMaster();
 
   const dispatch = useContext(DispatchContext);
@@ -53,7 +60,7 @@ export const CmusUIProvider: UIProviderComponent = ({ currentSong, nextSong, pre
     }
   }, [stateUI.skipSong, nextSong, prevSong]);
 
-  useVimBindings(dispatchUI, stateUI.commandMode || stateUI.searchMode);
+  useVimBindings(dispatchUI, !ready || stateUI.commandMode || stateUI.searchMode);
 
   useLibrary(stateUI, dispatchUI);
 
@@ -84,7 +91,7 @@ export const CmusUIProvider: UIProviderComponent = ({ currentSong, nextSong, pre
             </Styled.Overlay>
           )}
           {stateUI.searchMode && <Search />}
-          <PlayerStatus song={currentSong} />
+          <PlayerStatus song={currentSong} connecting={connecting} error={error} ready={ready} />
           <CommandView />
         </Styled.Wrapper>
       </CmusUIDispatchContext.Provider>

+ 3 - 0
gmus-web/src/components/ui/types.ts

@@ -8,6 +8,9 @@ export enum UIProvider {
 }
 
 export type UIProps = {
+  connecting: boolean;
+  ready: boolean;
+  error: boolean;
   isMaster: boolean;
   currentSong: Song | null;
   nextSong: () => void;

+ 3 - 3
gmus-web/src/hooks/vim.ts

@@ -1,6 +1,8 @@
 import { useThrottleCallback } from '@react-hook/throttle';
 import { Dispatch, useCallback, useEffect } from 'react';
 
+import { noop } from '../utils/noop';
+
 export const Keys = {
   tab: 'Tab',
   enter: 'Enter',
@@ -57,9 +59,7 @@ export function useVimBindings(dispatch: Dispatch<ActionKeyPressed>, skip = fals
 
   useEffect(() => {
     if (skip) {
-      return (): void => {
-        // pass
-      };
+      return noop;
     }
 
     window.addEventListener('keydown', listenerThrottled);