Add initial approach of recovering from bad window positions

This commit is contained in:
Jordan Eldredge 2018-09-20 16:51:08 -07:00
parent 40e31f0577
commit 1791babf1a
10 changed files with 160 additions and 63 deletions

View file

@ -4,6 +4,9 @@
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
/* Prevent accidental highlighting */

View file

@ -5,9 +5,11 @@ import {
CLOSE_REQUESTED,
MINIMIZE_WINAMP,
SET_FOCUS,
UNSET_FOCUS
UNSET_FOCUS,
LOAD_SERIALIZED_STATE
} from "../actionTypes";
import { Dispatchable } from "../types";
import { Dispatchable, SerializedStateV1 } from "../types";
import { ensureWindowsAreOnScreen } from "./windows";
export {
toggleDoubleSizeMode,
@ -21,7 +23,8 @@ export {
updateWindowPositions,
toggleMainWindowShadeMode,
windowsHaveBeenCentered,
centerWindowsIfNeeded
centerWindowsIfNeeded,
ensureWindowsAreOnScreen
} from "./windows";
export {
play,
@ -110,3 +113,12 @@ export function setFocus(input: string): Dispatchable {
export function unsetFocus(): Dispatchable {
return { type: UNSET_FOCUS };
}
export function loadSerializedState(
serializedState: SerializedStateV1
): Dispatchable {
return dispatch => {
dispatch({ type: LOAD_SERIALIZED_STATE, serializedState });
dispatch(ensureWindowsAreOnScreen());
};
}

View file

@ -1,13 +1,6 @@
import {
getWindowGraph,
getWindowSizes,
getWindowPositions,
getWindowsInfo,
getWindowOpen,
getGenWindows
} from "../selectors";
import * as Selectors from "../selectors";
import { objectMap } from "../utils";
import * as Utils from "../utils";
import {
UPDATE_WINDOW_POSITIONS,
TOGGLE_DOUBLESIZE_MODE,
@ -16,7 +9,8 @@ import {
CLOSE_WINDOW,
TOGGLE_WINDOW_SHADE_MODE,
SET_WINDOW_VISIBILITY,
WINDOWS_HAVE_BEEN_CENTERED
WINDOWS_HAVE_BEEN_CENTERED,
RESET_WINDOW_LAYOUT
} from "../actionTypes";
import { getPositionDiff, SizeDiff } from "../resizeUtils";
@ -35,12 +29,12 @@ import { calculateBoundingBox } from "../utils";
function withWindowGraphIntegrity(action: Action): Dispatchable {
return (dispatch, getState) => {
const state = getState();
const graph = getWindowGraph(state);
const originalSizes = getWindowSizes(state);
const graph = Selectors.getWindowGraph(state);
const originalSizes = Selectors.getWindowSizes(state);
dispatch(action);
const newSizes = getWindowSizes(getState());
const newSizes = Selectors.getWindowSizes(getState());
const sizeDiff: SizeDiff = {};
for (const window of Object.keys(newSizes)) {
const original = originalSizes[window];
@ -52,9 +46,9 @@ function withWindowGraphIntegrity(action: Action): Dispatchable {
}
const positionDiff = getPositionDiff(graph, sizeDiff);
const windowPositions = getWindowPositions(state);
const windowPositions = Selectors.getWindowPositions(state);
const newPositions = objectMap(windowPositions, (position, key) =>
const newPositions = Utils.objectMap(windowPositions, (position, key) =>
applyDiff(position, positionDiff[key])
);
@ -121,6 +115,10 @@ export function windowsHaveBeenCentered(): Dispatchable {
return { type: WINDOWS_HAVE_BEEN_CENTERED };
}
export function resetWindowLayout(): Dispatchable {
return { type: RESET_WINDOW_LAYOUT };
}
export function centerWindowsIfNeeded(container: HTMLElement): Dispatchable {
return (dispatch, getState) => {
const state = getState();
@ -128,9 +126,10 @@ export function centerWindowsIfNeeded(container: HTMLElement): Dispatchable {
if (!centerRequested) {
return;
}
const genWindows = getGenWindows(state);
const windowsInfo = getWindowsInfo(state);
const getOpen = getWindowOpen(state);
const genWindows = Selectors.getGenWindows(state);
const windowsInfo = Selectors.getWindowsInfo(state);
const getOpen = Selectors.getWindowOpen(state);
const getPixelSize = Selectors.getWindowPixelSize(state);
const rect = container.getBoundingClientRect();
const offsetLeft = rect.left + window.scrollX;
@ -145,15 +144,18 @@ export function centerWindowsIfNeeded(container: HTMLElement): Dispatchable {
const keys: string[] = Object.keys(genWindows).filter(
windowId => genWindows[windowId].open
);
const totalHeight = keys.length * WINDOW_HEIGHT;
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);
keys.forEach((key, i) => {
const offset = WINDOW_HEIGHT * i;
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 {
@ -184,3 +186,61 @@ export function centerWindowsIfNeeded(container: HTMLElement): Dispatchable {
dispatch(windowsHaveBeenCentered());
};
}
export function ensureWindowsAreOnScreen(): Dispatchable {
return (dispatch, getState) => {
const state = getState();
const windowsInfo = Selectors.getWindowsInfo(state);
const getOpen = Selectors.getWindowOpen(state);
const { height, width } = Utils.getWindowSize();
const bounding = calculateBoundingBox(
windowsInfo.filter(w => getOpen(w.key))
);
const positions = Selectors.getWindowPositions(state);
// Are we good?
if (
bounding.left >= 0 &&
bounding.top >= 0 &&
bounding.right <= width &&
bounding.bottom <= height
) {
// My work here is done.
return;
}
const boundingHeight = bounding.bottom - bounding.top;
const boundingWidth = bounding.right - bounding.left;
// Could we simply shift all the windows by a constant offset?
if (boundingWidth <= width && boundingHeight <= height) {
let moveY = 0;
let moveX = 0;
if (bounding.top <= 0) {
moveY = bounding.top;
} else if (bounding.bottom > height) {
moveY = bounding.bottom - height;
}
if (bounding.left <= 0) {
moveX = bounding.left;
} else if (bounding.right > width) {
moveX = bounding.right - width;
}
const newPositions = Utils.objectMap(positions, position => ({
x: position.x - moveX,
y: position.y - moveY
}));
dispatch(updateWindowPositions(newPositions, false));
return;
}
// TODO: Try moving the individual groups to try to fit them in
// I give up. Just reset everything.
dispatch(resetWindowLayout());
};
}

View file

@ -73,3 +73,4 @@ export const LOADING = "LOADING";
export const CLOSE_REQUESTED = "CLOSE_REQUESTED";
export const LOAD_SERIALIZED_STATE = "LOAD_SERIALIZED_STATE";
export const WINDOWS_HAVE_BEEN_CENTERED = "WINDOWS_HAVE_BEEN_CENTERED";
export const RESET_WINDOW_LAYOUT = "RESET_WINDOW_LAYOUT";

View file

@ -11,7 +11,8 @@ import {
applyDiff,
applyMultipleDiffs
} from "../snapUtils";
import { getWindowsInfo, getWindowHidden, getWindowOpen } from "../selectors";
import { getWindowSize } from "../utils";
import * as Selectors from "../selectors";
import {
updateWindowPositions,
windowsHaveBeenCentered,
@ -29,6 +30,10 @@ class WindowManager extends React.Component {
this.props.centerWindowsIfNeeded(this.props.container);
}
componentDidUpdate() {
this.props.centerWindowsIfNeeded(this.props.container);
}
movingAndStationaryNodes(key) {
const windows = this.props.windowsInfo.filter(
w =>
@ -60,24 +65,7 @@ class WindowManager extends React.Component {
const mouseStart = { x: e.clientX, y: e.clientY };
// Aparently this is crazy across browsers.
const browserSize = {
width: Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.body.clientWidth,
document.documentElement.clientWidth
),
height: Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
document.body.offsetHeight,
document.documentElement.offsetHeight,
document.body.clientHeight,
document.documentElement.clientHeight
)
};
const browserSize = getWindowSize();
const box = boundingBox(moving);
@ -149,9 +137,9 @@ WindowManager.propTypes = {
};
const mapStateToProps = state => ({
windowsInfo: getWindowsInfo(state),
getWindowHidden: getWindowHidden(state),
getWindowOpen: getWindowOpen(state)
windowsInfo: Selectors.getWindowsInfo(state),
getWindowHidden: Selectors.getWindowHidden(state),
getWindowOpen: Selectors.getWindowOpen(state)
});
const mapDispatchToProps = dispatch => {

View file

@ -9,9 +9,10 @@ import {
UPDATE_WINDOW_POSITIONS,
WINDOW_SIZE_CHANGED,
TOGGLE_WINDOW_SHADE_MODE,
LOAD_SERIALIZED_STATE
LOAD_SERIALIZED_STATE,
RESET_WINDOW_LAYOUT
} from "../actionTypes";
import { objectMap } from "../utils";
import * as Utils from "../utils";
export interface WindowsState {
focused: string;
@ -184,6 +185,17 @@ const windows = (
},
centerRequested: action.center
};
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,
@ -193,7 +205,7 @@ const windows = (
} = action.serializedState.windows;
return {
...state,
genWindows: objectMap(state.genWindows, (w, windowId) => {
genWindows: Utils.objectMap(state.genWindows, (w, windowId) => {
const serializedW = genWindows[windowId];
if (serializedW == null) {
return w;
@ -201,7 +213,7 @@ const windows = (
return { ...w, ...serializedW };
}),
// Note: We iterate genWindows here, since the positions object may be empty
positions: objectMap(state.genWindows, (position, windowId) => {
positions: Utils.objectMap(state.genWindows, (position, windowId) => {
const serializedPosition = positions[windowId];
if (serializedPosition == null) {
return state.positions[windowId];
@ -222,7 +234,7 @@ export function getSerializedState(
state: WindowsState
): WindowsSerializedStateV1 {
return {
genWindows: objectMap(state.genWindows, w => {
genWindows: Utils.objectMap(state.genWindows, w => {
return {
size: w.size,
open: w.open,

View file

@ -417,13 +417,6 @@ export function getSerlializedState(state: AppState): SerializedStateV1 {
};
}
export function getVolume(state: AppState): number {
return state.media.volume;
}
export function getBalance(state: AppState): number {
return state.media.balance;
}
export function getEqualizerEnabled(state: AppState): boolean {
return state.equalizer.on;
}

View file

@ -350,7 +350,8 @@ export type Action =
| {
type: "LOAD_SERIALIZED_STATE";
serializedState: SerializedStateV1;
};
}
| { type: "RESET_WINDOW_LAYOUT" };
export interface WebampWindow {
title: string;

View file

@ -342,3 +342,25 @@ export function findLastIndex<T>(arr: T[], cb: (val: T) => boolean) {
}
return -1;
}
export function getWindowSize(): { width: number; height: number } {
// Aparently this is crazy across browsers.
return {
width: Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.body.clientWidth,
document.documentElement.clientWidth
),
height: Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
document.body.offsetHeight,
document.documentElement.offsetHeight,
document.body.clientHeight,
document.documentElement.clientHeight
)
};
}

View file

@ -17,7 +17,9 @@ import {
seekForward,
next,
previous,
updateWindowPositions
updateWindowPositions,
loadSerializedState,
ensureWindowsAreOnScreen
} from "./actionCreators";
import { LOAD_STYLE } from "./constants";
import { uniqueId, objectMap, objectForEach } from "./utils";
@ -32,8 +34,7 @@ import {
LOADED,
REGISTER_VISUALIZER,
SET_Z_INDEX,
CLOSE_REQUESTED,
LOAD_SERIALIZED_STATE
CLOSE_REQUESTED
} from "./actionTypes";
import Emitter from "./emitter";
@ -128,6 +129,10 @@ class Winamp {
this.store.dispatch({ type: NETWORK_DISCONNECTED })
);
window.addEventListener("resize", () => {
this.store.dispatch(ensureWindowsAreOnScreen());
});
if (initialSkin) {
this.store.dispatch(setSkinFromUrl(initialSkin.url));
} else {
@ -233,7 +238,7 @@ class Winamp {
}
loadSerializedState(serializedState) {
this.store.dispatch({ type: LOAD_SERIALIZED_STATE, serializedState });
this.store.dispatch(loadSerializedState(serializedState));
}
getSerializedState() {