Maintain window relationships on resize

This commit is contained in:
Jordan Eldredge 2018-03-24 21:55:40 -07:00
parent 6200b87295
commit 6cd8e7112b
9 changed files with 169 additions and 63 deletions

View file

@ -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 };
}

View file

@ -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";

View file

@ -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 => (
<div id="clutter-bar">
@ -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 });
}
});

View file

@ -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 => {

View file

@ -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,

View file

@ -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 (
<div style={parentStyle}>
{this.windowKeys().map(key => {
const position = this.state[key];
const position = this.props.windowPositions[key];
return (
position && (
<div
onMouseDown={e => 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);

View file

@ -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

View file

@ -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;
}

View file

@ -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);
}
);