'use strict'; const React = require("react"); const createReactClass = require("create-react-class"); const throttleit = require("throttleit"); const euclideanDistance = require("euclidean-distance"); const Window = require("./window"); module.exports = createReactClass({ displayName: "WindowManager", getDefaultProps: function () { return { windowMoveThreshold: 6 }; }, getInitialState: function () { return { movingWindow: null, activeWindow: null, windowPositions: {} }; }, lastMousePositionX: null, lastMousePositionY: null, componentDidMount: function () { /* Due to how synthetic events work in React, we need to separate out the 'get coordinates from event' and 'do something with the coordinates' step; if we throttle the coordinate extraction logic, we'll run into problems when synthetic events have already been cleared for reuse by the time we try to obtain the coordinates. Therefore, `processMouseMove` contains all the throttled logic, whereas the coordinate extraction happens on *every* mousemove event. */ this.processMouseMove = throttleit(() => { /* Yes, the below check is there for a reason; just in case the `movingWindow` state changes between the call to the throttled wrapper and the wrapped function itself. */ if (this.state.movingWindow != null) { let thresholdMet = this.state.movingWindowThresholdMet; /* Since we're outside of a React-controlled handler, we can't rely on React to batch updates. Therefore, we have to do our own impromptu batching. */ let stateToUpdate = {}; if (thresholdMet === false) { let origin = [this.state.movingWindowOriginX, this.state.movingWindowOriginY]; let position = [this.lastMousePositionX, this.lastMousePositionY]; if (euclideanDistance(origin, position) > this.props.windowMoveThreshold) { thresholdMet = true; stateToUpdate.thresholdMet = true; } } if (thresholdMet === true) { /* TODO: Consider handling this move out-of-band, to avoid going through React's rendering cycle for every move event. */ stateToUpdate.windowPositions = Object.assign(this.state.windowPositions, { [this.state.movingWindow]: { x: this.lastMousePositionX - this.state.movingWindowBarX, y: this.lastMousePositionY - this.state.movingWindowBarY } }); } /* FIXME: Move this into the store? */ this.setState(stateToUpdate); } }, 10); }, handleMouseMove: function(event) { if (this.state.movingWindow != null) { this.lastMousePositionX = event.pageX; this.lastMousePositionY = event.pageY; this.processMouseMove(); } }, handleMouseUp: function () { this.setState({ movingWindow: null }); }, handleTitleMouseDown: function (window, event, barX, barY) { this.setState({ movingWindow: window.id, movingWindowThresholdMet: false, movingWindowBarX: barX, movingWindowBarY: barY, movingWindowOriginX: event.pageX, movingWindowOriginY: event.pageY }); }, render: function () { /* TODO: Tiled layout */ return (