sample-ui-component-library 0.0.0 → 0.0.1-dev
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/README.md +2 -2
- package/dist/LibraryManager-V1.svg +3 -0
- package/dist/cjs/index.js +31 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +31 -4
- package/dist/esm/index.js.map +1 -1
- package/package.json +13 -6
- package/rollup.config.mjs +2 -0
- package/src/components/Editor/Editor.jsx +59 -0
- package/src/components/Editor/Editor.scss +16 -0
- package/src/components/Editor/EditorTabs/EditorTabs.jsx +76 -0
- package/src/components/Editor/EditorTabs/EditorTabs.scss +22 -0
- package/src/components/Editor/MonacoInstance/MonacoInstance.jsx +66 -0
- package/src/components/Editor/MonacoInstance/MonacoInstance.scss +0 -0
- package/src/components/Editor/index.js +1 -0
- package/src/components/FileBrowser/FileBrowser.jsx +127 -0
- package/src/components/FileBrowser/FileBrowser.scss +40 -0
- package/src/components/FileBrowser/helper.js +70 -0
- package/src/components/FileBrowser/index.js +1 -0
- package/src/components/FlowDiagram/DagreLayout.js +33 -0
- package/src/components/FlowDiagram/FlowDiagram.jsx +64 -0
- package/src/components/FlowDiagram/FlowDiagram.scss +0 -0
- package/src/components/FlowDiagram/helper.js +60 -0
- package/src/components/FlowDiagram/index.js +1 -0
- package/src/components/Viewer/MonacoInstance/MonacoInstance.jsx +4 -1
- package/src/components/Viewer/Tabs/Tabs.jsx +6 -3
- package/src/components/Viewer/Viewer.jsx +3 -3
- package/src/components/Viewer/Viewer.scss +23 -3
- package/src/index.js +3 -1
- package/src/stories/Editor.stories.js +38 -0
- package/src/stories/EditorStories.scss +7 -0
- package/src/stories/FileBrowser.stories.js +48 -0
- package/src/stories/FileBrowserStories.scss +19 -0
- package/src/stories/FlowDiagram.scss +7 -0
- package/src/stories/FlowDiagram.stories.js +29 -0
- package/src/stories/data/FileBrowser/Tree1.json +57 -0
- package/src/stories/data/FileBrowser/Tree2.json +60 -0
- package/src/stories/data/flow/SampleTree.json +8 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sample-ui-component-library",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1-dev",
|
|
4
4
|
"description": "A library which contains sample UI elements that can be used for populating layouts.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
-
"storybook": "storybook dev -p
|
|
9
|
+
"storybook": "storybook dev -p 6007",
|
|
10
10
|
"build-storybook": "storybook build",
|
|
11
11
|
"build": "rollup -c",
|
|
12
12
|
"predeploy": "npm run build-storybook",
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/vishalpalaniappan/sample-component-library.git"
|
|
17
|
+
"url": "git+https://github.com/vishalpalaniappan/sample-ui-component-library.git"
|
|
18
18
|
},
|
|
19
19
|
"author": "Vishal Palaniappan",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"bugs": {
|
|
22
|
-
"url": "https://github.com/vishalpalaniappan/sample-component-library/issues"
|
|
22
|
+
"url": "https://github.com/vishalpalaniappan/sample-ui-component-library/issues"
|
|
23
23
|
},
|
|
24
|
-
"homepage": "https://github.com/vishalpalaniappan/sample-component-library.git",
|
|
24
|
+
"homepage": "https://github.com/vishalpalaniappan/sample-ui-component-library.git",
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@babel/core": "^7.26.10",
|
|
27
27
|
"@babel/preset-env": "^7.26.9",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@chromatic-com/storybook": "^3.2.6",
|
|
30
30
|
"@rollup/plugin-babel": "^6.0.4",
|
|
31
31
|
"@rollup/plugin-commonjs": "^28.0.3",
|
|
32
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
32
33
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
33
34
|
"@rollup/plugin-terser": "^0.4.4",
|
|
34
35
|
"@storybook/addon-actions": "^8.6.11",
|
|
@@ -61,6 +62,12 @@
|
|
|
61
62
|
"react-dom": "^18.2.0"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
|
-
"
|
|
65
|
+
"@dagrejs/dagre": "^1.1.4",
|
|
66
|
+
"@dnd-kit/core": "^6.3.1",
|
|
67
|
+
"@monaco-editor/react": "^4.7.0",
|
|
68
|
+
"@xyflow/react": "^12.6.0",
|
|
69
|
+
"bootstrap": "^5.3.4",
|
|
70
|
+
"react-bootstrap": "^2.10.9",
|
|
71
|
+
"react-bootstrap-icons": "^1.11.5"
|
|
65
72
|
}
|
|
66
73
|
}
|
package/rollup.config.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import commonjs from '@rollup/plugin-commonjs';
|
|
|
3
3
|
import terser from '@rollup/plugin-terser';
|
|
4
4
|
import external from 'rollup-plugin-peer-deps-external';
|
|
5
5
|
import postcss from 'rollup-plugin-postcss';
|
|
6
|
+
import json from '@rollup/plugin-json';
|
|
6
7
|
import { babel } from '@rollup/plugin-babel';
|
|
7
8
|
|
|
8
9
|
export default {
|
|
@@ -31,5 +32,6 @@ export default {
|
|
|
31
32
|
exclude: 'node_modules/**',
|
|
32
33
|
}),
|
|
33
34
|
commonjs(),
|
|
35
|
+
json()
|
|
34
36
|
]
|
|
35
37
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "./Editor.scss";
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import { MonacoInstance } from "./MonacoInstance/MonacoInstance";
|
|
5
|
+
import { EditorTabs } from "./EditorTabs/EditorTabs";
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders the editor component with support for tabs.
|
|
10
|
+
*
|
|
11
|
+
* @return {JSX}
|
|
12
|
+
*/
|
|
13
|
+
export const Editor = ({systemTree, onFileSelect}) => {
|
|
14
|
+
|
|
15
|
+
const [editorContent, setEditorContent] = useState("asdf");
|
|
16
|
+
|
|
17
|
+
const [activeTab, setActiveTab] = useState();
|
|
18
|
+
const [tabs, setTabs] = useState([
|
|
19
|
+
{ id: "tab1", label: "Tab 1" },
|
|
20
|
+
{ id: "tab2", label: "Tab 2" },
|
|
21
|
+
{ id: "tab3", label: "Tab 3" },
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (activeTab) {
|
|
26
|
+
setEditorContent(tabs[activeTab - 1].label);
|
|
27
|
+
}
|
|
28
|
+
}, [activeTab]);
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (tabs) {
|
|
33
|
+
if ((activeTab && activeTab > tabs.length) || activeTab == null) {
|
|
34
|
+
setActiveTab(tabs.length);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, [tabs]);
|
|
38
|
+
|
|
39
|
+
const onTabClick = (event) => {
|
|
40
|
+
const tabIndex = tabs.findIndex(obj => obj.id === event.target.id);
|
|
41
|
+
setActiveTab(tabIndex + 1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="editorContainer">
|
|
46
|
+
<div className="tabContainer">
|
|
47
|
+
<EditorTabs activeTab={1} tabs={tabs} selectTab={onTabClick} />
|
|
48
|
+
</div>
|
|
49
|
+
<div className="monacoContainer">
|
|
50
|
+
<MonacoInstance editorContent={editorContent}/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Editor.propTypes = {
|
|
57
|
+
systemTree: PropTypes.object,
|
|
58
|
+
onFileSelect: PropTypes.func
|
|
59
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import "./EditorTabs.scss";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import {
|
|
6
|
+
DndContext,
|
|
7
|
+
DragOverlay,
|
|
8
|
+
useDraggable,
|
|
9
|
+
useDroppable,
|
|
10
|
+
} from "@dnd-kit/core";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tab Component
|
|
14
|
+
* @param {String} id
|
|
15
|
+
* @param {String} label
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
function Tab({id, label, onSelectTab}) {
|
|
19
|
+
const { attributes, listeners, setNodeRef, transform } = useDraggable({id});
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div ref={setNodeRef} id={id} onClick={onSelectTab} className="tab" {...listeners} {...attributes}>
|
|
23
|
+
{label}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tab Gutter Component
|
|
30
|
+
* @param {String} id
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
function Gutter({id}) {
|
|
34
|
+
const { setNodeRef, isOver } = useDroppable({id});
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className="gutter"
|
|
38
|
+
ref={setNodeRef}
|
|
39
|
+
style={{background: isOver ? "white" : "#4da3ff33"}}
|
|
40
|
+
></div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tabs component.
|
|
46
|
+
* @param {Object} activeTab
|
|
47
|
+
* @param {Array} tabs
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
export const EditorTabs = ({activeTab, tabs, selectTab}) => {
|
|
51
|
+
|
|
52
|
+
const [tabsList, setTabsList] = useState();
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (tabs) {
|
|
56
|
+
const list = [];
|
|
57
|
+
tabs.forEach((tab, index) => {
|
|
58
|
+
list.push(
|
|
59
|
+
<>
|
|
60
|
+
<Gutter id={tab.id} />
|
|
61
|
+
<Tab onSelectTab={selectTab} key={tab.id} {...tab} />
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
})
|
|
65
|
+
setTabsList(list);
|
|
66
|
+
}
|
|
67
|
+
}, [tabs]);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div style={{ display: "flex", background: "#222425" }}>
|
|
71
|
+
{tabsList}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
EditorTabs.propTypes = {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.tabs {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
background-color: #1e1e1e;
|
|
5
|
+
height: 100%;
|
|
6
|
+
width: 100%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.tab {
|
|
10
|
+
padding: 0px 20px;
|
|
11
|
+
background-color: #2d2d2d;
|
|
12
|
+
color: #fff;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
height: 40px;
|
|
15
|
+
display:flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.gutter {
|
|
20
|
+
height: 40px;
|
|
21
|
+
width: 1px;
|
|
22
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import Editor from '@monaco-editor/react';
|
|
5
|
+
|
|
6
|
+
import "./MonacoInstance.scss"
|
|
7
|
+
|
|
8
|
+
function Gutter({ id }) {
|
|
9
|
+
const { setNodeRef, isOver } = useDroppable({
|
|
10
|
+
id,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
ref={setNodeRef}
|
|
16
|
+
style={{
|
|
17
|
+
height: 40,
|
|
18
|
+
width: 1,
|
|
19
|
+
background: isOver ? "white" : "#4da3ff33",
|
|
20
|
+
display: "flex",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
justifyContent: "center"
|
|
23
|
+
}}
|
|
24
|
+
></div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const MonacoInstance = ({editorContent}) => {
|
|
29
|
+
const editorRef = useRef(null);
|
|
30
|
+
|
|
31
|
+
const content = useRef();
|
|
32
|
+
|
|
33
|
+
const handleEditorDidMount = (editor, monaco) => {
|
|
34
|
+
editorRef.current = editor;
|
|
35
|
+
if(content?.current) {
|
|
36
|
+
editorRef.current.setValue(content.current);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
content.current = editorContent;
|
|
42
|
+
if (editorRef?.current && content.current) {
|
|
43
|
+
editorRef.current.setValue(content.current);
|
|
44
|
+
}
|
|
45
|
+
}, [editorContent]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Editor
|
|
49
|
+
defaultLanguage="python"
|
|
50
|
+
defaultValue=""
|
|
51
|
+
onMount={handleEditorDidMount}
|
|
52
|
+
theme="vs-dark"
|
|
53
|
+
options={{
|
|
54
|
+
scrollBeyondLastLine:false,
|
|
55
|
+
fontSize:"12px",
|
|
56
|
+
minimap: {
|
|
57
|
+
enabled: false
|
|
58
|
+
}
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
MonacoInstance.propTypes = {
|
|
65
|
+
editorContent: PropTypes.string,
|
|
66
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Editor.jsx"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useLayoutEffect, useCallback } from "react";
|
|
2
|
+
import "./FileBrowser.scss";
|
|
3
|
+
|
|
4
|
+
import { setDefaultCollapsed, collapseTree, selectNode, flattenTree } from "./helper";
|
|
5
|
+
|
|
6
|
+
import { FileCode, ChevronRight, ChevronDown, Braces, FiletypeScss, FiletypeJs, FiletypePy} from "react-bootstrap-icons";
|
|
7
|
+
import PropTypes from 'prop-types';
|
|
8
|
+
|
|
9
|
+
const INDENT_WIDTH = 20;
|
|
10
|
+
const SELECTED_FILE_COLOR = "#00426b";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Used for creating a stable callback function.
|
|
14
|
+
*/
|
|
15
|
+
function useEvent(fn) {
|
|
16
|
+
const fnRef = useRef(fn);
|
|
17
|
+
|
|
18
|
+
useLayoutEffect(() => {
|
|
19
|
+
fnRef.current = fn;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return useCallback((...args) => {
|
|
23
|
+
return fnRef.current(...args);
|
|
24
|
+
}, []);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Renders a single node in the file tree.
|
|
29
|
+
*/
|
|
30
|
+
const TreeNode = ({node, onRowClick}) => {
|
|
31
|
+
/**
|
|
32
|
+
* Gets the appropriate icon for the node based on its type and collapsed state.
|
|
33
|
+
* @returns <JSX>
|
|
34
|
+
*/
|
|
35
|
+
const getCollapsedIcon = () => {
|
|
36
|
+
if (node.collapsed) {
|
|
37
|
+
return <ChevronRight />;
|
|
38
|
+
} else {
|
|
39
|
+
return <ChevronDown />;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the background color of the row if the node is selected.
|
|
45
|
+
*/
|
|
46
|
+
const getRowStyle = () => {
|
|
47
|
+
if (node.selected) {
|
|
48
|
+
return {"backgroundColor": SELECTED_FILE_COLOR};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the file icon based on the extension.
|
|
55
|
+
*
|
|
56
|
+
* TODO: This can be improved by having a mapping of extensions to icons and color.
|
|
57
|
+
*/
|
|
58
|
+
const getFileIcon = () => {
|
|
59
|
+
const extension = node.name.split('.').pop();
|
|
60
|
+
switch (extension) {
|
|
61
|
+
case "js":
|
|
62
|
+
return <FiletypeJs color="#f1e05a" />;
|
|
63
|
+
case "json":
|
|
64
|
+
return <Braces color="#fffb00" />;
|
|
65
|
+
case "scss":
|
|
66
|
+
return <FiletypeScss color="#f12727" />;
|
|
67
|
+
case "py":
|
|
68
|
+
return <FiletypePy color="#686affbd" />;
|
|
69
|
+
default:
|
|
70
|
+
return <FileCode color="#ffffff" />;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="file-node-row" style={getRowStyle()} onClick={() => onRowClick(node)}>
|
|
76
|
+
<div className="indent" style={{ width: node.level * INDENT_WIDTH + "px"}} />
|
|
77
|
+
{
|
|
78
|
+
node.type === "folder" ?
|
|
79
|
+
<span className="center folder-icon">{getCollapsedIcon()}</span> :
|
|
80
|
+
<span className="center file-icon">{getFileIcon()}</span>
|
|
81
|
+
}
|
|
82
|
+
<span className="center file-name">{node.name}</span>
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Renders a file browser.
|
|
89
|
+
*
|
|
90
|
+
* @return {JSX}
|
|
91
|
+
*/
|
|
92
|
+
export const FileBrowser = ({tree, onNodeSelect}) => {
|
|
93
|
+
|
|
94
|
+
const [nodes, setNodes] = useState([]);
|
|
95
|
+
const treeRef = useRef();
|
|
96
|
+
|
|
97
|
+
const handleFileClick = useEvent((node) => {
|
|
98
|
+
selectNode(treeRef.current, node);
|
|
99
|
+
drawTree();
|
|
100
|
+
if (onNodeSelect) {
|
|
101
|
+
onNodeSelect(node);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
treeRef.current = flattenTree(tree);
|
|
107
|
+
setDefaultCollapsed(treeRef.current);
|
|
108
|
+
drawTree();
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Draws the tree given the nodes and the collapsed state of each node.
|
|
113
|
+
*/
|
|
114
|
+
const drawTree = () => {
|
|
115
|
+
const nodes = collapseTree(treeRef.current);
|
|
116
|
+
const rows = [];
|
|
117
|
+
nodes.forEach((node) => {
|
|
118
|
+
rows.push(<TreeNode key={node.id} node={node} onRowClick={handleFileClick}/>);
|
|
119
|
+
});
|
|
120
|
+
setNodes(rows);
|
|
121
|
+
}
|
|
122
|
+
return (
|
|
123
|
+
<div className="file-browser">
|
|
124
|
+
{nodes}
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
.file-browser{
|
|
2
|
+
width: 100%;
|
|
3
|
+
height:100%;
|
|
4
|
+
padding:5px;
|
|
5
|
+
color:white;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.file-node-row{
|
|
9
|
+
width:100%;
|
|
10
|
+
min-width: 200px;
|
|
11
|
+
display:flex;
|
|
12
|
+
padding: 5px 0;
|
|
13
|
+
font-size:13px;
|
|
14
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.indent {
|
|
19
|
+
height:100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.center {
|
|
23
|
+
display:flex;
|
|
24
|
+
align-self: center;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
.folder-icon {
|
|
29
|
+
display:flex;
|
|
30
|
+
margin-right: 5px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.file-icon {
|
|
34
|
+
margin-right: 5px;
|
|
35
|
+
font-size:16px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.file-name {
|
|
39
|
+
color: #CCC;
|
|
40
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets the default collapsed state to false.
|
|
3
|
+
*/
|
|
4
|
+
export const setDefaultCollapsed = (tree) => {
|
|
5
|
+
tree.forEach((node) => {
|
|
6
|
+
node.collapsed = false;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Given a file tree, it flattens the tree into a list of nodes with the level of each node in the tree.
|
|
13
|
+
*
|
|
14
|
+
* @param {Array} tree - The file tree to flatten.
|
|
15
|
+
* @param {number} level - The current level of the tree (used for indentation).
|
|
16
|
+
* @return {Array} - The flattened tree with level information.
|
|
17
|
+
*/
|
|
18
|
+
export const flattenTree = (tree, level) => {
|
|
19
|
+
if (!level) {
|
|
20
|
+
level = 0;
|
|
21
|
+
}
|
|
22
|
+
let rows = [];
|
|
23
|
+
for (let i = 0; i < tree.length; i++) {
|
|
24
|
+
const node = tree[i];
|
|
25
|
+
node.level = level;
|
|
26
|
+
rows.push(node);
|
|
27
|
+
if (node?.children) {
|
|
28
|
+
rows = rows.concat(flattenTree(node.children, level + 1));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return rows;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Draws the tree given the nodes and the collapsed state of each node.
|
|
36
|
+
*/
|
|
37
|
+
export const collapseTree = (tree) => {
|
|
38
|
+
const rows = [];
|
|
39
|
+
|
|
40
|
+
let collapsing, collapseLevel;
|
|
41
|
+
for (let i = 0; i < tree.length; i++) {
|
|
42
|
+
|
|
43
|
+
if (collapsing) {
|
|
44
|
+
if (tree[i].level <= collapseLevel) {
|
|
45
|
+
collapsing = false;
|
|
46
|
+
} else {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (tree[i].collapsed) {
|
|
52
|
+
collapsing = true;
|
|
53
|
+
collapseLevel = tree[i].level;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
rows.push(tree[i]);
|
|
57
|
+
}
|
|
58
|
+
return rows;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Selects the given node and deselects all other nodes in the tree.
|
|
63
|
+
*/
|
|
64
|
+
export const selectNode = (tree, node) => {
|
|
65
|
+
tree.forEach((n) => {
|
|
66
|
+
n.selected = false;
|
|
67
|
+
});
|
|
68
|
+
node.selected = true;
|
|
69
|
+
node.collapsed = !node.collapsed;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./FileBrowser.jsx"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
import Dagre from '@dagrejs/dagre';
|
|
3
|
+
|
|
4
|
+
export const getLayoutedElements = (nodes, edges, options) => {
|
|
5
|
+
// Reference: https://reactflow.dev/learn/layouting/layouting
|
|
6
|
+
|
|
7
|
+
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
|
8
|
+
g.setGraph({ rankdir: options.direction, ranksep: 100, nodesep: 170});
|
|
9
|
+
|
|
10
|
+
edges.forEach((edge) => g.setEdge(edge.source, edge.target));
|
|
11
|
+
nodes.forEach((node) =>
|
|
12
|
+
g.setNode(node.id, {
|
|
13
|
+
...node,
|
|
14
|
+
width: node.measured?.width ?? 0,
|
|
15
|
+
height: node.measured?.height ?? 0,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
Dagre.layout(g);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
nodes: nodes.map((node) => {
|
|
23
|
+
const position = g.node(node.id);
|
|
24
|
+
// We are shifting the dagre node position (anchor=center center) to the top left
|
|
25
|
+
// so it matches the React Flow node anchor point (top left).
|
|
26
|
+
const x = position.x - (node.measured?.width ?? 0) / 2;
|
|
27
|
+
const y = position.y - (node.measured?.height ?? 0) / 2;
|
|
28
|
+
|
|
29
|
+
return { ...node, position: { x, y } };
|
|
30
|
+
}),
|
|
31
|
+
edges,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ReactFlow,
|
|
4
|
+
ReactFlowProvider,
|
|
5
|
+
useNodesState,
|
|
6
|
+
useEdgesState,
|
|
7
|
+
Controls,
|
|
8
|
+
useReactFlow
|
|
9
|
+
} from "@xyflow/react";
|
|
10
|
+
import "@xyflow/react/dist/style.css";
|
|
11
|
+
|
|
12
|
+
import { getLayoutedElements } from "./DagreLayout.js";
|
|
13
|
+
import { getLayoutInfoFromTree } from "./helper.js"
|
|
14
|
+
|
|
15
|
+
const Flow = ({tree}) => {
|
|
16
|
+
const { fitView } = useReactFlow();
|
|
17
|
+
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
18
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (tree) {
|
|
22
|
+
const flowInfo = getLayoutInfoFromTree(tree.data, tree.animated ?? false);
|
|
23
|
+
|
|
24
|
+
//direction: TB, BT, LR, or RL, where T = top, B = bottom, L = left, and R = right.
|
|
25
|
+
const layouted = getLayoutedElements(
|
|
26
|
+
flowInfo.nodes,
|
|
27
|
+
flowInfo.edges,
|
|
28
|
+
{direction:tree.orientation}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
setNodes([...layouted.nodes]);
|
|
32
|
+
setEdges([...layouted.edges]);
|
|
33
|
+
|
|
34
|
+
fitView();
|
|
35
|
+
}
|
|
36
|
+
}, [tree]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<ReactFlow
|
|
40
|
+
nodes={nodes}
|
|
41
|
+
edges={edges}
|
|
42
|
+
onNodesChange={onNodesChange}
|
|
43
|
+
onEdgesChange={onEdgesChange}
|
|
44
|
+
colorMode={"dark"}
|
|
45
|
+
fitView
|
|
46
|
+
>
|
|
47
|
+
<Controls />
|
|
48
|
+
</ReactFlow>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const FlowDiagram = ({treeInfo}) => {
|
|
53
|
+
const [tree, setTree] = useState();
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
setTree(treeInfo)
|
|
57
|
+
}, [treeInfo])
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<ReactFlowProvider>
|
|
61
|
+
<Flow tree={tree} />
|
|
62
|
+
</ReactFlowProvider>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
File without changes
|