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.
Files changed (51) hide show
  1. package/.storybook/main.js +22 -0
  2. package/.storybook/manager.js +6 -0
  3. package/.storybook/preview.js +13 -0
  4. package/LICENSE +201 -0
  5. package/README.md +80 -0
  6. package/babel.config.js +6 -0
  7. package/dist/cjs/index.js +15 -0
  8. package/dist/cjs/index.js.map +1 -0
  9. package/dist/esm/LayoutWorker.js +494 -0
  10. package/dist/esm/index.js +15 -0
  11. package/dist/esm/index.js.map +1 -0
  12. package/jsconfig.json +13 -0
  13. package/package.json +68 -0
  14. package/rollup.config.mjs +49 -0
  15. package/src/components/LayoutManager/Components/Container/Container.jsx +136 -0
  16. package/src/components/LayoutManager/Components/Container/Container.scss +23 -0
  17. package/src/components/LayoutManager/Components/HandleBar/HandleBar.jsx +183 -0
  18. package/src/components/LayoutManager/Components/HandleBar/HandleBar.scss +74 -0
  19. package/src/components/LayoutManager/Components/LazyLoader/LazyLoader.js +33 -0
  20. package/src/components/LayoutManager/Components/LazyLoader/LazyLoader.scss +7 -0
  21. package/src/components/LayoutManager/Components/RootContainer/RootContainer.jsx +106 -0
  22. package/src/components/LayoutManager/Components/RootContainer/RootContainer.scss +13 -0
  23. package/src/components/LayoutManager/Controller/LAYOUT_WORKER_PROTOCOL.js +11 -0
  24. package/src/components/LayoutManager/Controller/LayoutController.js +201 -0
  25. package/src/components/LayoutManager/Controller/TRANSFORMATION_TYPES.js +6 -0
  26. package/src/components/LayoutManager/Controller/Worker/HandleRulesEnforcer.js +179 -0
  27. package/src/components/LayoutManager/Controller/Worker/LayoutEditor.js +183 -0
  28. package/src/components/LayoutManager/Controller/Worker/LayoutWorker.js +41 -0
  29. package/src/components/LayoutManager/Controller/Worker/ParentRuleEnforcer.js +79 -0
  30. package/src/components/LayoutManager/Controller/Worker/Size.js +29 -0
  31. package/src/components/LayoutManager/LayoutManager.jsx +31 -0
  32. package/src/components/LayoutManager/LayoutManager.scss +0 -0
  33. package/src/components/LayoutManager/Providers/ComponentRegistryContext.js +7 -0
  34. package/src/components/LayoutManager/Providers/LayoutProvider.js +48 -0
  35. package/src/components/LayoutManager/docs/ui_layout_manager_system_diagram.JPG +0 -0
  36. package/src/components/LayoutManager/index.js +1 -0
  37. package/src/index.js +1 -0
  38. package/src/stories/LayoutManager.stories.js +64 -0
  39. package/src/stories/LayoutManager.stories.scss +7 -0
  40. package/src/stories/layouts/vsCode/default.json +195 -0
  41. package/src/stories/layouts/vsCode/sample1.json +151 -0
  42. package/src/stories/sample_components/editor/EditorVSCode.jsx +11 -0
  43. package/src/stories/sample_components/editor/filetree.json +1 -0
  44. package/src/stories/sample_components/flow/Flow.jsx +10 -0
  45. package/src/stories/sample_components/flow/SampleTree.json +8 -0
  46. package/src/stories/sample_components/map/MapSample.jsx +43 -0
  47. package/src/stories/sample_components/map/MapSample.scss +3 -0
  48. package/src/stories/sample_components/stack/Stack.jsx +21 -0
  49. package/tests/LayoutEditor/LayoutEditor.test.js +14 -0
  50. package/tests/LayoutEditor/layouts/default.json +195 -0
  51. package/vitest.config.js +8 -0
