diff --git a/css/webamp.css b/css/webamp.css index 280a4d73..4b15138c 100644 --- a/css/webamp.css +++ b/css/webamp.css @@ -4,6 +4,9 @@ position: absolute; top: 0; left: 0; + right: 0; + bottom: 0; + overflow: hidden; } /* Prevent accidental highlighting */ diff --git a/js/actionCreators/index.ts b/js/actionCreators/index.ts index f51b5308..b7d41512 100644 --- a/js/actionCreators/index.ts +++ b/js/actionCreators/index.ts @@ -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()); + }; +} diff --git a/js/actionCreators/windows.ts b/js/actionCreators/windows.ts index 2e555052..9d5e44a7 100644 --- a/js/actionCreators/windows.ts +++ b/js/actionCreators/windows.ts @@ -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()); + }; +} diff --git a/js/actionTypes.ts b/js/actionTypes.ts index d6eb9f4d..5eeb3a01 100644 --- a/js/actionTypes.ts +++ b/js/actionTypes.ts @@ -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"; diff --git a/js/components/WindowManager.js b/js/components/WindowManager.js index d9c96a0e..b1fffb13 100644 --- a/js/components/WindowManager.js +++ b/js/components/WindowManager.js @@ -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 => { diff --git a/js/reducers/windows.ts b/js/reducers/windows.ts index bc16ae83..2bba0ad7 100644 --- a/js/reducers/windows.ts +++ b/js/reducers/windows.ts @@ -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, diff --git a/js/selectors.ts b/js/selectors.ts index 122202fb..90eacb82 100644 --- a/js/selectors.ts +++ b/js/selectors.ts @@ -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; } diff --git a/js/types.ts b/js/types.ts index cf8668a0..6a19b738 100644 --- a/js/types.ts +++ b/js/types.ts @@ -350,7 +350,8 @@ export type Action = | { type: "LOAD_SERIALIZED_STATE"; serializedState: SerializedStateV1; - }; + } + | { type: "RESET_WINDOW_LAYOUT" }; export interface WebampWindow { title: string; diff --git a/js/utils.ts b/js/utils.ts index 2fa24fbd..33d8002c 100644 --- a/js/utils.ts +++ b/js/utils.ts @@ -342,3 +342,25 @@ export function findLastIndex(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 + ) + }; +} diff --git a/js/webampLazy.js b/js/webampLazy.js index 4b6b06d6..37fef3da 100644 --- a/js/webampLazy.js +++ b/js/webampLazy.js @@ -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() {