Revert "Refactor how we manage window positions"

This reverts commit d6a1baece3.
This commit is contained in:
Jordan Eldredge 2018-10-01 12:50:21 -07:00 committed by GitHub
parent 928e9a482d
commit 469dfb96c2
9 changed files with 210 additions and 189 deletions

View file

@ -22,12 +22,11 @@ export {
toggleWindow,
updateWindowPositions,
toggleMainWindowShadeMode,
centerWindowsInContainer,
centerWindowsInView,
resetWindowSizes,
windowsHaveBeenCentered,
centerWindowsIfNeeded,
resetWindowLayout,
browserWindowSizeChanged,
ensureWindowsAreOnScreen,
stackWindows
ensureWindowsAreOnScreen
} from "./windows";
export {
play,

View file

@ -9,14 +9,18 @@ import {
CLOSE_WINDOW,
TOGGLE_WINDOW_SHADE_MODE,
SET_WINDOW_VISIBILITY,
WINDOWS_HAVE_BEEN_CENTERED,
BROWSER_WINDOW_SIZE_CHANGED,
RESET_WINDOW_SIZES
RESET_WINDOW_LAYOUT
} from "../actionTypes";
import { getPositionDiff, SizeDiff } from "../resizeUtils";
import { applyDiff } from "../snapUtils";
import { Action, Dispatchable, WindowId, WindowPositions } from "../types";
import { WINDOW_HEIGHT, WINDOW_WIDTH } from "../constants";
import { calculateBoundingBox } from "../utils";
// Dispatch an action and, if needed rearrange the windows to preserve
// the existing edge relationship.
//
@ -49,7 +53,7 @@ function withWindowGraphIntegrity(action: Action): Dispatchable {
applyDiff(position, positionDiff[key])
);
dispatch(updateWindowPositions(newPositions));
dispatch(updateWindowPositions(newPositions, false));
};
}
@ -102,64 +106,81 @@ export function toggleWindow(windowId: WindowId): Dispatchable {
}
export function updateWindowPositions(
positions: WindowPositions
positions: WindowPositions,
center: boolean
): Dispatchable {
return { type: UPDATE_WINDOW_POSITIONS, positions };
return { type: UPDATE_WINDOW_POSITIONS, positions, center };
}
export function centerWindowsInContainer(container: HTMLElement): Dispatchable {
const { left, top } = container.getBoundingClientRect();
const { scrollWidth: width, scrollHeight: height } = container;
return centerWindows({ left, top, width, height });
export function windowsHaveBeenCentered(): Dispatchable {
return { type: WINDOWS_HAVE_BEEN_CENTERED };
}
export function centerWindowsInView(): Dispatchable {
return centerWindows({
left: window.scrollX,
top: window.scrollY,
width: window.innerWidth,
height: window.innerHeight
});
}
export function centerWindows(box: {
left: number;
top: number;
width: number;
height: number;
}): Dispatchable {
export function centerWindowsIfNeeded(container: HTMLElement): Dispatchable {
return (dispatch, getState) => {
const state = getState();
const { centerRequested } = state.windows;
if (!centerRequested) {
return;
}
const genWindows = Selectors.getGenWindows(state);
const windowsInfo = Selectors.getWindowsInfo(state);
const getOpen = Selectors.getWindowOpen(state);
const { top, left, width, height } = box;
const getPixelSize = Selectors.getWindowPixelSize(state);
const rect = container.getBoundingClientRect();
const offsetLeft = left + window.scrollX;
const offsetTop = top + window.scrollY;
const offsetLeft = rect.left + window.scrollX;
const offsetTop = rect.top + window.scrollY;
const width = container.scrollWidth;
const height = container.scrollHeight;
// A layout has been suplied. We will compute the bounding box and
// center the given layout.
const bounding = Utils.calculateBoundingBox(
windowsInfo.filter(w => getOpen(w.key))
);
if (windowsInfo.some(w => w.x == null || w.y == null)) {
// Some windows do not have an initial position, so we'll come up
// with your own layout.
const windowPositions: WindowPositions = {};
const keys: string[] = Object.keys(genWindows).filter(
windowId => genWindows[windowId].open
);
const totalHeight = keys.reduce((height, key) => {
return height + getPixelSize(key).height;
}, 0);
const globalOffsetLeft = Math.max(0, width / 2 - WINDOW_WIDTH / 2);
const globalOffsetTop = Math.max(0, height / 2 - totalHeight / 2);
let offset = 0;
keys.forEach(key => {
windowPositions[key] = {
x: Math.ceil(offsetLeft + globalOffsetLeft),
y: Math.ceil(offsetTop + (globalOffsetTop + offset))
};
offset += getPixelSize(key).height;
});
dispatch(updateWindowPositions(windowPositions, false));
} else {
// A layout has been suplied. We will compute the bounding box and
// center the given layout.
const bounding = calculateBoundingBox(
windowsInfo.filter(w => getOpen(w.key))
);
const boxHeight = bounding.bottom - bounding.top;
const boxWidth = bounding.right - bounding.left;
const boxHeight = bounding.bottom - bounding.top;
const boxWidth = bounding.right - bounding.left;
const move = {
x: Math.ceil(offsetLeft - bounding.left + (width - boxWidth) / 2),
y: Math.ceil(offsetTop - bounding.top + (height - boxHeight) / 2)
};
const move = {
x: Math.ceil(offsetLeft - bounding.left + (width - boxWidth) / 2),
y: Math.ceil(offsetTop - bounding.top + (height - boxHeight) / 2)
};
const newPositions = windowsInfo.reduce(
(pos, w) => ({
...pos,
[w.key]: { x: move.x + w.x, y: move.y + w.y }
}),
{}
);
const newPositions = windowsInfo.reduce(
(pos, w) => ({
...pos,
[w.key]: { x: move.x + w.x, y: move.y + w.y }
}),
{}
);
dispatch(updateWindowPositions(newPositions));
dispatch(updateWindowPositions(newPositions, false));
}
dispatch(windowsHaveBeenCentered());
};
}
@ -168,16 +189,8 @@ export function browserWindowSizeChanged() {
return { type: BROWSER_WINDOW_SIZE_CHANGED, height, width };
}
export function resetWindowSizes(): Dispatchable {
return { type: RESET_WINDOW_SIZES };
}
export function stackWindows(): Dispatchable {
return (dispatch, getState) => {
dispatch(
updateWindowPositions(Selectors.getStackedLayoutPositions(getState()))
);
};
export function resetWindowLayout(): Dispatchable {
return { type: RESET_WINDOW_LAYOUT };
}
export function ensureWindowsAreOnScreen(): Dispatchable {
@ -187,7 +200,7 @@ export function ensureWindowsAreOnScreen(): Dispatchable {
const windowsInfo = Selectors.getWindowsInfo(state);
const getOpen = Selectors.getWindowOpen(state);
const { height, width } = Utils.getWindowSize();
const bounding = Utils.calculateBoundingBox(
const bounding = calculateBoundingBox(
windowsInfo.filter(w => getOpen(w.key))
);
const positions = Selectors.getWindowPositions(state);
@ -227,15 +240,13 @@ export function ensureWindowsAreOnScreen(): Dispatchable {
y: position.y - moveY
}));
dispatch(updateWindowPositions(newPositions));
dispatch(updateWindowPositions(newPositions, false));
return;
}
// TODO: Try moving the individual groups to try to fit them in
// I give up. Just reset everything.
dispatch(resetWindowSizes());
dispatch(stackWindows());
dispatch(centerWindowsInView());
dispatch(resetWindowLayout());
};
}

View file

@ -72,5 +72,6 @@ export const SET_WINDOW_VISIBILITY = "SET_WINDOW_VISIBILITY";
export const LOADING = "LOADING";
export const CLOSE_REQUESTED = "CLOSE_REQUESTED";
export const LOAD_SERIALIZED_STATE = "LOAD_SERIALIZED_STATE";
export const RESET_WINDOW_SIZES = "RESET_WINDOW_SIZES";
export const WINDOWS_HAVE_BEEN_CENTERED = "WINDOWS_HAVE_BEEN_CENTERED";
export const RESET_WINDOW_LAYOUT = "RESET_WINDOW_LAYOUT";
export const BROWSER_WINDOW_SIZE_CHANGED = "BROWSER_WINDOW_SIZE_CHANGED";

View file

@ -4,7 +4,11 @@ import { connect } from "react-redux";
import { Box, Diff } from "../snapUtils";
import * as SnapUtils from "../snapUtils";
import * as Selectors from "../selectors";
import { updateWindowPositions } from "../actionCreators";
import {
updateWindowPositions,
windowsHaveBeenCentered,
centerWindowsIfNeeded
} from "../actionCreators";
import {
WindowInfo,
Dispatch,
@ -20,6 +24,7 @@ const abuts = (a: Box, b: Box) => {
};
interface Props {
centerWindowsIfNeeded(container: HTMLElement): void;
container: HTMLElement;
windowsInfo: WindowInfo[];
browserWindowSize: { height: number; width: number };
@ -29,6 +34,14 @@ interface Props {
}
class WindowManager extends React.Component<Props> {
componentDidMount() {
this.props.centerWindowsIfNeeded(this.props.container);
}
componentDidUpdate() {
this.props.centerWindowsIfNeeded(this.props.container);
}
movingAndStationaryNodes(key: WindowId): [WindowInfo[], WindowInfo[]] {
const windows = this.props.windowsInfo.filter(
w =>
@ -150,8 +163,11 @@ const mapStateToProps = (state: AppState) => ({
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
updateWindowPositions: (positions: WindowPositions) =>
dispatch(updateWindowPositions(positions))
updateWindowPositions: (positions: WindowPositions, centered: boolean) =>
dispatch(updateWindowPositions(positions, centered)),
windowsHaveBeenCentered: () => dispatch(windowsHaveBeenCentered()),
centerWindowsIfNeeded: (container: HTMLElement) =>
dispatch(centerWindowsIfNeeded(container))
};
};

View file

@ -1,4 +1,4 @@
import { Action, WindowId } from "../types";
import { WebampWindow, WindowPositions, Action, WindowId } from "../types";
import { WINDOWS } from "../constants";
import {
SET_FOCUSED_WINDOW,
@ -10,44 +10,16 @@ import {
WINDOW_SIZE_CHANGED,
TOGGLE_WINDOW_SHADE_MODE,
LOAD_SERIALIZED_STATE,
BROWSER_WINDOW_SIZE_CHANGED,
RESET_WINDOW_SIZES
RESET_WINDOW_LAYOUT,
BROWSER_WINDOW_SIZE_CHANGED
} from "../actionTypes";
import * as Utils from "../utils";
export interface WindowPosition {
x: number;
y: number;
}
export type WindowPositions = {
[windowId: string]: WindowPosition;
};
export interface WebampWindow {
title: string;
size: [number, number];
open: boolean;
hidden: boolean;
shade?: boolean;
canResize: boolean;
canShade: boolean;
canDouble: boolean;
generic: boolean;
hotkey?: string;
position: WindowPosition;
}
export interface WindowInfo {
key: WindowId;
height: number;
width: number;
x: number;
y: number;
}
export interface WindowsState {
focused: string;
centerRequested: boolean;
genWindows: { [name: string]: WebampWindow };
positions: WindowPositions;
browserWindowSize: { height: number; width: number };
}
@ -56,16 +28,18 @@ interface SerializedWindow {
open: boolean;
hidden: boolean;
shade: boolean;
position: WindowPosition;
}
export interface WindowsSerializedStateV1 {
genWindows: { [windowId: string]: SerializedWindow };
focused: string;
centerRequested: boolean;
positions: WindowPositions;
}
const defaultWindowsState: WindowsState = {
focused: WINDOWS.MAIN,
centerRequested: false,
genWindows: {
// TODO: Remove static capabilites and derive them from ids/generic
main: {
@ -78,8 +52,7 @@ const defaultWindowsState: WindowsState = {
canShade: true,
canDouble: true,
generic: false,
hotkey: "Alt+W",
position: { x: 0, y: 0 }
hotkey: "Alt+W"
},
equalizer: {
title: "Equalizer",
@ -91,8 +64,7 @@ const defaultWindowsState: WindowsState = {
canShade: true,
canDouble: true,
generic: false,
hotkey: "Alt+G",
position: { x: 0, y: 0 }
hotkey: "Alt+G"
},
playlist: {
title: "Playlist Editor",
@ -104,10 +76,10 @@ const defaultWindowsState: WindowsState = {
canShade: true,
canDouble: false,
generic: false,
hotkey: "Alt+E",
position: { x: 0, y: 0 }
hotkey: "Alt+E"
}
},
positions: {},
browserWindowSize: { width: 0, height: 0 }
};
@ -186,8 +158,7 @@ const windows = (
canShade: false,
canResize: true,
canDouble: false,
generic: true,
position: { x: 0, y: 0 }
generic: true
}
}
};
@ -211,25 +182,30 @@ const windows = (
case UPDATE_WINDOW_POSITIONS:
return {
...state,
genWindows: Utils.objectMap(state.genWindows, (w, windowId) => {
const newPosition = action.positions[windowId];
if (newPosition == null) {
return w;
}
return { ...w, position: newPosition };
})
positions: {
...state.positions,
...action.positions
},
centerRequested: action.center
};
case RESET_WINDOW_SIZES:
case RESET_WINDOW_LAYOUT:
return {
...state,
genWindows: Utils.objectMap(state.genWindows, w => ({
...w,
// Not sure why TypeScript can't figure this out for itself.
size: [0, 0] as [number, number]
}))
})),
positions: {},
centerRequested: true
};
case LOAD_SERIALIZED_STATE: {
const { genWindows, focused } = action.serializedState.windows;
const {
genWindows,
positions,
centerRequested,
focused
} = action.serializedState.windows;
return {
...state,
genWindows: Utils.objectMap(state.genWindows, (w, windowId) => {
@ -239,6 +215,15 @@ const windows = (
}
return { ...w, ...serializedW };
}),
// Note: We iterate genWindows here, since the positions object may be empty
positions: Utils.objectMap(state.genWindows, (position, windowId) => {
const serializedPosition = positions[windowId];
if (serializedPosition == null) {
return state.positions[windowId];
}
return serializedPosition;
}),
centerRequested,
focused
};
}
@ -262,11 +247,12 @@ export function getSerializedState(
size: w.size,
open: w.open,
hidden: w.hidden,
shade: w.shade || false,
position: w.position
shade: w.shade || false
};
}),
focused: state.focused
focused: state.focused,
positions: state.positions,
centerRequested: state.centerRequested
};
}

View file

@ -5,8 +5,7 @@ import {
WindowId,
WindowInfo,
LoadedURLTrack,
SerializedStateV1,
WindowPositions
SerializedStateV1
} from "./types";
import { createSelector } from "reselect";
import * as Utils from "./utils";
@ -322,6 +321,10 @@ export const getPlaylistURL = createSelector(
})
);
export function getWindowPositions(state: AppState) {
return state.windows.positions;
}
const WINDOW_HEIGHT = 116;
const SHADE_WINDOW_HEIGHT = 14;
@ -358,11 +361,6 @@ export const getGenWindows = (state: AppState) => {
return state.windows.genWindows;
};
export const getWindowPositions = createSelector(
getGenWindows,
(windows): WindowPositions => Utils.objectMap(windows, w => w.position)
);
export function getDoubled(state: AppState) {
return state.display.doubled;
}
@ -379,7 +377,6 @@ export const getWindowPixelSize = createSelector(getWindowSizes, sizes => {
return (windowId: WindowId) => sizes[windowId];
});
// TODO: Now that both size and position are stored on genWindows this seems a bit silly.
export const getWindowsInfo = createSelector(
getWindowSizes,
getWindowPositions,
@ -422,25 +419,12 @@ export function getEqualizerEnabled(state: AppState): boolean {
return state.equalizer.on;
}
export function getCenterRequested(state: AppState): boolean {
return state.windows.centerRequested;
}
export function getBrowserWindowSize(
state: AppState
): { height: number; width: number } {
return state.windows.browserWindowSize;
}
export const getOpenWindows = createSelector(getGenWindows, genWindows =>
Utils.objectFilter(genWindows, w => w.open)
);
export const getStackedLayoutPositions = createSelector(
getOpenWindows,
getDoubled,
(openWindows, doubled): WindowPositions => {
let offset = 0;
return Utils.objectMap(openWindows, w => {
const position = { x: 0, y: offset };
offset += getWPixelSize(w, doubled).height;
return position;
});
}
);

View file

@ -3,24 +3,13 @@ import { SettingsState } from "./reducers/settings";
import { UserInputState } from "./reducers/userInput";
import { MediaState, MediaSerializedStateV1 } from "./reducers/media";
import { DisplayState, DisplaySerializedStateV1 } from "./reducers/display";
import {
WindowsState,
WindowsSerializedStateV1,
WindowPositions
} from "./reducers/windows";
import { WindowsState, WindowsSerializedStateV1 } from "./reducers/windows";
import {
EqualizerState,
EqualizerSerializedStateV1
} from "./reducers/equalizer";
import { NetworkState } from "./reducers/network";
export {
WebampWindow,
WindowInfo,
WindowPosition,
WindowPositions
} from "./reducers/windows";
export type Skin = {
url: string;
name: string;
@ -67,6 +56,13 @@ type SkinData = {
skinGenLetterWidths: GenLetterWidths;
};
interface WindowPosition {
x: number;
y: number;
}
export type WindowPositions = {
[windowId: string]: WindowPosition;
};
export type Action =
| {
type: "@@init";
@ -260,6 +256,7 @@ export type Action =
| {
type: "UPDATE_WINDOW_POSITIONS";
positions: WindowPositions;
center: boolean;
}
| {
type: "CLICKED_TRACK";
@ -345,6 +342,7 @@ export type Action =
| {
type: "MINIMIZE_WINAMP";
}
| { type: "WINDOWS_HAVE_BEEN_CENTERED" }
| {
type: "CLOSE_REQUESTED";
cancel: () => void;
@ -353,9 +351,30 @@ export type Action =
type: "LOAD_SERIALIZED_STATE";
serializedState: SerializedStateV1;
}
| { type: "RESET_WINDOW_SIZES" }
| { type: "RESET_WINDOW_LAYOUT" }
| { type: "BROWSER_WINDOW_SIZE_CHANGED"; height: number; width: number };
export interface WebampWindow {
title: string;
size: [number, number];
open: boolean;
hidden: boolean;
shade?: boolean;
canResize: boolean;
canShade: boolean;
canDouble: boolean;
generic: boolean;
hotkey?: string;
}
export interface WindowInfo {
key: WindowId;
height: number;
width: number;
x: number;
y: number;
}
export type MediaTagRequestStatus =
| "INITIALIZED"
| "FAILED"

View file

@ -309,7 +309,7 @@ export function objectMap<V, N>(
export function objectFilter<V>(
obj: { [key: string]: V },
predicate: (value: V, key: string) => boolean
): { [key: string]: V } {
) {
// TODO: Could return the original reference if no values change
return Object.keys(obj).reduce((newObj: { [key: string]: V }, key) => {
if (predicate(obj[key], key)) {

View file

@ -7,8 +7,21 @@ import App from "./components/App";
import { bindHotkeys } from "./hotkeys";
import Media from "./media";
import * as Selectors from "./selectors";
import * as Actions from "./actionCreators";
import {
setSkinFromUrl,
loadMediaFiles,
setWindowSize,
play,
pause,
seekBackward,
seekForward,
next,
previous,
updateWindowPositions,
loadSerializedState,
ensureWindowsAreOnScreen,
browserWindowSizeChanged
} from "./actionCreators";
import { LOAD_STYLE } from "./constants";
import * as Utils from "./utils";
@ -117,14 +130,14 @@ class Winamp {
this.store.dispatch({ type: NETWORK_DISCONNECTED })
);
this.store.dispatch(Actions.browserWindowSizeChanged());
this.store.dispatch(browserWindowSizeChanged());
window.addEventListener("resize", () => {
this.store.dispatch(Actions.browserWindowSizeChanged());
this.store.dispatch(Actions.ensureWindowsAreOnScreen());
this.store.dispatch(ensureWindowsAreOnScreen());
this.store.dispatch(browserWindowSizeChanged());
});
if (initialSkin) {
this.store.dispatch(Actions.setSkinFromUrl(initialSkin.url));
this.store.dispatch(setSkinFromUrl(initialSkin.url));
} else {
// We are using the default skin.
this.store.dispatch({ type: LOADED });
@ -144,19 +157,14 @@ class Winamp {
}
const layout = options.__initialWindowLayout;
if (layout == null) {
this.store.dispatch(Actions.stackWindows());
} else {
if (layout != null) {
Utils.objectForEach(layout, (w, windowId) => {
if (w.size != null) {
this.store.dispatch(Actions.setWindowSize(windowId, w.size));
this.store.dispatch(setWindowSize(windowId, w.size));
}
});
this.store.dispatch(
Actions.updateWindowPositions(
Utils.objectMap(layout, w => w.position),
true
)
updateWindowPositions(Utils.objectMap(layout, w => w.position), true)
);
}
@ -166,40 +174,38 @@ class Winamp {
}
play() {
this.store.dispatch(Actions.play());
this.store.dispatch(play());
}
pause() {
this.store.dispatch(Actions.pause());
this.store.dispatch(pause());
}
seekBackward(seconds) {
this.store.dispatch(Actions.seekBackward(seconds));
this.store.dispatch(seekBackward(seconds));
}
seekForward(seconds) {
this.store.dispatch(Actions.seekForward(seconds));
this.store.dispatch(seekForward(seconds));
}
nextTrack() {
this.store.dispatch(Actions.next());
this.store.dispatch(next());
}
previousTrack() {
this.store.dispatch(Actions.previous());
this.store.dispatch(previous());
}
// Append this array of tracks to the end of the current playlist.
appendTracks(tracks) {
const nextIndex = Selectors.getTrackCount(this.store.getState());
this.store.dispatch(
Actions.loadMediaFiles(tracks, LOAD_STYLE.BUFFER, nextIndex)
);
this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.BUFFER, nextIndex));
}
// Replace any existing tracks with this array of tracks, and begin playing.
setTracksToPlay(tracks) {
this.store.dispatch(Actions.loadMediaFiles(tracks, LOAD_STYLE.PLAY));
this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.PLAY));
}
onWillClose(cb) {
@ -235,7 +241,7 @@ class Winamp {
}
__loadSerializedState(serializedState) {
this.store.dispatch(Actions.loadSerializedState(serializedState));
this.store.dispatch(loadSerializedState(serializedState));
}
__getSerializedState() {
@ -247,7 +253,6 @@ class Winamp {
}
async renderWhenReady(node) {
this.store.dispatch(Actions.centerWindowsInContainer(node));
await this.skinIsLoaded();
const genWindowComponents = {};
this.genWindows.forEach(w => {