@@ -0,0 +1,179 @@
1
+ import TRANSFORMATION_TYPES from "../TRANSFORMATION_TYPES";
2
+ /**
3
+ * This class generates transformations based on the handle
4
+ * bars movements. It sets up thresholds at which it collapses
5
+ * and expands the containers. This isn't the width of the container,
6
+ * it is the position of the handle bar in the parent container. So
7
+ * even if a container has a min width of 200px, when the handle bar
8
+ * reaches 50px from the left, it will collapse it.
9
+ *
10
+ * This will be expanded in the future to have different collapsed states.
11
+ * So rather than fully collapsing the container, it sets up a
12
+ * minimum size, this willl be useful for accordians and other containers.
13
+ */
14
+ export class HandleRulesEnforcer {
15
+ /**
16
+ * Initialize the child rule enforcer.
17
+ * @param {Object} parent
18
+ * @param {Object} sibling1
19
+ * @param {Object} sibling2
20
+ * @param {Object} handleMetadata
21
+ */
22
+ constructor (parent, sibling1, sibling2, handleMetadata) {
23
+ this.parent = parent;
24
+ this.type = null;
25
+ this.args = {};
26
+ this.sibling1 = sibling1;
27
+ this.sibling2 = sibling2;
28
+ this.handleMetadata = handleMetadata;
29
+ }
30
+
31
+ /**
32
+ * Get props given node orientation.
33
+ * @param {Object} node
34
+ */
35
+ getProps(node) {
36
+ // Identify the dynamic property based on orientation.
37
+ if (node.orientation === "horizontal") {
38
+ return {"dynamic": "width", "fixed": "height"};
39
+ } else if (node.orientation === "vertical") {
40
+ return {"dynamic": "height", "fixed": "width"};
41
+ } else {
42
+ throw new Error(`Unknown orientation "${node.orientation}" in LDF configuration`);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Evaluate the rules based on childs
48
+ */
49
+ evaluate() {
50
+ this.activeSibling = null;
51
+ const props = this.getProps(this.parent);
52
+ if (props.dynamic === "width") {
53
+ this.processVerticalContainers();
54
+ } else if (props.dynamic === "height") {
55
+ this.processHorizontalContainers();
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Process the vertical containers.
61
+ */
62
+ processVerticalContainers () {
63
+ const totalWidth = this.handleMetadata.sizes[this.parent.id].width;
64
+ this.type = TRANSFORMATION_TYPES.UPDATE_SIZE;
65
+
66
+ // Currently only collapse the edge containers
67
+ // TODO: In the future add logic for middle containers (accordian style)
68
+ if (!this.checkIfEdgeContainer()) {
69
+ return;
70
+ }
71
+
72
+ if (this.handleMetadata.handle.x < 100) {
73
+ this.args = {style: {"display":"none", "min-width":0}}
74
+ this.sibling1.hidden = true;
75
+ this.activeSibling = this.sibling1.id;
76
+ } else if (this.handleMetadata.handle.x > 100 && this.sibling1.hidden) {
77
+ this.args = {style: {"display":"flex"}}
78
+ const sibling = this.getSiblingProps(this.sibling1.id);
79
+ if ("min" in sibling.size) {
80
+ this.args.style["min-width"] = sibling.size.min.value + sibling.size.min.unit;
81
+ }
82
+ this.sibling1.hidden = false;
83
+ this.activeSibling = this.sibling1.id;
84
+ }
85
+
86
+
87
+ if (this.handleMetadata.handle.x > totalWidth - 100) {
88
+ this.args = {style: {"display":"none", "min-width":0}}
89
+ this.sibling2.hidden = true;
90
+ this.activeSibling = this.sibling2.id;
91
+ } else if (this.handleMetadata.handle.x < totalWidth - 100 && this.sibling2.hidden) {
92
+ this.args = {style: {"display":"flex"}}
93
+ const sibling = this.getSiblingProps(this.sibling2.id);
94
+ if ("min" in sibling.size) {
95
+ this.args.style["min-width"] = sibling.size.min.value + sibling.size.min.unit;
96
+ }
97
+ this.sibling2.hidden = false;
98
+ this.activeSibling = this.sibling2.id;
99
+ }
100
+ }
101
+
102
+
103
+ /**
104
+ * Process the vertical containers.
105
+ */
106
+ processHorizontalContainers () {
107
+ const totalHeight = this.handleMetadata.sizes[this.parent.id].height;
108
+ this.type = TRANSFORMATION_TYPES.UPDATE_SIZE;
109
+
110
+ // Currently only collapse the edge containers
111
+ // TODO: In the future add logic for middle containers (accordian style)
112
+ if (!this.checkIfEdgeContainer()) {
113
+ return;
114
+ }
115
+
116
+ if (this.handleMetadata.handle.y < 100) {
117
+ this.args = {style: {"display":"none", "min-height":0}}
118
+ this.sibling1.hidden = true;
119
+ this.activeSibling = this.sibling1.id;
120
+ } else if (this.handleMetadata.handle.y > 100 && this.sibling1.hidden) {
121
+ this.args = {style: {"display":"flex"}}
122
+ const sibling = this.getSiblingProps(this.sibling1.id);
123
+ if ("min" in sibling.size) {
124
+ this.args.style["min-height"] = sibling.size.min.value + sibling.size.min.unit;
125
+ }
126
+ this.sibling1.hidden = false;
127
+ this.activeSibling = this.sibling1.id;
128
+ }
129
+
130
+
131
+ if (this.handleMetadata.handle.y > totalHeight - 100) {
132
+ this.args = {style: {"display":"none", "min-height":0}}
133
+ this.sibling2.hidden = true;
134
+ this.activeSibling = this.sibling2.id;
135
+ } else if (this.handleMetadata.handle.y < totalHeight - 100 && this.sibling2.hidden) {
136
+ this.args = {style: {"display":"flex"}}
137
+ const sibling = this.getSiblingProps(this.sibling2.id);
138
+ if ("min" in sibling.size) {
139
+ this.args.style["min-height"] = sibling.size.min.value + sibling.size.min.unit;
140
+ }
141
+ this.sibling2.hidden = false;
142
+ this.activeSibling = this.sibling2.id;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Given the sibling ID, it returns the child properties from the parent.
148
+ * This includes the min-size and max-size. This is done because the actual
149
+ * min and max sizes are saved in the children of the parent, not in the actual
150
+ * container itself in the LDF file.
151
+ * @param {String} siblingId
152
+ */
153
+ getSiblingProps (siblingId) {
154
+ for (let i = 0; i < this.parent.children.length; i++) {
155
+ const child = this.parent.children[i];
156
+ if (child.containerId === siblingId) {
157
+ return child;
158
+ }
159
+ }
160
+ }
161
+
162
+
163
+ /**
164
+ * Check if the handle bar is attached to the edge of the parent container.
165
+ * i.e. Is sibling1 on the very left or is sibling2 on the very right.
166
+ */
167
+ checkIfEdgeContainer () {
168
+ const firstSibling = this.parent.children[0];
169
+ const lastSibling = this.parent.children[this.parent.children.length -1];
170
+
171
+ if (firstSibling.containerId === this.sibling1.id) {
172
+ return true;
173
+ } else if (lastSibling.containerId === this.sibling2.id) {
174
+ return true;
175
+ }
176
+
177
+ return false;
178
+ }
179
+ }
@@ -0,0 +1,183 @@
1
+ import LAYOUT_WORKER_PROTOCOL from "../LAYOUT_WORKER_PROTOCOL";
2
+ import TRANSFORMATION_TYPES from "../TRANSFORMATION_TYPES";
3
+ import { ParentRuleEnforcer } from "./ParentRuleEnforcer";
4
+ import { HandleRulesEnforcer } from "./HandleRulesEnforcer";
5
+
6
+ export class LayoutEditor {
7
+
8
+ /**
9
+ * Initializes the editor with the given ldf file.
10
+ * @param {Object} ldf
11
+ */
12
+ constructor (ldf) {
13
+ this.ldf = ldf;
14
+ console.log("Created modifier with ", this.ldf);
15
+ this.transformations = [];
16
+ }
17
+
18
+ /**
19
+ * Initializes flexbox layout by processing LDF file.
20
+ */
21
+ initializeFlexBox() {
22
+ this.initializeNode(this.ldf.containers[this.ldf.layoutRoot]);
23
+ postMessage({
24
+ type: LAYOUT_WORKER_PROTOCOL.INITIALIZE_FLEXBOX,
25
+ data: this.transformations
26
+ });
27
+ this.transformations = []
28
+ }
29
+
30
+ /**
31
+ * Get props given node orientation.
32
+ * @param {Object} node
33
+ */
34
+ getProps(node) {
35
+ // Identify the dynamic property based on orientation.
36
+ if (node.orientation === "horizontal") {
37
+ return {"dynamic": "width", "fixed": "height"};
38
+ } else if (node.orientation === "vertical") {
39
+ return {"dynamic": "height", "fixed": "width"};
40
+ } else {
41
+ throw new Error(`Unknown orientation "${node.orientation}" in LDF configuration`);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Processes the node and applies the initial flex box styles. It recursively
47
+ * calls the initialization function on the nodes children until the entire
48
+ * layout is initialized.
49
+ *
50
+ * After initialization, the layout is recalculated when the handle bar moves
51
+ * or when the window is resized. In the future, I will add programatic control
52
+ * to modify the layout. This means that the API will ask to divide a container
53
+ * in two or to delete container.
54
+ *
55
+ * TODO: Implement the programmatic control of layout containers (see above).
56
+ *
57
+ * @param {Object} node Node to process.
58
+ */
59
+ initializeNode (node) {
60
+ const isSplit = node.type ? node.type === "split": false;
61
+
62
+ // If node is not split, then it has no children and is a leaf node, so we return.
63
+ if (!isSplit) {
64
+ return;
65
+ }
66
+
67
+ const props = this.getProps(node);
68
+
69
+ for (const child of node.children) {
70
+ if (child.type === "container") {
71
+ let childStyle = {};
72
+ switch(child.size.initial.type) {
73
+ case "fixed":
74
+ childStyle[props["dynamic"]] = child.size.initial.value + "px" ;
75
+ childStyle["flex"] = "0 0 auto";
76
+ break;
77
+ case "fill":
78
+ childStyle["flexGrow"] = 1;
79
+ break;
80
+ default:
81
+ throw new Error(`Unknown size type "${child.size.initial.type}" in LDF configuration`);
82
+ }
83
+
84
+ if ("min" in child.size) {
85
+ childStyle["min-" + props["dynamic"]] = child.size.min.value + "px" ;
86
+ }
87
+
88
+ if ("max" in child.size) {
89
+ childStyle["max-" + props["dynamic"]] = child.size.max.value + "px" ;
90
+ }
91
+
92
+ const childContainer = this.ldf.containers[child.containerId];
93
+ childContainer.collapsed = false;
94
+ this.transformations.push(
95
+ {
96
+ id: childContainer.id,
97
+ type: TRANSFORMATION_TYPES.UPDATE_SIZE,
98
+ args: {style: childStyle}
99
+ }
100
+ );
101
+ this.initializeNode(childContainer);
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Use the given sizes to perform layout calculations and generate
108
+ * transformations.
109
+ * @param {Object} sizes
110
+ */
111
+ applySizes(sizes) {
112
+ this.sizes = sizes;
113
+ this.applyLayoutToNode(this.ldf.layoutRoot);
114
+ postMessage({
115
+ type: LAYOUT_WORKER_PROTOCOL.TRANSFORMATIONS,
116
+ data: this.transformations
117
+ });
118
+ this.transformations = []
119
+ }
120
+
121
+ /**
122
+ * This function moves the handlebar.
123
+ * @param {Object} metadata
124
+ */
125
+ moveHandleBar(metadata) {
126
+ const parent = this.ldf.containers[metadata.parent];
127
+ const sibling1 = this.ldf.containers[metadata.sibling1];
128
+ const sibling2 = this.ldf.containers[metadata.sibling2];
129
+ const enforcer = new HandleRulesEnforcer(parent, sibling1, sibling2, metadata);
130
+ enforcer.evaluate();
131
+
132
+ if (enforcer.activeSibling !== null) {
133
+ this.transformations.push(
134
+ {
135
+ id: enforcer.activeSibling,
136
+ type: TRANSFORMATION_TYPES.UPDATE_SIZE,
137
+ args: enforcer.args
138
+ }
139
+ );
140
+ postMessage({
141
+ type: LAYOUT_WORKER_PROTOCOL.TRANSFORMATIONS,
142
+ data: this.transformations
143
+ });
144
+ this.transformations = []
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Applys the layout logic to the node with the given container id.
150
+ * @param {String} containerId
151
+ * @returns
152
+ */
153
+ applyLayoutToNode(containerId) {
154
+ const parent = this.ldf.containers[containerId];
155
+
156
+ // If node is not split, then it has no children and is a leaf node, so we return.
157
+ if ((!parent.type ? parent.type === "split": false) || (!("children" in parent))) {
158
+ return;
159
+ }
160
+
161
+ if (!this.sizes[containerId]) {
162
+ console.warn(`Parent size not found for node ${parent.id}. Skipping collapse checks.`);
163
+ return;
164
+ }
165
+
166
+ for (const child of parent.children) {
167
+ if (child.type === "container") {
168
+ const enforcer = new ParentRuleEnforcer(this.sizes, parent, child);
169
+ enforcer.evaluate();
170
+ if (enforcer.type) {
171
+ this.transformations.push(
172
+ {
173
+ id: this.ldf.containers[child.containerId].id,
174
+ type: enforcer.type,
175
+ args: enforcer.args,
176
+ }
177
+ );
178
+ }
179
+ this.applyLayoutToNode(child.containerId);
180
+ }
181
+ }
182
+ }
183
+ };
@@ -0,0 +1,41 @@
1
+ import { LayoutEditor } from "./LayoutEditor";
2
+ import LAYOUT_WORKER_PROTOCOL from "../LAYOUT_WORKER_PROTOCOL";
3
+
4
+ /**
5
+ * This function receives messages from the main thread and executes
6
+ * the layout manipulation logic.
7
+ * @param {Object} e
8
+ */
9
+ let editor;
10
+ self.onmessage = function (e) {
11
+
12
+ try {
13
+ const args = e.data.args;
14
+ switch (e.data.code) {
15
+ case LAYOUT_WORKER_PROTOCOL.INITIALIZE:
16
+ /** @type {LayoutEditor} */
17
+ editor = new LayoutEditor(args.ldf);
18
+ break;
19
+ case LAYOUT_WORKER_PROTOCOL.INITIALIZE_FLEXBOX:
20
+ editor.initializeFlexBox();
21
+ break;
22
+ case LAYOUT_WORKER_PROTOCOL.APPLY_SIZES:
23
+ editor.applySizes(args.sizes);
24
+ break;
25
+ case LAYOUT_WORKER_PROTOCOL.MOVE_HANDLE_BAR:
26
+ editor.moveHandleBar(args.metadata);
27
+ break;
28
+ default:
29
+ break;
30
+ }
31
+
32
+ } catch (e) {
33
+ postMessage({
34
+ type: LAYOUT_WORKER_PROTOCOL.ERROR,
35
+ error: {
36
+ message: e.message,
37
+ stack: e.stack
38
+ }
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,79 @@
1
+ import TRANSFORMATION_TYPES from "../TRANSFORMATION_TYPES";
2
+ /**
3
+ * This class generates transformations based on the
4
+ * parents layout configuration. For example, it collapses
5
+ * a container if the parent size reaches a threshold
6
+ * or expands it if the is above a threshold.
7
+ */
8
+ export class ParentRuleEnforcer {
9
+ /**
10
+ * Initialize the rule enforcer.
11
+ * @param {Object} sizes
12
+ * @param {Object} parent
13
+ * @param {Object} child
14
+ */
15
+ constructor (sizes, parent, child) {
16
+ this.sizes = sizes;
17
+ this.parent = parent;
18
+ this.child = child;
19
+ this.type = null;
20
+ this.args = {};
21
+ }
22
+
23
+ /**
24
+ * Get props given node orientation.
25
+ * @param {Object} node
26
+ */
27
+ getProps(node) {
28
+ // Identify the dynamic property based on orientation.
29
+ if (node.orientation === "horizontal") {
30
+ return {"dynamic": "width", "fixed": "height"};
31
+ } else if (node.orientation === "vertical") {
32
+ return {"dynamic": "height", "fixed": "width"};
33
+ } else {
34
+ throw new Error(`Unknown orientation "${node.orientation}" in LDF configuration`);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Evaluate the rules based on parent and child sizes and
40
+ * the specified layout configuration.
41
+ */
42
+ evaluate() {
43
+ if (this.child.hasOwnProperty("collapse") && this.child["collapse"]["relative"] === "parent") {
44
+ this.evaluateCollapseByParent();
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Evaluate the collapse by parent property.
50
+ */
51
+ evaluateCollapseByParent() {
52
+ const props = this.getProps(this.parent);
53
+ const parentSize = this.sizes[this.parent.id];
54
+
55
+ let args = {};
56
+ if (parentSize[props["dynamic"]] <= this.child.collapse.value && this.child.collapse.condition === "lessThan") {
57
+ // Collapse below threshold
58
+ if (!this.child.hidden) {
59
+ this.type = TRANSFORMATION_TYPES.UPDATE_SIZE;
60
+ args = {style: {"display":"none"}}
61
+ const prop = "min-" + props["dynamic"];
62
+ args.style[prop] = 0;
63
+ this.child.hidden = true;
64
+ }
65
+ } else {
66
+ // Expand above threshold
67
+ if (this.child.hidden) {
68
+ this.type = TRANSFORMATION_TYPES.UPDATE_SIZE;
69
+ args = {style: {"display":"flex"}}
70
+ if ("min" in this.child.size) {
71
+ const prop = "min-" + props["dynamic"];
72
+ args.style[prop] = this.child.size.min.value + this.child.size.min.unit;
73
+ }
74
+ this.child.hidden = false;
75
+ }
76
+ }
77
+ Object.assign(this.args, args);
78
+ }
79
+ }
@@ -0,0 +1,29 @@
1
+ export class Size {
2
+
3
+ /**
4
+ * Initializes the size object with the given width and height.
5
+ * @param {Number} width
6
+ * @param {Number} height
7
+ */
8
+ constructor (width, height) {
9
+ this.width = width;
10
+ this.height = height;
11
+ }
12
+
13
+ /**
14
+ *
15
+ * @returns {Number} Width of the size.
16
+ */
17
+ getWidth() {
18
+ return this.width;
19
+ }
20
+
21
+ /**
22
+ *
23
+ * @returns {Number} Height of the size.
24
+ */
25
+ getHeight() {
26
+ return this.height;
27
+ }
28
+
29
+ }
@@ -0,0 +1,31 @@
1
+ import React, { useEffect, useState, useContext } from "react";
2
+ import PropTypes from 'prop-types';
3
+ import { RootContainer } from "./Components/RootContainer/RootContainer";
4
+ import ComponentRegistryContext from "./Providers/ComponentRegistryContext";
5
+ import { LayoutControllerProvider } from "./Providers/LayoutProvider";
6
+
7
+ import "./LayoutManager.scss";
8
+
9
+ /**
10
+ * Renders the layout specified in the LDF file.
11
+ * @param {Object} props
12
+ * @param {Object} props.ldf - JSON object containing the Layout Definition File (LDF)
13
+ * @param {React.ReactNode} props.registry - An object containing the registered components that will be
14
+ * lazy loaded into the containers.
15
+ * @return {React.ReactElement}
16
+ */
17
+ export const LayoutManager = ({ldf, registry}) => {
18
+
19
+ return (
20
+ <LayoutControllerProvider layout={ldf}>
21
+ <ComponentRegistryContext.Provider value={registry}>
22
+ <RootContainer/>
23
+ </ComponentRegistryContext.Provider>
24
+ </LayoutControllerProvider>
25
+ );
26
+ }
27
+
28
+ LayoutManager.propTypes = {
29
+ ldf: PropTypes.object,
30
+ registry: PropTypes.object,
31
+ }
@@ -0,0 +1,7 @@
1
+ import {createContext} from "react";
2
+
3
+ // This context stores the registry for the components.
4
+ const ComponentRegistryContext = createContext({});
5
+ ComponentRegistryContext.displayName = 'ComponentRegistryContext';
6
+
7
+ export default ComponentRegistryContext;
@@ -0,0 +1,48 @@
1
+ import React, { createContext, useContext, useEffect, useMemo } from 'react';
2
+ import { LayoutController } from "../Controller/LayoutController";
3
+ import PropTypes from 'prop-types';
4
+
5
+ const LayoutContext = createContext(null);
6
+
7
+ /**
8
+ * A provider to expose the controller to all the children.
9
+ * @param {Object} props
10
+ * @param {Object} props.layout - Layout definition JSON object
11
+ * @param {React.ReactNode} props.children - React children to render
12
+ * @returns {React.ReactElement}
13
+ */
14
+ export function LayoutControllerProvider({ layout, children }) {
15
+
16
+ const controller = useMemo( () => {
17
+ return new LayoutController(layout);
18
+ }, [layout]);
19
+
20
+ useEffect(() => {
21
+ return () => {
22
+ controller.destroy();
23
+ }
24
+ }, [controller]);
25
+
26
+ return (
27
+ <LayoutContext.Provider value={controller}>
28
+ {children}
29
+ </LayoutContext.Provider>
30
+ )
31
+ }
32
+
33
+ LayoutControllerProvider.propTypes = {
34
+ layout: PropTypes.object.isRequired,
35
+ children: PropTypes.node.isRequired
36
+ }
37
+
38
+ /**
39
+ * A hook to access the controller within containers.
40
+ * @returns {Object}
41
+ */
42
+ export function useLayoutController() {
43
+ const context = useContext(LayoutContext);
44
+ if (!context) {
45
+ throw new Error("useLayoutController must be used within a LayoutControllerProvider")
46
+ }
47
+ return context;
48
+ }
@@ -0,0 +1 @@
1
+ export * from "./LayoutManager.jsx"
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./components/LayoutManager";
@@ -0,0 +1,64 @@
1
+ import { useEffect, useMemo, useCallback } from "react";
2
+ import { useArgs } from "@storybook/preview-api";
3
+ import { LayoutManager } from "../components/LayoutManager";
4
+ import defaultLayoutJSON from "./layouts/vsCode/default.json";
5
+ import sample1JSON from "./layouts/vsCode/sample1.json";
6
+
7
+ import "./LayoutManager.stories.scss";
8
+
9
+ export default {
10
+ title: 'Editors',
11
+ component: LayoutManager,
12
+ argTypes: {
13
+ ldf: {
14
+ type: 'object'
15
+ }
16
+ }
17
+ };
18
+
19
+ const Template = (args) => {
20
+ const [, updateArgs] = useArgs();
21
+
22
+ const registry = useMemo(() => ({
23
+ EditorVSCode: () =>
24
+ import('./sample_components/editor/EditorVSCode').then((m) => ({
25
+ default: m.default,
26
+ })),
27
+ Stack: () =>
28
+ import('./sample_components/stack/Stack').then((m) => ({
29
+ default: m.default,
30
+ })),
31
+ Flow: () =>
32
+ import('./sample_components/flow/Flow').then((m) => ({
33
+ default: m.default,
34
+ })),
35
+ MapSample: () =>
36
+ import('./sample_components/map/MapSample').then((m) => ({
37
+ default: m.default,
38
+ })),
39
+ }), []);
40
+
41
+ useEffect(() => {
42
+ updateArgs({registry : registry});
43
+ }, [updateArgs, registry]);
44
+
45
+ return (
46
+ <div className="rootContainer">
47
+ <LayoutManager {...args}/>
48
+ </div>
49
+ )
50
+ }
51
+
52
+ export const defaultLayout = Template.bind({})
53
+
54
+ defaultLayout.args = {
55
+ ldf: defaultLayoutJSON
56
+ }
57
+
58
+
59
+ export const sample1 = Template.bind({})
60
+
61
+ sample1.args = {
62
+ ldf: sample1JSON
63
+ }
64
+
@@ -0,0 +1,7 @@
1
+ .rootContainer {
2
+ position: absolute;
3
+ top: 0;
4
+ bottom: 0;
5
+ left: 0;
6
+ right: 0;
7
+ }