mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-25 11:04:00 +00:00
For now users can't control where Webamp inserts itself into the DOM. We want to add that as and option. In the mean time, we can enable users to hack it. For example, https://github.com/ShizukuIchi/winXP wants to be able to keep Webamp in a div in order to control how it gets layerd with other apps.
205 lines
6 KiB
JavaScript
205 lines
6 KiB
JavaScript
import React from "react";
|
|
import ReactDOM from "react-dom";
|
|
import { connect } from "react-redux";
|
|
import Emitter from "../emitter";
|
|
import { WINDOWS } from "../constants";
|
|
import * as Selectors from "../selectors";
|
|
import * as Actions from "../actionCreators";
|
|
import * as Utils from "../utils";
|
|
import MilkdropWindow from "../components/MilkdropWindow";
|
|
import ContextMenuWrapper from "./ContextMenuWrapper";
|
|
import MainContextMenu from "./MainWindow/MainContextMenu";
|
|
import WindowManager from "./WindowManager";
|
|
import MainWindow from "./MainWindow";
|
|
import PlaylistWindow from "./PlaylistWindow";
|
|
import EqualizerWindow from "./EqualizerWindow";
|
|
import MediaLibraryWindow from "./MediaLibraryWindow";
|
|
import Skin from "./Skin";
|
|
|
|
import "../../css/webamp.css";
|
|
|
|
/**
|
|
* Constructs the windows to render, and tracks focus.
|
|
*/
|
|
class App extends React.Component {
|
|
constructor() {
|
|
super();
|
|
this._emitter = new Emitter();
|
|
this._windowNodes = {};
|
|
this._bindings = {};
|
|
}
|
|
|
|
componentWillMount() {
|
|
this._webampNode = document.createElement("div");
|
|
this._webampNode.id = "webamp";
|
|
this._webampNode.role = "application";
|
|
this._webampNode.style.zIndex = this.props.zIndex;
|
|
document.body.appendChild(this._webampNode);
|
|
|
|
this.props.browserWindowSizeChanged(Utils.getWindowSize());
|
|
window.addEventListener("resize", this._handleWindowResize);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
window.removeEventListener("resize", this._handleWindowResize);
|
|
const parentNode = this._webampNode.parentNode;
|
|
if (parentNode != null) {
|
|
parentNode.removeChild(this._webampNode);
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
this._setFocus();
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (prevProps.focused !== this.props.focused) {
|
|
this._setFocus();
|
|
}
|
|
}
|
|
|
|
_handleWindowResize = () => {
|
|
if (this._webampNode == null) {
|
|
return;
|
|
}
|
|
// It's a bit tricky to measure the "natural" size of the browser window.
|
|
// Specifically we want to know how large the window would be without our
|
|
// own Webamp windows influencing it. To achieve this, we temporarily make
|
|
// our container `overflow: hidden;`. We then make our container full
|
|
// screen by setting the bottom/right properties to zero. This second part
|
|
// allows our Webamp windows to stay visible during the resize. After we
|
|
// measure, we set the style back so that we don't end up interfering with
|
|
// click events outside of our Webamp windows.
|
|
this._webampNode.style.right = 0;
|
|
this._webampNode.style.bottom = 0;
|
|
this._webampNode.style.overflow = "hidden";
|
|
this.props.browserWindowSizeChanged(Utils.getWindowSize());
|
|
this._webampNode.style.right = null;
|
|
this._webampNode.style.bottom = null;
|
|
this._webampNode.style.overflow = "visible";
|
|
};
|
|
|
|
_setFocus() {
|
|
const binding = this._bindings[this.props.focused];
|
|
if (binding && binding.node) {
|
|
binding.node.focus();
|
|
}
|
|
}
|
|
|
|
_gotRef(windowId, comp) {
|
|
if (comp == null) {
|
|
const binding = this._bindings[windowId];
|
|
if (binding.remove) {
|
|
binding.remove();
|
|
}
|
|
this._bindings[windowId] = null;
|
|
return;
|
|
}
|
|
|
|
const node = ReactDOM.findDOMNode(comp);
|
|
const binding = this._bindings[windowId];
|
|
if (binding && binding.node === node) {
|
|
return;
|
|
}
|
|
|
|
node.tabIndex = -1;
|
|
const listener = e => 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) => {
|
|
if (!w.open) {
|
|
return null;
|
|
}
|
|
switch (id) {
|
|
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)} />
|
|
);
|
|
case WINDOWS.PLAYLIST:
|
|
return (
|
|
<PlaylistWindow
|
|
ref={component => this._gotRef(id, component)}
|
|
analyser={media.getAnalyser()}
|
|
/>
|
|
);
|
|
case WINDOWS.MEDIA_LIBRARY:
|
|
return (
|
|
<MediaLibraryWindow
|
|
ref={component => this._gotRef(id, component)}
|
|
/>
|
|
);
|
|
case WINDOWS.MILKDROP:
|
|
return (
|
|
<MilkdropWindow
|
|
ref={component => this._gotRef(id, component)}
|
|
options={this.props.butterchurnOptions}
|
|
// TODO: Refactor this. I don't think we need this to be generic anymore.
|
|
onFocusedKeyDown={listener => this._emitter.on(id, listener)}
|
|
analyser={media.getAnalyser()}
|
|
/>
|
|
);
|
|
default:
|
|
throw new Error("Tried to render an unknown window:", id);
|
|
}
|
|
});
|
|
}
|
|
|
|
render() {
|
|
const { closed, container, filePickers } = this.props;
|
|
if (closed) {
|
|
return null;
|
|
}
|
|
return ReactDOM.createPortal(
|
|
<React.Fragment>
|
|
<Skin />
|
|
<ContextMenuWrapper
|
|
renderContents={() => <MainContextMenu filePickers={filePickers} />}
|
|
>
|
|
<WindowManager
|
|
windows={this._renderWindows()}
|
|
container={container}
|
|
/>
|
|
</ContextMenuWrapper>
|
|
</React.Fragment>,
|
|
this._webampNode
|
|
);
|
|
}
|
|
}
|
|
|
|
const mapStateToProps = state => ({
|
|
visualizerStyle: Selectors.getVisualizerStyle(state),
|
|
status: state.media.status,
|
|
focused: state.windows.focused,
|
|
closed: state.display.closed,
|
|
genWindowsInfo: state.windows.genWindows,
|
|
zIndex: state.display.zIndex,
|
|
});
|
|
|
|
const mapDispatchToProps = dispatch => ({
|
|
closeWindow: id => dispatch(Actions.closeWindow(id)),
|
|
browserWindowSizeChanged: size =>
|
|
dispatch(Actions.browserWindowSizeChanged(size)),
|
|
});
|
|
|
|
export default connect(
|
|
mapStateToProps,
|
|
mapDispatchToProps
|
|
)(App);
|