ui-layout-manager-dev 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.js +22 -0
- package/.storybook/manager.js +6 -0
- package/.storybook/preview.js +13 -0
- package/LICENSE +201 -0
- package/README.md +80 -0
- package/babel.config.js +6 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/LayoutWorker.js +494 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/index.js.map +1 -0
- package/jsconfig.json +13 -0
- package/package.json +68 -0
- package/rollup.config.mjs +49 -0
- package/src/components/LayoutManager/Components/Container/Container.jsx +136 -0
- package/src/components/LayoutManager/Components/Container/Container.scss +23 -0
- package/src/components/LayoutManager/Components/HandleBar/HandleBar.jsx +183 -0
- package/src/components/LayoutManager/Components/HandleBar/HandleBar.scss +74 -0
- package/src/components/LayoutManager/Components/LazyLoader/LazyLoader.js +33 -0
- package/src/components/LayoutManager/Components/LazyLoader/LazyLoader.scss +7 -0
- package/src/components/LayoutManager/Components/RootContainer/RootContainer.jsx +106 -0
- package/src/components/LayoutManager/Components/RootContainer/RootContainer.scss +13 -0
- package/src/components/LayoutManager/Controller/LAYOUT_WORKER_PROTOCOL.js +11 -0
- package/src/components/LayoutManager/Controller/LayoutController.js +201 -0
- package/src/components/LayoutManager/Controller/TRANSFORMATION_TYPES.js +6 -0
- package/src/components/LayoutManager/Controller/Worker/HandleRulesEnforcer.js +179 -0
- package/src/components/LayoutManager/Controller/Worker/LayoutEditor.js +183 -0
- package/src/components/LayoutManager/Controller/Worker/LayoutWorker.js +41 -0
- package/src/components/LayoutManager/Controller/Worker/ParentRuleEnforcer.js +79 -0
- package/src/components/LayoutManager/Controller/Worker/Size.js +29 -0
- package/src/components/LayoutManager/LayoutManager.jsx +31 -0
- package/src/components/LayoutManager/LayoutManager.scss +0 -0
- package/src/components/LayoutManager/Providers/ComponentRegistryContext.js +7 -0
- package/src/components/LayoutManager/Providers/LayoutProvider.js +48 -0
- package/src/components/LayoutManager/docs/ui_layout_manager_system_diagram.JPG +0 -0
- package/src/components/LayoutManager/index.js +1 -0
- package/src/index.js +1 -0
- package/src/stories/LayoutManager.stories.js +64 -0
- package/src/stories/LayoutManager.stories.scss +7 -0
- package/src/stories/layouts/vsCode/default.json +195 -0
- package/src/stories/layouts/vsCode/sample1.json +151 -0
- package/src/stories/sample_components/editor/EditorVSCode.jsx +11 -0
- package/src/stories/sample_components/editor/filetree.json +1 -0
- package/src/stories/sample_components/flow/Flow.jsx +10 -0
- package/src/stories/sample_components/flow/SampleTree.json +8 -0
- package/src/stories/sample_components/map/MapSample.jsx +43 -0
- package/src/stories/sample_components/map/MapSample.scss +3 -0
- package/src/stories/sample_components/stack/Stack.jsx +21 -0
- package/tests/LayoutEditor/LayoutEditor.test.js +14 -0
- package/tests/LayoutEditor/layouts/default.json +195 -0
- package/vitest.config.js +8 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import React, { useEffect, useState, useRef, useCallback } from "react";
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { useLayoutController } from "../../Providers/LayoutProvider";
|
|
5
|
+
|
|
6
|
+
import "./HandleBar.scss";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export const HandleBar = ({orientation, parent, sibling1, sibling2}) => {
|
|
12
|
+
|
|
13
|
+
const controller = useLayoutController();
|
|
14
|
+
const dragStartInfo = useRef(null)
|
|
15
|
+
const handleRef = useRef(null);
|
|
16
|
+
const timerRef = useRef(null);
|
|
17
|
+
|
|
18
|
+
const MIN_PANEL_SIZE = 50;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This function saves the relevant info on mouse down.
|
|
22
|
+
* It does the following:
|
|
23
|
+
* - Determines the dynamic prop being modified (width or height)
|
|
24
|
+
* - Determines the mouse down property to track (clientY or clientX)
|
|
25
|
+
* @param {MouseEvent} e
|
|
26
|
+
*/
|
|
27
|
+
const handleMouseDown = (e) => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
e.stopPropagation();
|
|
30
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
31
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
32
|
+
|
|
33
|
+
let downKey, propKey, hoverClass;
|
|
34
|
+
if (orientation === "horizontal") {
|
|
35
|
+
downKey = "clientY";
|
|
36
|
+
propKey = "height";
|
|
37
|
+
hoverClass = "handleBarHorizontalHover";
|
|
38
|
+
} else if (orientation === "vertical"){
|
|
39
|
+
downKey = "clientX";
|
|
40
|
+
propKey = "width";
|
|
41
|
+
hoverClass = "handleBarVerticalHover";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const parentRef = controller.containerRefs[parent];
|
|
45
|
+
const sibling1Ref = controller.containerRefs[sibling1];
|
|
46
|
+
const sibling2Ref = controller.containerRefs[sibling2];
|
|
47
|
+
|
|
48
|
+
// Get the min, max sizes of siblings 1 and 2
|
|
49
|
+
let sibling1LayoutConfig, sibling2LayoutConfig;
|
|
50
|
+
const parentContainer = controller.ldf.containers[parent];
|
|
51
|
+
for (let i = 0; i < parentContainer.children.length; i++) {
|
|
52
|
+
if (parentContainer.children[i].containerId === sibling1) {
|
|
53
|
+
sibling1LayoutConfig = parentContainer.children[i].size;
|
|
54
|
+
} else if (parentContainer.children[i].containerId === sibling2) {
|
|
55
|
+
sibling2LayoutConfig = parentContainer.children[i].size;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dragStartInfo.current = {
|
|
60
|
+
"downValueY": e[downKey],
|
|
61
|
+
"hoverClass": hoverClass,
|
|
62
|
+
"downKey": downKey,
|
|
63
|
+
"propKey": propKey,
|
|
64
|
+
"parentSize": parentRef.getBoundingClientRect()[propKey],
|
|
65
|
+
"parentRef": parentRef,
|
|
66
|
+
"sibling1Ref": sibling1Ref,
|
|
67
|
+
"sibling2Ref": sibling2Ref,
|
|
68
|
+
"sibling1LayoutConfig": sibling1LayoutConfig,
|
|
69
|
+
"sibling2LayoutConfig": sibling2LayoutConfig,
|
|
70
|
+
"sibling1Size": sibling1Ref.getBoundingClientRect()[propKey],
|
|
71
|
+
"sibling2Size": sibling2Ref.getBoundingClientRect()[propKey],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
handleRef.current.classList.add(hoverClass);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getRelativeMousePosition(event, parentContainer) {
|
|
78
|
+
const parentBounds = parentContainer.getBoundingClientRect();
|
|
79
|
+
const relativeX = event.clientX - parentBounds.left;
|
|
80
|
+
const relativeY = event.clientY - parentBounds.top;
|
|
81
|
+
return { x: relativeX, y: relativeY };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* This function is called when the mouse is being dragged and
|
|
86
|
+
* it uses the delta from the starting down point to calculate
|
|
87
|
+
* the new sizes (width or height).
|
|
88
|
+
* @param {Event} e
|
|
89
|
+
* @returns
|
|
90
|
+
*/
|
|
91
|
+
const handleMouseMove = (e) => {
|
|
92
|
+
if (!dragStartInfo.current) return;
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
|
|
96
|
+
const startInfo = dragStartInfo.current;
|
|
97
|
+
|
|
98
|
+
// Use delta from starting down point to calculate new heights
|
|
99
|
+
const delta = e[startInfo.downKey] - startInfo.downValueY;
|
|
100
|
+
const newSibling1Size = startInfo.sibling1Size + delta;
|
|
101
|
+
const newSibling2Size = startInfo.sibling2Size - delta;
|
|
102
|
+
|
|
103
|
+
clearTimeout(timerRef.current);
|
|
104
|
+
|
|
105
|
+
timerRef.current = setTimeout(() => {
|
|
106
|
+
// Resize here
|
|
107
|
+
controller.moveHandleBar({
|
|
108
|
+
handle: getRelativeMousePosition(e, startInfo.parentRef),
|
|
109
|
+
parent: parent,
|
|
110
|
+
sibling1: sibling1,
|
|
111
|
+
sibling2: sibling2
|
|
112
|
+
});
|
|
113
|
+
}, 0.1);
|
|
114
|
+
|
|
115
|
+
// Don't update container sizes we are past min or max values.
|
|
116
|
+
const sibling1SizeKeys = Object.keys(startInfo.sibling1LayoutConfig);
|
|
117
|
+
if (sibling1SizeKeys.includes("min") && newSibling1Size <= startInfo.sibling1LayoutConfig.min.value ||
|
|
118
|
+
sibling1SizeKeys.includes("max") && newSibling1Size >= startInfo.sibling1LayoutConfig.max.value) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Don't update container sizes we are past min or max values.
|
|
123
|
+
const sibling2SizeKeys = Object.keys(startInfo.sibling2LayoutConfig);
|
|
124
|
+
if (sibling2SizeKeys.includes("min") && newSibling2Size <= startInfo.sibling2LayoutConfig.min.value ||
|
|
125
|
+
sibling2SizeKeys.includes("max") && newSibling2Size >= startInfo.sibling2LayoutConfig.max.value) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If both siblings are type fill, then set sizes. Set min size of sibling sizes to 50px;
|
|
130
|
+
// TODO: Make into constants and I think this should be evaluated inside the controller.
|
|
131
|
+
const sibling1Type = startInfo.sibling1LayoutConfig.initial.type;
|
|
132
|
+
const sibling2Type = startInfo.sibling2LayoutConfig.initial.type;
|
|
133
|
+
if (sibling1Type === "fill" && sibling2Type === "fill" && newSibling1Size > 50 && newSibling2Size > 50) {
|
|
134
|
+
controller.containerRefs[sibling1].style[startInfo.propKey] = newSibling1Size + "px";
|
|
135
|
+
controller.containerRefs[sibling2].style[startInfo.propKey] = newSibling2Size + "px";
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Don't update fill types, flex box will take care of that
|
|
140
|
+
if (!(sibling1Type === "fill")) {
|
|
141
|
+
controller.containerRefs[sibling1].style[startInfo.propKey] = newSibling1Size + "px";
|
|
142
|
+
}
|
|
143
|
+
if (!(sibling2Type === "fill")) {
|
|
144
|
+
controller.containerRefs[sibling2].style[startInfo.propKey] = newSibling2Size + "px";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Perform cleanup after drag event has finished.
|
|
150
|
+
* @param {Event} e
|
|
151
|
+
*/
|
|
152
|
+
const handleMouseUp = (e) => {
|
|
153
|
+
e.preventDefault();
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
156
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
157
|
+
handleRef.current.classList.remove(dragStartInfo.current.hoverClass);
|
|
158
|
+
dragStartInfo.current = null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<React.Fragment >
|
|
163
|
+
{
|
|
164
|
+
orientation === "horizontal"?
|
|
165
|
+
<div onMouseDown={(e) => handleMouseDown(e)} className="handleBarHorizontalContainer">
|
|
166
|
+
<div ref={handleRef} className="handleBarHorizontal"></div>
|
|
167
|
+
</div>:
|
|
168
|
+
orientation === "vertical"?
|
|
169
|
+
<div onMouseDown={(e) => handleMouseDown(e)} className="handleBarVerticalContainer">
|
|
170
|
+
<div ref={handleRef} className="handleBarVertical"></div>
|
|
171
|
+
</div>:
|
|
172
|
+
null
|
|
173
|
+
}
|
|
174
|
+
</React.Fragment>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
HandleBar.propTypes = {
|
|
179
|
+
orientation: PropTypes.string,
|
|
180
|
+
sibling1: PropTypes.string,
|
|
181
|
+
sibling2: PropTypes.string,
|
|
182
|
+
parent: PropTypes.string
|
|
183
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
$handle-bar-color: #414141;
|
|
2
|
+
$handle-bar-size: 1px;
|
|
3
|
+
$handle-bar-size-expanded: 5px;
|
|
4
|
+
$handle-bar-hover-color: #007acc;
|
|
5
|
+
|
|
6
|
+
// Creates the container which will hold the handle bar
|
|
7
|
+
.handleBarHorizontalContainer {
|
|
8
|
+
position:relative;
|
|
9
|
+
width:100%;
|
|
10
|
+
height: $handle-bar-size;
|
|
11
|
+
background-color: $handle-bar-color;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Create handle bar and center it
|
|
15
|
+
.handleBarHorizontal{
|
|
16
|
+
position:absolute;
|
|
17
|
+
top: 50%;
|
|
18
|
+
height: $handle-bar-size;
|
|
19
|
+
left:0;
|
|
20
|
+
right:0;
|
|
21
|
+
transform: translateY(-50%);
|
|
22
|
+
transition: all 0.2s ease;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Update the height and let it animate
|
|
26
|
+
.handleBarHorizontal:hover {
|
|
27
|
+
background-color: $handle-bar-hover-color;
|
|
28
|
+
cursor: ns-resize;
|
|
29
|
+
height: $handle-bar-size-expanded;
|
|
30
|
+
z-index: 10;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Set by the component so hover is active even
|
|
34
|
+
// when mouse isn't over handle during drag
|
|
35
|
+
.handleBarHorizontalHover {
|
|
36
|
+
background-color: $handle-bar-hover-color;
|
|
37
|
+
cursor: ns-resize;
|
|
38
|
+
height: $handle-bar-size-expanded;
|
|
39
|
+
z-index: 10;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// See horizontal style comments, they are the same for vertical
|
|
44
|
+
.handleBarVerticalContainer {
|
|
45
|
+
position:relative;
|
|
46
|
+
height:100%;
|
|
47
|
+
width: $handle-bar-size;
|
|
48
|
+
background-color: $handle-bar-color;
|
|
49
|
+
z-index: 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.handleBarVertical{
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 0;
|
|
55
|
+
width: $handle-bar-size;
|
|
56
|
+
left: 50%;
|
|
57
|
+
bottom: 0;
|
|
58
|
+
transform: translateX(-50%);
|
|
59
|
+
transition: all 0.3s ease;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.handleBarVertical:hover {
|
|
63
|
+
background-color: $handle-bar-hover-color;
|
|
64
|
+
cursor: ew-resize;
|
|
65
|
+
width: $handle-bar-size-expanded;
|
|
66
|
+
z-index: 10;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.handleBarVerticalHover {
|
|
70
|
+
background-color: $handle-bar-hover-color;
|
|
71
|
+
cursor: ew-resize;
|
|
72
|
+
width: $handle-bar-size-expanded;
|
|
73
|
+
z-index: 10;
|
|
74
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { lazy, useMemo, Suspense, useContext } from "react";
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import ComponentRegistryContext from "../../Providers/ComponentRegistryContext";
|
|
4
|
+
|
|
5
|
+
import "./LazyLoader.scss"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* LazyLoader component that renders a component
|
|
9
|
+
* specified in the ldf file.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} content
|
|
12
|
+
*/
|
|
13
|
+
export const LazyLoader = ({content}) => {
|
|
14
|
+
const registry = useContext(ComponentRegistryContext);
|
|
15
|
+
|
|
16
|
+
const LazyComponent = useMemo(() => {
|
|
17
|
+
if (registry && content && "component" in content && content["component"] in registry) {
|
|
18
|
+
return lazy(registry[content["component"]]);
|
|
19
|
+
}
|
|
20
|
+
}, [registry, content]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="lazyContainer">
|
|
24
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
25
|
+
{LazyComponent && <LazyComponent />}
|
|
26
|
+
</Suspense>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
LazyLoader.propTypes = {
|
|
32
|
+
content: PropTypes.object,
|
|
33
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, { useEffect, useLayoutEffect, useState, useRef, useCallback, useContext } from "react";
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Container } from "../Container/Container";
|
|
4
|
+
import { useLayoutController } from "../../Providers/LayoutProvider";
|
|
5
|
+
|
|
6
|
+
import "./RootContainer.scss"
|
|
7
|
+
/**
|
|
8
|
+
* Root node for the layout tree. This component will start
|
|
9
|
+
* rendering the tree and it will also watch for changes in the
|
|
10
|
+
* root container sizes to process layout changes.
|
|
11
|
+
*
|
|
12
|
+
* @return {React.ReactElement}
|
|
13
|
+
*/
|
|
14
|
+
export const RootContainer = () => {
|
|
15
|
+
const controller = useLayoutController();
|
|
16
|
+
|
|
17
|
+
const rootRef = useRef(null);
|
|
18
|
+
const timerRef = useRef(null);
|
|
19
|
+
const resizingRef = useRef(false);
|
|
20
|
+
|
|
21
|
+
// Create the container API that will be used by the controller.
|
|
22
|
+
const rootContainerAPI = useRef({});
|
|
23
|
+
rootContainerAPI.current = {};
|
|
24
|
+
|
|
25
|
+
const [childElements, setChildElements] = useState(null);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Renders child containers recursively.
|
|
29
|
+
*/
|
|
30
|
+
const processContainer = useCallback((node) => {
|
|
31
|
+
const childElements = [];
|
|
32
|
+
for (let index = 0; index < node.children.length; index++) {
|
|
33
|
+
const childNode = node.children[index];
|
|
34
|
+
|
|
35
|
+
if (childNode.type === "container") {
|
|
36
|
+
const child = controller.ldf.containers[node.children[index].containerId];
|
|
37
|
+
child.parent = node;
|
|
38
|
+
childElements.push(
|
|
39
|
+
<Container key={index} meta={node.children[index]} id={child.id} node={child}/>
|
|
40
|
+
);
|
|
41
|
+
} else if (childNode.type === "handleBar") {
|
|
42
|
+
if (node.orientation === "horizontal") {
|
|
43
|
+
childElements.push(
|
|
44
|
+
<div className="verticalLine"></div>
|
|
45
|
+
);
|
|
46
|
+
} else if (node.orientation === "vertical") {
|
|
47
|
+
childElements.push(
|
|
48
|
+
<div className="horizontalLine"></div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return childElements;
|
|
54
|
+
},[controller]);
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
useLayoutEffect(() => {
|
|
58
|
+
if (controller) {
|
|
59
|
+
const rootNode = controller.ldf.containers[controller.ldf.layoutRoot];
|
|
60
|
+
const hasChildren = rootNode.children && rootNode.children.length > 0
|
|
61
|
+
controller.registerContainer(rootNode.id, rootContainerAPI, rootRef.current);
|
|
62
|
+
|
|
63
|
+
if (hasChildren) {
|
|
64
|
+
if (rootNode.orientation === "horizontal") {
|
|
65
|
+
rootRef.current.style.flexDirection = "row";
|
|
66
|
+
} else if (rootNode.orientation === "vertical") {
|
|
67
|
+
rootRef.current.style.flexDirection = "column";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setChildElements(hasChildren?processContainer(rootNode):null);
|
|
72
|
+
|
|
73
|
+
// Create resize observer to monitor changes in the root container size.
|
|
74
|
+
const observer = new ResizeObserver((entries) => {
|
|
75
|
+
|
|
76
|
+
if (!resizingRef.current) resizingRef.current = true;
|
|
77
|
+
|
|
78
|
+
for (let entry of entries) {
|
|
79
|
+
const { width, height } = entry.contentRect;
|
|
80
|
+
|
|
81
|
+
clearTimeout(timerRef.current);
|
|
82
|
+
|
|
83
|
+
timerRef.current = setTimeout(() => {
|
|
84
|
+
resizingRef.current = false;
|
|
85
|
+
controller.handleRootResize(width, height);
|
|
86
|
+
}, 1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
observer.observe(rootRef.current);
|
|
91
|
+
|
|
92
|
+
return () => {
|
|
93
|
+
controller.unregisterContainer(controller.ldf.layoutRoot);
|
|
94
|
+
observer.disconnect();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}, [controller]);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className="root-container">
|
|
101
|
+
<div ref={rootRef} className="relative-container">
|
|
102
|
+
{childElements}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
let LAYOUT_WORKER_PROTOCOL = {
|
|
2
|
+
INITIALIZE: 1,
|
|
3
|
+
INITIALIZE_FLEXBOX: 2,
|
|
4
|
+
APPLY_SIZES: 3,
|
|
5
|
+
ERROR: 4,
|
|
6
|
+
TRANSFORMATIONS: 5,
|
|
7
|
+
MOVE_HANDLE_BAR: 6
|
|
8
|
+
};
|
|
9
|
+
LAYOUT_WORKER_PROTOCOL = Object.freeze(LAYOUT_WORKER_PROTOCOL);
|
|
10
|
+
|
|
11
|
+
export default LAYOUT_WORKER_PROTOCOL;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import LAYOUT_WORKER_PROTOCOL from "./LAYOUT_WORKER_PROTOCOL";
|
|
2
|
+
import TRANSFORMATION_TYPES from "./TRANSFORMATION_TYPES";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This controller is responsible for managing the layout of the application.
|
|
6
|
+
* - It will handle the registration and unregistration of containers.
|
|
7
|
+
* - It will handle the layout changes and notify the worker to process the layout changes.
|
|
8
|
+
* - It will update the container sizes with the updated values calculated by the worker.
|
|
9
|
+
*
|
|
10
|
+
* @class LayoutController
|
|
11
|
+
*/
|
|
12
|
+
export class LayoutController {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Constructor
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} ldf - Layout Definition JSON object
|
|
18
|
+
*/
|
|
19
|
+
constructor(ldf) {
|
|
20
|
+
this.containers = {};
|
|
21
|
+
this.containerRefs = {};
|
|
22
|
+
this.ldf = ldf;
|
|
23
|
+
this.numberOfContainers = 0;
|
|
24
|
+
this.registeredContainers = 0;
|
|
25
|
+
this.layoutLoaded = false;
|
|
26
|
+
|
|
27
|
+
this.numberOfContainers = this.ldf.containers ? Object.keys(this.ldf.containers).length: 0;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
this.worker = new Worker(
|
|
31
|
+
new URL('./Worker/LayoutWorker.js', import.meta.url),
|
|
32
|
+
{ type: 'module' }
|
|
33
|
+
);
|
|
34
|
+
this.worker.onmessage = this.handleWorkerMessage.bind(this);
|
|
35
|
+
this.worker.onerror = (error) => console.error('Worker error:', error);
|
|
36
|
+
this.sendToWorker(LAYOUT_WORKER_PROTOCOL.INITIALIZE, {ldf: ldf})
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Failed to create worker:', error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sends message to worker with the provided arguments.
|
|
45
|
+
* @param {Number} code
|
|
46
|
+
* @param {Object} args
|
|
47
|
+
*/
|
|
48
|
+
sendToWorker(code, args) {
|
|
49
|
+
this.worker.postMessage({
|
|
50
|
+
code:code,
|
|
51
|
+
args: args
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Allows containers to register themselves with the controller.
|
|
57
|
+
* @param {String} id
|
|
58
|
+
* @param {Object} containerApi
|
|
59
|
+
* @param {HTMLElement} containerRef
|
|
60
|
+
*/
|
|
61
|
+
registerContainer(id, containerApi, containerRef) {
|
|
62
|
+
if (!(id in this.containers)) {
|
|
63
|
+
this.registeredContainers += 1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.containers[id] = containerApi;
|
|
67
|
+
this.containerRefs[id] = containerRef;
|
|
68
|
+
|
|
69
|
+
console.log(`Registered container with id: ${id} `);
|
|
70
|
+
|
|
71
|
+
if (this.registeredContainers === this.numberOfContainers && !this.layoutLoaded) {
|
|
72
|
+
console.log("All containers registered, layout is ready.");
|
|
73
|
+
this.sendToWorker(LAYOUT_WORKER_PROTOCOL.INITIALIZE_FLEXBOX);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Allows containers to unregister themselves with the controller.
|
|
79
|
+
* @param {String} id
|
|
80
|
+
*/
|
|
81
|
+
unregisterContainer(id) {
|
|
82
|
+
delete this.containers[id];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* This function is called when the root container is resized.
|
|
87
|
+
* It will notify the worker to process the layout changes.
|
|
88
|
+
*/
|
|
89
|
+
handleRootResize() {
|
|
90
|
+
if (!this.layoutLoaded) return;
|
|
91
|
+
// console.log("Root container resized to:", width, height);
|
|
92
|
+
const sizes = {};
|
|
93
|
+
for (const id in this.containerRefs) {
|
|
94
|
+
if (this.containerRefs.hasOwnProperty(id)) {
|
|
95
|
+
const boundingRect = this.containerRefs[id].getBoundingClientRect();
|
|
96
|
+
sizes[id] = {
|
|
97
|
+
width: boundingRect.width,
|
|
98
|
+
height: boundingRect.height
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
this.sendToWorker(
|
|
103
|
+
LAYOUT_WORKER_PROTOCOL.APPLY_SIZES,
|
|
104
|
+
{ sizes: sizes }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Move handle bar is called by the handle bar component with the
|
|
111
|
+
* metadata about its parent and the siblings being resized. This
|
|
112
|
+
* information is parsed and passed to the layout editor to enforce
|
|
113
|
+
* the layout rules.
|
|
114
|
+
* @param {Object} metadata
|
|
115
|
+
*/
|
|
116
|
+
moveHandleBar(metadata) {
|
|
117
|
+
let sizes = {};
|
|
118
|
+
const containerIds = [metadata.parent, metadata.sibling1, metadata.sibling2];
|
|
119
|
+
for (const containerId of containerIds) {
|
|
120
|
+
let boundingRect = this.containerRefs[containerId].getBoundingClientRect();
|
|
121
|
+
sizes[containerId] = {
|
|
122
|
+
width: boundingRect.width,
|
|
123
|
+
height: boundingRect.height
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
metadata.sizes = sizes;
|
|
128
|
+
this.sendToWorker(
|
|
129
|
+
LAYOUT_WORKER_PROTOCOL.MOVE_HANDLE_BAR,
|
|
130
|
+
{
|
|
131
|
+
metadata: metadata
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// TODO: This is temporary, after handle bar move, the layout rules are
|
|
136
|
+
// applied to react to new container sizes. This can be done more efficiently
|
|
137
|
+
// because we only need to react the containers that were changed. This calculates
|
|
138
|
+
// the entire layout.
|
|
139
|
+
this.handleRootResize();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Apply the given transformations
|
|
144
|
+
* @param {Object} transformations
|
|
145
|
+
* @param {Object} isInitial
|
|
146
|
+
*/
|
|
147
|
+
applyTransformations (transformations, isInitial) {
|
|
148
|
+
requestAnimationFrame(() => {
|
|
149
|
+
for (const transformation of transformations) {
|
|
150
|
+
switch (transformation.type) {
|
|
151
|
+
case TRANSFORMATION_TYPES.UPDATE_SIZE:
|
|
152
|
+
this.containers[transformation.id].current.updateStyles(
|
|
153
|
+
transformation.args.style
|
|
154
|
+
);
|
|
155
|
+
break;
|
|
156
|
+
case TRANSFORMATION_TYPES.REMOVE_NODE:
|
|
157
|
+
break;
|
|
158
|
+
default:
|
|
159
|
+
console.warn("Unknown transformation was requested.");
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
if (isInitial) {
|
|
164
|
+
this.layoutLoaded = true;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Handles messages from worker
|
|
171
|
+
* @param {Object} event
|
|
172
|
+
*/
|
|
173
|
+
handleWorkerMessage(event) {
|
|
174
|
+
let transformations;
|
|
175
|
+
switch(event.data.type) {
|
|
176
|
+
case LAYOUT_WORKER_PROTOCOL.INITIALIZE_FLEXBOX:
|
|
177
|
+
transformations = event.data.data;
|
|
178
|
+
this.applyTransformations(transformations, true);
|
|
179
|
+
break;
|
|
180
|
+
case LAYOUT_WORKER_PROTOCOL.TRANSFORMATIONS:
|
|
181
|
+
transformations = event.data.data;
|
|
182
|
+
this.applyTransformations(transformations, false);
|
|
183
|
+
break;
|
|
184
|
+
case LAYOUT_WORKER_PROTOCOL.ERROR:
|
|
185
|
+
console.error("Error from worker:", event.data);
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Performs cleanup when the controller is destroyed.
|
|
194
|
+
*/
|
|
195
|
+
destroy() {
|
|
196
|
+
if (this.worker) {
|
|
197
|
+
this.worker.terminate();
|
|
198
|
+
this.worker = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|