ui-layout-manager-dev 0.0.19 → 0.0.20
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/dist/cjs/index.js +3 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/Worker/LayoutWorker.js +38 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/LayoutManager/Components/Container/Container.jsx +1 -1
- package/src/components/LayoutManager/Components/HandleBar/HandleBar.jsx +6 -16
- package/src/components/LayoutManager/Components/LazyLoader/LazyLoader.js +74 -9
- package/src/components/LayoutManager/Components/LazyLoader/LazyLoader.scss +38 -1
- package/src/components/LayoutManager/Components/LazyLoader/MenuBar/MenuBar.js +40 -0
- package/src/components/LayoutManager/Components/LazyLoader/MenuBar/MenuBar.scss +22 -0
- package/src/components/LayoutManager/Components/LazyLoader/Tabs/Tabs.js +51 -0
- package/src/components/LayoutManager/Components/LazyLoader/Tabs/Tabs.scss +34 -0
- package/src/components/LayoutManager/Components/RootContainer/DragController.js +79 -0
- package/src/components/LayoutManager/Components/RootContainer/RootContainer.jsx +21 -21
- package/src/components/LayoutManager/Components/RootContainer/RootContainer.scss +1 -0
- package/src/components/LayoutManager/Controller/LAYOUT_WORKER_PROTOCOL.js +2 -1
- package/src/components/LayoutManager/Controller/LayoutController.js +14 -0
- package/src/components/LayoutManager/Controller/LayoutEventController.js +55 -0
- package/src/components/LayoutManager/Controller/Worker/HandleRulesEnforcer.js +4 -0
- package/src/components/LayoutManager/Controller/Worker/LayoutEditor.js +29 -0
- package/src/components/LayoutManager/Controller/Worker/LayoutWorker.js +3 -0
- package/src/components/LayoutManager/LayoutManager.jsx +5 -5
- package/src/components/LayoutManager/Providers/LayoutEventProvider.js +55 -0
- package/src/components/LayoutManager/index.js +1 -2
- package/src/stories/LayoutManager.stories.js +17 -0
- package/src/stories/layouts/vsCode/workbench.json +22 -5
- package/src/stories/layouts/vsCode/workbench2.json +176 -0
- package/src/stories/layouts/vsCode/workbench3.json +176 -0
- package/src/stories/sample_components/fileeditor/FileEditor.jsx +45 -21
- package/src/stories/sample_components/fileeditor/helper.js +22 -0
- package/src/stories/sample_components/filetree/FileTree.jsx +11 -5
- package/src/components/LayoutManager/Providers/DragProvider.js +0 -87
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui-layout-manager-dev",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "A react component to manage layout and themes in single page applications.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"storybook": "^8.6.8",
|
|
57
57
|
"style-loader": "^4.0.0",
|
|
58
58
|
"vitest": "^4.0.18",
|
|
59
|
-
"sample-ui-component-library": "^0.0.
|
|
59
|
+
"sample-ui-component-library": "^0.0.25-dev",
|
|
60
|
+
"leaflet": "^1.9.4"
|
|
60
61
|
},
|
|
61
62
|
"peerDependencies": {
|
|
62
63
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -64,6 +65,5 @@
|
|
|
64
65
|
"react-dom": "^18.2.0"
|
|
65
66
|
},
|
|
66
67
|
"dependencies": {
|
|
67
|
-
"leaflet": "^1.9.4"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -115,7 +115,7 @@ export const Container = ({node}) => {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
setChildElements(hasChildren?processContainer(node):<LazyLoader
|
|
118
|
+
setChildElements(hasChildren?processContainer(node):<LazyLoader node={node} />);
|
|
119
119
|
|
|
120
120
|
controller.registerContainer(node.id, containerAPI, containerRef.current);
|
|
121
121
|
return () => {
|
|
@@ -129,24 +129,14 @@ export const HandleBar = ({orientation, parent, sibling1, sibling2}) => {
|
|
|
129
129
|
sibling2SizeKeys.includes("max") && newSibling2Size >= startInfo.sibling2LayoutConfig.max.value) {
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
|
-
|
|
133
|
-
// If both siblings are type fill, then set sizes. Set min size of sibling sizes to 50px;
|
|
134
|
-
// TODO: Make into constants and I think this should be evaluated inside the controller.
|
|
135
|
-
const sibling1Type = startInfo.sibling1LayoutConfig.initial.type;
|
|
136
|
-
const sibling2Type = startInfo.sibling2LayoutConfig.initial.type;
|
|
137
|
-
if (sibling1Type === "fill" && sibling2Type === "fill" && newSibling1Size > 50 && newSibling2Size > 50) {
|
|
138
|
-
controller.containerRefs[sibling1].style[startInfo.propKey] = newSibling1Size + "px";
|
|
139
|
-
controller.containerRefs[sibling2].style[startInfo.propKey] = newSibling2Size + "px";
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
132
|
|
|
143
|
-
//
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
if (!(sibling2Type === "fill")) {
|
|
148
|
-
controller.containerRefs[sibling2].style[startInfo.propKey] = newSibling2Size + "px";
|
|
133
|
+
// If either of the containers are less than 50px, then don't update size.
|
|
134
|
+
if (newSibling1Size < MIN_PANEL_SIZE || newSibling2Size < MIN_PANEL_SIZE) {
|
|
135
|
+
return;
|
|
149
136
|
}
|
|
137
|
+
|
|
138
|
+
controller.containerRefs[sibling1].style[startInfo.propKey] = newSibling1Size + "px";
|
|
139
|
+
controller.containerRefs[sibling2].style[startInfo.propKey] = newSibling2Size + "px";
|
|
150
140
|
}
|
|
151
141
|
|
|
152
142
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React, { lazy, useMemo, Suspense, useContext } from "react";
|
|
1
|
+
import React, { lazy, useMemo, Suspense, useContext, useEffect, useState } from "react";
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import ComponentRegistryContext from "../../Providers/ComponentRegistryContext";
|
|
4
|
+
import { MenuBar } from "./MenuBar/MenuBar";
|
|
5
|
+
import { Tabs } from "./Tabs/Tabs";
|
|
4
6
|
|
|
5
7
|
import "./LazyLoader.scss"
|
|
6
8
|
|
|
@@ -10,20 +12,83 @@ import "./LazyLoader.scss"
|
|
|
10
12
|
*
|
|
11
13
|
* @param {Object} content
|
|
12
14
|
*/
|
|
13
|
-
export const LazyLoader = ({
|
|
15
|
+
export const LazyLoader = ({node}) => {
|
|
14
16
|
const registry = useContext(ComponentRegistryContext);
|
|
17
|
+
const [showTitle, setShowTitle] = useState(false);
|
|
18
|
+
const [showTab, setShowTab] = useState(false);
|
|
15
19
|
|
|
20
|
+
const [lazyContainerTop, setLazyContainerTop] = useState(0);
|
|
21
|
+
const [tabsTop, setTabsTop] = useState(0);
|
|
22
|
+
const [selectedComponent, setSelectedComponent] = useState("");
|
|
23
|
+
|
|
24
|
+
// Lazy load the component when the selected component changes.
|
|
16
25
|
const LazyComponent = useMemo(() => {
|
|
17
|
-
if (registry &&
|
|
18
|
-
return lazy(registry[
|
|
26
|
+
if (registry && selectedComponent in registry) {
|
|
27
|
+
return lazy(registry[selectedComponent]);
|
|
28
|
+
}
|
|
29
|
+
}, [registry, selectedComponent]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Note: I am setting the top of the absolute position
|
|
33
|
+
* of the containers based on if the title and tabs are
|
|
34
|
+
* shown. This is a temporary implementation, I will be
|
|
35
|
+
* revisiting this and formally implementing it after.
|
|
36
|
+
*/
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
let _lazyContainerTop = 0;
|
|
39
|
+
let _tabsTop = 0;
|
|
40
|
+
if ("menuBar" in node) {
|
|
41
|
+
setShowTitle(true);
|
|
42
|
+
_tabsTop += 35;
|
|
43
|
+
_lazyContainerTop += 35;
|
|
19
44
|
}
|
|
20
|
-
|
|
45
|
+
if ("tabsBar" in node) {
|
|
46
|
+
setShowTab(true);
|
|
47
|
+
_lazyContainerTop += 35;
|
|
48
|
+
selectTab(node.tabsBar.tabs[0]);
|
|
49
|
+
} else {
|
|
50
|
+
setSelectedComponent(node.component);
|
|
51
|
+
}
|
|
52
|
+
setLazyContainerTop(_lazyContainerTop)
|
|
53
|
+
setTabsTop(_tabsTop);
|
|
54
|
+
}, [node]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Selects the provided tab.
|
|
58
|
+
* @param {Object} selectedTab
|
|
59
|
+
*/
|
|
60
|
+
const selectTab = (selectedTab) => {
|
|
61
|
+
node.tabsBar.tabs.forEach((tab) => {
|
|
62
|
+
if (tab === selectedTab) {
|
|
63
|
+
tab.selected = true;
|
|
64
|
+
setSelectedComponent(tab.component);
|
|
65
|
+
} else {
|
|
66
|
+
tab.selected = false;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
21
70
|
|
|
22
71
|
return (
|
|
23
|
-
<div className="
|
|
24
|
-
<
|
|
25
|
-
{
|
|
26
|
-
|
|
72
|
+
<div className="absoluteContainer">
|
|
73
|
+
<div className="contentContainer">
|
|
74
|
+
{
|
|
75
|
+
showTitle &&
|
|
76
|
+
<div className="menuContainer">
|
|
77
|
+
<MenuBar node={node}/>
|
|
78
|
+
</div>
|
|
79
|
+
}
|
|
80
|
+
{
|
|
81
|
+
showTab &&
|
|
82
|
+
<div className="tabsContainer" style={{ top: `${tabsTop}px`}}>
|
|
83
|
+
<Tabs node={node} onTabClick={selectTab}/>
|
|
84
|
+
</div>
|
|
85
|
+
}
|
|
86
|
+
<div className="lazycontainer" style={{ top: `${lazyContainerTop}px`}}>
|
|
87
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
88
|
+
{LazyComponent && <LazyComponent />}
|
|
89
|
+
</Suspense>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
27
92
|
</div>
|
|
28
93
|
);
|
|
29
94
|
}
|
|
@@ -1,4 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
TODO: I agree that it is convoluted to use absolute, then relative and then absolute.
|
|
3
|
+
However, the reason I am doing it is, it makes the layout of the monaco editor trivial
|
|
4
|
+
and avoids resize observer loops when the container sizes change. I haven't thought
|
|
5
|
+
about this enough and I will revisit it later.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.absoluteContainer {
|
|
9
|
+
position: absolute;
|
|
10
|
+
top: 0px;
|
|
11
|
+
bottom: 0px;
|
|
12
|
+
left: 0px;
|
|
13
|
+
right: 0px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.contentContainer {
|
|
17
|
+
position: relative;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.menuContainer {
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 0px;
|
|
25
|
+
height: 35px;
|
|
26
|
+
left: 0px;
|
|
27
|
+
right: 0px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tabsContainer {
|
|
31
|
+
position: absolute;
|
|
32
|
+
top: 0px;
|
|
33
|
+
height: 35px;
|
|
34
|
+
left: 0px;
|
|
35
|
+
right: 0px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.lazycontainer {
|
|
2
39
|
position: absolute;
|
|
3
40
|
top: 0px;
|
|
4
41
|
bottom: 0px;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
|
|
3
|
+
import "./MenuBar.scss";
|
|
4
|
+
|
|
5
|
+
import { XLg } from "react-bootstrap-icons";
|
|
6
|
+
|
|
7
|
+
import { useLayoutController } from "../../../Providers/LayoutProvider";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MenuBar component for the containers.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} node
|
|
13
|
+
*/
|
|
14
|
+
export const MenuBar = ({ node }) => {
|
|
15
|
+
const controller = useLayoutController();
|
|
16
|
+
|
|
17
|
+
const closeContainer = () => {
|
|
18
|
+
controller.invokeAction({
|
|
19
|
+
id: node?.menuBar?.closeContainerId,
|
|
20
|
+
action: "close",
|
|
21
|
+
args: {},
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="titleContainer">
|
|
27
|
+
<div className="title">{node?.menuBar?.title}</div>
|
|
28
|
+
{
|
|
29
|
+
node?.menuBar?.showClose &&
|
|
30
|
+
<div onClick={closeContainer} className="close">
|
|
31
|
+
<XLg />
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
MenuBar.propTypes = {
|
|
39
|
+
node : PropTypes.object,
|
|
40
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.titleContainer {
|
|
2
|
+
position:absolute;
|
|
3
|
+
width:100%;
|
|
4
|
+
top:0;
|
|
5
|
+
bottom:1px;
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
color:#bbbbbb;
|
|
9
|
+
justify-content: space-between;
|
|
10
|
+
background: #222425;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.titleContainer > .title {
|
|
14
|
+
padding-left: 30px;
|
|
15
|
+
font-size: 11px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.titleContainer > .close {
|
|
19
|
+
padding-right: 15px;
|
|
20
|
+
font-size: 11px;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
|
|
3
|
+
import {XLg} from "react-bootstrap-icons";
|
|
4
|
+
import "./Tabs.scss"
|
|
5
|
+
|
|
6
|
+
import { useLayoutController } from "../../../Providers/LayoutProvider";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tabs component for the containers.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} node
|
|
12
|
+
*/
|
|
13
|
+
export const Tabs = ({node, onTabClick}) => {
|
|
14
|
+
const controller = useLayoutController();
|
|
15
|
+
|
|
16
|
+
const closeContainer = () => {
|
|
17
|
+
controller.invokeAction({
|
|
18
|
+
id: node?.tabsBar?.closeContainerId,
|
|
19
|
+
action: "close",
|
|
20
|
+
args: {},
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="container-tabs-row">
|
|
26
|
+
<div className="container-tabs">
|
|
27
|
+
{node?.tabsBar?.tabs?.map((tab, index) => (
|
|
28
|
+
<div
|
|
29
|
+
key= {tab.name + String(index)}
|
|
30
|
+
style={{ borderBottom: tab.selected ? "solid 1px white" : "none" }}
|
|
31
|
+
onClick={(e) => onTabClick(tab)}
|
|
32
|
+
className="container-tab">
|
|
33
|
+
{tab.name}
|
|
34
|
+
</div>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
node?.tabsBar?.showClose &&
|
|
40
|
+
<div className="container-close">
|
|
41
|
+
<XLg onClick={closeContainer}/>
|
|
42
|
+
</div>
|
|
43
|
+
}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Tabs.propTypes = {
|
|
49
|
+
node: PropTypes.array,
|
|
50
|
+
onTabsClick: PropTypes.func
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.container-tabs-row{
|
|
2
|
+
position:relative;
|
|
3
|
+
width:100%;
|
|
4
|
+
height:100%;
|
|
5
|
+
background: #222425;
|
|
6
|
+
display:flex;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container-tabs-row > .container-tabs {
|
|
11
|
+
display:flex;
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
gap: 5px;
|
|
14
|
+
margin-left:20px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.container-tabs-row > .container-tabs > .container-tab {
|
|
18
|
+
display:flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
padding: 0 10px;
|
|
22
|
+
height: 28px;
|
|
23
|
+
font-size: 13px;
|
|
24
|
+
color:#bbbbbb;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.container-tabs-row > .container-close {
|
|
28
|
+
display:flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
color:white;
|
|
32
|
+
font-size: 11px;
|
|
33
|
+
margin-right:10px;
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import { useLayoutEventPublisher } from "../../Providers/LayoutEventProvider";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Drag controller exposed through the hook. It exposes
|
|
7
|
+
* callback functions which are connected to the dnd context.
|
|
8
|
+
*
|
|
9
|
+
* The controller is also initialized with the publish function
|
|
10
|
+
* exposed by the useLayoutEventPublisher so it can notify
|
|
11
|
+
* subscribers of any relevant events.
|
|
12
|
+
*/
|
|
13
|
+
class DragController {
|
|
14
|
+
constructor({publish}) {
|
|
15
|
+
this.publish = publish;
|
|
16
|
+
this.resetDragState();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
resetDragState = () => {
|
|
20
|
+
this.dragState = {
|
|
21
|
+
activeId: null,
|
|
22
|
+
activeData: null,
|
|
23
|
+
overId: null,
|
|
24
|
+
overData: null,
|
|
25
|
+
isDragging: false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onDragStart = (event) => {
|
|
30
|
+
this.dragState = {
|
|
31
|
+
activeId: event.active?.id ?? null,
|
|
32
|
+
activeData: event.active?.data?.current ?? null,
|
|
33
|
+
overId: null,
|
|
34
|
+
overData: null,
|
|
35
|
+
isDragging: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onDragOver = (event) => {
|
|
40
|
+
this.dragState.overId = event.over?.id ?? null;
|
|
41
|
+
this.dragState.overData = event.over?.data?.current ?? null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onDragEnd = (event) => {
|
|
45
|
+
this.publish({
|
|
46
|
+
type: "drag:drop",
|
|
47
|
+
payload: {
|
|
48
|
+
activeId: event.active?.id ?? null,
|
|
49
|
+
activeData: event.active?.data?.current ?? null,
|
|
50
|
+
overId: event.over?.id ?? null,
|
|
51
|
+
overData: event.over?.data?.current ?? null,
|
|
52
|
+
},
|
|
53
|
+
source: "drag-provider",
|
|
54
|
+
})
|
|
55
|
+
this.resetDragState();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onDragCancel = (event) => {
|
|
59
|
+
this.resetDragState();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
isDragging = () => {
|
|
63
|
+
return this.dragState.isDragging;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getDragPreview = () => {
|
|
67
|
+
return this.dragState?.activeData?.preview;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const useDragEventController = function() {
|
|
72
|
+
const publish = useLayoutEventPublisher();
|
|
73
|
+
|
|
74
|
+
return useMemo(() => {
|
|
75
|
+
return new DragController({publish});
|
|
76
|
+
}, [publish]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default useDragEventController;
|
|
@@ -5,13 +5,13 @@ import { HandleBar } from "../HandleBar/HandleBar";
|
|
|
5
5
|
import { useLayoutController } from "../../Providers/LayoutProvider";
|
|
6
6
|
import {
|
|
7
7
|
DndContext,
|
|
8
|
+
closestCenter,
|
|
8
9
|
PointerSensor,
|
|
9
10
|
useSensor,
|
|
10
11
|
useSensors
|
|
11
12
|
} from "@dnd-kit/core";
|
|
12
13
|
|
|
13
|
-
import
|
|
14
|
-
|
|
14
|
+
import useDragEventController from "./DragController";
|
|
15
15
|
import "./RootContainer.scss"
|
|
16
16
|
|
|
17
17
|
|
|
@@ -23,9 +23,8 @@ import "./RootContainer.scss"
|
|
|
23
23
|
* @return {React.ReactElement}
|
|
24
24
|
*/
|
|
25
25
|
export const RootContainer = () => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const { dragState, handleDragStart, handleDragOver, clearDrag } = useDragState();
|
|
26
|
+
const layoutController = useLayoutController();
|
|
27
|
+
const dragController = useDragEventController();
|
|
29
28
|
|
|
30
29
|
const rootRef = useRef(null);
|
|
31
30
|
const timerRef = useRef(null);
|
|
@@ -46,7 +45,7 @@ export const RootContainer = () => {
|
|
|
46
45
|
const childNode = node.children[index];
|
|
47
46
|
|
|
48
47
|
if (childNode.type === "container") {
|
|
49
|
-
const child =
|
|
48
|
+
const child = layoutController.ldf.containers[node.children[index].containerId];
|
|
50
49
|
child.parent = node;
|
|
51
50
|
childElements.push(
|
|
52
51
|
<Container key={index} meta={node.children[index]} id={child.id} node={child} />
|
|
@@ -76,14 +75,14 @@ export const RootContainer = () => {
|
|
|
76
75
|
}
|
|
77
76
|
};
|
|
78
77
|
return childElements;
|
|
79
|
-
}, [
|
|
78
|
+
}, [layoutController]);
|
|
80
79
|
|
|
81
80
|
|
|
82
81
|
useLayoutEffect(() => {
|
|
83
|
-
if (
|
|
84
|
-
const rootNode =
|
|
82
|
+
if (layoutController) {
|
|
83
|
+
const rootNode = layoutController.ldf.containers[layoutController.ldf.layoutRoot];
|
|
85
84
|
const hasChildren = rootNode.children && rootNode.children.length > 0
|
|
86
|
-
|
|
85
|
+
layoutController.registerContainer(rootNode.id, rootContainerAPI, rootRef.current);
|
|
87
86
|
|
|
88
87
|
if (hasChildren) {
|
|
89
88
|
if (rootNode.orientation === "horizontal") {
|
|
@@ -107,7 +106,7 @@ export const RootContainer = () => {
|
|
|
107
106
|
|
|
108
107
|
timerRef.current = setTimeout(() => {
|
|
109
108
|
resizingRef.current = false;
|
|
110
|
-
|
|
109
|
+
layoutController.handleRootResize(width, height);
|
|
111
110
|
}, 1);
|
|
112
111
|
}
|
|
113
112
|
});
|
|
@@ -115,11 +114,11 @@ export const RootContainer = () => {
|
|
|
115
114
|
observer.observe(rootRef.current);
|
|
116
115
|
|
|
117
116
|
return () => {
|
|
118
|
-
|
|
117
|
+
layoutController.unregisterContainer(layoutController.ldf.layoutRoot);
|
|
119
118
|
observer.disconnect();
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
|
-
}, [
|
|
121
|
+
}, [layoutController]);
|
|
123
122
|
|
|
124
123
|
const sensors = useSensors(
|
|
125
124
|
useSensor(PointerSensor, {
|
|
@@ -132,20 +131,21 @@ export const RootContainer = () => {
|
|
|
132
131
|
// Manually track the drag position for smooth overlay
|
|
133
132
|
const [dragPos, setDragPos] = useState({ left: 0, top: 0 });
|
|
134
133
|
useEffect(() => {
|
|
135
|
-
if (!
|
|
134
|
+
if (!dragController.isDragging()) return;
|
|
136
135
|
const handleMove = (e) => {setDragPos({ left: e.clientX, top: e.clientY })};
|
|
137
136
|
window.addEventListener("pointermove", handleMove);
|
|
138
137
|
return () => {
|
|
139
138
|
window.removeEventListener("pointermove", handleMove);
|
|
140
139
|
};
|
|
141
|
-
}, [
|
|
140
|
+
}, [dragController.isDragging()]);
|
|
142
141
|
|
|
143
142
|
return (
|
|
144
143
|
<DndContext sensors={sensors}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
collisionDetection={closestCenter}
|
|
145
|
+
onDragStart={dragController.onDragStart}
|
|
146
|
+
onDragOver={dragController.onDragOver}
|
|
147
|
+
onDragEnd={dragController.onDragEnd}
|
|
148
|
+
onDragCancel={dragController.onDragCancel}>
|
|
149
149
|
|
|
150
150
|
<div className="root-container">
|
|
151
151
|
<div ref={rootRef} className="relative-container">
|
|
@@ -153,9 +153,9 @@ export const RootContainer = () => {
|
|
|
153
153
|
</div>
|
|
154
154
|
</div>
|
|
155
155
|
|
|
156
|
-
{
|
|
156
|
+
{dragController.isDragging() && (
|
|
157
157
|
<div className="drag-overlay" style={dragPos}>
|
|
158
|
-
{
|
|
158
|
+
{dragController.getDragPreview()}
|
|
159
159
|
</div>
|
|
160
160
|
)}
|
|
161
161
|
</DndContext>
|
|
@@ -198,6 +198,20 @@ export class LayoutController {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Invoke a specific action on a container given the id.
|
|
203
|
+
*/
|
|
204
|
+
invokeAction({id, action, args}) {
|
|
205
|
+
this.sendToWorker(
|
|
206
|
+
LAYOUT_WORKER_PROTOCOL.INVOKE_ACTION,
|
|
207
|
+
{
|
|
208
|
+
id: id,
|
|
209
|
+
action: action,
|
|
210
|
+
args: args
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
201
215
|
/**
|
|
202
216
|
* Performs cleanup when the controller is destroyed.
|
|
203
217
|
*/
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Event Controller used to subscribe to events
|
|
3
|
+
* and publish events.
|
|
4
|
+
*/
|
|
5
|
+
class LayoutEventController {
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.subscribers = new Map();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to event type and provide handler
|
|
13
|
+
* @param {String} type
|
|
14
|
+
* @param {Function} handler
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
subscribe(type, handler) {
|
|
18
|
+
// Create set for type if it doesn't exist
|
|
19
|
+
if (!this.subscribers.has(type)) {
|
|
20
|
+
this.subscribers.set(type, new Set());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Add handler to type set
|
|
24
|
+
const handlers = this.subscribers.get(type);
|
|
25
|
+
handlers.add(handler);
|
|
26
|
+
|
|
27
|
+
// Return unsubscribe function
|
|
28
|
+
const unsubscribe = () => {
|
|
29
|
+
handlers.delete(handler);
|
|
30
|
+
if (handlers.size === 0) {
|
|
31
|
+
this.subscribers.delete(type);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return unsubscribe;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Publishes the event to the subscribers.
|
|
40
|
+
* @param {Object} event
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
publish(event) {
|
|
44
|
+
const handlers = this.subscribers.get(event.type);
|
|
45
|
+
if (!handlers) return;
|
|
46
|
+
|
|
47
|
+
for (const handler of handlers) {
|
|
48
|
+
handler(event);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default LayoutEventController;
|
|
55
|
+
|