diff --git a/public/css/components.css b/public/css/components.css
index 8460782..9bacb0d 100644
--- a/public/css/components.css
+++ b/public/css/components.css
@@ -11,27 +11,36 @@
grid-template-rows: auto 1fr; }
.windowManager .windowSpace {
position: relative; }
- .windowManager .sidebar {
+ .windowManager .fixedAreas {
position: absolute;
top: 0px;
bottom: 0px;
- min-width: 80px;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.3);
- border-color: #121b26;
- border-width: 1px; }
- .windowManager .sidebar.side-left {
- left: 0px;
- border-right-style: solid; }
- .windowManager .sidebar.side-right {
- right: 0px;
- border-left-style: solid; }
- .windowManager .sidebar.highlight {
- background-color: rgba(9, 14, 54, 0.3);
- border-color: #0d4c99; }
+ left: 0px;
+ right: 0px;
+ display: flex; }
+ .windowManager .fixedAreas .sidebar {
+ min-width: 80px;
+ overflow-y: auto;
+ background-color: rgba(0, 0, 0, 0.5);
+ border-color: #121b26;
+ border-width: 1px; }
+ .windowManager .fixedAreas .sidebar.side-left {
+ left: 0px;
+ border-right-style: solid; }
+ .windowManager .fixedAreas .sidebar.side-right {
+ right: 0px;
+ border-left-style: solid;
+ margin-left: auto; }
+ .windowManager .fixedAreas .sidebar.highlight {
+ background-color: rgba(9, 14, 54, 0.5);
+ border-color: #0d4c99; }
+ .windowManager .fixedAreas .sidebar .window {
+ position: static;
+ /* FIXME: Below 19px should become 9px, once there's a way to account for the scrollbar that may be visible. */
+ margin: 0px 19px 12px 9px; }
.window {
- position: absolute;
+ position: fixed;
left: 0px;
top: 0px;
display: grid;
diff --git a/src/client/components/window-manager/floating-window-area.jsx b/src/client/components/window-manager/floating-window-area.jsx
new file mode 100644
index 0000000..9e3bc7e
--- /dev/null
+++ b/src/client/components/window-manager/floating-window-area.jsx
@@ -0,0 +1,37 @@
+"use strict";
+
+const React = require("react");
+
+const Window = require("../window");
+
+module.exports = function FloatingWindowArea({windows, onStartMove, onStartCornerResize, onClose, onFocus, onWindowRef}) {
+ return (
+ windows.map((window_) => {
+ let windowStyle = {
+ transform: `translate(${window_.x}px, ${window_.y}px)`,
+ width: window_.width,
+ height: window_.height,
+ zIndex: window_.zIndex
+ };
+
+ let handlers = {
+ onTitleMouseDown: (event) => {
+ onStartMove(window_, event);
+ },
+ onResizerMouseDown: (event) => {
+ onStartCornerResize(window_, event);
+ },
+ onMouseDown: () => {
+ onFocus(window_);
+ },
+ onClose: () => {
+ onClose(window_);
+ }
+ };
+
+ return (
+
+ );
+ })
+ );
+};
diff --git a/src/client/components/window-manager.jsx b/src/client/components/window-manager/index.jsx
similarity index 55%
rename from src/client/components/window-manager.jsx
rename to src/client/components/window-manager/index.jsx
index 84ec498..12d4635 100644
--- a/src/client/components/window-manager.jsx
+++ b/src/client/components/window-manager/index.jsx
@@ -1,42 +1,33 @@
'use strict';
const React = require("react");
-const classnames = require("classnames");
-
const defaultValue = require("default-value");
-const Window = require("./window");
-const { useDraggable, mouseMove } = require("../hooks/draggable");
-
-let windowRefs = new Map();
+const Sidebar = require("./sidebar");
+const FloatingWindowArea = require("./floating-window-area");
+const { useDraggable, mouseMove } = require("../../hooks/draggable");
-function trackRef(window_) {
- return function setRef(element) {
- /* FIXME: Verify that this doesn't leak memory! */
- if (element != null) {
- windowRefs.set(window_, element);
- } else {
- windowRefs.delete(window_);
- }
- };
-}
+/* FIXME: While the WindowManager area is used as a 'valid to drag' area, such that an embedded window manager in a specific section of the application is possible, all the actual coordination calculation code currently assumes that the WindowManager area starts at (0,0). That needs to eventually be fixed. */
-function SideBar({elementRef, side, onMouseOver, onMouseOut, highlight}) {
- function onMouseOverHandler(event) {
- onMouseOver(event, side);
- }
+let windowRefs = new Map();
- function onMouseOutHandler(event) {
- onMouseOut(event, side);
+function trackRef(window_, element) {
+ /* FIXME: Verify that this doesn't leak memory! */
+ if (element != null) {
+ windowRefs.set(window_, element);
+ } else {
+ windowRefs.delete(window_);
}
-
- return (
-
@@ -65,6 +56,7 @@ module.exports = function WindowManager({store, children, windowMoveThreshold})
let [rightBarRef, setRightBarRef] = React.useState(null);
let [leftBarBounds, setLeftBarBounds] = React.useState(null);
let [rightBarBounds, setRightBarBounds] = React.useState(null);
+ let [barUpdateTrigger, setBarUpdateTrigger] = React.useState(0);
let [newX, newY] = useDraggable({
isDragging: isResizing || isMoving,
@@ -111,12 +103,12 @@ module.exports = function WindowManager({store, children, windowMoveThreshold})
setRightBarBounds(null);
}
- }, [leftBarRef, rightBarRef]);
+ }, [leftBarRef, rightBarRef, barUpdateTrigger]);
React.useEffect(() => {
if (draggedWindow != null) {
if (lastDragType === "move") {
- store.setPosition(draggedWindow, newX, newY);
+ store.setUserPosition(draggedWindow, newX, newY);
} else if (lastDragType === "resize") {
store.setDimensions(draggedWindow, newX, newY);
}
@@ -125,6 +117,13 @@ module.exports = function WindowManager({store, children, windowMoveThreshold})
}
}, [newX, newY]);
+ function recalculateWindow(windowId) {
+ let element = windowRefs.get(windowId);
+
+ let currentPosition = element.getBoundingClientRect();
+ store.setCurrentPosition(windowId, currentPosition.left, currentPosition.top);
+ }
+
function isInArea(x, y, bounds) {
return (x >= bounds.left && x < bounds.right && y >= bounds.top && y < bounds.bottom);
}
@@ -150,8 +149,10 @@ module.exports = function WindowManager({store, children, windowMoveThreshold})
});
if (type === "move") {
+ store.markMoving(window_.id, true);
setIsMoving(true);
} else if (type === "resize") {
+ store.markResizing(window_.id, true);
setIsResizing(true);
}
@@ -161,18 +162,30 @@ module.exports = function WindowManager({store, children, windowMoveThreshold})
mouseMove(event.pageX, event.pageY);
}
- function handleTitleMouseDown (window_, event) {
- return handleDragStart("move", window_, event, window_.x, window_.y);
- }
-
- function handleResizerMouseDown(window_, event) {
- return handleDragStart("resize", window_, event, window_.width, window_.height);
- }
-
function handleMouseUp () {
if (draggedWindow != null) {
+ let window_ = store.get(draggedWindow);
+
+ store.markMoving(window_.id, false);
+ store.markResizing(window_.id, false);
setIsMoving(false);
setIsResizing(false);
+
+ if (highlightBar != null) {
+ if (window_.sidebar !== highlightBar) {
+ store.moveToSidebar(window_.id, highlightBar);
+ setBarUpdateTrigger(Math.random());
+ } else {
+ windowRefs.get(window_.id).style.transform = null;
+ }
+ } else {
+ if (window_.sidebar != null) {
+ store.makeFloating(window_.id);
+ setBarUpdateTrigger(Math.random());
+ }
+ }
+
+ setHighlightBar(null);
}
}
@@ -190,6 +203,34 @@ module.exports = function WindowManager({store, children, windowMoveThreshold})
setHighlightBar(null);
}
+ let windowHandlers = {
+ onClose: function (window_) {
+ store.close(window_.id);
+ },
+ onFocus: function (window_) {
+ store.focus(window_.id);
+ },
+ onStartMove: function (window_, event) {
+ // /* NOTE: We use the current actual on-screen position of the window's DOM element here instead of the position in the store, because when a window is docked in the sidebar, the store-provided position is incorrect. */
+ // let currentPosition = event.target.getBoundingClientRect();
+ // return handleDragStart("move", window_, event, currentPosition.left, currentPosition.top);
+ /* FIXME: Immediatelly call onChange from the draggable hook, to prevent a jump when originating from a sidebar dock? */
+ return handleDragStart("move", window_, event, window_.x, window_.y);
+ },
+ onStartCornerResize: function (window_, event) {
+ return handleDragStart("resize", window_, event, window_.width, window_.height);
+ },
+ onStartBottomResize: function (window_, _event) {
+ // TODO
+ },
+ onRecalculateWindowPosition: function (windowId) {
+ recalculateWindow(windowId);
+ },
+ onWindowRef: function (windowId, ref) {
+ trackRef(windowId, ref);
+ }
+ };
+
/* TODO: Tiled layout */
return (
@@ -197,37 +238,11 @@ module.exports = function WindowManager({store, children, windowMoveThreshold})
{children}
-
- {store.getFloating().toArray().map((window_) => {
- let windowStyle = {
- transform: `translate(${window_.x}px, ${window_.y}px)`,
- width: window_.width,
- height: window_.height,
- zIndex: window_.zIndex
- };
-
- let handlers = {
- onTitleMouseDown: (...args) => {
- return handleTitleMouseDown(window_, ...args);
- },
- onResizerMouseDown: (...args) => {
- return handleResizerMouseDown(window_, ...args);
- },
- onMouseDown: () => {
- store.focus(window_.id);
- },
- onClose: () => {
- store.close(window_.id);
- }
- };
-
- return (
-
- {window_.contents}
-
- );
- })}
-
+
+
+
+
+
);
diff --git a/src/client/components/window-manager/sidebar.jsx b/src/client/components/window-manager/sidebar.jsx
new file mode 100644
index 0000000..e058dee
--- /dev/null
+++ b/src/client/components/window-manager/sidebar.jsx
@@ -0,0 +1,74 @@
+"use strict";
+
+const React = require("react");
+const throttleit = require("throttleit");
+const classnames = require("classnames");
+
+const Window = require("../window");
+
+module.exports = function Sidebar({elementRef, side, windows, onMouseOver, onMouseOut, highlight, onStartMove, onStartBottomResize, onClose, onFocus, onRecalculateWindowPosition, onWindowRef}) {
+ let [onScroll, setOnScroll] = React.useState(null);
+
+ React.useEffect(() => {
+ onScroll = throttleit(() => {
+ windows.forEach((window_) => {
+ onRecalculateWindowPosition(window_.id);
+ });
+ }, 100);
+
+ /* NOTE: Lazy initializer */
+ setOnScroll(() => {
+ return onScroll;
+ });
+ }, [windows]);
+
+ function onMouseOverHandler(event) {
+ onMouseOver(event, side);
+ }
+
+ function onMouseOutHandler(event) {
+ onMouseOut(event, side);
+ }
+
+ function trackRef(windowId) {
+ return function (ref) {
+ onWindowRef(windowId, ref);
+
+ if (ref != null) {
+ onRecalculateWindowPosition(windowId);
+ }
+ };
+ }
+
+ return (
+
+ {windows.map((window_) => {
+ let windowStyle = {
+ width: window_.width,
+ height: window_.height,
+ /* The below is a bit of a hack to ensure that a being-dragged window always displays with coordinates absolute to the page, not relative to the sidebar. */
+ position: (window_.isMoving) ? "fixed" : "static"
+ };
+
+ let handlers = {
+ onTitleMouseDown: (event) => {
+ onStartMove(window_, event);
+ },
+ onResizerMouseDown: (event) => {
+ onStartBottomResize(window_, event);
+ },
+ onMouseDown: () => {
+ onFocus(window_);
+ },
+ onClose: () => {
+ onClose(window_);
+ }
+ };
+
+ return (
+
+ );
+ })}
+
+ );
+};
diff --git a/src/client/components/window.jsx b/src/client/components/window.jsx
index 38bf44c..cb15eb1 100644
--- a/src/client/components/window.jsx
+++ b/src/client/components/window.jsx
@@ -77,17 +77,17 @@ function Resizer({onMouseDown}) {
);
}
-module.exports = function Window(props) {
+module.exports = function Window({elementRef, style, window: window_, onMouseDown, onTitleMouseDown, onClose, onResizerMouseDown}) {
return (
-
-
+
+
- {props.children}
+ {window_.contents}
- {renderIf(props.resizable,
)}
+ {renderIf(window_.resizable,
)}
);
diff --git a/src/client/hooks/draggable.js b/src/client/hooks/draggable.js
index b05efc5..22cec54 100644
--- a/src/client/hooks/draggable.js
+++ b/src/client/hooks/draggable.js
@@ -56,12 +56,13 @@ module.exports = {
}
},
useDraggable: function (options) {
- let dragEnded;
+ let dragEnded = false;
+ let dragStarted = false;
if (isDragging === true && options.isDragging === false) {
dragEnded = true;
- } else {
- dragEnded = false;
+ } else if (isDragging === false && options.isDragging === true) {
+ dragStarted = true;
}
operationData = options.operationData;
@@ -69,6 +70,10 @@ module.exports = {
onChangeCallback = options.onChange;
isDragging = options.isDragging;
+ if (dragStarted) {
+ processMouseMove();
+ }
+
if (dragEnded) {
return [newX, newY];
} else {
diff --git a/src/client/scss/components.scss b/src/client/scss/components.scss
index 0ef8d64..e95b448 100644
--- a/src/client/scss/components.scss
+++ b/src/client/scss/components.scss
@@ -23,36 +23,54 @@
position: relative;
}
- .sidebar {
+ .fixedAreas {
position: absolute;
top: 0px;
bottom: 0px;
- min-width: 80px;
- height: 100%;
+ left: 0px;
+ right: 0px;
+ display: flex;
+
+ .sidebar {
+ // position: absolute;
+ // top: 0px;
+ // bottom: 0px;
+ min-width: 80px;
+ // height: 100%;
+ // overflow-x: hidden;
+ overflow-y: auto;
- background-color: rgba(0, 0, 0, 0.3);
- border-color: rgb(18, 27, 38);
- border-width: 1px;
+ background-color: rgba(0, 0, 0, 0.5);
+ border-color: rgb(18, 27, 38);
+ border-width: 1px;
- &.side-left {
- left: 0px;
- border-right-style: solid;
- }
+ &.side-left {
+ left: 0px;
+ border-right-style: solid;
+ }
- &.side-right {
- right: 0px;
- border-left-style: solid;
- }
+ &.side-right {
+ right: 0px;
+ border-left-style: solid;
+ margin-left: auto;
+ }
- &.highlight {
- background-color: rgba(9, 14, 54, 0.3);
- border-color: rgb(13, 76, 153);
+ &.highlight {
+ background-color: rgba(9, 14, 54, 0.5);
+ border-color: rgb(13, 76, 153);
+ }
+
+ .window {
+ position: static;
+ /* FIXME: Below 19px should become 9px, once there's a way to account for the scrollbar that may be visible. */
+ margin: 0px 19px 12px 9px;
+ }
}
}
}
.window {
- position: absolute;
+ position: fixed;
left: 0px;
top: 0px;
diff --git a/src/client/stores/windows.js b/src/client/stores/windows.js
index ce02ea8..9098208 100644
--- a/src/client/stores/windows.js
+++ b/src/client/stores/windows.js
@@ -6,27 +6,42 @@ const immutable = require("immutable");
module.exports = function createWindowStore({onUpdated}) {
let windows = immutable.OrderedMap([]);
- let floatingWindows = immutable.OrderedMap([]);
- let leftSidebarWindows = immutable.OrderedMap([]);
- let rightSidebarWindows = immutable.OrderedMap([]);
+ let floatingWindows = [];
+ let leftSidebarWindows = [];
+ let rightSidebarWindows = [];
let activeWindow;
let lastZIndex = 0;
function updateWindowIndexes() {
- floatingWindows = windows.filter((window_) => window_.sidebar == null);
- leftSidebarWindows = windows.filter((window_) => window_.sidebar === "left");
- rightSidebarWindows = windows.filter((window_) => window_.sidebar === "right");
+ floatingWindows = windows.filter((window_) => window_.sidebar == null).toArray();
+ leftSidebarWindows = windows.filter((window_) => window_.sidebar === "left").toArray();
+ rightSidebarWindows = windows.filter((window_) => window_.sidebar === "right").toArray();
}
function setSide(id, side) {
+ setValues(id, {
+ sidebar: side
+ }, () => {
+ updateWindowIndexes();
+ });
+ }
+
+ function setValues(id, values, postProcessing) {
let targetWindow = windows.get(id);
- targetWindow.sidebar = side;
+ Object.assign(targetWindow, values);
+
+ if (postProcessing != null) {
+ postProcessing(targetWindow);
+ }
- updateWindowIndexes();
onUpdated();
}
+ function setValuesWithoutUpdate(id, values) {
+ Object.assign( windows.get(id), values);
+ }
+
return {
add: function (window_) {
let id = nanoid();
@@ -35,6 +50,8 @@ module.exports = function createWindowStore({onUpdated}) {
id: id,
x: window_.initialX,
y: window_.initialY,
+ userX: 0,
+ userY: 0,
width: window_.initialWidth,
height: window_.initialHeight
}));
@@ -59,21 +76,35 @@ module.exports = function createWindowStore({onUpdated}) {
onUpdated();
},
- setPosition: function (id, x, y) {
- let targetWindow = windows.get(id);
-
- targetWindow.x = x;
- targetWindow.y = y;
-
- onUpdated();
+ setUserPosition: function (id, x, y) {
+ setValues(id, {
+ userX: x,
+ userY: y,
+ x: x,
+ y: y
+ });
+ },
+ setCurrentPosition: function (id, x, y) {
+ setValuesWithoutUpdate(id, {
+ x: x,
+ y: y
+ });
},
setDimensions: function (id, width, height) {
- let targetWindow = windows.get(id);
-
- targetWindow.width = width;
- targetWindow.height = height;
-
- onUpdated();
+ setValues(id, {
+ width: width,
+ height: height
+ });
+ },
+ markMoving: function (id, isMoving) {
+ setValues(id, {
+ isMoving: isMoving
+ });
+ },
+ markResizing: function (id, isResizing) {
+ setValues(id, {
+ isResizing: isResizing
+ });
},
moveToSidebar: function (id, side) {
setSide(id, side);
@@ -86,6 +117,9 @@ module.exports = function createWindowStore({onUpdated}) {
updateWindowIndexes();
onUpdated();
},
+ get: function (id) {
+ return windows.get(id);
+ },
getAll: function () {
return windows;
},