mirror of
https://github.com/captbaritone/webamp.git
synced 2026-08-01 14:33:38 +00:00
Extract focus management to a component
This commit is contained in:
parent
67441f3c1b
commit
24d446e47b
8 changed files with 297 additions and 323 deletions
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
66
js/components/FocusTarget.tsx
Normal file
66
js/components/FocusTarget.tsx
Normal 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);
|
||||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue