diff --git a/js/actionCreators.js b/js/actionCreators.js
index 2e8ad3c5..775782db 100644
--- a/js/actionCreators.js
+++ b/js/actionCreators.js
@@ -16,7 +16,10 @@ import {
getPlaylistURL,
getSelectedTrackObjects,
getTracks,
- getTrackIsVisibleFunction
+ getTrackIsVisibleFunction,
+ getWindowGraph,
+ getWindowPositions,
+ getWindowSizes
} from "./selectors";
import {
@@ -59,10 +62,14 @@ import {
TOGGLE_PLAYLIST_SHADE_MODE,
MEDIA_TAG_REQUEST_INITIALIZED,
MEDIA_TAG_REQUEST_FAILED,
- PLAYLIST_SIZE_CHANGED
+ PLAYLIST_SIZE_CHANGED,
+ UPDATE_WINDOW_POSITIONS,
+ TOGGLE_DOUBLESIZE_MODE
} from "./actionTypes";
import LoadQueue from "./loadQueue";
+import { getPositionDiff } from "./resizeUtils";
+import { applyDiff } from "./snapUtils";
// Lower is better
const DURATION_VISIBLE_PRIORITY = 5;
@@ -469,16 +476,57 @@ export function downloadPreset() {
};
}
+// Dispatch an action and, if needed rearrange the windows to preserve
+// the existing edge relationship.
+//
+// Works by checking the edges before the action is dispatched. Then,
+// after disatching, calculating what position change would be required
+// to restore those relationships.
+function withWindowGraphIntegrity(action) {
+ return (dispatch, getState) => {
+ const state = getState();
+ const graph = getWindowGraph(state);
+ const originalSizes = getWindowSizes(state);
+
+ dispatch(action);
+
+ const newSizes = getWindowSizes(getState());
+ const sizeDiff = {};
+ for (const window of Object.keys(newSizes)) {
+ const original = originalSizes[window];
+ const current = newSizes[window];
+ sizeDiff[window] = {
+ height: current.height - original.height,
+ width: current.width - original.width
+ };
+ }
+
+ const positionDiff = getPositionDiff(graph, sizeDiff);
+ const windowPositions = getWindowPositions(state);
+
+ const newPositions = {};
+ for (const key of Object.keys(windowPositions)) {
+ newPositions[key] = applyDiff(windowPositions[key], positionDiff[key]);
+ }
+
+ dispatch(updateWindowPositions(newPositions));
+ };
+}
+
+export function toggleDoubleSizeMode() {
+ return withWindowGraphIntegrity({ type: TOGGLE_DOUBLESIZE_MODE });
+}
+
export function toggleEqualizerShadeMode() {
- return { type: TOGGLE_EQUALIZER_SHADE_MODE };
+ return withWindowGraphIntegrity({ type: TOGGLE_EQUALIZER_SHADE_MODE });
}
export function toggleMainWindowShadeMode() {
- return { type: TOGGLE_MAIN_SHADE_MODE };
+ return withWindowGraphIntegrity({ type: TOGGLE_MAIN_SHADE_MODE });
}
export function togglePlaylistShadeMode() {
- return { type: TOGGLE_PLAYLIST_SHADE_MODE };
+ return withWindowGraphIntegrity({ type: TOGGLE_PLAYLIST_SHADE_MODE });
}
export function closeEqualizerWindow() {
@@ -625,3 +673,7 @@ export function downloadHtmlPlaylist() {
downloadURI(uri, "Winamp Playlist.html");
};
}
+
+export function updateWindowPositions(positions) {
+ return { type: UPDATE_WINDOW_POSITIONS, positions };
+}
diff --git a/js/actionTypes.js b/js/actionTypes.js
index 3fdb2ee7..25f833c7 100644
--- a/js/actionTypes.js
+++ b/js/actionTypes.js
@@ -64,3 +64,4 @@ export const MEDIA_TAG_REQUEST_INITIALIZED = "MEDIA_TAG_REQUEST_INITIALIZED";
export const MEDIA_TAG_REQUEST_FAILED = "MEDIA_TAG_REQUEST_FAILED";
export const NETWORK_CONNECTED = "NETWORK_CONNECTED";
export const NETWORK_DISCONNECTED = "NETWORK_DISCONNECTED";
+export const UPDATE_WINDOW_POSITIONS = "UPDATE_WINDOW_POSITIONS";
diff --git a/js/components/MainWindow/ClutterBar.js b/js/components/MainWindow/ClutterBar.js
index 5566db71..9272b7bf 100644
--- a/js/components/MainWindow/ClutterBar.js
+++ b/js/components/MainWindow/ClutterBar.js
@@ -2,11 +2,8 @@ import React from "react";
import { connect } from "react-redux";
import classnames from "classnames";
-import {
- SET_FOCUS,
- TOGGLE_DOUBLESIZE_MODE,
- UNSET_FOCUS
-} from "../../actionTypes";
+import { SET_FOCUS, UNSET_FOCUS } from "../../actionTypes";
+import { toggleDoubleSizeMode } from "../../actionCreators";
const ClutterBar = props => (
@@ -31,7 +28,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
handleMouseDown: () => dispatch({ type: SET_FOCUS, input: "double" }),
handleMouseUp: () => {
- dispatch({ type: TOGGLE_DOUBLESIZE_MODE });
+ dispatch(toggleDoubleSizeMode());
dispatch({ type: UNSET_FOCUS });
}
});
diff --git a/js/components/PlaylistWindow/PlaylistShade.js b/js/components/PlaylistWindow/PlaylistShade.js
index 5e7a2046..3fa343f7 100644
--- a/js/components/PlaylistWindow/PlaylistShade.js
+++ b/js/components/PlaylistWindow/PlaylistShade.js
@@ -3,11 +3,7 @@ import { connect } from "react-redux";
import classnames from "classnames";
import { getOrderedTracks, getMinimalMediaText } from "../../selectors";
import { getTimeStr } from "../../utils";
-import {
- TOGGLE_PLAYLIST_WINDOW,
- TOGGLE_PLAYLIST_SHADE_MODE,
- SET_FOCUSED_WINDOW
-} from "../../actionTypes";
+import { TOGGLE_PLAYLIST_WINDOW, SET_FOCUSED_WINDOW } from "../../actionTypes";
import {
WINDOWS,
@@ -16,6 +12,7 @@ import {
CHARACTER_WIDTH,
UTF8_ELLIPSIS
} from "../../constants";
+import { togglePlaylistShadeMode } from "../../actionCreators";
import CharacterString from "../CharacterString";
import PlaylistResizeTarget from "./PlaylistResizeTarget";
@@ -83,7 +80,7 @@ const mapDispatchToProps = dispatch => ({
focusPlaylist: () =>
dispatch({ type: SET_FOCUSED_WINDOW, window: WINDOWS.PLAYLIST }),
close: () => dispatch({ type: TOGGLE_PLAYLIST_WINDOW }),
- toggleShade: () => dispatch({ type: TOGGLE_PLAYLIST_SHADE_MODE })
+ toggleShade: () => dispatch(togglePlaylistShadeMode())
});
const mapStateToProps = state => {
diff --git a/js/components/PlaylistWindow/index.js b/js/components/PlaylistWindow/index.js
index a40ff779..924b9bea 100644
--- a/js/components/PlaylistWindow/index.js
+++ b/js/components/PlaylistWindow/index.js
@@ -2,18 +2,8 @@ import React from "react";
import { connect } from "react-redux";
import classnames from "classnames";
-import {
- WINDOWS,
- PLAYLIST_RESIZE_SEGMENT_WIDTH,
- PLAYLIST_RESIZE_SEGMENT_HEIGHT,
- MIN_PLAYLIST_WINDOW_WIDTH,
- TRACK_HEIGHT
-} from "../../constants";
-import {
- TOGGLE_PLAYLIST_WINDOW,
- TOGGLE_PLAYLIST_SHADE_MODE,
- SET_FOCUSED_WINDOW
-} from "../../actionTypes";
+import { WINDOWS, TRACK_HEIGHT } from "../../constants";
+import { TOGGLE_PLAYLIST_WINDOW, SET_FOCUSED_WINDOW } from "../../actionTypes";
import {
toggleVisualizerStyle,
scrollUpFourTracks,
@@ -22,7 +12,7 @@ import {
togglePlaylistShadeMode,
scrollVolume
} from "../../actionCreators";
-import { getScrollOffset } from "../../selectors";
+import { getScrollOffset, getPlaylistWindowPixelSize } from "../../selectors";
import { clamp } from "../../utils";
import DropTarget from "../DropTarget";
@@ -39,8 +29,6 @@ import ScrollBar from "./ScrollBar";
import "../../../css/playlist-window.css";
-const MIN_WINDOW_HEIGHT = 116;
-
class PlaylistWindow extends React.Component {
constructor(props) {
super(props);
@@ -63,6 +51,7 @@ class PlaylistWindow extends React.Component {
focusPlaylist,
focused,
playlistSize,
+ playlistWindowPixelSize,
playlistShade,
close,
toggleShade
@@ -75,10 +64,8 @@ class PlaylistWindow extends React.Component {
color: skinPlaylistStyle.normal,
backgroundColor: skinPlaylistStyle.normalbg,
fontFamily: `${skinPlaylistStyle.font}, Arial, sans-serif`,
- height: `${MIN_WINDOW_HEIGHT +
- playlistSize[1] * PLAYLIST_RESIZE_SEGMENT_HEIGHT}px`,
- width: `${MIN_PLAYLIST_WINDOW_WIDTH +
- playlistSize[0] * PLAYLIST_RESIZE_SEGMENT_WIDTH}px`
+ height: `${playlistWindowPixelSize.height}px`,
+ width: `${playlistWindowPixelSize.width}px`
};
const classes = classnames("window", "draggable", {
@@ -162,7 +149,7 @@ const mapDispatchToProps = {
window: WINDOWS.PLAYLIST
}),
close: () => ({ type: TOGGLE_PLAYLIST_WINDOW }),
- toggleShade: () => ({ type: TOGGLE_PLAYLIST_SHADE_MODE }),
+ toggleShade: togglePlaylistShadeMode,
toggleVisualizerStyle,
scrollUpFourTracks,
scrollDownFourTracks,
@@ -183,6 +170,7 @@ const mapStateToProps = state => {
return {
offset: getScrollOffset(state),
maxTrackIndex: trackOrder.length - 1,
+ playlistWindowPixelSize: getPlaylistWindowPixelSize(state),
focused,
skinPlaylistStyle,
playlistSize,
diff --git a/js/components/WindowManager.js b/js/components/WindowManager.js
index db991f8b..0526859c 100644
--- a/js/components/WindowManager.js
+++ b/js/components/WindowManager.js
@@ -1,5 +1,6 @@
import React from "react";
import PropTypes from "prop-types";
+import { connect } from "react-redux";
import {
snapDiffManyToMany,
@@ -10,6 +11,8 @@ import {
applyDiff,
applyMultipleDiffs
} from "../snapUtils";
+import { getWindowPositions } from "../selectors";
+import { updateWindowPositions } from "../actionCreators";
const WINDOW_HEIGHT = 116;
const WINDOW_WIDTH = 275;
@@ -25,7 +28,6 @@ class WindowManager extends React.Component {
constructor(props) {
super(props);
this.windowNodes = {};
- this.state = {};
this.getRef = this.getRef.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.centerWindows = this.centerWindows.bind(this);
@@ -62,17 +64,17 @@ class WindowManager extends React.Component {
width = container.scrollWidth;
height = container.scrollHeight;
}
- const state = {};
+ const windowPositions = {};
const keys = this.windowKeys();
const totalHeight = keys.length * WINDOW_HEIGHT;
keys.forEach((key, i) => {
const offset = WINDOW_HEIGHT * i;
- state[key] = {
- left: offsetLeft + (width / 2 - WINDOW_WIDTH / 2),
- top: offsetTop + (height / 2 - totalHeight / 2 + offset)
+ windowPositions[key] = {
+ x: offsetLeft + (width / 2 - WINDOW_WIDTH / 2),
+ y: offsetTop + (height / 2 - totalHeight / 2 + offset)
};
});
- this.setState(state);
+ this.props.updateWindowPositions(windowPositions);
}
getRef(key, node) {
@@ -154,16 +156,12 @@ class WindowManager extends React.Component {
const finalDiff = applyMultipleDiffs(proposedDiff, snapDiff, withinDiff);
- const stateDiff = moving.reduce((diff, window) => {
- const newWindowLocation = applyDiff(window, finalDiff);
- diff[window.key] = {
- top: newWindowLocation.y,
- left: newWindowLocation.x
- };
+ const windowPositionDiff = moving.reduce((diff, window) => {
+ diff[window.key] = applyDiff(window, finalDiff);
return diff;
}, {});
- this.setState(stateDiff);
+ this.props.updateWindowPositions(windowPositionDiff);
};
const removeListeners = () => {
@@ -198,13 +196,13 @@ class WindowManager extends React.Component {
return (
{this.windowKeys().map(key => {
- const position = this.state[key];
+ const position = this.props.windowPositions[key];
return (
position && (
this.handleMouseDown(key, e)}
ref={node => this.getRef(key, node)}
- style={{ ...style, ...position }}
+ style={{ ...style, left: position.x, top: position.y }}
key={key}
>
{this.props.windows[key]}
@@ -222,4 +220,12 @@ WindowManager.propTypes = {
container: PropTypes.instanceOf(Element)
};
-export default WindowManager;
+const mapStateToProps = state => ({
+ windowPositions: getWindowPositions(state)
+});
+
+const mapDispatchToProps = {
+ updateWindowPositions
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(WindowManager);
diff --git a/js/hotkeys.js b/js/hotkeys.js
index 9629b4a9..6d85f913 100644
--- a/js/hotkeys.js
+++ b/js/hotkeys.js
@@ -11,14 +11,11 @@ import {
reverseList,
nextN,
next,
- previous
+ previous,
+ toggleDoubleSizeMode
} from "./actionCreators";
-import {
- TOGGLE_DOUBLESIZE_MODE,
- TOGGLE_TIME_MODE,
- TOGGLE_LLAMA_MODE
-} from "./actionTypes";
+import { TOGGLE_TIME_MODE, TOGGLE_LLAMA_MODE } from "./actionTypes";
import { arraysAreEqual } from "./utils";
@@ -39,7 +36,7 @@ export default function(dispatch) {
// Is CTRL depressed?
switch (e.keyCode) {
case 68: // CTRL+D
- dispatch({ type: TOGGLE_DOUBLESIZE_MODE });
+ dispatch(toggleDoubleSizeMode());
e.preventDefault(); // Supress the "Bookmark" action on windows.
break;
case 76: // CTRL+L FIXME
diff --git a/js/reducers/windows.js b/js/reducers/windows.js
index 5240abd1..8492ac74 100644
--- a/js/reducers/windows.js
+++ b/js/reducers/windows.js
@@ -5,7 +5,8 @@ import {
CLOSE_EQUALIZER_WINDOW,
TOGGLE_PLAYLIST_WINDOW,
CLOSE_GEN_WINDOW,
- OPEN_GEN_WINDOW
+ OPEN_GEN_WINDOW,
+ UPDATE_WINDOW_POSITIONS
} from "../actionTypes";
import { arrayWith, arrayWithout } from "../utils";
@@ -15,7 +16,8 @@ const defaultWindowsState = {
equalizer: true,
playlist: true,
// openGenWindows: ["AVS_WINDOW"]
- openGenWindows: []
+ openGenWindows: [],
+ positions: {}
};
const windows = (state = defaultWindowsState, action) => {
@@ -38,6 +40,11 @@ const windows = (state = defaultWindowsState, action) => {
...state,
openGenWindows: arrayWith(state.openGenWindow, action.windowId)
};
+ case UPDATE_WINDOW_POSITIONS:
+ return {
+ ...state,
+ positions: { ...state.positions, ...action.positions }
+ };
default:
return state;
}
diff --git a/js/selectors.js b/js/selectors.js
index 9fbbe2e5..c622e64a 100644
--- a/js/selectors.js
+++ b/js/selectors.js
@@ -2,11 +2,14 @@ import { createSelector } from "reselect";
import { denormalize, getTimeStr, clamp, percentToIndex } from "./utils";
import {
BANDS,
+ TRACK_HEIGHT,
+ PLAYLIST_RESIZE_SEGMENT_WIDTH,
PLAYLIST_RESIZE_SEGMENT_HEIGHT,
- TRACK_HEIGHT
+ MIN_PLAYLIST_WINDOW_WIDTH
} from "./constants";
import { createPlaylistURL } from "./playlistHtml";
import * as fromPlaylist from "./reducers/playlist";
+import { generateGraph } from "./resizeUtils";
export const getEqfData = state => {
const { sliders } = state.equalizer;
@@ -228,3 +231,61 @@ export const getPlaylistURL = createSelector(
)
})
);
+
+export function getWindowPositions(state) {
+ return state.windows.positions;
+}
+
+const WINDOW_WIDTH = 275;
+const WINDOW_HEIGHT = 116;
+const DEFAUT_WINDOW_SIZE = {
+ height: WINDOW_HEIGHT,
+ width: WINDOW_WIDTH
+};
+const SHADE_WINDOW_HEIGHT = 14;
+
+export function getPlaylistWindowPixelSize(state) {
+ const { playlistSize } = state.display;
+ return {
+ height: WINDOW_HEIGHT + playlistSize[1] * PLAYLIST_RESIZE_SEGMENT_HEIGHT,
+ width:
+ MIN_PLAYLIST_WINDOW_WIDTH +
+ playlistSize[0] * PLAYLIST_RESIZE_SEGMENT_WIDTH
+ };
+}
+
+function getGenericWindowSize(size, shade, doubled) {
+ const doubledMultiplier = doubled ? 2 : 1;
+ return {
+ height: (shade ? SHADE_WINDOW_HEIGHT : size.height) * doubledMultiplier,
+ width: size.width * doubledMultiplier
+ };
+}
+
+export function getWindowSizes(state) {
+ const { doubled, mainShade, equalizerShade, playlistShade } = state.display;
+ const main = getGenericWindowSize(DEFAUT_WINDOW_SIZE, mainShade, doubled);
+ const equalizer = getGenericWindowSize(
+ DEFAUT_WINDOW_SIZE,
+ equalizerShade,
+ doubled
+ );
+ const playlist = getGenericWindowSize(
+ getPlaylistWindowPixelSize(state),
+ playlistShade,
+ false // The playlist cannot be doubled
+ );
+ return { main, equalizer, playlist };
+}
+
+export const getWindowGraph = createSelector(
+ getWindowPositions,
+ getWindowSizes,
+ (windowPositions, windowSizes) => {
+ const windowData = [];
+ for (const key of Object.keys(windowPositions)) {
+ windowData.push({ key, ...windowPositions[key], ...windowSizes[key] });
+ }
+ return generateGraph(windowData);
+ }
+);