Extract focus management to a component

This commit is contained in:
Jordan Eldredge 2019-04-15 08:12:52 -07:00
parent 67441f3c1b
commit 24d446e47b
8 changed files with 297 additions and 323 deletions

View file

@ -55,18 +55,9 @@ type Props = StateProps & DispatchProps & OwnProps;
* Constructs the windows to render, and tracks focus.
*/
class App extends React.Component<Props> {
_emitter: Emitter;
_bindings: {
[windowId: string]: { node: HTMLElement; remove(): void } | null;
};
_webampNode: HTMLDivElement | null;
constructor(props: Props) {
super(props);
// TODO #leak
this._emitter = new Emitter();
// TODO #leak
this._bindings = {};
this._webampNode = null;
}
@ -94,16 +85,6 @@ class App extends React.Component<Props> {
}
}
componentDidMount() {
this._setFocus();
}
componentDidUpdate(prevProps: Props) {
if (prevProps.focused !== this.props.focused) {
this._setFocus();
}
}
_handleWindowResize = () => {
if (this._webampNode == null) {
return;
@ -125,41 +106,6 @@ class App extends React.Component<Props> {
this._webampNode.style.overflow = "visible";
};
_setFocus() {
const binding = this._bindings[this.props.focused];
if (binding && binding.node) {
binding.node.focus();
}
}
_gotRef(windowId: WindowId, comp: React.Component<any> | null) {
if (comp == null) {
const binding = this._bindings[windowId];
if (binding != null && binding.remove) {
binding.remove();
}
this._bindings[windowId] = null;
return;
}
const node = ReactDOM.findDOMNode(comp) as HTMLElement;
const binding = this._bindings[windowId];
if (node == null || (binding && binding.node === node)) {
return;
}
node.tabIndex = -1;
const listener = (e: Event) => this._emitter.trigger(windowId, e);
node.addEventListener("keydown", listener);
this._bindings[windowId] = {
node,
remove: () => {
node.removeEventListener("keydown", listener);
},
};
}
_renderWindows() {
const { media, genWindowsInfo, filePickers } = this.props;
return Utils.objectMap(genWindowsInfo, (w, id) => {
@ -170,37 +116,18 @@ class App extends React.Component<Props> {
case WINDOWS.MAIN:
return (
<MainWindow
ref={component => this._gotRef(id, component)}
analyser={media.getAnalyser()}
filePickers={filePickers}
/>
);
case WINDOWS.EQUALIZER:
return (
<EqualizerWindow ref={component => this._gotRef(id, component)} />
);
return <EqualizerWindow />;
case WINDOWS.PLAYLIST:
return (
<PlaylistWindow
ref={component => this._gotRef(id, component)}
analyser={media.getAnalyser()}
/>
);
return <PlaylistWindow analyser={media.getAnalyser()} />;
case WINDOWS.MEDIA_LIBRARY:
return (
<MediaLibraryWindow
ref={component => this._gotRef(id, component)}
/>
);
return <MediaLibraryWindow />;
case WINDOWS.MILKDROP:
return (
<MilkdropWindow
ref={component => this._gotRef(id, component)}
// TODO: Refactor this. I don't think we need this to be generic anymore.
onFocusedKeyDown={listener => this._emitter.on(id, listener)}
analyser={media.getAnalyser()}
/>
);
return <MilkdropWindow analyser={media.getAnalyser()} />;
default:
throw new Error(`Tried to render an unknown window: ${id}`);
}

View file

@ -13,7 +13,6 @@ import {
toggleEqualizerShadeMode,
} from "../../actionCreators";
import { SET_FOCUSED_WINDOW } from "../../actionTypes";
import { getWindowShade } from "../../selectors";
import Band from "./Band";
@ -25,6 +24,7 @@ import EqualizerShade from "./EqualizerShade";
import "../../../css/equalizer-window.css";
import { Dispatch, Band as BandType, AppState } from "../../types";
import FocusTarget from "../FocusTarget";
interface StateProps {
doubled: boolean;
@ -33,7 +33,6 @@ interface StateProps {
}
interface DispatchProps {
focusWindow(): void;
setPreampValue(value: number): void;
setEqToMin(): void;
setEqToMid(): void;
@ -56,43 +55,41 @@ const EqualizerWindow = (props: StateProps & DispatchProps) => {
draggable: true,
});
return (
<div
id="equalizer-window"
className={className}
onMouseDown={props.focusWindow}
>
{props.shade ? (
<EqualizerShade />
) : (
<div>
<div
className="equalizer-top title-bar draggable"
onDoubleClick={props.toggleEqualizerShadeMode}
>
<div id="equalizer-window" className={className}>
<FocusTarget windowId={WINDOWS.EQUALIZER}>
{props.shade ? (
<EqualizerShade />
) : (
<div>
<div
id="equalizer-shade"
onClick={props.toggleEqualizerShadeMode}
/>
<div id="equalizer-close" onClick={props.closeEqualizerWindow} />
className="equalizer-top title-bar draggable"
onDoubleClick={props.toggleEqualizerShadeMode}
>
<div
id="equalizer-shade"
onClick={props.toggleEqualizerShadeMode}
/>
<div id="equalizer-close" onClick={props.closeEqualizerWindow} />
</div>
<EqOn />
<EqAuto />
<EqGraph />
<PresetsContextMenu />
<Band id="preamp" band="preamp" onChange={props.setPreampValue} />
<div id="plus12db" onClick={props.setEqToMax} />
<div id="zerodb" onClick={props.setEqToMid} />
<div id="minus12db" onClick={props.setEqToMin} />
{BANDS.map(hertz => (
<Band
key={hertz}
id={bandClassName(hertz)}
band={hertz}
onChange={props.setHertzValue(hertz)}
/>
))}
</div>
<EqOn />
<EqAuto />
<EqGraph />
<PresetsContextMenu />
<Band id="preamp" band="preamp" onChange={props.setPreampValue} />
<div id="plus12db" onClick={props.setEqToMax} />
<div id="zerodb" onClick={props.setEqToMid} />
<div id="minus12db" onClick={props.setEqToMin} />
{BANDS.map(hertz => (
<Band
key={hertz}
id={bandClassName(hertz)}
band={hertz}
onChange={props.setHertzValue(hertz)}
/>
))}
</div>
)}
)}
</FocusTarget>
</div>
);
};
@ -100,8 +97,6 @@ const EqualizerWindow = (props: StateProps & DispatchProps) => {
// This does not use the shorthand object syntax becuase `setHertzValue` needs
// to return a function.
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
focusWindow: () =>
dispatch({ type: SET_FOCUSED_WINDOW, window: WINDOWS.EQUALIZER }),
setPreampValue: (value: number) => dispatch(setPreamp(value)),
setEqToMin: () => dispatch(setEqToMin()),
setEqToMid: () => dispatch(setEqToMid()),

View file

@ -0,0 +1,66 @@
import React, { useCallback } from "react";
import { WindowId, AppState, Dispatch } from "../types";
import * as Actions from "../actionCreators";
import * as Selectors from "../selectors";
import { connect } from "react-redux";
interface DispatchProps {
setFocus(windowId: WindowId): void;
}
interface StateProps {
focusedWindowId: WindowId;
}
interface OwnProps {
onKeyDown?(e: React.KeyboardEvent<HTMLDivElement>): void;
windowId: WindowId;
children: React.ReactNode;
}
type Props = StateProps & DispatchProps & OwnProps;
function FocusTarget(props: Props) {
const { onKeyDown, focusedWindowId, windowId, setFocus, children } = props;
const keyDownHandler = useCallback(
e => {
if (windowId === focusedWindowId && onKeyDown != null) {
onKeyDown(e);
}
},
[onKeyDown, windowId, focusedWindowId]
);
const focusHandler = useCallback(() => {
if (windowId !== focusedWindowId) {
setFocus(windowId);
}
}, [windowId, focusedWindowId]);
return (
<div
onFocus={keyDownHandler}
onMouseDown={focusHandler}
tabIndex={-1}
style={{ height: "100%", width: "100%" }}
>
{children}
</div>
);
}
function mapStateToProps(state: AppState): StateProps {
return {
focusedWindowId: Selectors.getFocusedWindow(state),
};
}
function mapDispatchToProps(dispatch: Dispatch): DispatchProps {
return {
setFocus: windowId => dispatch(Actions.setFocusedWindow(windowId)),
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(FocusTarget);

View file

@ -3,11 +3,11 @@ import { connect } from "react-redux";
import classnames from "classnames";
import "../../../css/gen-window.css";
import { SET_FOCUSED_WINDOW } from "../../actionTypes";
import { setWindowSize, closeWindow } from "../../actionCreators";
import { getWindowPixelSize } from "../../selectors";
import ResizeTarget from "../ResizeTarget";
import { AppState, WindowId, Dispatch } from "../../types";
import FocusTarget from "../FocusTarget";
interface TextProps {
children: string;
@ -41,10 +41,10 @@ interface OwnProps {
windowId: WindowId;
children: (windowSize: WindowSize) => React.ReactNode;
title: string;
onKeyDown?(e: React.KeyboardEvent<HTMLDivElement>): void;
}
interface DispatchProps {
setFocus: (windowId: WindowId) => void;
close: (windowId: WindowId) => void;
setGenWindowSize: (windowId: WindowId, size: [number, number]) => void;
}
@ -64,57 +64,62 @@ export const GenWindow = ({
children,
close,
title,
setFocus,
windowId,
windowSize,
setGenWindowSize,
width,
height,
onKeyDown,
}: Props) => {
return (
<div
className={classnames("gen-window", "window", { selected })}
onMouseDown={() => setFocus(windowId)}
style={{ width, height }}
>
<div className="gen-top draggable">
<div className="gen-top-left draggable" />
<div className="gen-top-left-fill draggable" />
<div className="gen-top-left-end draggable" />
<div className="gen-top-title draggable">
<Text>{title}</Text>
<FocusTarget windowId={windowId}>
<div
className={classnames("gen-window", "window", { selected })}
style={{ width, height }}
onKeyDown={onKeyDown}
>
<div className="gen-top draggable">
<div className="gen-top-left draggable" />
<div className="gen-top-left-fill draggable" />
<div className="gen-top-left-end draggable" />
<div className="gen-top-title draggable">
<Text>{title}</Text>
</div>
<div className="gen-top-right-end draggable" />
<div className="gen-top-right-fill draggable" />
<div className="gen-top-right draggable">
<div
className="gen-close selected"
onClick={() => close(windowId)}
/>
</div>
</div>
<div className="gen-top-right-end draggable" />
<div className="gen-top-right-fill draggable" />
<div className="gen-top-right draggable">
<div className="gen-close selected" onClick={() => close(windowId)} />
<div className="gen-middle">
<div className="gen-middle-left draggable">
<div className="gen-middle-left-bottom draggable" />
</div>
<div className="gen-middle-center">
{children({
width: width - CHROME_WIDTH,
height: height - CHROME_HEIGHT,
})}
</div>
<div className="gen-middle-right draggable">
<div className="gen-middle-right-bottom draggable" />
</div>
</div>
<div className="gen-bottom draggable">
<div className="gen-bottom-left draggable" />
<div className="gen-bottom-right draggable">
<ResizeTarget
currentSize={windowSize}
setWindowSize={size => setGenWindowSize(windowId, size)}
id={"gen-resize-target"}
/>
</div>
</div>
</div>
<div className="gen-middle">
<div className="gen-middle-left draggable">
<div className="gen-middle-left-bottom draggable" />
</div>
<div className="gen-middle-center">
{children({
width: width - CHROME_WIDTH,
height: height - CHROME_HEIGHT,
})}
</div>
<div className="gen-middle-right draggable">
<div className="gen-middle-right-bottom draggable" />
</div>
</div>
<div className="gen-bottom draggable">
<div className="gen-bottom-left draggable" />
<div className="gen-bottom-right draggable">
<ResizeTarget
currentSize={windowSize}
setWindowSize={size => setGenWindowSize(windowId, size)}
id={"gen-resize-target"}
/>
</div>
</div>
</div>
</FocusTarget>
);
};
@ -130,8 +135,6 @@ const mapStateToProps = (state: AppState, ownProps: OwnProps): StateProps => {
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
return {
setFocus: (windowId: WindowId) =>
dispatch({ type: SET_FOCUSED_WINDOW, window: windowId }),
close: (windowId: WindowId) => dispatch(closeWindow(windowId)),
setGenWindowSize: (windowId: WindowId, size: [number, number]) =>
dispatch(setWindowSize(windowId, size)),

View file

@ -45,6 +45,7 @@ import {
Dispatch,
FilePicker,
} from "../../types";
import FocusTarget from "../FocusTarget";
interface StateProps {
focused: WindowId;
@ -57,7 +58,6 @@ interface StateProps {
}
interface DispatchProps {
setFocus(): void;
loadFilesFromReferences(files: FileList): void;
scrollVolume(e: React.WheelEvent<HTMLDivElement>): void;
toggleMainWindowShadeMode(): void;
@ -72,10 +72,6 @@ interface OwnProps {
type Props = StateProps & DispatchProps & OwnProps;
export class MainWindow extends React.Component<Props> {
_handleClick = () => {
this.props.setFocus();
};
render() {
const {
focused,
@ -105,65 +101,66 @@ export class MainWindow extends React.Component<Props> {
<DropTarget
id="main-window"
className={className}
onMouseDown={this._handleClick}
handleDrop={this.props.loadMedia}
onWheel={this.props.scrollVolume}
>
<div
id="title-bar"
className="selected draggable"
onDoubleClick={this.props.toggleMainWindowShadeMode}
>
<ContextMenuTarget
id="option-context"
bottom
handle={<ClickedDiv id="option" title="Winamp Menu" />}
>
<MainContextMenu filePickers={filePickers} />
</ContextMenuTarget>
{mainShade && <MiniTime />}
<Minimize />
<Shade />
<Close />
</div>
<div className="status">
<ClutterBar />
{!working && <div id="play-pause" />}
<FocusTarget windowId={WINDOWS.MAIN}>
<div
id="work-indicator"
className={classnames({ selected: working })}
id="title-bar"
className="selected draggable"
onDoubleClick={this.props.toggleMainWindowShadeMode}
>
<ContextMenuTarget
id="option-context"
bottom
handle={<ClickedDiv id="option" title="Winamp Menu" />}
>
<MainContextMenu filePickers={filePickers} />
</ContextMenuTarget>
{mainShade && <MiniTime />}
<Minimize />
<Shade />
<Close />
</div>
<div className="status">
<ClutterBar />
{!working && <div id="play-pause" />}
<div
id="work-indicator"
className={classnames({ selected: working })}
/>
<Time />
</div>
<Visualizer
// @ts-ignore Visualizer is not typed yet
analyser={this.props.analyser}
/>
<Time />
</div>
<Visualizer
// @ts-ignore Visualizer is not typed yet
analyser={this.props.analyser}
/>
<div className="media-info">
<Marquee />
<Kbps />
<Khz />
<MonoStereo />
</div>
<MainVolume />
<MainBalance />
<div className="windows">
<EqToggleButton />
<PlaylistToggleButton />
</div>
<Position />
<ActionButtons />
<Eject />
<div className="shuffle-repeat">
<Shuffle />
<Repeat />
</div>
<a
id="about"
target="blank"
href="https://webamp.org/about"
title="About"
/>
<div className="media-info">
<Marquee />
<Kbps />
<Khz />
<MonoStereo />
</div>
<MainVolume />
<MainBalance />
<div className="windows">
<EqToggleButton />
<PlaylistToggleButton />
</div>
<Position />
<ActionButtons />
<Eject />
<div className="shuffle-repeat">
<Shuffle />
<Repeat />
</div>
<a
id="about"
target="blank"
href="https://webamp.org/about"
title="About"
/>
</FocusTarget>
</DropTarget>
);
}
@ -188,8 +185,6 @@ const mapStateToProps = (state: AppState): StateProps => {
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
return {
setFocus: () =>
dispatch({ type: SET_FOCUSED_WINDOW, window: WINDOWS.MAIN }),
loadFilesFromReferences: (files: FileList) =>
dispatch(loadFilesFromReferences(files)),
toggleMainWindowShadeMode: () => dispatch(toggleMainWindowShadeMode()),
@ -200,5 +195,7 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
};
export default connect(
mapStateToProps,
mapDispatchToProps
mapDispatchToProps,
null,
{ forwardRef: true }
)(MainWindow);

View file

@ -52,9 +52,6 @@ interface DispatchProps {
interface OwnProps {
height: number;
width: number;
onFocusedKeyDown(
cb: (e: React.KeyboardEvent<HTMLDivElement>) => void
): () => void;
}
type Props = StateProps & DispatchProps & OwnProps;
@ -69,12 +66,6 @@ class PresetOverlay extends React.Component<Props, State> {
}
componentDidMount() {
this._disposable.add(
// Technically we should handle the case where this prop changes, but we don't.
// If we convert to hooks for this component, this would get much easier.
// _Also_ ideally we could avoid this prop all together.
this.props.onFocusedKeyDown(this._handleFocusedKeyboardInput)
);
const { currentPresetIndex } = this.props;
if (currentPresetIndex != null) {
this.setState({
@ -188,7 +179,10 @@ class PresetOverlay extends React.Component<Props, State> {
);
}
return (
<div style={OUTER_WRAPPER_STYLE}>
<div
style={OUTER_WRAPPER_STYLE}
onKeyDown={this._handleFocusedKeyboardInput}
>
<div
style={{
...INNER_WRAPPER_STYLE,

View file

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useCallback } from "react";
import Fullscreen from "../Fullscreen";
import { connect } from "react-redux";
import { useWindowSize, useScreenSize } from "../../hooks";
@ -44,16 +44,12 @@ interface DispatchProps {
interface OwnProps {
analyser: AnalyserNode;
onFocusedKeyDown(
cb: (e: React.KeyboardEvent<HTMLDivElement>) => void
): () => void;
}
type Props = StateProps & DispatchProps & OwnProps;
function useMilkdropKeyBindings(props: Props) {
function useKeyHandler(props: Props) {
const {
onFocusedKeyDown,
selectNextPreset,
selectPreviousPreset,
toggleRandomize,
@ -63,8 +59,8 @@ function useMilkdropKeyBindings(props: Props) {
toggleCycling,
} = props;
// Handle keyboard events
useEffect(() => {
return onFocusedKeyDown(e => {
return useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
switch (e.keyCode) {
case 32: // spacebar
selectNextPreset();
@ -93,21 +89,21 @@ function useMilkdropKeyBindings(props: Props) {
toggleCycling();
break;
}
});
}, [
onFocusedKeyDown,
selectNextPreset,
selectPreviousPreset,
toggleRandomize,
togglePresetOverlay,
scheduleMilkdropMessage,
trackTitle,
toggleCycling,
]);
},
[
selectNextPreset,
selectPreviousPreset,
toggleRandomize,
togglePresetOverlay,
scheduleMilkdropMessage,
trackTitle,
toggleCycling,
]
);
}
function Milkdrop(props: Props) {
useMilkdropKeyBindings(props);
const handleKeyDown = useKeyHandler(props);
// Cycle presets
useEffect(() => {
@ -139,19 +135,18 @@ function Milkdrop(props: Props) {
}
return (
<GenWindow title={"Milkdrop"} windowId={WINDOWS.MILKDROP}>
<GenWindow
title={"Milkdrop"}
windowId={WINDOWS.MILKDROP}
onKeyDown={handleKeyDown}
>
{(windowSize: { width: number; height: number }) => {
const size = props.fullscreen ? screenSize : windowSize;
return (
<MilkdropContextMenu>
<Background>
<DropTarget handleDrop={props.handlePresetDrop}>
{props.overlay && (
<PresetOverlay
{...size}
onFocusedKeyDown={props.onFocusedKeyDown}
/>
)}
{props.overlay && <PresetOverlay {...size} />}
<Fullscreen
enabled={props.fullscreen}
onChange={props.setFullscreen}

View file

@ -3,7 +3,6 @@ import { connect } from "react-redux";
import classnames from "classnames";
import { WINDOWS, TRACK_HEIGHT, LOAD_STYLE } from "../../constants";
import { SET_FOCUSED_WINDOW } from "../../actionTypes";
import {
scrollUpFourTracks,
scrollDownFourTracks,
@ -30,6 +29,7 @@ import ScrollBar from "./ScrollBar";
import "../../../css/playlist-window.css";
import { AppState, PlaylistStyle, Dispatch } from "../../types";
import FocusTarget from "../FocusTarget";
interface StateProps {
offset: number;
@ -45,7 +45,6 @@ interface StateProps {
}
interface DispatchProps {
focusPlaylist(): void;
close(): void;
toggleShade(): void;
scrollUpFourTracks(): void;
@ -77,7 +76,6 @@ class PlaylistWindow extends React.Component<Props> {
render() {
const {
skinPlaylistStyle,
focusPlaylist,
selected,
playlistSize,
playlistWindowPixelSize,
@ -109,69 +107,70 @@ class PlaylistWindow extends React.Component<Props> {
id="playlist-window"
className={classes}
style={style}
onMouseDown={focusPlaylist}
handleDrop={this._handleDrop}
onWheel={this.props.scrollVolume}
>
<div className="playlist-top draggable" onDoubleClick={toggleShade}>
<div className="playlist-top-left draggable" />
{showSpacers && (
<div className="playlist-top-left-spacer draggable" />
)}
<div className="playlist-top-left-fill draggable" />
<div className="playlist-top-title draggable" />
{showSpacers && (
<div className="playlist-top-right-spacer draggable" />
)}
<div className="playlist-top-right-fill draggable" />
<div className="playlist-top-right draggable">
<div id="playlist-shade-button" onClick={toggleShade} />
<div id="playlist-close-button" onClick={close} />
</div>
</div>
<div className="playlist-middle draggable">
<div className="playlist-middle-left draggable" />
<div className="playlist-middle-center">
<TrackList />
</div>
<div className="playlist-middle-right draggable">
<ScrollBar />
</div>
</div>
<div className="playlist-bottom draggable">
<div className="playlist-bottom-left draggable">
<AddMenu />
<RemoveMenu />
<SelectionMenu />
<MiscMenu />
</div>
<div className="playlist-bottom-center draggable" />
<div className="playlist-bottom-right draggable">
{showVisualizer && (
<div className="playlist-visualizer">
{activateVisualizer && (
<div className="visualizer-wrapper">
<Visualizer
// @ts-ignore Visualizer is not yet typed
analyser={analyser}
/>
</div>
)}
</div>
<FocusTarget windowId={WINDOWS.PLAYLIST}>
<div className="playlist-top draggable" onDoubleClick={toggleShade}>
<div className="playlist-top-left draggable" />
{showSpacers && (
<div className="playlist-top-left-spacer draggable" />
)}
<PlaylistActionArea />
<ListMenu />
<div
id="playlist-scroll-up-button"
onClick={this.props.scrollUpFourTracks}
/>
<div
id="playlist-scroll-down-button"
onClick={this.props.scrollDownFourTracks}
/>
<PlaylistResizeTarget />
<div className="playlist-top-left-fill draggable" />
<div className="playlist-top-title draggable" />
{showSpacers && (
<div className="playlist-top-right-spacer draggable" />
)}
<div className="playlist-top-right-fill draggable" />
<div className="playlist-top-right draggable">
<div id="playlist-shade-button" onClick={toggleShade} />
<div id="playlist-close-button" onClick={close} />
</div>
</div>
</div>
<div className="playlist-middle draggable">
<div className="playlist-middle-left draggable" />
<div className="playlist-middle-center">
<TrackList />
</div>
<div className="playlist-middle-right draggable">
<ScrollBar />
</div>
</div>
<div className="playlist-bottom draggable">
<div className="playlist-bottom-left draggable">
<AddMenu />
<RemoveMenu />
<SelectionMenu />
<MiscMenu />
</div>
<div className="playlist-bottom-center draggable" />
<div className="playlist-bottom-right draggable">
{showVisualizer && (
<div className="playlist-visualizer">
{activateVisualizer && (
<div className="visualizer-wrapper">
<Visualizer
// @ts-ignore Visualizer is not yet typed
analyser={analyser}
/>
</div>
)}
</div>
)}
<PlaylistActionArea />
<ListMenu />
<div
id="playlist-scroll-up-button"
onClick={this.props.scrollUpFourTracks}
/>
<div
id="playlist-scroll-down-button"
onClick={this.props.scrollDownFourTracks}
/>
<PlaylistResizeTarget />
</div>
</div>
</FocusTarget>
</DropTarget>
);
}
@ -179,8 +178,6 @@ class PlaylistWindow extends React.Component<Props> {
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
return {
focusPlaylist: () =>
dispatch({ type: SET_FOCUSED_WINDOW, window: WINDOWS.PLAYLIST }),
close: () => dispatch(closeWindow(WINDOWS.PLAYLIST)),
toggleShade: () => dispatch(togglePlaylistShadeMode()),
scrollUpFourTracks: () => dispatch(scrollUpFourTracks()),