webamp/js/components/WindowManager.js
Jordan Eldredge a053818c5e
Revert "Serialize to indexdb" (#664)
* Revert "Don't re-center windows when loading from serialized state"

This reverts commit d778c03f61.

* Revert "Clean up comment"

This reverts commit 8253093a2b.

* Revert "Fix spelling of IndexedDB"

This reverts commit b34d90e998.

* Revert "Add a few more generic assertions about serialized state"

This reverts commit a8267581d0.

* Revert "Refactor debounce and throttle"

This reverts commit 6ecc5142b5.

* Revert "Double check that the serialized state we generated equals the one we have saved"

This reverts commit 82a81bf584.

* Revert "Add many tests for serialization"

This reverts commit 55603346c2.

* Revert "Split serialized state types into their own file"

This reverts commit 4c9c5b99d3.

* Revert "Refactor how we manage window positions"

This reverts commit d6a1baece3.

* Revert "Fix export"

This reverts commit 462cf4b42d.

* Revert "Clean up selectors file"

This reverts commit cdfa99383a.

* Revert "Mark serialization methods priviate for now"

This reverts commit fc79c77451.

* Revert "Add types for WindowManager"

This reverts commit 0a7f258b64.

* Revert "Export Box"

This reverts commit e94cdef100.

* Revert "Use browserSize from state. Make it non-optional"

This reverts commit 53481ba892.

* Revert "Add back resetWindowLayout"

This reverts commit 35f4004caa.

* Revert "Begin tracking window size"

This reverts commit 96ed2b353c.

* Revert "Use namespaced Utils for webamplazy"

This reverts commit 949a2bc771.

* Revert "Use namespaced Utils"

This reverts commit 3049350701.

* Revert "Add initial approach of recovering from bad window positions"

This reverts commit 1791babf1a.

* Revert "Move centerWindowsIfNeeded to an action creatorThis forces us to type it, as a nice side benefit"

This reverts commit 40e31f0577.

* Revert "Make state serialization opt-in (for now)"

This reverts commit bef421ebed.

* Revert "Persist focus, and handle bad focus"

This reverts commit 3f1861d4f8.

* Revert "Handle the case where `positions` might be an empty object to begin with"

This reverts commit f8544ed126.

* Revert "Don't center windows when restoring from serialized state"

This reverts commit ca1cfe3dc6.

* Revert "Center windows correctly, even if the windows don't start at 0,0"

This reverts commit 777d482e73.

* Revert "Make hotkeys a function not a singleton class that has side effects when you construct it"

This reverts commit 87ca43ba45.

* Revert "Move global file input out of NPM module"

This reverts commit 9f726899c7.

* Revert "Don't exclude generic windows"

This reverts commit 245dd166a2.

* Revert "Serialize window position as well"

This reverts commit b71e09284e.

* Revert "Persist window states (exept position)"

This reverts commit 690f650e4c.

* Revert "Serialize media state, and apply equalizer state to on deserialize"

This reverts commit 94e105b104.

* Revert "Add a flag to clear IndexDB state"

This reverts commit 364ddb7411.

* Revert "Serialize state to indexdb"

This reverts commit 60429b280a.
2018-10-01 12:50:43 -07:00

220 lines
6.3 KiB
JavaScript

import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
snapDiffManyToMany,
boundingBox,
snapWithinDiff,
snap,
traceConnection,
applyDiff,
applyMultipleDiffs
} from "../snapUtils";
import { getWindowsInfo, getWindowHidden, getWindowOpen } from "../selectors";
import { updateWindowPositions } from "../actionCreators";
import { WINDOW_HEIGHT, WINDOW_WIDTH } from "../constants";
import { calculateBoundingBox } from "../utils";
const abuts = (a, b) => {
// TODO: This is kinda a hack. They should really be touching, not just within snapping distance.
// Also, overlapping should not count.
const wouldMoveTo = snap(a, b);
return wouldMoveTo.x !== undefined || wouldMoveTo.y !== undefined;
};
class WindowManager extends React.Component {
componentDidMount() {
this.centerWindows();
}
centerWindows = () => {
const { container } = this.props;
const rect = container.getBoundingClientRect();
const offsetLeft = rect.left + window.scrollX;
const offsetTop = rect.top + window.scrollY;
const width = container.scrollWidth;
const height = container.scrollHeight;
if (this.props.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 = {};
const keys = this.windowKeys();
const totalHeight = keys.length * WINDOW_HEIGHT;
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;
windowPositions[key] = {
x: Math.ceil(offsetLeft + globalOffsetLeft),
y: Math.ceil(offsetTop + (globalOffsetTop + offset))
};
});
this.props.updateWindowPositions(windowPositions);
} else {
// A layout has been suplied. We will compute the bounding box and
// center the given layout.
const bounding = calculateBoundingBox(
this.props.windowsInfo.filter(w => this.props.getWindowOpen(w.key))
);
const boxHeight = bounding.bottom - bounding.top;
const boxWidth = bounding.right - bounding.left;
const move = {
x: Math.ceil(offsetLeft + (width - boxWidth) / 2),
y: Math.ceil(offsetTop + (height - boxHeight) / 2)
};
const newPositions = this.props.windowsInfo.reduce(
(pos, w) => ({ ...pos, [w.key]: { x: move.x + w.x, y: move.y + w.y } }),
{}
);
this.props.updateWindowPositions(newPositions);
}
};
movingAndStationaryNodes(key) {
const windows = this.props.windowsInfo.filter(
w =>
this.props.windows[w.key] != null && !this.props.getWindowHidden(w.key)
);
const targetNode = windows.find(node => node.key === key);
let movingSet = new Set([targetNode]);
// Only the main window brings other windows along.
if (key === "main") {
const findAllConnected = traceConnection(abuts);
movingSet = findAllConnected(windows, targetNode);
}
const stationary = windows.filter(w => !movingSet.has(w));
const moving = Array.from(movingSet);
return [moving, stationary];
}
handleMouseDown = (key, e) => {
if (!e.target.classList.contains("draggable")) {
return;
}
// Prevent dragging from highlighting text.
e.preventDefault();
const [moving, stationary] = this.movingAndStationaryNodes(key);
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 box = boundingBox(moving);
const handleMouseMove = ee => {
const proposedDiff = {
x: ee.clientX - mouseStart.x,
y: ee.clientY - mouseStart.y
};
const proposedWindows = moving.map(node => ({
...node,
...applyDiff(node, proposedDiff)
}));
const proposedBox = {
...box,
...applyDiff(box, proposedDiff)
};
const snapDiff = snapDiffManyToMany(proposedWindows, stationary);
const withinDiff = snapWithinDiff(proposedBox, browserSize);
const finalDiff = applyMultipleDiffs(proposedDiff, snapDiff, withinDiff);
const windowPositionDiff = moving.reduce((diff, window) => {
diff[window.key] = applyDiff(window, finalDiff);
return diff;
}, {});
this.props.updateWindowPositions(windowPositionDiff);
};
const removeListeners = () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", removeListeners);
};
window.addEventListener("mouseup", removeListeners);
window.addEventListener("mousemove", handleMouseMove);
};
// Keys for the visible windows
windowKeys() {
// TODO: Iterables can probably do this better.
return Object.keys(this.props.windows).filter(
key => !!this.props.windows[key]
);
}
render() {
const style = {
position: "absolute",
top: 0,
left: 0
};
const windows = this.props.windowsInfo.filter(
w => this.props.windows[w.key]
);
return windows.map(w => (
<div
key={w.key}
onMouseDown={e => this.handleMouseDown(w.key, e)}
style={{ ...style, transform: `translate(${w.x}px, ${w.y}px)` }}
>
{this.props.windows[w.key]}
</div>
));
}
}
WindowManager.propTypes = {
windows: PropTypes.object.isRequired,
container: PropTypes.instanceOf(Element).isRequired
};
const mapStateToProps = state => ({
windowsInfo: getWindowsInfo(state),
getWindowHidden: getWindowHidden(state),
getWindowOpen: getWindowOpen(state)
});
const mapDispatchToProps = {
updateWindowPositions
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(WindowManager);