├── .DS_Store ├── .gitignore ├── babel.config.json ├── build ├── 1447088ba51e75d456b11844af8ffb47.png ├── 155.js ├── 155.js.LICENSE.txt ├── 19830cc33389d04486642c21a7318055.png ├── 1d117c4b02ea1b730465d4163dddd023.png ├── 26fbb59b16d5d573d4f30c898f53e713.png ├── 406.js ├── 406.js.LICENSE.txt ├── 455.js ├── 455.js.LICENSE.txt ├── 54dfb90430f1724620888ea4f2c8cc40.png ├── 58f1f4b6ca4101d2be09b056b731f23e.png ├── 60.js ├── 60.js.LICENSE.txt ├── 636b3b4616369268f3cd117451b467d0.png ├── 6946ac93990b755f69618215e5735a86.png ├── 787d662736b5ce645e80a2dfc2d036a9.png ├── 8140a3021832f52ec8422bbd2aae31bc.png ├── 83a7b472e426b6fca2a2b0526af7e33e.png ├── 89159d95eb50cb9633cd0953087c7f22.png ├── 9e3527d7cbb6a0c033769da4d554b19f.png ├── a912723cad5207d2593e9bf3573725ab.png ├── ff47e325a60b8225631a40c54454e244.png ├── index.html ├── main.js └── main.js.LICENSE.txt ├── package-lock.json ├── package.json ├── readme.md ├── src ├── App.js ├── Component1 │ ├── Button │ │ ├── Button.js │ │ ├── __test__ │ │ │ └── button.test.js │ │ └── button.css │ └── Table │ │ └── index.js ├── components │ ├── Editor │ │ ├── configuration.js │ │ └── index.js │ ├── LineCompo.js │ ├── NodeFlow.js │ ├── ReactFlowDnd │ │ ├── Sidebar.js │ │ ├── index.js │ │ └── sidebar.css │ ├── ReactFlowPoc │ │ ├── CustomColumn.js │ │ ├── CustomNode.js │ │ ├── edges.js │ │ ├── elements.js │ │ ├── index.js │ │ ├── nodes.js │ │ └── style.scss │ ├── ReactFlowPoc2 │ │ ├── CustomColumn.js │ │ ├── CustomNode.js │ │ ├── contextMenu.js │ │ ├── edges.js │ │ ├── elements.js │ │ ├── index.js │ │ ├── nodes.js │ │ └── style.scss │ ├── Reactflow │ │ ├── CustomEdge.js │ │ ├── CustomNodeSelector.js │ │ ├── ReactDndFlow.js │ │ ├── Reactflow.js │ │ ├── Sidebar.js │ │ ├── contextMenu.js │ │ ├── editorConfig.js │ │ ├── elements.js │ │ ├── styles.css │ │ └── suneditor.min.css │ ├── Sidebar.js │ ├── Xarrow │ │ ├── Line │ │ │ ├── Xarrow.js │ │ │ ├── anchors.js │ │ │ ├── propTypes.js │ │ │ ├── useXarrowProps.js │ │ │ └── utils │ │ │ │ ├── GetPosition.js │ │ │ │ ├── buzzier.js │ │ │ │ └── index.js │ │ ├── NodeArrow.js │ │ ├── Xwrapper.js │ │ ├── constants.js │ │ ├── edgeStyle.js │ │ ├── elements.js │ │ ├── index.js │ │ └── useXarrow.js │ ├── Xarrow2 │ │ ├── data.js │ │ ├── draggablebox.js │ │ ├── index.js │ │ └── shape.js │ ├── chatview.js │ ├── component2.js │ ├── flow │ │ └── index.js │ ├── sidebar.css │ ├── style.css │ └── style.scss ├── container │ ├── Route │ │ └── index.js │ └── home │ │ └── index.js ├── dnd.css ├── index.css ├── index.html ├── index.js ├── tree.css └── utils │ ├── ApiContext.js │ └── firebase.js └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-proposal-optional-chaining"] 4 | } -------------------------------------------------------------------------------- /build/1447088ba51e75d456b11844af8ffb47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/1447088ba51e75d456b11844af8ffb47.png -------------------------------------------------------------------------------- /build/155.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2017 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | Copyright (c) 2018 Jed Watson. 15 | Licensed under the MIT License (MIT), see 16 | http://jedwatson.github.io/classnames 17 | */ 18 | 19 | /*! ../../constants */ 20 | 21 | /*! ../Xwrapper */ 22 | 23 | /*! ../anchors */ 24 | 25 | /*! ../constants */ 26 | 27 | /*! ./Xarrow/Xarrow */ 28 | 29 | /*! ./Xwrapper */ 30 | 31 | /*! ./buzzier */ 32 | 33 | /*! ./constants */ 34 | 35 | /*! ./index */ 36 | 37 | /*! ./propTypes */ 38 | 39 | /*! ./types */ 40 | 41 | /*! ./useXarrow */ 42 | 43 | /*! ./useXarrowProps */ 44 | 45 | /*! ./utils */ 46 | 47 | /*! ./utils/GetPosition */ 48 | 49 | /*! lodash */ 50 | 51 | /*! prop-types */ 52 | 53 | /*! react */ 54 | 55 | /*!**********************!*\ 56 | !*** ./src/types.ts ***! 57 | \**********************/ 58 | 59 | /*!***********************!*\ 60 | !*** ./src/index.tsx ***! 61 | \***********************/ 62 | 63 | /*!************************!*\ 64 | !*** external "react" ***! 65 | \************************/ 66 | 67 | /*!*************************!*\ 68 | !*** external "lodash" ***! 69 | \*************************/ 70 | 71 | /*!**************************!*\ 72 | !*** ./src/Xwrapper.tsx ***! 73 | \**************************/ 74 | 75 | /*!***************************!*\ 76 | !*** ./src/constants.tsx ***! 77 | \***************************/ 78 | 79 | /*!***************************!*\ 80 | !*** ./src/useXarrow.tsx ***! 81 | \***************************/ 82 | 83 | /*!*****************************!*\ 84 | !*** external "prop-types" ***! 85 | \*****************************/ 86 | 87 | /*!*******************************!*\ 88 | !*** ./src/Xarrow/Xarrow.tsx ***! 89 | \*******************************/ 90 | 91 | /*!*******************************!*\ 92 | !*** ./src/Xarrow/anchors.ts ***! 93 | \*******************************/ 94 | 95 | /*!*********************************!*\ 96 | !*** ./src/Xarrow/propTypes.ts ***! 97 | \*********************************/ 98 | 99 | /*!***********************************!*\ 100 | !*** ./src/Xarrow/utils/index.ts ***! 101 | \***********************************/ 102 | 103 | /*!*************************************!*\ 104 | !*** ./src/Xarrow/utils/buzzier.js ***! 105 | \*************************************/ 106 | 107 | /*!**************************************!*\ 108 | !*** ./src/Xarrow/useXarrowProps.ts ***! 109 | \**************************************/ 110 | 111 | /*!******************************************!*\ 112 | !*** ./src/Xarrow/utils/GetPosition.tsx ***! 113 | \******************************************/ 114 | 115 | /** 116 | * @license 117 | * Lodash 118 | * Copyright OpenJS Foundation and other contributors 119 | * Released under MIT license 120 | * Based on Underscore.js 1.8.3 121 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 122 | */ 123 | 124 | /** 125 | * A better abstraction over CSS. 126 | * 127 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 128 | * @website https://github.com/cssinjs/jss 129 | * @license MIT 130 | */ 131 | 132 | /** @license React v0.20.2 133 | * scheduler.production.min.js 134 | * 135 | * Copyright (c) Facebook, Inc. and its affiliates. 136 | * 137 | * This source code is licensed under the MIT license found in the 138 | * LICENSE file in the root directory of this source tree. 139 | */ 140 | 141 | /** @license React v16.13.1 142 | * react-is.production.min.js 143 | * 144 | * Copyright (c) Facebook, Inc. and its affiliates. 145 | * 146 | * This source code is licensed under the MIT license found in the 147 | * LICENSE file in the root directory of this source tree. 148 | */ 149 | 150 | /** @license React v17.0.2 151 | * react-dom.production.min.js 152 | * 153 | * Copyright (c) Facebook, Inc. and its affiliates. 154 | * 155 | * This source code is licensed under the MIT license found in the 156 | * LICENSE file in the root directory of this source tree. 157 | */ 158 | 159 | /** @license React v17.0.2 160 | * react.production.min.js 161 | * 162 | * Copyright (c) Facebook, Inc. and its affiliates. 163 | * 164 | * This source code is licensed under the MIT license found in the 165 | * LICENSE file in the root directory of this source tree. 166 | */ 167 | -------------------------------------------------------------------------------- /build/19830cc33389d04486642c21a7318055.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/19830cc33389d04486642c21a7318055.png -------------------------------------------------------------------------------- /build/1d117c4b02ea1b730465d4163dddd023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/1d117c4b02ea1b730465d4163dddd023.png -------------------------------------------------------------------------------- /build/26fbb59b16d5d573d4f30c898f53e713.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/26fbb59b16d5d573d4f30c898f53e713.png -------------------------------------------------------------------------------- /build/406.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | * Sizzle CSS Selector Engine v2.3.6 15 | * https://sizzlejs.com/ 16 | * 17 | * Copyright JS Foundation and other contributors 18 | * Released under the MIT license 19 | * https://js.foundation/ 20 | * 21 | * Date: 2021-02-16 22 | */ 23 | 24 | /*! 25 | * jQuery JavaScript Library v3.6.0 26 | * https://jquery.com/ 27 | * 28 | * Includes Sizzle.js 29 | * https://sizzlejs.com/ 30 | * 31 | * Copyright OpenJS Foundation and other contributors 32 | * Released under the MIT license 33 | * https://jquery.org/license 34 | * 35 | * Date: 2021-03-02T17:08Z 36 | */ 37 | 38 | /*! 39 | Transformation Matrix v2.0 40 | (c) Epistemex 2014-2015 41 | www.epistemex.com 42 | By Ken Fyrstenberg 43 | Contributions by leeoniya. 44 | License: MIT, header required. 45 | */ 46 | 47 | /** 48 | * A better abstraction over CSS. 49 | * 50 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 51 | * @website https://github.com/cssinjs/jss 52 | * @license MIT 53 | */ 54 | 55 | /** @license React v0.20.2 56 | * scheduler.production.min.js 57 | * 58 | * Copyright (c) Facebook, Inc. and its affiliates. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE file in the root directory of this source tree. 62 | */ 63 | 64 | /** @license React v16.13.1 65 | * react-is.production.min.js 66 | * 67 | * Copyright (c) Facebook, Inc. and its affiliates. 68 | * 69 | * This source code is licensed under the MIT license found in the 70 | * LICENSE file in the root directory of this source tree. 71 | */ 72 | 73 | /** @license React v17.0.2 74 | * react-dom.production.min.js 75 | * 76 | * Copyright (c) Facebook, Inc. and its affiliates. 77 | * 78 | * This source code is licensed under the MIT license found in the 79 | * LICENSE file in the root directory of this source tree. 80 | */ 81 | 82 | /** @license React v17.0.2 83 | * react.production.min.js 84 | * 85 | * Copyright (c) Facebook, Inc. and its affiliates. 86 | * 87 | * This source code is licensed under the MIT license found in the 88 | * LICENSE file in the root directory of this source tree. 89 | */ 90 | -------------------------------------------------------------------------------- /build/455.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | * Sizzle CSS Selector Engine v2.3.6 9 | * https://sizzlejs.com/ 10 | * 11 | * Copyright JS Foundation and other contributors 12 | * Released under the MIT license 13 | * https://js.foundation/ 14 | * 15 | * Date: 2021-02-16 16 | */ 17 | 18 | /*! 19 | * jQuery JavaScript Library v3.6.0 20 | * https://jquery.com/ 21 | * 22 | * Includes Sizzle.js 23 | * https://sizzlejs.com/ 24 | * 25 | * Copyright OpenJS Foundation and other contributors 26 | * Released under the MIT license 27 | * https://jquery.org/license 28 | * 29 | * Date: 2021-03-02T17:08Z 30 | */ 31 | 32 | /** @license React v0.20.2 33 | * scheduler.production.min.js 34 | * 35 | * Copyright (c) Facebook, Inc. and its affiliates. 36 | * 37 | * This source code is licensed under the MIT license found in the 38 | * LICENSE file in the root directory of this source tree. 39 | */ 40 | 41 | /** @license React v16.13.1 42 | * react-is.production.min.js 43 | * 44 | * Copyright (c) Facebook, Inc. and its affiliates. 45 | * 46 | * This source code is licensed under the MIT license found in the 47 | * LICENSE file in the root directory of this source tree. 48 | */ 49 | 50 | /** @license React v17.0.2 51 | * react-dom.production.min.js 52 | * 53 | * Copyright (c) Facebook, Inc. and its affiliates. 54 | * 55 | * This source code is licensed under the MIT license found in the 56 | * LICENSE file in the root directory of this source tree. 57 | */ 58 | 59 | /** @license React v17.0.2 60 | * react.production.min.js 61 | * 62 | * Copyright (c) Facebook, Inc. and its affiliates. 63 | * 64 | * This source code is licensed under the MIT license found in the 65 | * LICENSE file in the root directory of this source tree. 66 | */ 67 | -------------------------------------------------------------------------------- /build/54dfb90430f1724620888ea4f2c8cc40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/54dfb90430f1724620888ea4f2c8cc40.png -------------------------------------------------------------------------------- /build/58f1f4b6ca4101d2be09b056b731f23e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/58f1f4b6ca4101d2be09b056b731f23e.png -------------------------------------------------------------------------------- /build/60.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2017 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | * Sizzle CSS Selector Engine v2.3.6 15 | * https://sizzlejs.com/ 16 | * 17 | * Copyright JS Foundation and other contributors 18 | * Released under the MIT license 19 | * https://js.foundation/ 20 | * 21 | * Date: 2021-02-16 22 | */ 23 | 24 | /*! 25 | * jQuery JavaScript Library v3.6.0 26 | * https://jquery.com/ 27 | * 28 | * Includes Sizzle.js 29 | * https://sizzlejs.com/ 30 | * 31 | * Copyright OpenJS Foundation and other contributors 32 | * Released under the MIT license 33 | * https://jquery.org/license 34 | * 35 | * Date: 2021-03-02T17:08Z 36 | */ 37 | 38 | /** @license React v0.20.2 39 | * scheduler.production.min.js 40 | * 41 | * Copyright (c) Facebook, Inc. and its affiliates. 42 | * 43 | * This source code is licensed under the MIT license found in the 44 | * LICENSE file in the root directory of this source tree. 45 | */ 46 | 47 | /** @license React v16.13.1 48 | * react-is.production.min.js 49 | * 50 | * Copyright (c) Facebook, Inc. and its affiliates. 51 | * 52 | * This source code is licensed under the MIT license found in the 53 | * LICENSE file in the root directory of this source tree. 54 | */ 55 | 56 | /** @license React v17.0.2 57 | * react-dom.production.min.js 58 | * 59 | * Copyright (c) Facebook, Inc. and its affiliates. 60 | * 61 | * This source code is licensed under the MIT license found in the 62 | * LICENSE file in the root directory of this source tree. 63 | */ 64 | 65 | /** @license React v17.0.2 66 | * react-is.production.min.js 67 | * 68 | * Copyright (c) Facebook, Inc. and its affiliates. 69 | * 70 | * This source code is licensed under the MIT license found in the 71 | * LICENSE file in the root directory of this source tree. 72 | */ 73 | 74 | /** @license React v17.0.2 75 | * react.production.min.js 76 | * 77 | * Copyright (c) Facebook, Inc. and its affiliates. 78 | * 79 | * This source code is licensed under the MIT license found in the 80 | * LICENSE file in the root directory of this source tree. 81 | */ 82 | -------------------------------------------------------------------------------- /build/636b3b4616369268f3cd117451b467d0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/636b3b4616369268f3cd117451b467d0.png -------------------------------------------------------------------------------- /build/6946ac93990b755f69618215e5735a86.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/6946ac93990b755f69618215e5735a86.png -------------------------------------------------------------------------------- /build/787d662736b5ce645e80a2dfc2d036a9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/787d662736b5ce645e80a2dfc2d036a9.png -------------------------------------------------------------------------------- /build/8140a3021832f52ec8422bbd2aae31bc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/8140a3021832f52ec8422bbd2aae31bc.png -------------------------------------------------------------------------------- /build/83a7b472e426b6fca2a2b0526af7e33e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/83a7b472e426b6fca2a2b0526af7e33e.png -------------------------------------------------------------------------------- /build/89159d95eb50cb9633cd0953087c7f22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/89159d95eb50cb9633cd0953087c7f22.png -------------------------------------------------------------------------------- /build/9e3527d7cbb6a0c033769da4d554b19f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/9e3527d7cbb6a0c033769da4d554b19f.png -------------------------------------------------------------------------------- /build/a912723cad5207d2593e9bf3573725ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/a912723cad5207d2593e9bf3573725ab.png -------------------------------------------------------------------------------- /build/ff47e325a60b8225631a40c54454e244.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kumarabhishek008/React-Node-Flow/cacf66289c1ecdb7b5b088be93e2c5792186ce26/build/ff47e325a60b8225631a40c54454e244.png -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | JJo template
-------------------------------------------------------------------------------- /build/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2017 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | Copyright (c) 2018 Jed Watson. 15 | Licensed under the MIT License (MIT), see 16 | http://jedwatson.github.io/classnames 17 | */ 18 | 19 | /*! ../../constants */ 20 | 21 | /*! ../Xwrapper */ 22 | 23 | /*! ../anchors */ 24 | 25 | /*! ../constants */ 26 | 27 | /*! ./Xarrow/Xarrow */ 28 | 29 | /*! ./Xwrapper */ 30 | 31 | /*! ./buzzier */ 32 | 33 | /*! ./constants */ 34 | 35 | /*! ./index */ 36 | 37 | /*! ./propTypes */ 38 | 39 | /*! ./types */ 40 | 41 | /*! ./useXarrow */ 42 | 43 | /*! ./useXarrowProps */ 44 | 45 | /*! ./utils */ 46 | 47 | /*! ./utils/GetPosition */ 48 | 49 | /*! lodash */ 50 | 51 | /*! prop-types */ 52 | 53 | /*! react */ 54 | 55 | /*!**********************!*\ 56 | !*** ./src/types.ts ***! 57 | \**********************/ 58 | 59 | /*!***********************!*\ 60 | !*** ./src/index.tsx ***! 61 | \***********************/ 62 | 63 | /*!************************!*\ 64 | !*** external "react" ***! 65 | \************************/ 66 | 67 | /*!*************************!*\ 68 | !*** external "lodash" ***! 69 | \*************************/ 70 | 71 | /*!**************************!*\ 72 | !*** ./src/Xwrapper.tsx ***! 73 | \**************************/ 74 | 75 | /*!***************************!*\ 76 | !*** ./src/constants.tsx ***! 77 | \***************************/ 78 | 79 | /*!***************************!*\ 80 | !*** ./src/useXarrow.tsx ***! 81 | \***************************/ 82 | 83 | /*!*****************************!*\ 84 | !*** external "prop-types" ***! 85 | \*****************************/ 86 | 87 | /*!*******************************!*\ 88 | !*** ./src/Xarrow/Xarrow.tsx ***! 89 | \*******************************/ 90 | 91 | /*!*******************************!*\ 92 | !*** ./src/Xarrow/anchors.ts ***! 93 | \*******************************/ 94 | 95 | /*!*********************************!*\ 96 | !*** ./src/Xarrow/propTypes.ts ***! 97 | \*********************************/ 98 | 99 | /*!***********************************!*\ 100 | !*** ./src/Xarrow/utils/index.ts ***! 101 | \***********************************/ 102 | 103 | /*!*************************************!*\ 104 | !*** ./src/Xarrow/utils/buzzier.js ***! 105 | \*************************************/ 106 | 107 | /*!**************************************!*\ 108 | !*** ./src/Xarrow/useXarrowProps.ts ***! 109 | \**************************************/ 110 | 111 | /*!******************************************!*\ 112 | !*** ./src/Xarrow/utils/GetPosition.tsx ***! 113 | \******************************************/ 114 | 115 | /** 116 | * @license 117 | * Lodash 118 | * Copyright OpenJS Foundation and other contributors 119 | * Released under MIT license 120 | * Based on Underscore.js 1.8.3 121 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 122 | */ 123 | 124 | /** 125 | * A better abstraction over CSS. 126 | * 127 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 128 | * @website https://github.com/cssinjs/jss 129 | * @license MIT 130 | */ 131 | 132 | /** @license React v0.20.2 133 | * scheduler.production.min.js 134 | * 135 | * Copyright (c) Facebook, Inc. and its affiliates. 136 | * 137 | * This source code is licensed under the MIT license found in the 138 | * LICENSE file in the root directory of this source tree. 139 | */ 140 | 141 | /** @license React v16.13.1 142 | * react-is.production.min.js 143 | * 144 | * Copyright (c) Facebook, Inc. and its affiliates. 145 | * 146 | * This source code is licensed under the MIT license found in the 147 | * LICENSE file in the root directory of this source tree. 148 | */ 149 | 150 | /** @license React v17.0.2 151 | * react-dom.production.min.js 152 | * 153 | * Copyright (c) Facebook, Inc. and its affiliates. 154 | * 155 | * This source code is licensed under the MIT license found in the 156 | * LICENSE file in the root directory of this source tree. 157 | */ 158 | 159 | /** @license React v17.0.2 160 | * react.production.min.js 161 | * 162 | * Copyright (c) Facebook, Inc. and its affiliates. 163 | * 164 | * This source code is licensed under the MIT license found in the 165 | * LICENSE file in the root directory of this source tree. 166 | */ 167 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "travelviser", 3 | "version": "1.0.0", 4 | "description": "this is a trvel app", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "react-scripts test", 8 | "dev": "webpack --mode development", 9 | "start": "webpack serve --open", 10 | "server": "node server.js", 11 | "build": "webpack --mode production" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@editorjs/editorjs": "^2.22.2", 17 | "@emotion/react": "^11.11.0", 18 | "@emotion/styled": "^11.11.0", 19 | "@material-ui/core": "^4.11.3", 20 | "@material-ui/icons": "^4.11.2", 21 | "@mui/icons-material": "^5.11.16", 22 | "@mui/material": "^5.13.2", 23 | "@mui/styled-engine-sc": "^5.12.0", 24 | "axios": "^0.27.2", 25 | "bootstrap": "^4.6.0", 26 | "dagre": "^0.8.5", 27 | "firebase": "^8.10.0", 28 | "jquery": "^3.6.0", 29 | "lodash": "^4.17.21", 30 | "moment": "^2.29.1", 31 | "re-resizable": "^6.10.0", 32 | "react": "^17.0.1", 33 | "react-bootstrap": "^1.4.3", 34 | "react-country-region-selector": "^3.0.2", 35 | "react-dom": "^17.0.1", 36 | "react-draggable": "^4.4.4", 37 | "react-flow-renderer": "^10.0.8", 38 | "react-lottie": "^1.2.3", 39 | "react-router-dom": "^5.3.4", 40 | "react-scripts": "^4.0.3", 41 | "react-xarrows": "^2.0.2", 42 | "sinon": "^14.0.0", 43 | "styled-components": "^5.3.11", 44 | "suneditor": "^2.42.0", 45 | "suneditor-react": "^3.3.1", 46 | "uuid": "^8.3.2" 47 | }, 48 | "devDependencies": { 49 | "@babel/core": "^7.15.5", 50 | "@babel/plugin-proposal-optional-chaining": "^7.14.5", 51 | "@babel/preset-env": "^7.12.11", 52 | "@babel/preset-react": "^7.12.10", 53 | "@testing-library/react": "^12.1.2", 54 | "babel-loader": "^8.2.2", 55 | "css-loader": "^5.0.1", 56 | "enzyme": "^3.11.0", 57 | "enzyme-adapter-react-16": "^1.15.6", 58 | "express": "^4.17.1", 59 | "file-loader": "^6.2.0", 60 | "html-webpack-plugin": "^4.5.1", 61 | "install": "^0.13.0", 62 | "jest-dom": "^4.0.0", 63 | "npm": "^8.1.1", 64 | "postcss": "^8.2.4", 65 | "postcss-loader": "^5.0.0", 66 | "react-pixel-perfect": "0.0.3", 67 | "react-test-renderer": "^17.0.2", 68 | "sass": "^1.32.6", 69 | "sass-loader": "^10.1.1", 70 | "scss-loader": "^0.0.1", 71 | "style-loader": "^2.0.0", 72 | "terser-webpack-plugin": "^5.2.4", 73 | "webpack": "^5.19.0", 74 | "webpack-cli": "^4.4.0", 75 | "webpack-dev-middleware": "^3.7.3", 76 | "webpack-dev-server": "^3.11.2" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # draggble and connect nodes 2 | 3 | # git clone repo url 4 | 5 | # npm i --legacy-peer-deps 6 | 7 | # export NODE_OPTIONS=--openssl-legacy-provider 8 | 9 | # set NODE_OPTIONS=--openssl-legacy-provider 10 | 11 | # npm update postcss --legacy-peer-deps 12 | 13 | # npm start 14 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Routes from "./container/Route"; 3 | 4 | const App = (props) => { 5 | return ; 6 | }; 7 | 8 | export default App; 9 | -------------------------------------------------------------------------------- /src/Component1/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './button.css'; 3 | 4 | const Button = ({ label }) => { 5 | const handleSubmit = (e) => { e.preventDefault() } 6 | return ( 7 |
8 |
9 | 10 | 11 |
12 |
13 | ) 14 | } 15 | 16 | export default Button 17 | -------------------------------------------------------------------------------- /src/Component1/Button/__test__/button.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDom from 'react-dom'; 3 | import Button from './../Button'; 4 | import { isTSAnyKeyword } from '@babel/types'; 5 | import { shallow } from 'enzyme' 6 | 7 | it('renders without crashing', ()=>{ 8 | const div = document.createElement('div'); 9 | const comp = shallow( 33 |
34 |
35 |
36 |
37 | ); 38 | }; 39 | 40 | export default Editor; 41 | -------------------------------------------------------------------------------- /src/components/LineCompo.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import './style.css' 3 | 4 | const LineCompo = (props) => {debugger 5 | const { start, end, posx, posy } = props 6 | 7 | const ax = document.getElementById(start).clientTop; 8 | const ay = document.getElementById(start).clientLeft; 9 | 10 | const bx = document.getElementById(end).clientTop; 11 | const by = document.getElementById(end).clientLeft; 12 | 13 | return ( 14 | 15 | 24 | 25 | ) 26 | } 27 | 28 | export default LineCompo 29 | -------------------------------------------------------------------------------- /src/components/NodeFlow.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import "./style.scss"; 3 | 4 | const NodeFlow = () => { 5 | const [pos, setpos] = useState(); 6 | const [dragPos, setDragPos] = useState({ 7 | x: "", 8 | y: "", 9 | }); 10 | 11 | const handleDragPosition = (e) => { 12 | console.log(e.pageX, e.pageY); 13 | setDragPos({ 14 | x: e.pageX, 15 | y: e.pageY, 16 | }); 17 | }; 18 | 19 | const onDragEnd = (e) => { 20 | console.log(e.pageX, e.pageY); 21 | setDragPos({ 22 | x: e.pageX, 23 | y: e.pageY, 24 | }); 25 | }; 26 | const a = { b: { c : { d: 'fhfbdhvjh'}}}; 27 | console.log(a?.b?.c?.d) 28 | 29 | return ( 30 | <> 31 | {/*
38 |
    39 |

    ferfgergrtgrtege

    40 |
41 |
*/} 42 | 43 | 44 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default NodeFlow; 55 | -------------------------------------------------------------------------------- /src/components/ReactFlowDnd/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => { 4 | const onDragStart = (event, nodeType) => { 5 | event.dataTransfer.setData('application/reactflow', nodeType); 6 | event.dataTransfer.effectAllowed = 'move'; 7 | }; 8 | 9 | return ( 10 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/ReactFlowDnd/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import ReactFlow, { 3 | ReactFlowProvider, 4 | addEdge, 5 | // removeElements, 6 | Controls, 7 | } from "react-flow-renderer"; 8 | 9 | import Sidebar from "./Sidebar"; 10 | 11 | // import '../../dnd.css'; 12 | 13 | const initialElements = [ 14 | { 15 | id: "1", 16 | type: "input", 17 | data: { label: "input node" }, 18 | position: { x: 250, y: 5 }, 19 | }, 20 | ]; 21 | 22 | let id = 0; 23 | const getId = () => `dndnode_${id++}`; 24 | 25 | const DnDFlow = () => { 26 | const reactFlowWrapper = useRef(null); 27 | const [reactFlowInstance, setReactFlowInstance] = useState(null); 28 | const [elements, setElements] = useState(initialElements); 29 | const onConnect = (params) => setElements((els) => addEdge(params, els)); 30 | const onElementsRemove = (elementsToRemove) => setElements((els) => els); 31 | 32 | const onLoad = (_reactFlowInstance) => 33 | setReactFlowInstance(_reactFlowInstance); 34 | 35 | const onDragOver = (event) => { 36 | event.preventDefault(); 37 | event.dataTransfer.dropEffect = "move"; 38 | }; 39 | 40 | const onDrop = (event) => { 41 | event.preventDefault(); 42 | 43 | const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); 44 | const type = event.dataTransfer.getData("application/reactflow"); 45 | const position = reactFlowInstance.project({ 46 | x: event.clientX - reactFlowBounds.left, 47 | y: event.clientY - reactFlowBounds.top, 48 | }); 49 | const newNode = { 50 | id: getId(), 51 | type, 52 | position, 53 | data: { label: `${type} node` }, 54 | }; 55 | 56 | setElements((es) => es.concat(newNode)); 57 | }; 58 | 59 | return ( 60 |
61 | 62 |
63 | 71 | 72 | 73 |
74 | 75 |
76 |
77 | ); 78 | }; 79 | 80 | export default DnDFlow; 81 | -------------------------------------------------------------------------------- /src/components/ReactFlowDnd/sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar{ 2 | animation: openSideBar 200ms ease-in; 3 | background: linear-gradient(to left, #b3b3b3, #fff); 4 | /* width: 20rem; */ 5 | height: 100vh; 6 | padding: 1rem; 7 | position: fixed; 8 | left: 0; 9 | width: 20rem; 10 | } 11 | .removeSidebar{ 12 | background: linear-gradient(to left, #b3b3b3, #fff); 13 | /* width: 20rem; */ 14 | height: 100vh; 15 | padding: 1rem; 16 | position: fixed; 17 | left: 0; 18 | width: 20rem; 19 | animation: removeSideBar 300ms ease-in; 20 | } 21 | .sidebar_cover{ 22 | position: relative; 23 | } 24 | .sidebar_header{ 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: center; 28 | margin-bottom: 2rem; 29 | } 30 | h1{ 31 | font-size: 32px; 32 | color: rgb(54, 54, 54); 33 | margin: 0; 34 | } 35 | ul{ 36 | padding: 0; 37 | } 38 | li{ 39 | list-style: none; 40 | padding: 1rem 1.5rem; 41 | font-weight: 600; 42 | color: #828282; 43 | background-color: #b5cffb; 44 | margin-top: 0.5rem; 45 | border-radius: 5px; 46 | } 47 | li.active{ 48 | background-color: #367bee; 49 | color: white; 50 | } 51 | li:hover{ 52 | background-color: #367bee; 53 | color: white; 54 | } 55 | 56 | @keyframes openSideBar { 57 | 0%{ 58 | width: 0rem; 59 | } 60 | 100%{ 61 | width: 20rem; 62 | } 63 | } 64 | @keyframes removeSideBar { 65 | 0%{ 66 | width: 20rem; 67 | } 68 | 100%{ 69 | width: 0rem; 70 | } 71 | } -------------------------------------------------------------------------------- /src/components/ReactFlowPoc/CustomColumn.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { Handle, Position } from 'react-flow-renderer'; 3 | 4 | const handleStyle = { left: 10 }; 5 | 6 | function CustomColumn({ data }) { 7 | const onChange = useCallback((evt) => { 8 | console.log(evt.target.value); 9 | }, []); 10 | 11 | return ( 12 |
13 | {/* */} 14 | 15 |
16 | {data?.label} 17 |
18 | {/* */} 19 | 20 |
21 | ); 22 | } 23 | 24 | export default CustomColumn; 25 | -------------------------------------------------------------------------------- /src/components/ReactFlowPoc/CustomNode.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { Handle, Position } from 'react-flow-renderer'; 3 | import './style.scss' 4 | 5 | const handleStyle = { left: 10 }; 6 | 7 | function CustomeNodeParent({ data }) { 8 | const onChange = useCallback((evt) => { 9 | console.log(evt.target.value); 10 | }, []); 11 | 12 | return ( 13 |
14 | 15 |
16 |
17 | 18 |
19 | ); 20 | } 21 | 22 | export default CustomeNodeParent; 23 | -------------------------------------------------------------------------------- /src/components/ReactFlowPoc/edges.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | // { id: 'a1-a2', source: 'A-1', target: 'A-2' }, 3 | // { id: 'a2-b', source: 'A-2', target: 'B' }, 4 | // { id: 'a2-c', source: 'A-2', target: 'C' }, 5 | // { id: 'b1-b2', source: 'B-1', target: 'B-2' }, 6 | // { id: 'b1-b3', source: 'B-1', target: 'B-3' }, 7 | ]; -------------------------------------------------------------------------------- /src/components/ReactFlowPoc/elements.js: -------------------------------------------------------------------------------- 1 | export const elements = [ 2 | { 3 | id: 'A', 4 | type: 'group', 5 | position: { x: 0, y: 0 }, 6 | style: { 7 | width: 170, 8 | height: 140, 9 | }, 10 | }, 11 | { 12 | id: 'A-1', 13 | type: 'input', 14 | data: { label: 'Child Node 1' }, 15 | position: { x: 10, y: 10 }, 16 | parentNode: 'A', 17 | extent: 'parent', 18 | }, 19 | { 20 | id: 'A-2', 21 | data: { label: 'Child Node 2' }, 22 | position: { x: 10, y: 90 }, 23 | parentNode: 'A', 24 | extent: 'parent', 25 | }, 26 | { 27 | id: 'B', 28 | type: 'output', 29 | position: { x: -100, y: 200 }, 30 | data: { label: 'Node B' }, 31 | }, 32 | { 33 | id: 'C', 34 | type: 'output', 35 | position: { x: 100, y: 200 }, 36 | data: { label: 'Node C' }, 37 | }, 38 | ] -------------------------------------------------------------------------------- /src/components/ReactFlowPoc/index.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | import ReactFlow, { 3 | addEdge, 4 | applyEdgeChanges, 5 | applyNodeChanges, 6 | Background, 7 | } from 'react-flow-renderer'; 8 | 9 | import initialNodes from './nodes.js'; 10 | import initialEdges from './edges.js'; 11 | import CustomeNodeParent from './CustomNode.js'; 12 | import CustomColumn from './CustomColumn.js'; 13 | import { MarkerType } from 'react-flow-renderer'; 14 | import { elements } from './elements.js'; 15 | 16 | const rfStyle = { 17 | backgroundColor: '#D0C0F7', 18 | height :'100vh' 19 | }; 20 | 21 | const nodeTypes = { customParentnode: CustomeNodeParent, customColummn : CustomColumn }; 22 | 23 | function ReactFlowPoc(props) { 24 | const [nodes, setNodes] = useState(elements); 25 | const [edges, setEdges] = useState(initialEdges); 26 | 27 | const onNodesChange = useCallback( 28 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 29 | [setNodes] 30 | ); 31 | const onEdgesChange = useCallback( 32 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 33 | [setEdges] 34 | ); 35 | const onConnect = useCallback( 36 | (connection) => setEdges((eds) => addEdge({...connection, type:'step', markerStart:{type:MarkerType.Arrow}, markerEnd:{type:MarkerType.ArrowClosed}}, eds)), 37 | [setEdges] 38 | ); 39 | 40 | return ( 41 | 54 | {/* */} 55 | 56 | ); 57 | } 58 | 59 | export default ReactFlowPoc; 60 | -------------------------------------------------------------------------------- /src/components/ReactFlowPoc/nodes.js: -------------------------------------------------------------------------------- 1 | 2 | const nodes = [ 3 | { 4 | id: 'A', 5 | type: 'customParentnode', 6 | position: { x: 0, y: 0 }, 7 | className:"custom_node_parent", 8 | }, 9 | { 10 | id: 'A-1', 11 | type:'customColummn', 12 | className:'custom_column_child', 13 | data: { label: 'Column 1' }, 14 | position: { x: 0, y: 0 }, 15 | draggable:false, 16 | parentNode: 'A', 17 | extent: 'parent', 18 | }, 19 | { 20 | id: 'A-2', 21 | type:'customColummn', 22 | data: { label: 'Column 2' }, 23 | className:'custom_column_child', 24 | position: { x: 0, y: 20}, 25 | draggable:false, 26 | parentNode: 'A', 27 | extent: 'parent', 28 | }, 29 | { 30 | id: 'B', 31 | type: 'customParentnode', 32 | position: { x: 100, y: 0 }, 33 | className:"custom_node_parent", 34 | }, 35 | { 36 | id: 'B-1', 37 | type:'customColummn', 38 | className:'custom_column_child', 39 | data: { label: 'Column 1' }, 40 | position: { x: 0, y: 0 }, 41 | draggable:false, 42 | parentNode: 'B', 43 | extent: 'parent', 44 | }, 45 | { 46 | id: 'B-2', 47 | type:'customColummn', 48 | data: { label: 'Column 2' }, 49 | className:'custom_column_child', 50 | position: { x: 0, y: 20}, 51 | draggable:false, 52 | parentNode: 'B', 53 | extent: 'parent', 54 | }, 55 | ]; 56 | 57 | export default nodes; -------------------------------------------------------------------------------- /src/components/ReactFlowPoc/style.scss: -------------------------------------------------------------------------------- 1 | .custom_node_parent{ 2 | border: 1px solid #b3b3b3; 3 | width: 150px; 4 | height: 170px; 5 | } 6 | .custom_column_child{ 7 | border: 1px solid #b3b3b3; 8 | width: 150px; 9 | height: 20px; 10 | font-size: 12px; 11 | } 12 | .react-flow__handle{ 13 | // opacity: 0.01; 14 | } -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/CustomColumn.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { Handle, Position } from 'react-flow-renderer'; 3 | 4 | const handleStyle = { left: 10 }; 5 | 6 | function CustomColumn({ data }) { 7 | const onChange = useCallback((evt) => { 8 | console.log(evt.target.value); 9 | }, []); 10 | 11 | return ( 12 |
13 | {/* */} 14 | 15 |
16 | {data?.label} 17 |
18 | {/* */} 19 | 20 |
21 | ); 22 | } 23 | 24 | export default CustomColumn; 25 | -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/CustomNode.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { Handle, Position } from 'react-flow-renderer'; 3 | import './style.scss' 4 | 5 | const handleStyle = { left: 10 }; 6 | 7 | function CustomeNodeParent({ data }) { 8 | const onChange = useCallback((evt) => { 9 | console.log(evt.target.value); 10 | }, []); 11 | 12 | return ( 13 |
14 | 15 |
16 |
17 | 18 |
19 | ); 20 | } 21 | 22 | export default CustomeNodeParent; 23 | -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/contextMenu.js: -------------------------------------------------------------------------------- 1 | import { FC, memo } from "react"; 2 | import Menu from "@mui/material/Menu"; 3 | import MenuItem from "@mui/material/MenuItem"; 4 | 5 | // type Action = { 6 | // label: string; 7 | // effect: (...args: any[]) => any; 8 | // }; 9 | 10 | // type Position = { 11 | // x: number; 12 | // y: number; 13 | // }; 14 | 15 | // type Props = { 16 | // actions: Action[]; 17 | // isOpen: boolean; 18 | // position: Position; 19 | // onMouseLeave: () => void; 20 | // }; 21 | 22 | export const ContextMenu = memo( 23 | ({ isOpen, position, anchorEle, actions = [], onMouseLeave }) => 24 | isOpen ? ( 25 | <> 26 | 35 | {actions.map((action) => ( 36 | 37 | {action.label} 38 | 39 | ))} 40 | 41 | 42 | ) : //
57 | // 60 | //
61 | null 62 | ); 63 | -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/edges.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | // { id: 'a1-a2', source: 'A-1', target: 'A-2' }, 3 | // { id: 'a2-b', source: 'A-2', target: 'B' }, 4 | // { id: 'a2-c', source: 'A-2', target: 'C' }, 5 | // { id: 'b1-b2', source: 'B-1', target: 'B-2' }, 6 | // { id: 'b1-b3', source: 'B-1', target: 'B-3' }, 7 | ]; -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/elements.js: -------------------------------------------------------------------------------- 1 | export const elements = [ 2 | { 3 | id: 'A', 4 | type: 'group', 5 | position: { x: 0, y: 0 }, 6 | style: { 7 | width: 170, 8 | height: 140, 9 | }, 10 | }, 11 | { 12 | id: 'A-1', 13 | type: 'input', 14 | data: { label: 'Child Node 1' }, 15 | position: { x: 10, y: 10 }, 16 | parentNode: 'A', 17 | extent: 'parent', 18 | }, 19 | { 20 | id: 'A-2', 21 | data: { label: 'Child Node 2' }, 22 | position: { x: 10, y: 90 }, 23 | parentNode: 'A', 24 | extent: 'parent', 25 | }, 26 | { 27 | id: 'B', 28 | type: 'output', 29 | position: { x: -100, y: 200 }, 30 | data: { label: 'Node B' }, 31 | }, 32 | { 33 | id: 'C', 34 | type: 'output', 35 | position: { x: 100, y: 200 }, 36 | data: { label: 'Node C' }, 37 | }, 38 | ] -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/index.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | import ReactFlow, { 3 | addEdge, 4 | applyEdgeChanges, 5 | applyNodeChanges, 6 | Background, 7 | MiniMap, 8 | } from "react-flow-renderer"; 9 | 10 | import initialNodes from "./nodes.js"; 11 | import initialEdges from "./edges.js"; 12 | import CustomeNodeParent from "./CustomNode.js"; 13 | import CustomColumn from "./CustomColumn.js"; 14 | import { MarkerType } from "react-flow-renderer"; 15 | import { elements } from "./elements.js"; 16 | import { ContextMenu } from "./contextMenu"; 17 | 18 | const rfStyle = { 19 | backgroundColor: "#D0C0F7", 20 | height: "100vh", 21 | }; 22 | 23 | const bgColor = "#1A192B"; 24 | 25 | const nodeTypes = { 26 | customParentnode: CustomeNodeParent, 27 | customColummn: CustomColumn, 28 | }; 29 | 30 | function ReactFlowPoc2(props) { 31 | const [nodes, setNodes] = useState(elements); 32 | const [edges, setEdges] = useState(initialEdges); 33 | const [isOpen, setIsOpen] = useState(false); 34 | const [position, setPosition] = useState({ x: 0, y: 0 }); 35 | const [anchorEle, setAnchorEle] = useState(null); 36 | 37 | const onNodesChange = useCallback( 38 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 39 | [setNodes] 40 | ); 41 | const onEdgesChange = useCallback( 42 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 43 | [setEdges] 44 | ); 45 | const onConnect = useCallback( 46 | (connection) => 47 | setEdges((eds) => 48 | addEdge( 49 | { 50 | ...connection, 51 | type: "step", 52 | markerStart: { type: MarkerType.Arrow }, 53 | markerEnd: { type: MarkerType.ArrowClosed }, 54 | }, 55 | eds 56 | ) 57 | ), 58 | [setEdges] 59 | ); 60 | 61 | const deleteNode = () => { 62 | setNodes((elements) => 63 | elements.filter((element) => element.id != nodes.id) 64 | ); 65 | setIsOpen(false); 66 | }; 67 | 68 | const onContextMenu = (e) => { 69 | e.preventDefault(); 70 | setIsOpen(true); 71 | setAnchorEle(e.currentTarget); 72 | }; 73 | 74 | const resetFunc = (e) => { 75 | // e.preventDefault(); 76 | setIsOpen(false); 77 | // setAnchorEle(null); 78 | }; 79 | 80 | return ( 81 | 98 | {/* */} 99 | setIsOpen(false)} 104 | actions={[ 105 | { label: "Delete", effect: deleteNode }, 106 | { label: "Edit", effect: deleteNode }, 107 | ]} 108 | /> 109 | { 111 | if (n.type === "input") return "#0041d0"; 112 | if (n.type === "selectorNode") return bgColor; 113 | if (n.type === "output") return "#ff0072"; 114 | }} 115 | nodeColor={(n) => { 116 | if (n.type === "selectorNode") return bgColor; 117 | return "#fff"; 118 | }} 119 | /> 120 | 121 | ); 122 | } 123 | 124 | export default ReactFlowPoc2; 125 | -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/nodes.js: -------------------------------------------------------------------------------- 1 | 2 | const nodes = [ 3 | { 4 | id: 'A', 5 | type: 'customParentnode', 6 | position: { x: 0, y: 0 }, 7 | className:"custom_node_parent", 8 | }, 9 | { 10 | id: 'A-1', 11 | type:'customColummn', 12 | className:'custom_column_child', 13 | data: { label: 'Column 1' }, 14 | position: { x: 0, y: 0 }, 15 | draggable:false, 16 | parentNode: 'A', 17 | extent: 'parent', 18 | }, 19 | { 20 | id: 'A-2', 21 | type:'customColummn', 22 | data: { label: 'Column 2' }, 23 | className:'custom_column_child', 24 | position: { x: 0, y: 20}, 25 | draggable:false, 26 | parentNode: 'A', 27 | extent: 'parent', 28 | }, 29 | { 30 | id: 'B', 31 | type: 'customParentnode', 32 | position: { x: 100, y: 0 }, 33 | className:"custom_node_parent", 34 | }, 35 | { 36 | id: 'B-1', 37 | type:'customColummn', 38 | className:'custom_column_child', 39 | data: { label: 'Column 1' }, 40 | position: { x: 0, y: 0 }, 41 | draggable:false, 42 | parentNode: 'B', 43 | extent: 'parent', 44 | }, 45 | { 46 | id: 'B-2', 47 | type:'customColummn', 48 | data: { label: 'Column 2' }, 49 | className:'custom_column_child', 50 | position: { x: 0, y: 20}, 51 | draggable:false, 52 | parentNode: 'B', 53 | extent: 'parent', 54 | }, 55 | ]; 56 | 57 | export default nodes; -------------------------------------------------------------------------------- /src/components/ReactFlowPoc2/style.scss: -------------------------------------------------------------------------------- 1 | .custom_node_parent{ 2 | border: 1px solid #b3b3b3; 3 | width: 150px; 4 | height: 170px; 5 | } 6 | .custom_column_child{ 7 | border: 1px solid #b3b3b3; 8 | width: 150px; 9 | height: 20px; 10 | font-size: 12px; 11 | } 12 | .react-flow__handle{ 13 | // opacity: 0.01; 14 | } -------------------------------------------------------------------------------- /src/components/Reactflow/CustomEdge.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | getBezierPath, 4 | getSmoothStepPath, 5 | getEdgeCenter, 6 | getMarkerEnd, 7 | } from 'react-flow-renderer'; 8 | 9 | import './styles.css'; 10 | 11 | const foreignObjectSize = 20; 12 | 13 | const onEdgeClick = (evt, id) => { 14 | evt.stopPropagation(); 15 | alert(`remove ${id}`); 16 | }; 17 | 18 | export default function CustomEdge({ 19 | id, 20 | sourceX, 21 | sourceY, 22 | targetX, 23 | targetY, 24 | sourcePosition, 25 | targetPosition, 26 | style = {}, 27 | data, 28 | arrowHeadType, 29 | markerEndId, 30 | }) { 31 | const edgePath = getSmoothStepPath({ 32 | sourceX, 33 | sourceY, 34 | sourcePosition, 35 | targetX, 36 | targetY, 37 | targetPosition, 38 | }); 39 | const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); 40 | const [edgeCenterX, edgeCenterY] = getEdgeCenter({ 41 | sourceX, 42 | sourceY, 43 | targetX, 44 | targetY, 45 | }); 46 | 47 | return ( 48 | <> 49 | 57 | 65 | 66 |
67 | 68 | 69 | 70 | ); 71 | } -------------------------------------------------------------------------------- /src/components/Reactflow/CustomNodeSelector.js: -------------------------------------------------------------------------------- 1 | import React, { } from 'react'; 2 | 3 | import { Handle } from 'react-flow-renderer'; 4 | const CustomNodeSelector = ({ data, isConnectable }) => { 5 | return ( 6 | <> 7 | console.log('handle onConnect', params)} 12 | isConnectable={isConnectable} 13 | /> 14 |
15 | 22 | 23 | ); 24 | }; 25 | 26 | export default CustomNodeSelector; -------------------------------------------------------------------------------- /src/components/Reactflow/ReactDndFlow.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react'; 2 | import ReactFlow, { 3 | ReactFlowProvider, 4 | addEdge, 5 | removeElements, 6 | Controls, 7 | } from 'react-flow-renderer'; 8 | 9 | import Sidebar from './Sidebar'; 10 | 11 | import '../../dnd.css'; 12 | 13 | const initialElements = [{ id: '1', type: 'input', data: { label: 'input node' }, position: { x: 250, y: 5 } }]; 14 | 15 | const onDragOver = (event) => { 16 | event.preventDefault(); 17 | event.dataTransfer.dropEffect = 'move'; 18 | }; 19 | 20 | let id = 0; 21 | const getId = () => `dndnode_${id++}`; 22 | 23 | const DnDFlow = () => { 24 | 25 | const reactFlowWrapper = useRef(null); 26 | const [reactFlowInstance, setReactFlowInstance] = useState(); 27 | const [elements, setElements] = useState(initialElements); 28 | 29 | const onConnect = (params => setElements((els) => addEdge(params, els))) 30 | const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els)); 31 | const onLoad = (_reactFlowInstance) => setReactFlowInstance(_reactFlowInstance); 32 | 33 | const onDrop = (event) => { 34 | event.preventDefault(); 35 | 36 | if (reactFlowInstance) { 37 | const type = event.dataTransfer.getData('application/reactflow'); 38 | const position = reactFlowInstance.project({ x: event.clientX, y: event.clientY - 40 }); 39 | const newNode = { 40 | id: getId(), 41 | type, 42 | position, 43 | data: { label: `${type} node` }, 44 | }; 45 | 46 | setElements((es) => es.concat(newNode)); 47 | } 48 | }; 49 | 50 | return ( 51 |
52 | 53 |
54 | 62 | 63 | 64 |
65 | 66 |
67 |
68 | ); 69 | }; 70 | 71 | export default DnDFlow; -------------------------------------------------------------------------------- /src/components/Reactflow/Reactflow.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import Dialog from '@material-ui/core/Dialog'; 4 | import DialogActions from '@material-ui/core/DialogActions'; 5 | import DialogContent from '@material-ui/core/DialogContent'; 6 | import DialogContentText from '@material-ui/core/DialogContentText'; 7 | import DialogTitle from '@material-ui/core/DialogTitle'; 8 | import { TextField } from '@material-ui/core'; 9 | import elementsArr from './elements'; 10 | import { ContextMenu } from './contextMenu'; 11 | import './styles.css'; 12 | /** 13 | * imported these two component *********************** 14 | */ 15 | import CustomEdge from './CustomEdge'; 16 | import CustomNodeSelector from './CustomNodeSelector'; 17 | import Editor from './editorConfig'; 18 | /***************************************************** */ 19 | 20 | import ReactFlow, { 21 | isEdge, 22 | removeElements, 23 | addEdge, 24 | MiniMap, 25 | Controls, 26 | } from 'react-flow-renderer'; 27 | 28 | import '../../index.css'; 29 | 30 | 31 | 32 | const initBgColor = '#1A192B'; 33 | 34 | const connectionLineStyle = { stroke: '#fff' }; 35 | const snapGrid = [20, 20]; 36 | 37 | //added new onload function 38 | const onLoad = (reactFlowInstance) => { 39 | reactFlowInstance.fitView(); 40 | console.log(reactFlowInstance.getElements()); 41 | }; 42 | 43 | // ************* added node types ******************** 44 | const edgeTypes = { 45 | customedge: CustomEdge, 46 | }; 47 | 48 | const nodeTypes = { 49 | customnode : CustomNodeSelector 50 | } 51 | // ************************************** 52 | const Reactflow = () => { 53 | const [open, setOpen] = useState(false); 54 | const [reactflowInstance, setReactflowInstance] = useState(null); 55 | const [elements, setElements] = useState([]); // main data elements for save 56 | const [bgColor, setBgColor] = useState(initBgColor); 57 | const [nodeData, setnodeData] = useState(null); 58 | const [inputChange, setinputChange] = useState("") 59 | const [selectedElement, setselectedElement] = useState(null); 60 | const [isOpen, setIsOpen] = useState(false); 61 | const [position, setPosition] = useState({ x: 0, y: 0 }); 62 | const [permission, setpermission] = useState(true) 63 | 64 | const updateNodedata = (text, node) => { 65 | const findElementindex = elements.findIndex((items)=>items.id===node.id); 66 | if(findElementindex>-1 && elements[findElementindex]?.data?.label){ 67 | elements[findElementindex].data.label = text; 68 | setElements([...elements]) 69 | } 70 | } 71 | 72 | const onNodeDragStop = (event, node) => { 73 | const findElementindex = elements.findIndex((items)=>items.id===node.id); 74 | if(findElementindex>-1){ 75 | elements[findElementindex]= node; 76 | setElements([...elements]) 77 | } 78 | console.log(node, elements) 79 | }; 80 | 81 | const onElementClick = (event, node) => { 82 | handleClickOpen(); 83 | setnodeData(node); 84 | const findElement = elements.find(items=>items.id===node.id); 85 | if(findElement){ 86 | setinputChange(findElement?.data?.label||findElement?.label); 87 | } 88 | }; 89 | 90 | useEffect(() => { 91 | setElements(elementsArr); 92 | }, []) 93 | 94 | const handleClickOpen = () => { 95 | setOpen(true); 96 | }; 97 | 98 | const handleClose = () => { 99 | setOpen(false); 100 | setElements([...elements]) 101 | setnodeData(null); 102 | }; 103 | 104 | useEffect(() => { 105 | if (reactflowInstance && elements.length > 0) { 106 | reactflowInstance.fitView(); 107 | } 108 | }, [reactflowInstance, elements.length]); 109 | 110 | const onElementsRemove = useCallback( 111 | (elementsToRemove) => 112 | setElements((els) => removeElements(elementsToRemove, els)), 113 | [] 114 | ); 115 | 116 | // chnaged label in data 117 | const onConnect = useCallback( 118 | (params) => 119 | setElements((els) => 120 | addEdge({ ...params,id : `edge_${elements.length+1}`, animated: false, type:'customedge', style: { stroke: '#fff' },label:'dhvsdhvd',data:{ type:'edge', label:'dhvsdhvd'}, arrowHeadType: 'arrowclosed', }, els) 121 | ), 122 | [] 123 | ); 124 | 125 | const deleteNode = () => { 126 | setElements((elements) => elements.filter((element) => element.id != nodeData.id)); 127 | setIsOpen(false); 128 | }; 129 | 130 | // const onLoad = useCallback( 131 | // (rfi) => { 132 | // if (!reactflowInstance) { 133 | // setReactflowInstance(rfi); 134 | // console.log('flow loaded:', rfi); 135 | // } 136 | // }, 137 | // [reactflowInstance] 138 | // ); 139 | 140 | let id = elements.length; 141 | const getId = () => `node_${id+1}`; 142 | 143 | const handleSave = () => { 144 | const findIndex = elements.findIndex(items=>items.id===nodeData.id); 145 | if(nodeData?.data?.type==="node"){ 146 | if(findIndex>-1){ 147 | elements[findIndex].data.label = inputChange; 148 | elements[findIndex].position.x = elements[findIndex].position.x + 1; 149 | elements[findIndex].position.y = elements[findIndex].position.y + 1; 150 | setElements([...elements]) 151 | } 152 | }else if(nodeData?.data?.type==="edge"){ 153 | if(findIndex>-1){ 154 | elements[findIndex].label = inputChange; 155 | setElements([...elements]) 156 | } 157 | } 158 | handleClose(); 159 | setinputChange(""); 160 | setnodeData(null); 161 | } 162 | 163 | const createNew = () => { 164 | const newNode = { 165 | id: getId(), 166 | type: 'customnode', 167 | data: { label: 'An input node', type:"node" }, 168 | position: { x: 20, y: 20 }, 169 | sourcePosition: 'right', 170 | } 171 | setElements((es) => es.concat(newNode)); 172 | } 173 | 174 | useEffect(() => { 175 | if(nodeData){ 176 | setElements((els) => 177 | els.map((el) => { 178 | if (el.id === nodeData.id) { 179 | // it's important that you create a new object here 180 | // in order to notify react flow about the change 181 | el.data = { 182 | ...el.data, 183 | label: inputChange, 184 | }; 185 | } 186 | return el; 187 | }) 188 | ); 189 | } 190 | }, [inputChange]); 191 | 192 | const onContextMenu = (e) => { 193 | e.preventDefault(); 194 | setIsOpen(true); 195 | setPosition({ x: e.clientX - 20, y: e.clientY - 20 }); 196 | } 197 | 198 | const handleSaveFlowData = () => { 199 | console.log(elements) 200 | } 201 | 202 | const handleMouseEnter = (e, node) => {console.log(node) 203 | setnodeData(node) 204 | } 205 | 206 | 207 | return ( 208 |
209 | { 210 | permission && 211 | <> 212 | 215 | 218 | 219 | } 220 | 226 | {/* addded editor config */} 227 | { 228 | nodeData && 229 | 235 | } 236 | 237 | {/* **************************** */} 238 | 239 | {/* {"Change Node Label"} 240 | 241 | setinputChange(e.target.value)} 244 | value={inputChange} 245 | /> 246 | 247 | 248 | 251 | 254 | */} 255 | 256 | 275 | setIsOpen(false)} 279 | actions={[{label:'Delete', effect:deleteNode}]} 280 | /> 281 | { 283 | if (n.type === 'input') return '#0041d0'; 284 | if (n.type === 'selectorNode') return bgColor; 285 | if (n.type === 'output') return '#ff0072'; 286 | }} 287 | nodeColor={(n) => { 288 | if (n.type === 'selectorNode') return bgColor; 289 | return '#fff'; 290 | }} 291 | /> 292 | 293 | 294 |
295 | ) 296 | } 297 | 298 | export default Reactflow 299 | -------------------------------------------------------------------------------- /src/components/Reactflow/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { DragEvent } from 'react'; 2 | 3 | const onDragStart = (event, nodeType) => { 4 | event.stopPropagation(); 5 | event.dataTransfer.setData('application/reactflow', nodeType); 6 | event.dataTransfer.effectAllowed = 'move'; 7 | }; 8 | 9 | const Sidebar = () => { 10 | return ( 11 | 23 | ); 24 | }; 25 | 26 | export default Sidebar; -------------------------------------------------------------------------------- /src/components/Reactflow/contextMenu.js: -------------------------------------------------------------------------------- 1 | import { FC, memo } from 'react'; 2 | 3 | // type Action = { 4 | // label: string; 5 | // effect: (...args: any[]) => any; 6 | // }; 7 | 8 | // type Position = { 9 | // x: number; 10 | // y: number; 11 | // }; 12 | 13 | // type Props = { 14 | // actions: Action[]; 15 | // isOpen: boolean; 16 | // position: Position; 17 | // onMouseLeave: () => void; 18 | // }; 19 | 20 | export const ContextMenu = memo( 21 | ({ isOpen, position, actions = [], onMouseLeave }) => 22 | isOpen ? ( 23 |
38 | {actions.map((action) => ( 39 | 42 | ))} 43 |
44 | ) : null 45 | ); 46 | -------------------------------------------------------------------------------- /src/components/Reactflow/editorConfig.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { Button } from '@material-ui/core'; 3 | import SunEditor from 'suneditor-react'; 4 | // import SunEditorCore from "suneditor/src/lib/core"; 5 | import './suneditor.min.css'; 6 | 7 | const Editor = (props) => { 8 | const { 9 | setInputChange, 10 | value, 11 | handleClose, 12 | handleSave 13 | } = props; 14 | const editor = useRef(); 15 | 16 | const getSunEditorInstance = (sunEditor) => { 17 | editor.current = sunEditor; 18 | }; 19 | 20 | useEffect(() => { 21 | // getSunEditorInstance(); 22 | }, []) 23 | 24 | const handleInputChnage = (content) => { 25 | setInputChange(content) 26 | } 27 | 28 | return ( 29 |
30 | 38 |
39 | 40 | 41 |
42 |
43 | ) 44 | } 45 | 46 | export default Editor -------------------------------------------------------------------------------- /src/components/Reactflow/elements.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // chnaged data label in edge type 4 | export default [ 5 | { 6 | id: '1', 7 | type: 'customnode', 8 | data: { 9 | label: 'welcome to react flow', 10 | type : 'node' 11 | }, 12 | position: { x: 250, y: 0 }, 13 | }, 14 | { 15 | id: '2', 16 | type: "customnode", 17 | data: { 18 | label: 'This is default node', 19 | type : 'node' 20 | }, 21 | position: { x: 100, y: 100 }, 22 | }, 23 | { id: 'e1-2', type : "customedge", source: '1', target: '2', label: 'this is an edge label',arrowHeadType: 'arrowclosed', data : { type : 'edge', label: 'this is an edge label' }, }, 24 | ]; 25 | -------------------------------------------------------------------------------- /src/components/Reactflow/styles.css: -------------------------------------------------------------------------------- 1 | .customNode-1{ 2 | background-color: aqua; 3 | padding: 1rem; 4 | border-radius: 50%; 5 | width: 2rem; 6 | height: 2rem; 7 | font-size: 9px; 8 | } 9 | p{ 10 | margin: 0; 11 | } -------------------------------------------------------------------------------- /src/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './sidebar.css'; 3 | import { CloseOutlined } from '@material-ui/icons' 4 | import { Button } from '@material-ui/core'; 5 | 6 | const options = [ 7 | { 8 | key : 'option_1', 9 | value : 'Option 1', 10 | active : false, 11 | }, 12 | { 13 | key : 'option_2', 14 | value : 'Option 2', 15 | active : false, 16 | }, 17 | { 18 | key : 'option_3', 19 | value : 'Option 3', 20 | active : false, 21 | }, 22 | { 23 | key : 'option_4', 24 | value : 'Option 4', 25 | active : false, 26 | }, 27 | { 28 | key : 'option_5', 29 | value : 'Option 5', 30 | active : false, 31 | }, 32 | ] 33 | 34 | const Component1 = () => { 35 | const [data, setData] = useState(options); 36 | const [open, setOpen] = useState(true); 37 | 38 | const handleClickActive = (e, key) => { 39 | 40 | // const activeElements = Array.from(document.getElementsByClassName('active')); 41 | // activeElements.forEach(ele=>{ 42 | // if(ele.classList.contains('active')){ 43 | // ele.classList.remove('active'); 44 | // } 45 | // }); 46 | // e.target.classList.add('active'); 47 | 48 | setData([...data.map(item => { 49 | if(item.key == key){ 50 | item.active = true; 51 | }else{ 52 | item.active = false 53 | } 54 | return item 55 | })]); 56 | } 57 | 58 | const handleOpenSidebar = () => { 59 | setOpen(true); 60 | const ele = Array.from(document.getElementsByClassName('sidebar')); 61 | ele[0].classList.remove('removeSidebar'); 62 | ele[0].classList.add('sidebar'); 63 | } 64 | 65 | 66 | const handleCloseSidebar = () => { 67 | const ele = Array.from(document.getElementsByClassName('sidebar')); 68 | ele[0].classList.remove('sidebar'); 69 | ele[0].classList.add('removeSidebar'); 70 | setTimeout(() => { 71 | setOpen(false); 72 | }, 200); 73 | } 74 | 75 | 76 | return ( 77 |
78 | 79 |
80 | { 81 | open && 82 |
83 |
84 |

React Sidebar

85 | 86 |
87 |
    88 | { 89 | data.map(items=> 90 |
  • handleClickActive(e,items.key)} className={items.active ? 'active' : ''}> 91 | { 92 | items.value 93 | } 94 |
  • 95 | ) 96 | } 97 |
98 |
99 | } 100 |
101 |
102 | ) 103 | } 104 | 105 | export default Component1 106 | 107 | -------------------------------------------------------------------------------- /src/components/Xarrow/Line/Xarrow.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; 2 | import useXarrowProps from './useXarrowProps'; 3 | import { XarrowContext } from '../Xwrapper'; 4 | import XarrowPropTypes from './propTypes'; 5 | import { getPosition } from './utils/GetPosition'; 6 | 7 | const log = console.log; 8 | 9 | const Xarrow = (props) => { 10 | // log('xarrow update'); 11 | 12 | const mainRef = useRef({ 13 | svgRef: useRef(null), 14 | lineRef: useRef(null), 15 | headRef: useRef(null), 16 | tailRef: useRef(null), 17 | lineDrawAnimRef: useRef(null), 18 | lineDashAnimRef: useRef(null), 19 | headOpacityAnimRef: useRef(null), 20 | }); 21 | const { svgRef, lineRef, headRef, tailRef, lineDrawAnimRef, lineDashAnimRef, headOpacityAnimRef } = mainRef.current; 22 | useContext(XarrowContext); 23 | const xProps = useXarrowProps(props, mainRef.current); 24 | const [propsRefs] = xProps; 25 | 26 | let { 27 | labels, 28 | lineColor, 29 | headColor, 30 | tailColor, 31 | strokeWidth, 32 | showHead, 33 | showTail, 34 | dashness, 35 | headShape, 36 | tailShape, 37 | showXarrow, 38 | animateDrawing, 39 | zIndex, 40 | passProps, 41 | arrowBodyProps, 42 | arrowHeadProps, 43 | arrowTailProps, 44 | SVGcanvasProps, 45 | divContainerProps, 46 | divContainerStyle, 47 | SVGcanvasStyle, 48 | _debug, 49 | shouldUpdatePosition, 50 | } = propsRefs; 51 | 52 | animateDrawing = props.animateDrawing; 53 | const [drawAnimEnded, setDrawAnimEnded] = useState(!animateDrawing); 54 | 55 | const [, setRender] = useState({}); 56 | const forceRerender = () => setRender({}); 57 | 58 | const [st, setSt] = useState({ 59 | //initial state 60 | cx0: 0, //x start position of the canvas 61 | cy0: 0, //y start position of the canvas 62 | cw: 0, // the canvas width 63 | ch: 0, // the canvas height 64 | x1: 0, //the x starting point of the line inside the canvas 65 | y1: 0, //the y starting point of the line inside the canvas 66 | x2: 0, //the x ending point of the line inside the canvas 67 | y2: 0, //the y ending point of the line inside the canvas 68 | dx: 0, // the x difference between 'start' anchor to 'end' anchor 69 | dy: 0, // the y difference between 'start' anchor to 'end' anchor 70 | absDx: 0, // the x length(positive) difference 71 | absDy: 0, // the y length(positive) difference 72 | cpx1: 0, // control points - control the curviness of the line 73 | cpy1: 0, 74 | cpx2: 0, 75 | cpy2: 0, 76 | headOrient: 0, // determines to what side the arrowhead will point 77 | tailOrient: 0, // determines to what side the arrow tail will point 78 | arrowHeadOffset: { x: 0, y: 0 }, 79 | arrowTailOffset: { x: 0, y: 0 }, 80 | headOffset: 0, 81 | excRight: 0, //expand canvas to the right 82 | excLeft: 0, //expand canvas to the left 83 | excUp: 0, //expand canvas upwards 84 | excDown: 0, // expand canvas downward 85 | startPoints: [], 86 | endPoints: [], 87 | mainDivPos: { x: 0, y: 0 }, 88 | xSign: 1, 89 | ySign: 1, 90 | lineLength: 0, 91 | fHeadSize: 1, 92 | fTailSize: 1, 93 | arrowPath: ``, 94 | labelStartPos: { x: 0, y: 0 }, 95 | labelMiddlePos: { x: 0, y: 0 }, 96 | labelEndPos: { x: 0, y: 0 }, 97 | }); 98 | 99 | /** 100 | * The Main logic of path calculation for the arrow. 101 | * calculate new path, adjusting canvas, and set state based on given properties. 102 | * */ 103 | useLayoutEffect(() => { 104 | if (shouldUpdatePosition.current) { 105 | // log('xarrow getPosition'); 106 | const pos = getPosition(xProps, mainRef); 107 | // log('pos', pos); 108 | setSt(pos); 109 | shouldUpdatePosition.current = false; 110 | } 111 | }); 112 | 113 | // log('st', st); 114 | 115 | const xOffsetHead = st.x2 - st.arrowHeadOffset.x; 116 | const yOffsetHead = st.y2 - st.arrowHeadOffset.y; 117 | const xOffsetTail = st.x1 - st.arrowTailOffset.x; 118 | const yOffsetTail = st.y1 - st.arrowTailOffset.y; 119 | 120 | let dashoffset = dashness.strokeLen + dashness.nonStrokeLen; 121 | let animDirection = 1; 122 | if (dashness.animation < 0) { 123 | dashness.animation *= -1; 124 | animDirection = -1; 125 | } 126 | let dashArray, 127 | animation, 128 | animRepeatCount, 129 | animStartValue, 130 | animEndValue = 0; 131 | 132 | if (animateDrawing && drawAnimEnded == false) { 133 | if (typeof animateDrawing === 'boolean') animateDrawing = 1; 134 | animation = animateDrawing + 's'; 135 | dashArray = st.lineLength; 136 | animStartValue = st.lineLength; 137 | animRepeatCount = 1; 138 | if (animateDrawing < 0) { 139 | [animStartValue, animEndValue] = [animEndValue, animStartValue]; 140 | animation = animateDrawing * -1 + 's'; 141 | } 142 | } else { 143 | dashArray = `${dashness.strokeLen} ${dashness.nonStrokeLen}`; 144 | animation = `${1 / dashness.animation}s`; 145 | animStartValue = dashoffset * animDirection; 146 | animRepeatCount = 'indefinite'; 147 | animEndValue = 0; 148 | } 149 | 150 | // handle draw animation 151 | useLayoutEffect(() => { 152 | if (lineRef.current) setSt((prevSt) => ({ ...prevSt, lineLength: lineRef.current?.getTotalLength() ?? 0 })); 153 | }, [lineRef.current]); 154 | 155 | // set all props on first render 156 | useEffect(() => { 157 | const monitorDOMchanges = () => { 158 | window.addEventListener('resize', forceRerender); 159 | 160 | const handleDrawAmimEnd = () => { 161 | setDrawAnimEnded(true); 162 | // @ts-ignore 163 | headOpacityAnimRef.current?.beginElement(); 164 | // @ts-ignore 165 | lineDashAnimRef.current?.beginElement(); 166 | }; 167 | const handleDrawAmimBegin = () => (headRef.current.style.opacity = '0'); 168 | if (lineDrawAnimRef.current && headRef.current) { 169 | lineDrawAnimRef.current.addEventListener('endEvent', handleDrawAmimEnd); 170 | lineDrawAnimRef.current.addEventListener('beginEvent', handleDrawAmimBegin); 171 | } 172 | return () => { 173 | window.removeEventListener('resize', forceRerender); 174 | if (lineDrawAnimRef.current) { 175 | lineDrawAnimRef.current.removeEventListener('endEvent', handleDrawAmimEnd); 176 | if (headRef.current) lineDrawAnimRef.current.removeEventListener('beginEvent', handleDrawAmimBegin); 177 | } 178 | }; 179 | }; 180 | 181 | const cleanMonitorDOMchanges = monitorDOMchanges(); 182 | return () => { 183 | setDrawAnimEnded(false); 184 | cleanMonitorDOMchanges(); 185 | }; 186 | }, [showXarrow]); 187 | 188 | //todo: could make some advanced generic typescript inferring. for example get type from headShape.elem:T and 189 | // tailShape.elem:K force the type for passProps,arrowHeadProps,arrowTailProps property. for now `as any` is used to 190 | // avoid typescript conflicts 191 | // so todo- fix all the `passProps as any` assertions 192 | 193 | return ( 194 |
195 | {showXarrow ? ( 196 | <> 197 | 211 | {/* body of the arrow */} 212 | 223 | <> 224 | {drawAnimEnded ? ( 225 | <> 226 | {/* moving dashed line animation */} 227 | {dashness.animation ? ( 228 | 235 | ) : null} 236 | 237 | ) : ( 238 | <> 239 | {/* the creation of the line animation */} 240 | {animateDrawing ? ( 241 | 249 | ) : null} 250 | 251 | )} 252 | 253 | 254 | {/* arrow tail */} 255 | {showTail ? ( 256 | 262 | {tailShape.svgElem} 263 | 264 | ) : null} 265 | 266 | {/* head of the arrow */} 267 | {showHead ? ( 268 | 277 | 287 | 288 | {headShape.svgElem} 289 | 290 | ) : null} 291 | {/* debug elements */} 292 | {_debug ? ( 293 | <> 294 | {/* control points circles */} 295 | 296 | 297 | {/* start to end rectangle wrapper */} 298 | 307 | 308 | ) : null} 309 | 310 | 311 | {labels.start ? ( 312 |
320 | {labels.start} 321 |
322 | ) : null} 323 | {labels.middle ? ( 324 |
333 | {labels.middle} 334 |
335 | ) : null} 336 | {labels.end ? ( 337 |
0 ? 'translate(-100% , -50%)' : 'translate(-0% , -50%)', 340 | width: 'max-content', 341 | position: 'absolute', 342 | left: st.cx0 + st.labelEndPos.x, 343 | top: st.cy0 + st.labelEndPos.y + strokeWidth + 5, 344 | }}> 345 | {labels.end} 346 |
347 | ) : null} 348 | {_debug ? ( 349 | <> 350 | {/* possible anchor connections */} 351 | {[...st.startPoints, ...st.endPoints].map((p, i) => { 352 | return ( 353 |
367 | ); 368 | })} 369 | 370 | ) : null} 371 | 372 | ) : null} 373 |
374 | ); 375 | }; 376 | 377 | ////////////////////////////// 378 | // propTypes 379 | 380 | Xarrow.propTypes = XarrowPropTypes; 381 | 382 | export default Xarrow; 383 | -------------------------------------------------------------------------------- /src/components/Xarrow/Line/anchors.js: -------------------------------------------------------------------------------- 1 | 2 | const getAnchorsDefaultOffsets = (width, height) => { 3 | return { 4 | middle: { x: width * 0.5, y: height * 0.5 }, 5 | left: { x: 0, y: height * 0.5 }, 6 | right: { x: width, y: height * 0.5 }, 7 | top: { x: width * 0.5, y: 0 }, 8 | bottom: { x: width * 0.5, y: height }, 9 | }; 10 | }; 11 | 12 | export const calcAnchors = (anchors, anchorPos) => { 13 | // now prepare this list of anchors to object expected by the `getShortestLine` function 14 | return anchors.map((anchor) => { 15 | let defsOffsets = getAnchorsDefaultOffsets(anchorPos.right - anchorPos.x, anchorPos.bottom - anchorPos.y); 16 | let { x, y } = defsOffsets[anchor.position]; 17 | return { 18 | x: anchorPos.x + x + anchor.offset.x, 19 | y: anchorPos.y + y + anchor.offset.y, 20 | anchor: anchor, 21 | }; 22 | }); 23 | }; 24 | 25 | if (require.main === module) { 26 | // const res = parseAnchor(['auto'], { 27 | // x: 0, 28 | // y: 0, 29 | // bottom: 10, 30 | // right: 20, 31 | // }); 32 | // console.log(res); 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Xarrow/Line/propTypes.js: -------------------------------------------------------------------------------- 1 | import PT from 'prop-types'; 2 | import { arrowShapes, cAnchorEdge, cPaths, cSvgElems } from '../constants'; 3 | 4 | const pAnchorPositionType = PT.oneOf(cAnchorEdge); 5 | 6 | const pAnchorCustomPositionType = PT.exact({ 7 | position: pAnchorPositionType.isRequired, 8 | offset: PT.exact({ 9 | x: PT.number, 10 | y: PT.number, 11 | }).isRequired, 12 | }); 13 | 14 | const _pAnchorType = PT.oneOfType([pAnchorPositionType, pAnchorCustomPositionType]); 15 | 16 | const pAnchorType = PT.oneOfType([_pAnchorType, PT.arrayOf(_pAnchorType)]); 17 | 18 | const pRefType = PT.oneOfType([PT.string, PT.exact({ current: PT.any })]); 19 | 20 | const _pLabelType = PT.oneOfType([PT.element, PT.string]); 21 | 22 | const pLabelsType = PT.exact({ 23 | start: _pLabelType, 24 | middle: _pLabelType, 25 | end: _pLabelType, 26 | }); 27 | 28 | const pSvgEdgeShapeType = PT.oneOf(Object.keys(arrowShapes)); 29 | // const pSvgElemType = PT.oneOf(cSvgElems); 30 | const pSvgElemType = PT.any; 31 | const pSvgEdgeType = PT.oneOfType([ 32 | pSvgEdgeShapeType, 33 | PT.exact({ 34 | svgElem: pSvgElemType, 35 | offsetForward: PT.number, 36 | }).isRequired, 37 | ]); 38 | 39 | const XarrowPropTypes = { 40 | start: pRefType.isRequired, 41 | end: pRefType.isRequired, 42 | startAnchor: pAnchorType, 43 | endAnchor: pAnchorType, 44 | labels: PT.oneOfType([_pLabelType, pLabelsType]), 45 | color: PT.string, 46 | lineColor: PT.string, 47 | showHead: PT.bool, 48 | headColor: PT.string, 49 | headSize: PT.number, 50 | tailSize: PT.number, 51 | tailColor: PT.string, 52 | strokeWidth: PT.number, 53 | showTail: PT.bool, 54 | path: PT.oneOf(cPaths), 55 | showXarrow: PT.bool, 56 | curveness: PT.number, 57 | gridBreak: PT.string, 58 | dashness: PT.oneOfType([PT.bool, PT.object]), 59 | headShape: pSvgEdgeType, 60 | tailShape: pSvgEdgeType, 61 | animateDrawing: PT.oneOfType([PT.bool, PT.number]), 62 | zIndex: PT.number, 63 | passProps: PT.object, 64 | arrowBodyProps: PT.object, 65 | arrowHeadProps: PT.object, 66 | arrowTailProps: PT.object, 67 | SVGcanvasProps: PT.object, 68 | divContainerProps: PT.object, 69 | _extendSVGcanvas: PT.number, 70 | _debug: PT.bool, 71 | _cpx1Offset: PT.number, 72 | _cpy1Offset: PT.number, 73 | _cpx2Offset: PT.number, 74 | _cpy2Offset: PT.number, 75 | }; 76 | 77 | export default XarrowPropTypes; 78 | -------------------------------------------------------------------------------- /src/components/Xarrow/Line/useXarrowProps.js: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useRef, useState } from 'react'; 2 | import { getElementByPropGiven, getElemPos, xStr2absRelative } from './utils'; 3 | import _ from 'lodash'; 4 | import { arrowShapes, cAnchorEdge, cArrowShapes } from '../constants'; 5 | 6 | const parseLabels = (label) => { 7 | let parsedLabel = { start: null, middle: null, end: null }; 8 | if (label) { 9 | if (typeof label === 'string' || React.isValidElement(label)) parsedLabel.middle = label; 10 | else { 11 | for (let key in label) { 12 | parsedLabel[key] = label[key]; 13 | } 14 | } 15 | } 16 | return parsedLabel; 17 | }; 18 | 19 | // remove 'auto' as possible anchor from anchorCustomPositionType.position 20 | // interface anchorCustomPositionType2 extends Omit, 'position'> { 21 | // position: Exclude; 22 | // } 23 | 24 | const parseAnchor = (anchor) => { 25 | // convert to array 26 | let anchorChoice = Array.isArray(anchor) ? anchor : [anchor]; 27 | 28 | //convert to array of objects 29 | let anchorChoice2 = anchorChoice.map((anchorChoice) => { 30 | if (typeof anchorChoice === 'string') { 31 | return { position: anchorChoice }; 32 | } else return anchorChoice; 33 | }); 34 | 35 | //remove any invalid anchor names 36 | anchorChoice2 = anchorChoice2.filter((an) => cAnchorEdge.includes(an.position)); 37 | if (anchorChoice2.length == 0) anchorChoice2 = [{ position: 'auto' }]; 38 | 39 | //replace any 'auto' with ['left','right','bottom','top'] 40 | let autosAncs = anchorChoice2.filter((an) => an.position === 'auto'); 41 | if (autosAncs.length > 0) { 42 | anchorChoice2 = anchorChoice2.filter((an) => an.position !== 'auto'); 43 | anchorChoice2.push( 44 | ...autosAncs.flatMap((anchorObj) => { 45 | return (['left', 'right', 'top', 'bottom']).map((anchorName) => { 46 | return { ...anchorObj, position: anchorName }; 47 | }); 48 | }) 49 | ); 50 | } 51 | 52 | // default values 53 | let anchorChoice3 = anchorChoice2.map((anchorChoice) => { 54 | if (typeof anchorChoice === 'object') { 55 | let anchorChoiceCustom = anchorChoice; 56 | if (!anchorChoiceCustom.position) anchorChoiceCustom.position = 'auto'; 57 | if (!anchorChoiceCustom.offset) anchorChoiceCustom.offset = { x: 0, y: 0 }; 58 | if (!anchorChoiceCustom.offset.y) anchorChoiceCustom.offset.y = 0; 59 | if (!anchorChoiceCustom.offset.x) anchorChoiceCustom.offset.x = 0; 60 | anchorChoiceCustom = anchorChoiceCustom; 61 | return anchorChoiceCustom; 62 | } else return anchorChoice; 63 | }); 64 | 65 | return anchorChoice3; 66 | }; 67 | 68 | const parseDashness = (dashness, props) => { 69 | let dashStroke = 0, 70 | dashNone = 0, 71 | animDashSpeed, 72 | animDirection = 1; 73 | if (typeof dashness === 'object') { 74 | dashStroke = dashness.strokeLen || props.strokeWidth * 2; 75 | dashNone = dashness.strokeLen ? dashness.nonStrokeLen : props.strokeWidth; 76 | animDashSpeed = dashness.animation ? dashness.animation : null; 77 | } else if (typeof dashness === 'boolean' && dashness) { 78 | dashStroke = props.strokeWidth * 2; 79 | dashNone = props.strokeWidth; 80 | animDashSpeed = null; 81 | } 82 | return { strokeLen: dashStroke, nonStrokeLen: dashNone, animation: animDashSpeed, animDirection }; 83 | }; 84 | 85 | const parseEdgeShape = (svgEdge) => { 86 | if (typeof svgEdge == 'string') { 87 | if (svgEdge in arrowShapes) svgEdge = arrowShapes[svgEdge]; 88 | else { 89 | console.warn( 90 | `'${svgEdge}' is not supported arrow shape. the supported arrow shapes is one of ${cArrowShapes}. 91 | reverting to default shape.` 92 | ); 93 | svgEdge = arrowShapes['arrow1']; 94 | } 95 | } 96 | svgEdge = svgEdge; 97 | if (svgEdge?.offsetForward === undefined) svgEdge.offsetForward = 0.25; 98 | if (svgEdge?.svgElem === undefined) svgEdge.svgElem = 'path'; 99 | // if (svgEdge?.svgProps === undefined) svgEdge.svgProps = arrowShapes.arrow1.svgProps; 100 | return svgEdge; 101 | }; 102 | 103 | const parseGridBreak = (gridBreak) => { 104 | let resGridBreak = xStr2absRelative(gridBreak); 105 | if (!resGridBreak) resGridBreak = { relative: 0.5, abs: 0 }; 106 | return resGridBreak; 107 | }; 108 | 109 | /** 110 | * should be wrapped with any changed prop that is affecting the points path positioning 111 | * @param propVal 112 | * @param updateRef 113 | */ 114 | const withUpdate = (propVal, updateRef) => { 115 | if (updateRef) updateRef.current = true; 116 | return propVal; 117 | }; 118 | 119 | const noParse = (userProp) => userProp; 120 | const noParseWithUpdatePos = (userProp, _, updatePos) => withUpdate(userProp, updatePos); 121 | const parseNumWithUpdatePos = (userProp, _, updatePos) => withUpdate(Number(userProp), updatePos); 122 | const parseNum = (userProp) => Number(userProp); 123 | 124 | const parsePropsFuncs = { 125 | start: (userProp) => getElementByPropGiven(userProp), 126 | end: (userProp) => getElementByPropGiven(userProp), 127 | startAnchor: (userProp, _, updatePos) => withUpdate(parseAnchor(userProp), updatePos), 128 | endAnchor: (userProp, _, updatePos) => withUpdate(parseAnchor(userProp), updatePos), 129 | labels: (userProp) => parseLabels(userProp), 130 | color: noParse, 131 | lineColor: (userProp, propsRefs) => userProp || propsRefs.color, 132 | headColor: (userProp, propsRefs) => userProp || propsRefs.color, 133 | tailColor: (userProp, propsRefs) => userProp || propsRefs.color, 134 | strokeWidth: parseNumWithUpdatePos, 135 | showHead: noParseWithUpdatePos, 136 | headSize: parseNumWithUpdatePos, 137 | showTail: noParseWithUpdatePos, 138 | tailSize: parseNumWithUpdatePos, 139 | path: noParseWithUpdatePos, 140 | curveness: parseNumWithUpdatePos, 141 | gridBreak: (userProp, _, updatePos) => withUpdate(parseGridBreak(userProp), updatePos), 142 | // // gridRadius = strokeWidth * 2, //todo 143 | dashness: (userProp, propsRefs) => parseDashness(userProp, propsRefs), 144 | headShape: (userProp) => parseEdgeShape(userProp), 145 | tailShape: (userProp) => parseEdgeShape(userProp), 146 | showXarrow: noParse, 147 | animateDrawing: noParse, 148 | zIndex: parseNum, 149 | passProps: noParse, 150 | arrowBodyProps: noParseWithUpdatePos, 151 | arrowHeadProps: noParseWithUpdatePos, 152 | arrowTailProps: noParseWithUpdatePos, 153 | SVGcanvasProps: noParseWithUpdatePos, 154 | divContainerProps: noParseWithUpdatePos, 155 | divContainerStyle: noParseWithUpdatePos, 156 | SVGcanvasStyle: noParseWithUpdatePos, 157 | _extendSVGcanvas: noParseWithUpdatePos, 158 | _debug: noParseWithUpdatePos, 159 | _cpx1Offset: noParseWithUpdatePos, 160 | _cpy1Offset: noParseWithUpdatePos, 161 | _cpx2Offset: noParseWithUpdatePos, 162 | _cpy2Offset: noParseWithUpdatePos, 163 | }; 164 | 165 | //build dependencies 166 | const propsDeps = {}; 167 | //each prop depends on himself 168 | for (let propName in parsePropsFuncs) { 169 | propsDeps[propName] = [propName]; 170 | } 171 | // 'lineColor', 'headColor', 'tailColor' props also depends on 'color' prop 172 | for (let propName of ['lineColor', 'headColor', 'tailColor']) { 173 | propsDeps[propName].push('color'); 174 | } 175 | 176 | const parseGivenProps = (props, propsRef) => { 177 | for (let [name, val] of Object.entries(props)) { 178 | propsRef[name] = parsePropsFuncs?.[name]?.(val, propsRef); 179 | } 180 | return propsRef; 181 | }; 182 | 183 | const defaultProps= { 184 | start: null, 185 | end: null, 186 | startAnchor: 'auto', 187 | endAnchor: 'auto', 188 | labels: null, 189 | color: 'CornflowerBlue', 190 | lineColor: null, 191 | headColor: null, 192 | tailColor: null, 193 | strokeWidth: 4, 194 | showHead: true, 195 | headSize: 6, 196 | showTail: false, 197 | tailSize: 6, 198 | path: 'smooth', 199 | curveness: 0.8, 200 | gridBreak: '50%', 201 | // gridRadius : strokeWidth * 2, //todo 202 | dashness: false, 203 | headShape: 'arrow1', 204 | tailShape: 'arrow1', 205 | showXarrow: true, 206 | animateDrawing: false, 207 | zIndex: 0, 208 | passProps: {}, 209 | arrowBodyProps: {}, 210 | arrowHeadProps: {}, 211 | arrowTailProps: {}, 212 | SVGcanvasProps: {}, 213 | divContainerProps: {}, 214 | divContainerStyle: {}, 215 | SVGcanvasStyle: {}, 216 | _extendSVGcanvas: 0, 217 | _debug: false, 218 | _cpx1Offset: 0, 219 | _cpy1Offset: 0, 220 | _cpx2Offset: 0, 221 | _cpy2Offset: 0, 222 | }; 223 | 224 | // type parsedXarrowProps = { 225 | // shouldUpdatePosition: React.MutableRefObject; 226 | // start: HTMLElement; 227 | // end: HTMLElement; 228 | // startAnchor: anchorCustomPositionType[]; 229 | // endAnchor: anchorCustomPositionType[]; 230 | // labels: Required; 231 | // color: string; 232 | // lineColor: string; 233 | // headColor: string; 234 | // tailColor: string; 235 | // strokeWidth: number; 236 | // showHead: boolean; 237 | // headSize: number; 238 | // showTail: boolean; 239 | // tailSize: number; 240 | // path: pathType; 241 | // showXarrow: boolean; 242 | // curveness: number; 243 | // gridBreak: { relative: number; abs: number }; 244 | // // gridRadius: number; 245 | // dashness: { 246 | // strokeLen: number; 247 | // nonStrokeLen: number; 248 | // animation: number; 249 | // }; 250 | // headShape: svgCustomEdgeType; 251 | // tailShape: svgCustomEdgeType; 252 | // animateDrawing: number; 253 | // zIndex: number; 254 | // passProps: JSX.IntrinsicElements[svgElemType]; 255 | // SVGcanvasProps: React.SVGAttributes; 256 | // arrowBodyProps: React.SVGProps; 257 | // arrowHeadProps: JSX.IntrinsicElements[svgElemType]; 258 | // arrowTailProps: JSX.IntrinsicElements[svgElemType]; 259 | // divContainerProps: React.HTMLProps; 260 | // SVGcanvasStyle: React.CSSProperties; 261 | // divContainerStyle: React.CSSProperties; 262 | // _extendSVGcanvas: number; 263 | // _debug: boolean; 264 | // _cpx1Offset: number; 265 | // _cpy1Offset: number; 266 | // _cpx2Offset: number; 267 | // _cpy2Offset: number; 268 | // }; 269 | 270 | let initialParsedProps = {}; 271 | initialParsedProps = parseGivenProps(defaultProps, initialParsedProps); 272 | 273 | const initialValVars = { 274 | startPos: { x: 0, y: 0, right: 0, bottom: 0 }, 275 | endPos: { x: 0, y: 0, right: 0, bottom: 0 }, 276 | }; 277 | 278 | // const parseAllProps = () => parseGivenProps(defaultProps, initialParsedProps); 279 | 280 | function deepCompareEquals(a, b) { 281 | return _.isEqual(a, b); 282 | } 283 | 284 | function useDeepCompareMemoize(value) { 285 | const ref = useRef(); 286 | // it can be done by using useMemo as well 287 | // but useRef is rather cleaner and easier 288 | 289 | if (!deepCompareEquals(value, ref.current)) { 290 | ref.current = value; 291 | } 292 | 293 | return ref.current; 294 | } 295 | 296 | function useDeepCompareEffect(callback, dependencies) { 297 | useLayoutEffect(callback, dependencies.map(useDeepCompareMemoize)); 298 | } 299 | 300 | /** 301 | * smart hook that provides parsed props to Xarrow and will trigger rerender whenever given prop is changed. 302 | */ 303 | const useXarrowProps = ( 304 | userProps, 305 | refs 306 | ) => { 307 | const [propsRefs, setPropsRefs] = useState(initialParsedProps); 308 | const shouldUpdatePosition = useRef(false); 309 | // const _propsRefs = useRef(initialParsedProps); 310 | // const propsRefs = _propsRefs.current; 311 | propsRefs['shouldUpdatePosition'] = shouldUpdatePosition; 312 | const curProps = { ...defaultProps, ...userProps }; 313 | 314 | // react states the number of hooks per render must stay constant, 315 | // this is ok we are using these hooks in a loop, because the number of props in defaultProps is constant, 316 | // so the number of hook we will fire each render will always be the same. 317 | 318 | // update the value of the ref that represents the corresponding prop 319 | // for example: if given 'start' prop would change call getElementByPropGiven(props.start) and save value into propsRefs.start.current 320 | // why to save refs to props parsed values? some of the props require relatively expensive computations(like 'start' and 'startAnchor'). 321 | // this will always run in the same order and THAT'S WAY ITS LEGAL 322 | for (let propName in defaultProps) { 323 | useLayoutEffect( 324 | () => { 325 | propsRefs[propName] = parsePropsFuncs?.[propName]?.(curProps[propName], propsRefs, shouldUpdatePosition); 326 | // console.log('prop update:', propName, 'with value', propsRefs[propName]); 327 | setPropsRefs({ ...propsRefs }); 328 | }, 329 | propsDeps[propName].map((name) => userProps[name]) 330 | ); 331 | } 332 | 333 | // rerender whenever position of start element or end element changes 334 | const [valVars, setValVars] = useState(initialValVars); 335 | const startPos = getElemPos(propsRefs.start); 336 | useDeepCompareEffect(() => { 337 | valVars.startPos = startPos; 338 | shouldUpdatePosition.current = true; 339 | setValVars({ ...valVars }); 340 | // console.log('start update pos', startPos); 341 | }, [startPos]); 342 | const endPos = getElemPos(propsRefs.end); 343 | useDeepCompareEffect(() => { 344 | valVars.endPos = endPos; 345 | shouldUpdatePosition.current = true; 346 | setValVars({ ...valVars }); 347 | // console.log('end update pos', endPos); 348 | }, [endPos]); 349 | 350 | useLayoutEffect(() => { 351 | // console.log('svg shape changed!'); 352 | shouldUpdatePosition.current = true; 353 | setValVars({ ...valVars }); 354 | }, [propsRefs.headShape.svgElem, propsRefs.tailShape.svgElem]); 355 | 356 | return [propsRefs, valVars]; 357 | }; 358 | 359 | // export type useXarrowPropsResType = ReturnType; 360 | export default useXarrowProps; 361 | -------------------------------------------------------------------------------- /src/components/Xarrow/Line/utils/GetPosition.js: -------------------------------------------------------------------------------- 1 | import { useXarrowPropsResType } from '../useXarrowProps'; 2 | import React from 'react'; 3 | import { calcAnchors } from '../anchors'; 4 | import { getShortestLine, getSvgPos } from './index'; 5 | import _ from 'lodash'; 6 | import { cPaths } from '../../constants'; 7 | import { buzzierMinSols, bzFunction } from './buzzier'; 8 | 9 | /** 10 | * The Main logic of path calculation for the arrow. 11 | * calculate new path, adjusting canvas, and set state based on given properties. 12 | * */ 13 | export const getPosition = (xProps, mainRef) => { 14 | let [propsRefs, valVars] = xProps; 15 | let { 16 | startAnchor, 17 | endAnchor, 18 | strokeWidth, 19 | showHead, 20 | headSize, 21 | showTail, 22 | tailSize, 23 | path, 24 | curveness, 25 | gridBreak, 26 | headShape, 27 | tailShape, 28 | _extendSVGcanvas, 29 | _cpx1Offset, 30 | _cpy1Offset, 31 | _cpx2Offset, 32 | _cpy2Offset, 33 | } = propsRefs; 34 | const { startPos, endPos } = valVars; 35 | const { svgRef, lineRef } = mainRef.current; 36 | 37 | let headOrient = 0; 38 | let tailOrient = 0; 39 | 40 | // convert startAnchor and endAnchor to list of objects represents allowed anchors. 41 | let startPoints = calcAnchors(startAnchor, startPos); 42 | let endPoints = calcAnchors(endAnchor, endPos); 43 | 44 | // choose the smallest path for 2 points from these possibilities. 45 | let { chosenStart, chosenEnd } = getShortestLine(startPoints, endPoints); 46 | 47 | let startAnchorPosition = chosenStart.anchor.position, 48 | endAnchorPosition = chosenEnd.anchor.position; 49 | let startPoint = _.pick(chosenStart, ['x', 'y']), 50 | endPoint = _.pick(chosenEnd, ['x', 'y']); 51 | 52 | let mainDivPos = getSvgPos(svgRef); 53 | let cx0 = Math.min(startPoint.x, endPoint.x) - mainDivPos.x; 54 | let cy0 = Math.min(startPoint.y, endPoint.y) - mainDivPos.y; 55 | let dx = endPoint.x - startPoint.x; 56 | let dy = endPoint.y - startPoint.y; 57 | let absDx = Math.abs(endPoint.x - startPoint.x); 58 | let absDy = Math.abs(endPoint.y - startPoint.y); 59 | let xSign = dx > 0 ? 1 : -1; 60 | let ySign = dy > 0 ? 1 : -1; 61 | let [headOffset, tailOffset] = [headShape.offsetForward, tailShape.offsetForward]; 62 | let fHeadSize = headSize * strokeWidth; //factored head size 63 | let fTailSize = tailSize * strokeWidth; //factored head size 64 | 65 | // const { current: _headBox } = headBox; 66 | let xHeadOffset = 0; 67 | let yHeadOffset = 0; 68 | let xTailOffset = 0; 69 | let yTailOffset = 0; 70 | 71 | let _headOffset = fHeadSize * headOffset; 72 | let _tailOffset = fTailSize * tailOffset; 73 | 74 | let cu = Number(curveness); 75 | // gridRadius = Number(gridRadius); 76 | if (!cPaths.includes(path)) path = 'smooth'; 77 | if (path === 'straight') { 78 | cu = 0; 79 | path = 'smooth'; 80 | } 81 | 82 | let biggerSide = headSize > tailSize ? headSize : tailSize; 83 | let _calc = strokeWidth + (strokeWidth * biggerSide) / 2; 84 | let excRight = _calc; 85 | let excLeft = _calc; 86 | let excUp = _calc; 87 | let excDown = _calc; 88 | excLeft += Number(_extendSVGcanvas); 89 | excRight += Number(_extendSVGcanvas); 90 | excUp += Number(_extendSVGcanvas); 91 | excDown += Number(_extendSVGcanvas); 92 | 93 | //////////////////////////////////// 94 | // arrow point to point calculations 95 | let x1 = 0, 96 | x2 = absDx, 97 | y1 = 0, 98 | y2 = absDy; 99 | if (dx < 0) [x1, x2] = [x2, x1]; 100 | if (dy < 0) [y1, y2] = [y2, y1]; 101 | 102 | //////////////////////////////////// 103 | // arrow curviness and arrowhead placement calculations 104 | 105 | if (cu === 0) { 106 | // in case of straight path 107 | let headAngel = Math.atan(absDy / absDx); 108 | 109 | if (showHead) { 110 | x2 -= fHeadSize * (1 - headOffset) * xSign * Math.cos(headAngel); 111 | y2 -= fHeadSize * (1 - headOffset) * ySign * Math.sin(headAngel); 112 | 113 | headAngel *= ySign; 114 | if (xSign < 0) headAngel = (Math.PI - headAngel * xSign) * xSign; 115 | xHeadOffset = Math.cos(headAngel) * _headOffset - (Math.sin(headAngel) * fHeadSize) / 2; 116 | yHeadOffset = (Math.cos(headAngel) * fHeadSize) / 2 + Math.sin(headAngel) * _headOffset; 117 | headOrient = (headAngel * 180) / Math.PI; 118 | } 119 | 120 | let tailAngel = Math.atan(absDy / absDx); 121 | if (showTail) { 122 | x1 += fTailSize * (1 - tailOffset) * xSign * Math.cos(tailAngel); 123 | y1 += fTailSize * (1 - tailOffset) * ySign * Math.sin(tailAngel); 124 | tailAngel *= -ySign; 125 | if (xSign > 0) tailAngel = (Math.PI - tailAngel * xSign) * xSign; 126 | xTailOffset = Math.cos(tailAngel) * _tailOffset - (Math.sin(tailAngel) * fTailSize) / 2; 127 | yTailOffset = (Math.cos(tailAngel) * fTailSize) / 2 + Math.sin(tailAngel) * _tailOffset; 128 | tailOrient = (tailAngel * 180) / Math.PI; 129 | } 130 | } else { 131 | // in case of smooth path 132 | if (endAnchorPosition === 'middle') { 133 | // in case a middle anchor is chosen for endAnchor choose from which side to attach to the middle of the element 134 | if (absDx > absDy) { 135 | endAnchorPosition = xSign ? 'left' : 'right'; 136 | } else { 137 | endAnchorPosition = ySign ? 'top' : 'bottom'; 138 | } 139 | } 140 | if (showHead) { 141 | if (['left', 'right'].includes(endAnchorPosition)) { 142 | xHeadOffset += _headOffset * xSign; 143 | x2 -= fHeadSize * (1 - headOffset) * xSign; //same! 144 | yHeadOffset += (fHeadSize * xSign) / 2; 145 | if (endAnchorPosition === 'left') { 146 | headOrient = 0; 147 | if (xSign < 0) headOrient += 180; 148 | } else { 149 | headOrient = 180; 150 | if (xSign > 0) headOrient += 180; 151 | } 152 | } else if (['top', 'bottom'].includes(endAnchorPosition)) { 153 | xHeadOffset += (fHeadSize * -ySign) / 2; 154 | yHeadOffset += _headOffset * ySign; 155 | y2 -= fHeadSize * ySign - yHeadOffset; 156 | if (endAnchorPosition === 'top') { 157 | headOrient = 270; 158 | if (ySign > 0) headOrient += 180; 159 | } else { 160 | headOrient = 90; 161 | if (ySign < 0) headOrient += 180; 162 | } 163 | } 164 | } 165 | } 166 | 167 | if (showTail && cu !== 0) { 168 | if (['left', 'right'].includes(startAnchorPosition)) { 169 | xTailOffset += _tailOffset * -xSign; 170 | x1 += fTailSize * xSign + xTailOffset; 171 | yTailOffset += -(fTailSize * xSign) / 2; 172 | if (startAnchorPosition === 'left') { 173 | tailOrient = 180; 174 | if (xSign < 0) tailOrient += 180; 175 | } else { 176 | tailOrient = 0; 177 | if (xSign > 0) tailOrient += 180; 178 | } 179 | } else if (['top', 'bottom'].includes(startAnchorPosition)) { 180 | yTailOffset += _tailOffset * -ySign; 181 | y1 += fTailSize * ySign + yTailOffset; 182 | xTailOffset += (fTailSize * ySign) / 2; 183 | if (startAnchorPosition === 'top') { 184 | tailOrient = 90; 185 | if (ySign > 0) tailOrient += 180; 186 | } else { 187 | tailOrient = 270; 188 | if (ySign < 0) tailOrient += 180; 189 | } 190 | } 191 | } 192 | 193 | let arrowHeadOffset = { x: xHeadOffset, y: yHeadOffset }; 194 | let arrowTailOffset = { x: xTailOffset, y: yTailOffset }; 195 | 196 | let cpx1 = x1, 197 | cpy1 = y1, 198 | cpx2 = x2, 199 | cpy2 = y2; 200 | 201 | let curvesPossibilities = {}; 202 | if (path === 'smooth') 203 | curvesPossibilities = { 204 | hh: () => { 205 | //horizontal - from right to left or the opposite 206 | cpx1 += absDx * cu * xSign; 207 | cpx2 -= absDx * cu * xSign; 208 | }, 209 | vv: () => { 210 | //vertical - from top to bottom or opposite 211 | cpy1 += absDy * cu * ySign; 212 | cpy2 -= absDy * cu * ySign; 213 | }, 214 | hv: () => { 215 | // start horizontally then vertically 216 | // from v side to h side 217 | cpx1 += absDx * cu * xSign; 218 | cpy2 -= absDy * cu * ySign; 219 | }, 220 | vh: () => { 221 | // start vertically then horizontally 222 | // from h side to v side 223 | cpy1 += absDy * cu * ySign; 224 | cpx2 -= absDx * cu * xSign; 225 | }, 226 | }; 227 | else if (path === 'grid') { 228 | curvesPossibilities = { 229 | hh: () => { 230 | cpx1 += (absDx * gridBreak.relative + gridBreak.abs) * xSign; 231 | cpx2 -= (absDx * (1 - gridBreak.relative) - gridBreak.abs) * xSign; 232 | if (showHead) { 233 | cpx1 -= ((fHeadSize * (1 - headOffset)) / 2) * xSign; 234 | cpx2 += ((fHeadSize * (1 - headOffset)) / 2) * xSign; 235 | } 236 | if (showTail) { 237 | cpx1 -= ((fTailSize * (1 - tailOffset)) / 2) * xSign; 238 | cpx2 += ((fTailSize * (1 - tailOffset)) / 2) * xSign; 239 | } 240 | }, 241 | vv: () => { 242 | cpy1 += (absDy * gridBreak.relative + gridBreak.abs) * ySign; 243 | cpy2 -= (absDy * (1 - gridBreak.relative) - gridBreak.abs) * ySign; 244 | if (showHead) { 245 | cpy1 -= ((fHeadSize * (1 - headOffset)) / 2) * ySign; 246 | cpy2 += ((fHeadSize * (1 - headOffset)) / 2) * ySign; 247 | } 248 | if (showTail) { 249 | cpy1 -= ((fTailSize * (1 - tailOffset)) / 2) * ySign; 250 | cpy2 += ((fTailSize * (1 - tailOffset)) / 2) * ySign; 251 | } 252 | }, 253 | hv: () => { 254 | cpx1 = x2; 255 | }, 256 | vh: () => { 257 | cpy1 = y2; 258 | }, 259 | }; 260 | } 261 | // smart select best curve for the current anchors 262 | let selectedCurviness = ''; 263 | if (['left', 'right'].includes(startAnchorPosition)) selectedCurviness += 'h'; 264 | else if (['bottom', 'top'].includes(startAnchorPosition)) selectedCurviness += 'v'; 265 | else if (startAnchorPosition === 'middle') selectedCurviness += 'm'; 266 | if (['left', 'right'].includes(endAnchorPosition)) selectedCurviness += 'h'; 267 | else if (['bottom', 'top'].includes(endAnchorPosition)) selectedCurviness += 'v'; 268 | else if (endAnchorPosition === 'middle') selectedCurviness += 'm'; 269 | if (absDx > absDy) selectedCurviness = selectedCurviness.replace(/m/g, 'h'); 270 | else selectedCurviness = selectedCurviness.replace(/m/g, 'v'); 271 | curvesPossibilities[selectedCurviness](); 272 | 273 | cpx1 += _cpx1Offset; 274 | cpy1 += _cpy1Offset; 275 | cpx2 += _cpx2Offset; 276 | cpy2 += _cpy2Offset; 277 | 278 | //////////////////////////////////// 279 | // canvas smart size adjustments 280 | const [xSol1, xSol2] = buzzierMinSols(x1, cpx1, cpx2, x2); 281 | const [ySol1, ySol2] = buzzierMinSols(y1, cpy1, cpy2, y2); 282 | if (xSol1 < 0) excLeft += -xSol1; 283 | if (xSol2 > absDx) excRight += xSol2 - absDx; 284 | if (ySol1 < 0) excUp += -ySol1; 285 | if (ySol2 > absDy) excDown += ySol2 - absDy; 286 | 287 | if (path === 'grid') { 288 | excLeft += _calc; 289 | excRight += _calc; 290 | excUp += _calc; 291 | excDown += _calc; 292 | } 293 | 294 | x1 += excLeft; 295 | x2 += excLeft; 296 | y1 += excUp; 297 | y2 += excUp; 298 | cpx1 += excLeft; 299 | cpx2 += excLeft; 300 | cpy1 += excUp; 301 | cpy2 += excUp; 302 | 303 | const cw = absDx + excLeft + excRight, 304 | ch = absDy + excUp + excDown; 305 | cx0 -= excLeft; 306 | cy0 -= excUp; 307 | 308 | //labels 309 | const bzx = bzFunction(x1, cpx1, cpx2, x2); 310 | const bzy = bzFunction(y1, cpy1, cpy2, y2); 311 | const labelStartPos = { x: bzx(0.01), y: bzy(0.01) }; 312 | const labelMiddlePos = { x: bzx(0.5), y: bzy(0.5) }; 313 | const labelEndPos = { x: bzx(0.99), y: bzy(0.99) }; 314 | 315 | let arrowPath; 316 | if (path === 'grid') { 317 | // todo: support gridRadius 318 | // arrowPath = `M ${x1} ${y1} L ${cpx1 - 10} ${cpy1} a10,10 0 0 1 10,10 319 | // L ${cpx2} ${cpy2 - 10} a10,10 0 0 0 10,10 L ${x2} ${y2}`; 320 | arrowPath = `M ${x1} ${y1} L ${cpx1} ${cpy1} L ${cpx2} ${cpy2} ${x2} ${y2}`; 321 | } else if (path === 'smooth') arrowPath = `M ${x1} ${y1} C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${x2} ${y2}`; 322 | return { 323 | cx0, 324 | cy0, 325 | x1, 326 | x2, 327 | y1, 328 | y2, 329 | cw, 330 | ch, 331 | cpx1, 332 | cpy1, 333 | cpx2, 334 | cpy2, 335 | dx, 336 | dy, 337 | absDx, 338 | absDy, 339 | headOrient, 340 | tailOrient, 341 | labelStartPos, 342 | labelMiddlePos, 343 | labelEndPos, 344 | excLeft, 345 | excRight, 346 | excUp, 347 | excDown, 348 | headOffset: _headOffset, 349 | arrowHeadOffset, 350 | arrowTailOffset, 351 | startPoints, 352 | endPoints, 353 | mainDivPos, 354 | xSign, 355 | ySign, 356 | lineLength: lineRef.current?.getTotalLength() ?? 0, 357 | fHeadSize, 358 | fTailSize, 359 | arrowPath, 360 | }; 361 | }; 362 | -------------------------------------------------------------------------------- /src/components/Xarrow/Line/utils/buzzier.js: -------------------------------------------------------------------------------- 1 | // Buzier curve calculations 2 | 3 | /** 4 | * returns buzzier curve function with 2 controls points 5 | * bzCurve with 2 control points function(4 points total): bz = (1−t)^3*p1 + 3(1−t)^2*t*p2 +3(1−t)*t^2*p3 + t^3*p4 6 | */ 7 | export const bzFunction = (p1, p2, p3, p4) => (t) => 8 | (1 - t) ** 3 * p1 + 3 * (1 - t) ** 2 * t * p2 + 3 * (1 - t) * t ** 2 * p3 + t ** 3 * p4; 9 | 10 | /** 11 | * returns 2 solutions from extram points for buzzier curve with 2 controls points 12 | */ 13 | export const buzzierMinSols = (p1, p2, p3, p4) => { 14 | const bz = bzFunction(p1, p2, p3, p4); 15 | // dt(bz) = -3 p1 (1 - t)^2 + 3 p2 (1 - t)^2 - 6 p2 (1 - t) t + 6 p3 (1 - t) t - 3 p3 t^2 + 3 p4 t^2 16 | // when p1=(x1,y1),p2=(cpx1,cpy1),p3=(cpx2,cpy2),p4=(x2,y2) 17 | // then extrema points is when dt(bz) = 0 18 | // solutions => t = ((-6 p1 + 12 p2 - 6 p3) ± sqrt((6 p1 - 12 p2 + 6 p3)^2 - 4 (3 p2 - 3 p1) (-3 p1 + 9 p2 - 9 p3 + 3 p4)))/(2 (-3 p1 + 9 p2 - 9 p3 + 3 p4)) when (p1 + 3 p3!=3 p2 + p4) 19 | // if we mark A=(-6 p1 + 12 p2 - 6 p3) and B=(6 p1 - 12 p2 + 6 p3)^2 - 4 (3 p2 - 3 p1) (-3 p1 + 9 p2 - 9 p3 + 3 p4)) and C =(2 (-3 p1 + 9 p2 - 9 p3 + 3 p4) then 20 | // tSol = A ± sqrt(B) 21 | // then solution we want is: bz(tSol) 22 | const A = -6 * p1 + 12 * p2 - 6 * p3; 23 | const B = (-6 * p1 + 12 * p2 - 6 * p3) ** 2 - 4 * (3 * p2 - 3 * p1) * (-3 * p1 + 9 * p2 - 9 * p3 + 3 * p4); 24 | const C = 2 * (-3 * p1 + 9 * p2 - 9 * p3 + 3 * p4); 25 | 26 | const sol1 = bz((A + Math.sqrt(B)) / C); 27 | const sol2 = bz((A - Math.sqrt(B)) / C); 28 | return [sol1, sol2]; 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/Xarrow/Line/utils/index.js: -------------------------------------------------------------------------------- 1 | // import { anchorCustomPositionType, refType } from '../../types'; 2 | import React from 'react'; 3 | 4 | export const getElementByPropGiven = (ref) => { 5 | let myRef; 6 | if (typeof ref === 'string') { 7 | // myRef = document.getElementById(ref); 8 | myRef = document.getElementById(ref); 9 | } else myRef = ref?.current; 10 | return myRef; 11 | }; 12 | 13 | // receives string representing a d path and factoring only the numbers 14 | export const factorDpathStr = (d, factor) => { 15 | let l = d.split(/(\d+(?:\.\d+)?)/); 16 | l = l.map((s) => { 17 | if (Number(s)) return (Number(s) * factor).toString(); 18 | else return s; 19 | }); 20 | return l.join(''); 21 | }; 22 | 23 | // return relative,abs 24 | export const xStr2absRelative = (str) => { 25 | if (typeof str !== 'string') return { abs: 0, relative: 0.5 }; 26 | let sp = str.split('%'); 27 | let absLen = 0, 28 | percentLen = 0; 29 | if (sp.length == 1) { 30 | let p = parseFloat(sp[0]); 31 | if (!isNaN(p)) { 32 | absLen = p; 33 | return { abs: absLen, relative: 0 }; 34 | } 35 | } else if (sp.length == 2) { 36 | let [p1, p2] = [parseFloat(sp[0]), parseFloat(sp[1])]; 37 | if (!isNaN(p1)) percentLen = p1 / 100; 38 | if (!isNaN(p2)) absLen = p2; 39 | if (!isNaN(p1) || !isNaN(p2)) return { abs: absLen, relative: percentLen }; 40 | } 41 | }; 42 | 43 | const dist = (p1, p2) => { 44 | //length of line 45 | return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2); 46 | }; 47 | 48 | // type t1 = { x: number; y: number; anchor: anchorCustomPositionType }; 49 | 50 | export const getShortestLine = (sPoints, ePoints) => { 51 | // closes tPair Of Points which feet to the specified anchors 52 | let minDist = Infinity, 53 | d = Infinity; 54 | let closestPair; 55 | sPoints.forEach((sp) => { 56 | ePoints.forEach((ep) => { 57 | d = dist(sp, ep); 58 | if (d < minDist) { 59 | minDist = d; 60 | closestPair = { chosenStart: sp, chosenEnd: ep }; 61 | } 62 | }); 63 | }); 64 | return closestPair; 65 | }; 66 | 67 | export const getElemPos = (elem) => { 68 | if (!elem) return { x: 0, y: 0, right: 0, bottom: 0 }; 69 | const pos = elem.getBoundingClientRect(); 70 | return { 71 | x: pos.left, 72 | y: pos.top, 73 | right: pos.right, 74 | bottom: pos.bottom, 75 | }; 76 | }; 77 | 78 | export const getSvgPos = (svgRef) => { 79 | if (!svgRef.current) return { x: 0, y: 0 }; 80 | let { left: xarrowElemX, top: xarrowElemY } = svgRef.current.getBoundingClientRect(); 81 | let xarrowStyle = getComputedStyle(svgRef.current); 82 | let xarrowStyleLeft = Number(xarrowStyle.left.slice(0, -2)); 83 | let xarrowStyleTop = Number(xarrowStyle.top.slice(0, -2)); 84 | return { 85 | x: xarrowElemX - xarrowStyleLeft, 86 | y: xarrowElemY - xarrowStyleTop, 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /src/components/Xarrow/NodeArrow.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import Line from "./index"; 3 | import LinkIcon from '@material-ui/icons/Link'; 4 | import Draggable from "react-draggable"; 5 | import "../style.css"; 6 | import LineCompo from "../LineCompo"; 7 | import { getSmoothStepPath } from "./edgeStyle"; 8 | import { initialState } from "./elements"; 9 | 10 | const boxStyle = { 11 | border: "1px #999 solid", 12 | borderRadius: "0px", 13 | textAlign: "center", 14 | width: "12.5rem", 15 | height: "30px", 16 | color: "black", 17 | alignItems: "center", 18 | display: "flex", 19 | justifyContent: "start", 20 | }; 21 | 22 | const canvasStyle = { 23 | width: "100%", 24 | height: "100vh", 25 | background: "white", 26 | overflow: "auto", 27 | display: "flex", 28 | color: "black", 29 | cursor: "pointer", 30 | }; 31 | 32 | const returnRotateFlow = (moveX, posX, moveY, posY) => { 33 | if (moveX - posX > 1 && moveY - posY > 1) { 34 | return "rotateX(0deg)"; 35 | } 36 | if (moveX - posX < 1 && moveY - posY > 1) { 37 | return "rotateY(180deg)"; 38 | } 39 | if (moveX - posX > 1 && moveY - posY < 1) { 40 | return "rotateX(180deg)"; 41 | } 42 | if (moveX - posX < 1 && moveY - posY < 1) { 43 | return "rotate(180deg)"; 44 | } 45 | }; 46 | 47 | const SimpleTemplate = ({updateXarrow}) => { 48 | const a = useRef(0); 49 | 50 | // const updateXarrow = useXarrow(); 51 | const scrollFunc = () => { 52 | updateXarrow(); 53 | }; 54 | 55 | const [flag, setflag] = useState(false) 56 | 57 | const [boxArray, setBoxArray] = useState(initialState); 58 | 59 | const [actveEle, setActveEle] = useState(null); 60 | const [point, setPoint] = useState({ 61 | start: null, 62 | end: null, 63 | parentStart: null, 64 | parentEnd: null, 65 | }); 66 | const [parentConnection, setParentConnection] = useState([]) 67 | const [connection, setConnection] = useState([]); 68 | const [position, setposition] = useState({ 69 | display: false, 70 | sX: "0", 71 | sy: "0", 72 | lx: "", 73 | ly: "", 74 | style: { 75 | top: "", 76 | left: "", 77 | position: "absolute", 78 | }, 79 | }); 80 | 81 | const [pos, setpos] = useState() 82 | const [dragPos, setDragPos] = useState({ 83 | x:'', 84 | y:'' 85 | }) 86 | 87 | const handleDragPosition = (e, id) => { 88 | updateXarrow() 89 | console.log(e.pageX, e.pageY) 90 | setDragPos({ 91 | x:e.pageX-50, 92 | y:e.pageY-50 93 | }) 94 | setBoxArray([...boxArray.map((item)=>{ 95 | if(item.id===id){ 96 | item.xpos = e.pageX-50, 97 | item.ypos = e.pageY-50 98 | } 99 | return item 100 | })]) 101 | } 102 | 103 | const onDragEnd = (e, id) => { 104 | 105 | console.log(e.pageX, e.pageY) 106 | setDragPos({ 107 | x:e.pageX-50, 108 | y:e.pageY-50 109 | }) 110 | setBoxArray([...boxArray.map((item)=>{ 111 | if(item.id===id){ 112 | item.xpos = e.pageX-50, 113 | item.ypos = e.pageY-50 114 | } 115 | return item 116 | })]) 117 | } 118 | 119 | // establish connection 120 | const setEndpoints = (e, id, parentId, key) => {console.log(id, parentId) 121 | e.stopPropagation(); 122 | if (point.start && point.start !== id && key === 'end') { 123 | setPoint({ 124 | ...point, 125 | end: id, 126 | parentEnd: parentId, 127 | }); 128 | setConnection([ 129 | ...connection, 130 | { 131 | start: point.start, 132 | end: id, 133 | parentStart: point.parentStart, 134 | parentEnd: parentId, 135 | position:position 136 | }, 137 | ]);console.log(parentConnection) 138 | if(!parentConnection.some(items=>items.end===parentId)){ 139 | setParentConnection([...parentConnection,{start:point.parentStart, end: parentId}]) 140 | } 141 | setPoint({ 142 | start: null, 143 | end: null, 144 | parentStart: null, 145 | parentEnd: null, 146 | }); 147 | } else { 148 | setPoint({ 149 | start: null, 150 | end: null, 151 | parentStart: null, 152 | parentEnd: null, 153 | }); 154 | setPoint({ 155 | ...point, 156 | start: id, 157 | parentStart: parentId, 158 | }); 159 | } 160 | }; 161 | 162 | useEffect(() => { 163 | console.log(connection) 164 | }, [connection]) 165 | 166 | //on mouse down start making connection 167 | const connectNode = (e, id, parentId, key) => { 168 | e.preventDefault(); 169 | e.stopPropagation(); 170 | var element = e.target; 171 | let boxCenter = { 172 | x: 173 | element.getBoundingClientRect().left, 174 | y: 175 | element.getBoundingClientRect().top-5, 176 | }; 177 | var topPos = element.getBoundingClientRect().top + window.scrollY; 178 | var leftPos = element.getBoundingClientRect().left + window.scrollX; 179 | setEndpoints(e, id, parentId, key); 180 | console.log(e.pageX, e.pageY, boxCenter.x, boxCenter.y); 181 | setposition({ 182 | ...position, 183 | display: true, 184 | sX: boxCenter.x, 185 | sy: boxCenter.y, 186 | lX: e.pageX, 187 | ly: e.pageY, 188 | style: { 189 | position: "absolute", 190 | top: boxCenter.y, 191 | left: boxCenter.x, 192 | }, 193 | }); 194 | document.onmousemove = (event) => {console.log(event.pageX, event.pageY, event.target.id) 195 | event.preventDefault(); 196 | let angle = 197 | Math.atan2(event.pageX - boxCenter.x, -(event.pageY - boxCenter.y)) * 198 | (180 / Math.PI); 199 | console.log(event.screenX, event.screenY, event.target.offsetTop); 200 | const posVal = event.target.getBoundingClientRect(); 201 | console.log(posVal) 202 | setposition({ 203 | ...position, 204 | display: true, 205 | lx: Math.abs(event.pageX - e.pageX), 206 | ly: Math.abs(event.pageY - e.pageY), 207 | style: { 208 | position: "absolute", 209 | top: Math.abs(boxCenter.y+10), 210 | left: Math.abs(boxCenter.x), 211 | width: Math.abs(event.pageX - e.pageX), 212 | height: Math.abs(event.pageY - e.pageY), 213 | transformOrigin: "0% 0%", 214 | transform: returnRotateFlow( 215 | event.pageX, 216 | e.pageX, 217 | event.pageY, 218 | e.pageY 219 | ), 220 | }, 221 | }); 222 | }; 223 | document.onmouseup = stopConnection; 224 | }; 225 | /// stop connection 226 | const stopConnection = (e) => { 227 | document.onmousemove = null; 228 | document.onMouseDown = null; 229 | setposition({ 230 | display: false, 231 | sX: "0", 232 | sy: "0", 233 | lx: "", 234 | ly: "", 235 | style: { 236 | top: "", 237 | left: "", 238 | position: "absolute", 239 | }, 240 | }); 241 | }; 242 | 243 | //on mouse up 244 | const onMouseUp = (e, id, parentId, key) => { 245 | setEndpoints(e, id, parentId, key); 246 | stopConnection(e); 247 | }; 248 | 249 | //remove connsction here 250 | const removeConnection = (item) => { 251 | const newarray = connection; 252 | const filteredElements = newarray.filter(items=>(items.parentStart===item.parentStart && items.parentEnd === item.parentEnd)); 253 | if(filteredElements.length<2){ 254 | const parentIndex = parentConnection.findIndex(ele=>(ele.start === item.parentStart && ele.end === item.parentEnd)) 255 | parentConnection.splice(parentIndex,1); 256 | setParentConnection([...parentConnection]); 257 | } 258 | const index = newarray.findIndex((items) => items === item); 259 | newarray.splice(index, 1); 260 | setConnection([...newarray]); 261 | }; 262 | 263 | const handleChange = (elementId) => { 264 | setActveEle(elementId); 265 | a.current.clear(); 266 | }; 267 | 268 | const setAnchorPoints = (value) => { 269 | const nVlaue = value.split(","); 270 | if (actveEle) { 271 | setBoxArray([ 272 | ...boxArray.map((items) => { 273 | items.child.map((item) => { 274 | if (actveEle === item.id) { 275 | if (nVlaue.includes("left")) { 276 | item.left = true; 277 | } 278 | if (nVlaue.includes("right")) { 279 | item.right = true; 280 | } 281 | } 282 | }); 283 | return items; 284 | }), 285 | ]); 286 | } 287 | }; 288 | 289 | const onexpand = (id) => { 290 | 291 | setBoxArray([ 292 | ...boxArray.map((items) => { 293 | if (items.id === id) { 294 | items.expand = !items.expand; 295 | } 296 | return items; 297 | }), 298 | ]); 299 | updateXarrow() 300 | document.onclick = () =>{ 301 | updateXarrow() 302 | } 303 | setflag(!flag) 304 | }; 305 | 306 | const getIdandParentid = (items, key) => { 307 | if(key==='end'){ 308 | return document.getElementById(items.end) ? items.end : document.getElementById(items.start) ? items.parentEnd :null; 309 | }else if(key==='start'){ 310 | return (document.getElementById(items.start)) ? items.start : (document.getElementById(items.end)) ? items.parentStart :null; 311 | } 312 | } 313 | 314 | return ( 315 | <> 316 |
317 | {/* */} 318 | {position.display && ( 319 | 320 | 321 | 332 | 333 | 334 | )} 335 | { 336 | connection.map((items)=>{ 337 | 338 | 347 | 348 | }) 349 | } 350 | {/* 351 | 352 | */} 353 | 354 | {/* 355 | 356 | */} 357 | 358 | {boxArray.map((items) => ( 359 | handleDragPosition(e,items.id)} 363 | onDragEnd={(e)=>onDragEnd(e,items.id)} 364 | style={{position:'absolute',left:items.xpos, top:items.ypos}} 365 | 366 | > 367 |
371 |
372 | { 373 | // item.left && 374 | ( 375 | 379 | connectNode(e, items.id+items.name, null,'start') 380 | } 381 | onMouseUp={(e) => onMouseUp(e, items.id+items.name, null,'end')} 382 | > 383 | )} 384 |

{items.name}

385 | { 386 | // item.right && 387 | ( 388 | 392 | connectNode(e, items.id+items.name, null,'start') 393 | } 394 | onMouseUp={(e) => onMouseUp(e, items.id+items.name, null,'end')} 395 | > 396 | )} 397 |
398 | 399 |
400 | {items.expand && 401 | items.child.map((item) => ( 402 | <> 403 |
404 |
405 |
412 | { 413 | // item.left && 414 | ( 415 | 419 | connectNode(e, item.id, items.id,'start') 420 | } 421 | onMouseUp={(e) => onMouseUp(e, item.id, items.id,'end')} 422 | > 423 | )} 424 |
onMouseUp(e, item.id, items.id,'end')} 426 | > 427 |

Column

428 |
429 | { 430 | // item.right && 431 | ( 432 | 436 | connectNode(e, item.id, items.id,'start') 437 | } 438 | onMouseUp={(e) => onMouseUp(e, item.id, items.id,'end')} 439 | > 440 | )} 441 |
442 |
443 |
444 | 445 | ))} 446 |
447 |
448 |
449 | ))} 450 | {/* { 451 | parentConnection.length && 452 | parentConnection.map((items)=> 453 | 463 | } 464 | startAnchor="auto" 465 | endAnchor="auto" 466 | /> 467 | ) 468 | } */} 469 | 470 | {connection.length 471 | ? connection.map((items) => ( 472 | removeConnection(items)} 485 | > 486 | } 487 | startAnchor="auto" 488 | endAnchor="auto" 489 | /> 490 | )) 491 | : ""} 492 | {/*
*/} 493 |
494 | 495 | ); 496 | }; 497 | 498 | export default SimpleTemplate; 499 | 500 | // setEndpoints(id) 501 | -------------------------------------------------------------------------------- /src/components/Xarrow/Xwrapper.js: -------------------------------------------------------------------------------- 1 | import React, { FC, useEffect, useRef, useState } from 'react'; 2 | 3 | export const XelemContext = React.createContext(null); 4 | export const XarrowContext = React.createContext(null); 5 | 6 | const updateRef = {}; 7 | let updateRefCount = 0; 8 | 9 | const log = console.log; 10 | 11 | const XarrowProvider = ({ children, instanceCount }) => { 12 | const [, setRender] = useState({}); 13 | const updateXarrow = () => setRender({}); 14 | useEffect(() => { 15 | instanceCount.current = updateRefCount; // so this instance would know what is id 16 | updateRef[instanceCount.current] = updateXarrow; 17 | }, []); 18 | // log('XarrowProvider', updateRefCount); 19 | return {children}; 20 | }; 21 | 22 | const XelemProvider = ({ children, instanceCount }) => { 23 | return {children}; 24 | }; 25 | 26 | const Xwrapper = ({ children }) => { 27 | const instanceCount = useRef(updateRefCount); 28 | const [, setRender] = useState({}); 29 | useEffect(() => { 30 | updateRefCount++; 31 | setRender({}); 32 | return () => { 33 | delete updateRef[instanceCount.current]; 34 | }; 35 | }, []); 36 | 37 | return ( 38 | 39 | {children} 40 | 41 | ); 42 | }; 43 | 44 | export default Xwrapper; 45 | -------------------------------------------------------------------------------- /src/components/Xarrow/constants.js: -------------------------------------------------------------------------------- 1 | // constants used for typescript and proptypes definitions 2 | import React from 'react'; 3 | 4 | export const cAnchorEdge = ['middle', 'left', 'right', 'top', 'bottom', 'auto'] ; 5 | export const cPaths = ['smooth', 'grid', 'straight'] ; 6 | export const cSvgElems = ['circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'rect'] ; 7 | 8 | //default arrows svgs 9 | export const arrowShapes = { 10 | arrow1: { svgElem: , offsetForward: 0.25 }, 11 | heart: { 12 | svgElem: ( 13 | 14 | ), 15 | offsetForward: 0.1, 16 | }, 17 | circle: { 18 | svgElem: , 19 | offsetForward: 0, 20 | }, 21 | }; 22 | 23 | export const cArrowShapes = Object.keys(arrowShapes); 24 | -------------------------------------------------------------------------------- /src/components/Xarrow/edgeStyle.js: -------------------------------------------------------------------------------- 1 | // These are some helper methods for drawing the round corners 2 | // The name indicates the direction of the path. "bottomLeftCorner" goes 3 | // from bottom to the left and "leftBottomCorner" goes from left to the bottom. 4 | // We have to consider the direction of the paths because of the animated lines. 5 | export const Position = { 6 | Left : 'left', 7 | Top : 'top', 8 | Right : 'right', 9 | Bottom : 'bottom', 10 | } 11 | 12 | // export interface GetCenterParams { 13 | // sourceX: number; 14 | // sourceY: number; 15 | // targetX: number; 16 | // targetY: number; 17 | // sourcePosition?: Position; 18 | // targetPosition?: Position; 19 | // } 20 | 21 | const LeftOrRight = [Position.Left, Position.Right]; 22 | 23 | export const getCenter = ({ 24 | sourceX, 25 | sourceY, 26 | targetX, 27 | targetY, 28 | sourcePosition = Position.Bottom, 29 | targetPosition = Position.Top, 30 | }) => { 31 | const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition); 32 | const targetIsLeftOrRight = LeftOrRight.includes(targetPosition); 33 | 34 | // we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom) 35 | // a mixed edge is when one the source is on the left and the target is on the top for example. 36 | const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight); 37 | 38 | if (mixedEdge) { 39 | const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0; 40 | const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset; 41 | 42 | const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY); 43 | const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset; 44 | 45 | return [centerX, centerY, xOffset, yOffset]; 46 | } 47 | 48 | const xOffset = Math.abs(targetX - sourceX) / 2; 49 | const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; 50 | 51 | const yOffset = Math.abs(targetY - sourceY) / 2; 52 | const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; 53 | 54 | return [centerX, centerY, xOffset, yOffset]; 55 | }; 56 | 57 | const bottomLeftCorner = (x, y, size) => 58 | `L ${x},${y - size}Q ${x},${y} ${x + size},${y}`; 59 | const leftBottomCorner = (x, y, size) => 60 | `L ${x + size},${y}Q ${x},${y} ${x},${y - size}`; 61 | const bottomRightCorner = (x, y, size) => 62 | `L ${x},${y - size}Q ${x},${y} ${x - size},${y}`; 63 | const rightBottomCorner = (x, y, size) => 64 | `L ${x - size},${y}Q ${x},${y} ${x},${y - size}`; 65 | const leftTopCorner = (x, y, size) => `L ${x + size},${y}Q ${x},${y} ${x},${y + size}`; 66 | const topLeftCorner = (x, y, size) => `L ${x},${y + size}Q ${x},${y} ${x + size},${y}`; 67 | const topRightCorner = (x, y, size) => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}`; 68 | const rightTopCorner = (x, y, size) => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}`; 69 | 70 | // export interface GetSmoothStepPathParams { 71 | // sourceX: number; 72 | // sourceY: number; 73 | // sourcePosition?: Position; 74 | // targetX: number; 75 | // targetY: number; 76 | // targetPosition?: Position; 77 | // borderRadius?: number; 78 | // centerX?: number; 79 | // centerY?: number; 80 | // } 81 | 82 | export function getSmoothStepPath({ 83 | sourceX, 84 | sourceY, 85 | sourcePosition = Position.Bottom, 86 | targetX, 87 | targetY, 88 | targetPosition = Position.Top, 89 | borderRadius = 5, 90 | centerX, 91 | centerY, 92 | }) { 93 | const [_centerX, _centerY, offsetX, offsetY] = getCenter({ sourceX, sourceY, targetX, targetY }); 94 | const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX)); 95 | const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY)); 96 | const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY); 97 | const leftAndRight = [Position.Left, Position.Right]; 98 | const cX = typeof centerX !== 'undefined' ? centerX : _centerX; 99 | const cY = typeof centerY !== 'undefined' ? centerY : _centerY; 100 | 101 | let firstCornerPath = null; 102 | let secondCornerPath = null; 103 | 104 | if (sourceX <= targetX) { 105 | firstCornerPath = 106 | sourceY <= targetY ? bottomLeftCorner(sourceX, cY, cornerSize) : topLeftCorner(sourceX, cY, cornerSize); 107 | secondCornerPath = 108 | sourceY <= targetY ? rightTopCorner(targetX, cY, cornerSize) : rightBottomCorner(targetX, cY, cornerSize); 109 | } else { 110 | firstCornerPath = 111 | sourceY < targetY ? bottomRightCorner(sourceX, cY, cornerSize) : topRightCorner(sourceX, cY, cornerSize); 112 | secondCornerPath = 113 | sourceY < targetY ? leftTopCorner(targetX, cY, cornerSize) : leftBottomCorner(targetX, cY, cornerSize); 114 | } 115 | 116 | if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { 117 | if (sourceX <= targetX) { 118 | firstCornerPath = 119 | sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize); 120 | secondCornerPath = 121 | sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize); 122 | } else if ( 123 | (sourcePosition === Position.Right && targetPosition === Position.Left) || 124 | (sourcePosition === Position.Left && targetPosition === Position.Right) || 125 | (sourcePosition === Position.Left && targetPosition === Position.Left) 126 | ) { 127 | // and sourceX > targetX 128 | firstCornerPath = 129 | sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize); 130 | secondCornerPath = 131 | sourceY <= targetY ? bottomRightCorner(cX, targetY, cornerSize) : topRightCorner(cX, targetY, cornerSize); 132 | } 133 | } else if (leftAndRight.includes(sourcePosition) && !leftAndRight.includes(targetPosition)) { 134 | if (sourceX <= targetX) { 135 | firstCornerPath = 136 | sourceY <= targetY 137 | ? rightTopCorner(targetX, sourceY, cornerSize) 138 | : rightBottomCorner(targetX, sourceY, cornerSize); 139 | } else { 140 | firstCornerPath = 141 | sourceY <= targetY 142 | ? leftTopCorner(targetX, sourceY, cornerSize) 143 | : leftBottomCorner(targetX, sourceY, cornerSize); 144 | } 145 | secondCornerPath = ''; 146 | } else if (!leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { 147 | if (sourceX <= targetX) { 148 | firstCornerPath = 149 | sourceY <= targetY 150 | ? bottomLeftCorner(sourceX, targetY, cornerSize) 151 | : topLeftCorner(sourceX, targetY, cornerSize); 152 | } else { 153 | firstCornerPath = 154 | sourceY <= targetY 155 | ? bottomRightCorner(sourceX, targetY, cornerSize) 156 | : topRightCorner(sourceX, targetY, cornerSize); 157 | } 158 | secondCornerPath = ''; 159 | } 160 | 161 | return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}`; 162 | } -------------------------------------------------------------------------------- /src/components/Xarrow/elements.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | 3 | export const initialState = [ 4 | { 5 | id: uuidv4(), 6 | expand: false, 7 | name: "Table 1", 8 | xpos:'', 9 | ypos:'', 10 | child: [ 11 | { id: uuidv4(), left: false, right: false }, 12 | { id: uuidv4(), left: false, right: false }, 13 | { id: uuidv4(), left: false, right: false }, 14 | { id: uuidv4(), left: false, right: false }, 15 | ], 16 | }, 17 | { 18 | id: uuidv4(), 19 | expand: false, 20 | name: "Table 2", 21 | xpos:200, 22 | ypos:'', 23 | child: [ 24 | { id: uuidv4(), left: false, right: false }, 25 | { id: uuidv4(), left: false, right: false }, 26 | { id: uuidv4(), left: false, right: false }, 27 | { id: uuidv4(), left: false, right: false }, 28 | ], 29 | }, 30 | { 31 | id: uuidv4(), 32 | expand: false, 33 | name: "Table 3", 34 | child: [ 35 | { id: uuidv4(), left: false, right: false }, 36 | { id: uuidv4(), left: false, right: false }, 37 | { id: uuidv4(), left: false, right: false }, 38 | { id: uuidv4(), left: false, right: false }, 39 | ], 40 | }, 41 | { 42 | id: uuidv4(), 43 | expand: false, 44 | name: "Table", 45 | child: [ 46 | { id: uuidv4(), left: false, right: false }, 47 | { id: uuidv4(), left: false, right: false }, 48 | { id: uuidv4(), left: false, right: false }, 49 | { id: uuidv4(), left: false, right: false }, 50 | ], 51 | }, 52 | { 53 | id: uuidv4(), 54 | expand: false, 55 | name: "Table", 56 | child: [ 57 | { id: uuidv4(), left: false, right: false }, 58 | { id: uuidv4(), left: false, right: false }, 59 | { id: uuidv4(), left: false, right: false }, 60 | { id: uuidv4(), left: false, right: false }, 61 | ], 62 | }, 63 | { 64 | id: uuidv4(), 65 | expand: false, 66 | name: "Table", 67 | child: [ 68 | { id: uuidv4(), left: false, right: false }, 69 | { id: uuidv4(), left: false, right: false }, 70 | { id: uuidv4(), left: false, right: false }, 71 | { id: uuidv4(), left: false, right: false }, 72 | ], 73 | }, 74 | { 75 | id: uuidv4(), 76 | expand: false, 77 | name: "Table", 78 | child: [ 79 | { id: uuidv4(), left: false, right: false }, 80 | { id: uuidv4(), left: false, right: false }, 81 | { id: uuidv4(), left: false, right: false }, 82 | { id: uuidv4(), left: false, right: false }, 83 | ], 84 | }, 85 | { 86 | id: uuidv4(), 87 | expand: false, 88 | name: "Table", 89 | child: [ 90 | { id: uuidv4(), left: false, right: false }, 91 | { id: uuidv4(), left: false, right: false }, 92 | { id: uuidv4(), left: false, right: false }, 93 | { id: uuidv4(), left: false, right: false }, 94 | ], 95 | }, 96 | { 97 | id: uuidv4(), 98 | expand: false, 99 | name: "Table", 100 | child: [ 101 | { id: uuidv4(), left: false, right: false }, 102 | { id: uuidv4(), left: false, right: false }, 103 | { id: uuidv4(), left: false, right: false }, 104 | { id: uuidv4(), left: false, right: false }, 105 | ], 106 | }, 107 | { 108 | id: uuidv4(), 109 | expand: false, 110 | name: "Table", 111 | child: [ 112 | { id: uuidv4(), left: false, right: false }, 113 | { id: uuidv4(), left: false, right: false }, 114 | { id: uuidv4(), left: false, right: false }, 115 | { id: uuidv4(), left: false, right: false }, 116 | ], 117 | }, 118 | ]; -------------------------------------------------------------------------------- /src/components/Xarrow/index.js: -------------------------------------------------------------------------------- 1 | import Xarrow from './Line/Xarrow'; 2 | import Xwrapper from './Xwrapper'; 3 | import useXarrow from './useXarrow'; 4 | // export * from './types'; 5 | // export * from './constants'; 6 | 7 | export { Xwrapper, useXarrow }; 8 | export default Xarrow; 9 | -------------------------------------------------------------------------------- /src/components/Xarrow/useXarrow.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useLayoutEffect, useState } from 'react'; 2 | import { XelemContext } from './Xwrapper'; 3 | 4 | const useXarrow = () => { 5 | const [, setRender] = useState({}); 6 | const reRender = () => setRender({}); 7 | 8 | let updateXarrow = useContext(XelemContext); 9 | if (!updateXarrow) updateXarrow = () => {}; 10 | // throw new Error( 11 | // "'Xwrapper' is required around element using 'useXarrow' hook! wrap your xarrows and connected elements with Xwrapper! " 12 | // ); 13 | 14 | useLayoutEffect(() => { 15 | updateXarrow(); 16 | }); 17 | return reRender; 18 | }; 19 | 20 | export default useXarrow; 21 | -------------------------------------------------------------------------------- /src/components/Xarrow2/data.js: -------------------------------------------------------------------------------- 1 | import { capitalize, uniqueId } from "lodash"; 2 | 3 | export const data = [ 4 | { 5 | id: "ele1", 6 | name: "Ele 1", 7 | type: "simple", 8 | positions: { 9 | x: 0, 10 | y: 100, 11 | }, 12 | situation: { 13 | child: ["ele2"], 14 | parent: [], 15 | }, 16 | targetPosition: "top", 17 | sourcePosition: "bottom", 18 | childrens: [], 19 | position: { 20 | x: 500, 21 | y: 0, 22 | }, 23 | }, 24 | { 25 | id: "ele2", 26 | name: "Ele 2", 27 | type: "simple", 28 | positions: { 29 | x: 100, 30 | y: 200, 31 | }, 32 | situation: { 33 | child: [], 34 | parent: ["ele1"], 35 | }, 36 | targetPosition: "top", 37 | sourcePosition: "bottom", 38 | childrens: [], 39 | position: { 40 | x: 500, 41 | y: 100, 42 | }, 43 | }, 44 | { 45 | id: "ele3", 46 | name: "Ele 3", 47 | type: "simple", 48 | positions: { 49 | x: 300, 50 | y: 300, 51 | }, 52 | situation: { 53 | child: [], 54 | parent: ["ele2"], 55 | }, 56 | targetPosition: "top", 57 | sourcePosition: "bottom", 58 | childrens: [], 59 | position: { 60 | x: 500, 61 | y: 200, 62 | }, 63 | }, 64 | { 65 | id: "ele4", 66 | name: "Ele 4", 67 | type: "simple", 68 | positions: { 69 | x: 400, 70 | y: 400, 71 | }, 72 | situation: { 73 | child: [], 74 | parent: ["ele3"], 75 | }, 76 | targetPosition: "top", 77 | sourcePosition: "bottom", 78 | childrens: [], 79 | position: { 80 | x: 500, 81 | y: 300, 82 | }, 83 | }, 84 | ]; 85 | 86 | export const edgeConn = [ 87 | { 88 | sId: "ele1", 89 | tId: "ele2", 90 | }, 91 | { 92 | sId: "ele1", 93 | tId: "ele3", 94 | }, 95 | { 96 | sId: "ele1", 97 | tId: "ele4", 98 | }, 99 | ]; 100 | 101 | export const rawData = [ 102 | { 103 | id: uniqueId("dcvghd"), 104 | name: capitalize(uniqueId("dcvghd")[(0, 4)]), 105 | parent: "ele4", 106 | }, 107 | { 108 | id: uniqueId("dcvghd"), 109 | name: capitalize(uniqueId("dcvghd")[(0, 4)]), 110 | parent: "ele2", 111 | }, 112 | { 113 | id: uniqueId("dcvghd"), 114 | name: capitalize(uniqueId("dcvghd")[(0, 4)]), 115 | parent: "ele3", 116 | }, 117 | { 118 | id: uniqueId("dcvghd"), 119 | name: capitalize(uniqueId("dcvghd")[(0, 4)]), 120 | parent: "ele4", 121 | }, 122 | { 123 | id: uniqueId("dcvghd"), 124 | name: capitalize(uniqueId("dcvghd")[(0, 4)]), 125 | parent: "ele4", 126 | }, 127 | { 128 | id: uniqueId("dcvghd"), 129 | name: capitalize(uniqueId("dcvghd")[(0, 4)]), 130 | parent: "ele3", 131 | }, 132 | ]; 133 | -------------------------------------------------------------------------------- /src/components/Xarrow2/draggablebox.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import Xarrow, { useXarrow, Xwrapper } from "react-xarrows"; 3 | import Draggable from "react-draggable"; 4 | import { data, edgeConn } from "./data"; 5 | import { AddOutlined, Close } from "@mui/icons-material"; 6 | import { Box, CylinderShape, EndNode, Simple } from "./shape"; 7 | import dagre from "dagre"; 8 | import { IconButton } from "@mui/material"; 9 | import { get, has, random, stubString } from "lodash"; 10 | import { Resizable } from "re-resizable"; 11 | 12 | const shapes = { 13 | circle: (props) => , 14 | cylinder: (props) => , 15 | endnode: (props) => , 16 | simple: (props) => , 17 | }; 18 | 19 | const boxStyle = { 20 | border: "grey solid 2px", 21 | borderRadius: "4px", 22 | padding: "5px", 23 | width: "5rem", 24 | cursor: "grab", 25 | }; 26 | 27 | const DraggableBox = ({ 28 | id, 29 | name, 30 | addConnect, 31 | sourceId, 32 | setSourceId, 33 | setmovableEle, 34 | setmovePos, 35 | shapeType, 36 | position, 37 | addNewNode, 38 | dragEle, 39 | setDragEle, 40 | nodeClick, 41 | }) => { 42 | const updateXarrow = useXarrow(); 43 | 44 | function handleDragFunc(e) { 45 | updateXarrow(); 46 | console.log("hi"); 47 | if (shapeType === "endnode") setDragEle("endnode"); 48 | } 49 | 50 | function onMouseDrag(e, id) { 51 | e.preventDefault(); 52 | e.stopPropagation(); 53 | if (shapeType !== "endnode") { 54 | setSourceId(id); 55 | document.onmousemove = onMousemove; 56 | document.onmouseup = onMouseDragStop; 57 | } 58 | } 59 | 60 | function onMousemove(e) { 61 | if (shapeType !== "endnode") { 62 | setmovePos({ 63 | top: e.clientY, 64 | left: e.clientX, 65 | }); 66 | setmovableEle("moveEle"); 67 | } 68 | } 69 | 70 | function onMouseDragStop(e) { 71 | setmovableEle(null); 72 | document.onmousemove = null; 73 | } 74 | 75 | function onMouseStop(e, id) { 76 | addConnect(sourceId, id); 77 | } 78 | 79 | function handleClickNode(e) { 80 | console.log(dragEle); 81 | if (dragEle === "endnode") { 82 | setDragEle(null); 83 | return null; 84 | } 85 | 86 | e.preventDefault(); 87 | 88 | if (shapeType === "endnode") addNewNode(id); 89 | else { 90 | nodeClick(id); 91 | } 92 | } 93 | 94 | return ( 95 | 96 |
104 | {shapeType !== "endnode" && ( 105 | <> 106 | onMouseDrag(e, id)} 110 | onMouseUp={(e) => onMouseStop(e, id)} 111 | style={{ 112 | fontSize: "10px", 113 | position: "absolute", 114 | top: "-10px", 115 | left: "45%", 116 | }} 117 | /> 118 | onMouseDrag(e, id)} 122 | onMouseUp={(e) => onMouseStop(e, id)} 123 | style={{ 124 | fontSize: "10px", 125 | position: "absolute", 126 | top: "35%", 127 | left: "-10px", 128 | }} 129 | /> 130 | onMouseDrag(e, id)} 134 | onMouseUp={(e) => onMouseStop(e, id)} 135 | style={{ 136 | fontSize: "10px", 137 | position: "absolute", 138 | bottom: "-10px", 139 | left: "45%", 140 | }} 141 | /> 142 | onMouseDrag(e, id)} 146 | onMouseUp={(e) => onMouseStop(e, id)} 147 | style={{ 148 | fontSize: "10px", 149 | position: "absolute", 150 | top: "35%", 151 | right: "-10px", 152 | }} 153 | /> 154 | 155 | )} 156 | {shapeType && has(shapes, shapeType) ? ( 157 | shapes[shapeType]({ id, name, onClick: handleClickNode }) 158 | ) : ( 159 |
160 | {name} 161 |
162 | )} 163 |
164 |
165 | ); 166 | }; 167 | 168 | export default React.memo(DraggableBox); 169 | -------------------------------------------------------------------------------- /src/components/Xarrow2/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import Xarrow, { useXarrow, Xwrapper } from "react-xarrows"; 3 | import Draggable from "react-draggable"; 4 | import { data, edgeConn, rawData } from "./data"; 5 | import { AddOutlined, Close } from "@mui/icons-material"; 6 | import { Box, CylinderShape, EndNode, Simple } from "./shape"; 7 | import dagre from "dagre"; 8 | import { IconButton } from "@mui/material"; 9 | import { 10 | clone, 11 | filter, 12 | find, 13 | get, 14 | has, 15 | includes, 16 | map, 17 | random, 18 | reduce, 19 | stubString, 20 | } from "lodash"; 21 | import Draggablebox from "./draggablebox"; 22 | 23 | const dagreGraph = new dagre.graphlib.Graph(); 24 | dagreGraph.setDefaultEdgeLabel(() => ({})); 25 | 26 | const nodeWidth = 172; 27 | const nodeHeight = 50; 28 | 29 | const getLayoutedElements = (nodes, edges, direction = "TB") => { 30 | let newNodes = [], 31 | newEdges = edges; 32 | const iniNodes = nodes; 33 | 34 | newNodes = iniNodes.reduce((acc, item, i) => { 35 | const cihd = !item?.situation?.child?.length && item?.type !== "endnode"; 36 | if (cihd) { 37 | let addNode = { 38 | id: String(random(999999, 99999999)), 39 | type: "endnode", 40 | name: "Add", 41 | situation: { 42 | child: [], 43 | parent: [item.id], 44 | }, 45 | positions: { 46 | x: 400, 47 | y: 400, 48 | }, 49 | }; 50 | item.situation.child = [...item?.situation?.child, addNode?.id]; 51 | newEdges = [...newEdges, { sId: item?.id, tId: addNode.id }]; 52 | acc.push(item); 53 | acc.push(addNode); 54 | } else { 55 | acc.push(item); 56 | } 57 | return acc; 58 | }, []); 59 | 60 | const isHorizontal = direction === "LR"; 61 | dagreGraph.setGraph({ rankdir: direction }); 62 | 63 | newNodes.forEach((node) => { 64 | dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }); 65 | }); 66 | 67 | newEdges.forEach((edge) => { 68 | dagreGraph.setEdge(edge.sId, edge.tId); 69 | }); 70 | 71 | dagre.layout(dagreGraph); 72 | 73 | newNodes = newNodes.map((node) => { 74 | const nodeWithPosition = dagreGraph.node(node.id); 75 | const newNode = { 76 | ...node, 77 | targetPosition: isHorizontal ? "left" : "top", 78 | sourcePosition: isHorizontal ? "right" : "bottom", 79 | // We are shifting the dagre node position (anchor=center center) to the top left 80 | // so it matches the React Flow node anchor point (top left). 81 | position: { 82 | x: 500 + nodeWithPosition.x - nodeWidth / 2, 83 | y: nodeWithPosition.y - nodeHeight / 2, 84 | }, 85 | }; 86 | 87 | return newNode; 88 | }); 89 | 90 | return { nodes: newNodes, edges: newEdges }; 91 | }; 92 | 93 | const XarrowComponent = () => { 94 | const updateXarrow = useXarrow(); 95 | 96 | const [sourceId, setSourceId] = useState(null); 97 | const [movableEle, setmovableEle] = useState(null); 98 | const [dragEle, setDragEle] = useState(null); 99 | 100 | const [movePos, setmovePos] = useState({ 101 | top: 0, 102 | left: 0, 103 | }); 104 | 105 | let localNodes = localStorage.getItem("nodes"), 106 | localEdges = localStorage.getItem("edges"); 107 | 108 | let n = localNodes ? JSON.parse(localNodes) : data, 109 | e = localEdges ? JSON.parse(localEdges) : edgeConn; 110 | 111 | const [nodes, setNodes] = useState([]); 112 | const [connections, setConnections] = useState([]); 113 | const [flag, setflag] = useState(false); 114 | 115 | useEffect(() => { 116 | const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( 117 | n, 118 | e 119 | ); 120 | setNodes(layoutedNodes); 121 | setConnections(layoutedEdges); 122 | }, []); 123 | 124 | function addConnect(sId, tId) { 125 | handleSetNodesAndConnections(nodes, [...connections, { sId, tId }]); 126 | } 127 | 128 | function deleteMe(sid, tid) { 129 | const tgNodes = map(nodes, (item) => { 130 | if (item.id === sid && includes(item.situation.child, tid)) { 131 | if (item?.situation?.child?.length > 1) { 132 | item.situation.child = filter( 133 | item.situation.child, 134 | (ele) => ele !== tid 135 | ); 136 | } else { 137 | item.situation.child = []; 138 | } 139 | } 140 | return item; 141 | }); 142 | 143 | // change connetion 144 | const conn = connections.findIndex( 145 | (item) => item.sId === sid && item.tId === tid 146 | ); 147 | 148 | connections.splice(conn, 1); 149 | 150 | const findTargetele = find( 151 | nodes, 152 | (item) => item.id === tid && item?.type === "endnode" 153 | ); 154 | 155 | setflag(findTargetele); 156 | 157 | handleSetNodesAndConnections([...tgNodes], [...connections]); 158 | } 159 | 160 | useEffect(() => { 161 | if (flag) { 162 | let nn = clone(nodes); 163 | nn = filter( 164 | nn, 165 | (item) => !(item.id === flag?.id && item?.type === "endnode") 166 | ); 167 | setNodes([...nn]); 168 | setflag(null); 169 | } 170 | }, [connections]); 171 | 172 | // useEffect(() => { 173 | // updateXarrow(); 174 | // }, [nodes]); 175 | 176 | function customLabel(conn) { 177 | return ( 178 | deleteMe(conn?.sId, conn?.tId)} 180 | sx={{ 181 | background: "lightgray", 182 | padding: "2px", 183 | boxShadow: "0px 3px 4px #b2b2b2", 184 | }} 185 | > 186 | 187 | 188 | ); 189 | } 190 | 191 | function addNewNode(endNodeId) { 192 | const r = document.getElementById(endNodeId); 193 | let positions = r.getBoundingClientRect(); 194 | const endNode = nodes.find((item) => item.id === endNodeId); 195 | let addNode = { 196 | id: String(random(999999, 99999999)), 197 | type: "simple", 198 | name: "New Ele", 199 | situation: { 200 | child: [endNode.id], 201 | parent: endNode.situation.parent, 202 | }, 203 | position: { 204 | x: positions.x, 205 | y: positions.y, 206 | }, 207 | }; 208 | 209 | // 210 | handleSetNodesAndConnections( 211 | [ 212 | ...nodes.map((item) => { 213 | if (item.id === endNodeId) { 214 | item.situation.parent = [addNode.id]; 215 | item.position.y = positions.y + 2 * nodeHeight; 216 | } 217 | return item; 218 | }), 219 | addNode, 220 | ], 221 | [ 222 | ...connections.map((item) => { 223 | if (item?.tId === endNodeId) { 224 | item.tId = addNode.id; 225 | } 226 | return item; 227 | }), 228 | { sId: addNode.id, tId: endNodeId }, 229 | ] 230 | ); 231 | } 232 | 233 | function handleSetNodesAndConnections(nodes, connections) { 234 | setNodes([...nodes]); 235 | setConnections([...connections]); 236 | // localStorage.setItem("edges", JSON.stringify(connections)); 237 | // localStorage.setItem("nodes", JSON.stringify(nodes)); 238 | } 239 | 240 | function handleNodeClick(id) { 241 | console.log(id); 242 | const ele = find(nodes, { id: id }); 243 | console.log(ele); 244 | const childs = filter(rawData, (r) => r.parent === id); 245 | console.log(childs); 246 | let newNodes = []; 247 | let newEdges = []; 248 | if (!ele?.childrens?.length) { 249 | childs?.forEach((ch) => { 250 | newNodes.push({ 251 | id: ch?.id, 252 | type: "simple", 253 | name: ch.name, 254 | situation: { 255 | child: [], 256 | parent: id, 257 | }, 258 | position: { 259 | x: 0, 260 | y: 0, 261 | }, 262 | positions: { 263 | x: 0, 264 | y: 0, 265 | }, 266 | }); 267 | newEdges.push({ 268 | sId: id, 269 | tId: ch.id, 270 | }); 271 | }); 272 | 273 | nodes.forEach((items) => { 274 | if (items?.id === ele?.id) { 275 | items.childrens = map(childs, "id"); 276 | } 277 | }); 278 | 279 | const { nodes: layoutedNodes, edges: layoutedEdges } = 280 | getLayoutedElements( 281 | [...nodes, ...newNodes], 282 | [...connections, ...newEdges] 283 | ); 284 | setNodes(layoutedNodes); 285 | setConnections(layoutedEdges); 286 | } else { 287 | let childrens = [...ele?.childrens]; 288 | nodes.forEach((elements) => { 289 | if (includes(childrens, elements?.id)) { 290 | childrens = [...childrens, ...elements?.situation?.child]; 291 | } else if (ele?.id === elements?.id) { 292 | elements.childrens = []; 293 | } 294 | }); 295 | 296 | const filterNodes = filter( 297 | nodes, 298 | (item) => !includes(childrens, item.id) 299 | ); 300 | const filteredConnections = filter( 301 | connections, 302 | (item) => !includes(childrens, item?.tId) 303 | ); 304 | console.log(filterNodes, filteredConnections); 305 | const { nodes: layoutedNodes, edges: layoutedEdges } = 306 | getLayoutedElements([...filterNodes], [...filteredConnections]); 307 | setNodes(layoutedNodes); 308 | setConnections(layoutedEdges); 309 | } 310 | } 311 | 312 | return ( 313 |
316 | 317 | {nodes.map((item, i) => ( 318 | 333 | ))} 334 | {connections.map((item, i) => ( 335 | {customLabel(item)}} 343 | dashness={{ strokeLen: 5, nonStrokeLen: 5, animation: true }} 344 | /> 345 | ))} 346 | {sourceId && movableEle && ( 347 | Connect me

} 352 | color="green" 353 | strokeWidth={1} 354 | path="smooth" 355 | // animateDrawing={true} 356 | dashness={{ strokeLen: 5, nonStrokeLen: 5, animation: true }} 357 | /> 358 | )} 359 | {sourceId && movableEle && ( 360 |
372 | )} 373 |
374 |
375 | ); 376 | }; 377 | 378 | export default XarrowComponent; 379 | -------------------------------------------------------------------------------- /src/components/Xarrow2/shape.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Resizable } from "re-resizable"; 3 | 4 | const boxStyle = { 5 | border: "grey solid 2px", 6 | borderRadius: "4px", 7 | padding: "10px 20px", 8 | width: "5rem", 9 | height: "5rem", 10 | borderRadius: "50px", 11 | }; 12 | 13 | const Box = ({ id, name, onClick }) => { 14 | return ( 15 | 16 |
20 | 21 | ); 22 | }; 23 | 24 | const cylinderStyle = { 25 | // border: "grey solid 2px", 26 | borderRadius: "4px", 27 | padding: "5px", 28 | width: "5rem", 29 | height: "10rem", 30 | background: `radial-gradient(50% 40px at 50% 40px, #0003 99.99%, #0000 0), 31 | radial-gradient(50% 40px at 50% calc(100% - 40px), #fff3 99.99%, #0000 0), 32 | red`, 33 | }; 34 | 35 | const CylinderShape = ({ id, name, onClick }) => { 36 | return
; 37 | }; 38 | 39 | const endNodeStyle = { 40 | border: "grey solid 2px", 41 | borderRadius: "4px", 42 | padding: "5px", 43 | width: "5rem", 44 | height: "20px", 45 | cursor: "grab", 46 | }; 47 | 48 | const EndNode = ({ id, name, onClick }) => { 49 | return ( 50 |
62 | {name && ( 63 |

64 | {name} 65 |

66 | )} 67 |
68 | ); 69 | }; 70 | 71 | const Simple = ({ id, name, onClick }) => { 72 | return ( 73 | 74 |
78 | {name && ( 79 |

80 | {name} 81 |

82 | )} 83 |
84 |
85 | ); 86 | }; 87 | 88 | export { Box, CylinderShape, EndNode, Simple }; 89 | -------------------------------------------------------------------------------- /src/components/chatview.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useEffect, useCallback } from 'react'; 2 | import { ApiContextProvider } from '../utils/ApiContext'; 3 | 4 | const ChatView = (props) => { 5 | const value = useContext(ApiContextProvider); 6 | const { addMessage } = value; 7 | const [inputChnage, setInputChnage] = useState(""); 8 | 9 | // useEffect(() => { 10 | // let timeout; 11 | // clearTimeout(timeout); 12 | // timeout = setTimeout(() => { 13 | // console.log(inputChnage); 14 | // }, 2000); 15 | // }, [inputChnage]) 16 | 17 | const handleChnage = (e) => { 18 | setInputChnage(e.target.value); 19 | console.log(e.target.value) 20 | } 21 | 22 | const updateDebouceText = useCallback(debonceText(handleChnage,1000),[]) 23 | 24 | function debonceText (cb, delay=1000){ 25 | let timeout 26 | return (...args) =>{ 27 | if(timeout) clearTimeout(timeout); 28 | timeout = setTimeout(()=>{ 29 | timeout = null 30 | cb(...args) 31 | },delay) 32 | } 33 | } 34 | return ( 35 |
36 | This is my chat app 37 | 38 |
39 | ) 40 | } 41 | 42 | export default ChatView 43 | -------------------------------------------------------------------------------- /src/components/component2.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './sidebar.css'; 3 | import { CloseOutlined } from '@material-ui/icons' 4 | import { Button } from '@material-ui/core'; 5 | 6 | const options = [ 7 | { 8 | key : 'option_1', 9 | value : 'Option 1', 10 | active : false, 11 | }, 12 | { 13 | key : 'option_2', 14 | value : 'Option 2', 15 | active : false, 16 | }, 17 | { 18 | key : 'option_3', 19 | value : 'Option 3', 20 | active : false, 21 | }, 22 | { 23 | key : 'option_4', 24 | value : 'Option 4', 25 | active : false, 26 | }, 27 | { 28 | key : 'option_5', 29 | value : 'Option 5', 30 | active : false, 31 | }, 32 | ] 33 | 34 | const Component2 = () => { 35 | const [data, setData] = useState(options); 36 | const [open, setOpen] = useState(true); 37 | 38 | const handleClickActive = (e, key) => { 39 | 40 | // const activeElements = Array.from(document.getElementsByClassName('active')); 41 | // activeElements.forEach(ele=>{ 42 | // if(ele.classList.contains('active')){ 43 | // ele.classList.remove('active'); 44 | // } 45 | // }); 46 | // e.target.classList.add('active'); 47 | 48 | setData([...data.map(item => { 49 | if(item.key == key){ 50 | item.active = true; 51 | }else{ 52 | item.active = false 53 | } 54 | return item 55 | })]); 56 | } 57 | 58 | const handleOpenSidebar = () => { 59 | setOpen(true) 60 | const element = Array.from(document.getElementsByClassName('sidebar')); 61 | element[0].classList.remove('removeSidebar') 62 | element[0].classList.add('sidebar'); 63 | } 64 | 65 | 66 | const handleCloseSidebar = () => { 67 | // setOpen(false); 68 | const element = Array.from(document.getElementsByClassName('sidebar')); 69 | element[0].classList.remove('sidebar'); 70 | element[0].classList.add('removeSidebar'); 71 | setTimeout(() => { 72 | setOpen(false) 73 | }, 200); 74 | } 75 | 76 | 77 | return ( 78 |
79 | 80 |
81 | { 82 | open && 83 |
84 |
85 |

React Sidebar

86 | 87 |
88 |
    89 | { 90 | data.map(items=> 91 |
  • handleClickActive(e,items.key)} className={items.active ? 'active' : ''}> 92 | { 93 | items.value 94 | } 95 |
  • 96 | ) 97 | } 98 |
99 |
100 | } 101 |
102 |
103 | ) 104 | } 105 | 106 | export default Component2 107 | 108 | -------------------------------------------------------------------------------- /src/components/flow/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import "../../tree.css"; 3 | 4 | const treeData = [ 5 | { 6 | id: "1", 7 | text: "xdbcjhsdvjcsdf", 8 | diamond: false, 9 | children: [ 10 | { 11 | id: "2", 12 | text: "dvdfjvbfd", 13 | diamond: false, 14 | children: [ 15 | { 16 | id: "3", 17 | text: "jkvbdfhv", 18 | diamond: false, 19 | children: [ 20 | { 21 | id: "4", 22 | text: "dbvhd", 23 | diamond: false, 24 | }, 25 | ], 26 | }, 27 | { 28 | id: "5", 29 | text: "grgrgrefg", 30 | diamond: false, 31 | children: [ 32 | { 33 | id: "4", 34 | text: "dbvhd", 35 | diamond: false, 36 | }, 37 | ], 38 | }, 39 | ], 40 | }, 41 | ], 42 | }, 43 | ]; 44 | 45 | const Flow = () => { 46 | const [state, setstate] = useState([]); 47 | // useEffect(() => { 48 | // mapTree(treeData, state, setstate); 49 | // }, []); 50 | // useEffect(() => { 51 | // console.log(state); 52 | // }, [state]); 53 | // let arr = []; 54 | // const mapTree = (treeData) => { 55 | // treeData.forEach((ele) => { 56 | // console.log(ele, state); 57 | // setstate([...state, ele]); 58 | // arr.push(ele); 59 | // const children = ele.children && JSON.parse(JSON.stringify(ele.children)); 60 | // delete ele.children && ele["children"]; 61 | // if (children && children.length) { 62 | // return mapTree(children); 63 | // } 64 | // }); 65 | // }; 66 | 67 | return ( 68 |
69 | {treeRendering(treeData)} 70 | {/*
    71 |
  • 72 |
    73 | Main
    74 |
    75 |
      76 |
    • 77 |
      wqhdveghdf
      78 |
    • 79 |
    • 80 |
      efjejfg
      81 |
        82 |
      • 83 |
        wqhdveghdf
        84 |
      • 85 |
      • 86 |
        efjejfg
        87 |
          88 |
        • 89 |
          wqhdveghdf
          90 |
            91 |
          • 92 |
            wqhdveghdf
            93 |
          • 94 |
          95 |
        • 96 |
        • 97 |
          efjejfg
          98 |
            99 |
          • 100 |
            wqhdveghdf
            101 |
          • 102 |
          103 |
        • 104 |
        105 |
      • 106 |
      107 |
    • 108 |
    109 |
  • 110 |
*/} 111 |
112 | ); 113 | }; 114 | 115 | const treeRendering = (treeData) => { 116 | return ( 117 | <> 118 |
    119 | {treeData.map((item) => ( 120 |
  • 121 |
    {item.id}
    122 | {item.children && item.children.length 123 | ? treeRendering(item.children) 124 | : ""} 125 |
  • 126 | ))} 127 |
128 | 129 | ); 130 | }; 131 | 132 | export default Flow; 133 | -------------------------------------------------------------------------------- /src/components/sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar{ 2 | animation: openSideBar 200ms ease-in; 3 | background: linear-gradient(to left, #b3b3b3, #fff); 4 | /* width: 20rem; */ 5 | height: 100vh; 6 | padding: 1rem; 7 | position: fixed; 8 | left: 0; 9 | width: 20rem; 10 | } 11 | .removeSidebar{ 12 | background: linear-gradient(to left, #b3b3b3, #fff); 13 | /* width: 20rem; */ 14 | height: 100vh; 15 | padding: 1rem; 16 | position: fixed; 17 | left: 0; 18 | width: 20rem; 19 | animation: removeSideBar 200ms ease-in; 20 | } 21 | .sidebar_cover{ 22 | position: relative; 23 | } 24 | .sidebar_header{ 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: center; 28 | margin-bottom: 2rem; 29 | } 30 | h1{ 31 | font-size: 32px; 32 | color: rgb(54, 54, 54); 33 | margin: 0; 34 | } 35 | ul{ 36 | padding: 0; 37 | } 38 | li{ 39 | list-style: none; 40 | padding: 1rem 1.5rem; 41 | font-weight: 600; 42 | color: #828282; 43 | background-color: #b5cffb; 44 | margin-top: 0.5rem; 45 | border-radius: 5px; 46 | } 47 | li.active{ 48 | background-color: #367bee; 49 | color: white; 50 | } 51 | li:hover{ 52 | background-color: #367bee; 53 | color: white; 54 | } 55 | 56 | @keyframes openSideBar { 57 | 0%{ 58 | width: 0rem; 59 | } 60 | 100%{ 61 | width: 20rem; 62 | } 63 | } 64 | @keyframes removeSideBar { 65 | 0%{ 66 | width: 20rem; 67 | } 68 | 100%{ 69 | width: 0rem; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/components/style.css: -------------------------------------------------------------------------------- 1 | .active{ 2 | background-color: aquamarine; 3 | } 4 | 5 | .line{ 6 | stroke: black; 7 | stroke-width: 1px; 8 | /* stroke-dasharray: 3px; */ 9 | } 10 | /* svg{ 11 | background-color: blue; 12 | } */ 13 | .container-side{ 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | /* position: relative; */ 19 | border:1px solid #b3b3b3; 20 | } 21 | .expandButton{ 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | } 26 | .fa{ 27 | font-size: x-small; 28 | opacity: 0.01; 29 | } -------------------------------------------------------------------------------- /src/components/style.scss: -------------------------------------------------------------------------------- 1 | 2 | .drag-eleemnt{ 3 | cursor: pointer; 4 | position: absolute; 5 | padding: 2rem; 6 | background-color: #ccc1c1; 7 | box-shadow: 0px 0px 8px 4px #b3b3b3; 8 | } -------------------------------------------------------------------------------- /src/container/Route/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactFlowPoc from "../../components/ReactFlowPoc"; 3 | import Flow from "../../components/flow"; 4 | import Home from "../home"; 5 | import ReactflowDnd from "../../components/ReactFlowDnd"; 6 | import ReactXarrow from "../../components/Xarrow2"; 7 | import ReactSidebar from "../../components/Sidebar"; 8 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 9 | 10 | const RouteContainer = (props) => { 11 | console.log(props); 12 | return ( 13 | <> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | export default RouteContainer; 41 | -------------------------------------------------------------------------------- /src/container/home/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Home = () => { 5 | return ( 6 |
7 |

All features link

8 | React Flow POC 9 |
10 | Flow 11 |
12 | React flow poc 2 13 |
14 | React flow dnd 15 |
16 | Sidebar 17 |
18 | Xarrow 19 |
20 | ); 21 | }; 22 | 23 | export default Home; 24 | -------------------------------------------------------------------------------- /src/dnd.css: -------------------------------------------------------------------------------- 1 | .dndflow { 2 | flex-direction: column; 3 | display: flex; 4 | height: 100%; 5 | width: 100%; 6 | } 7 | 8 | .dndflow aside { 9 | border-right: 1px solid #eee; 10 | padding: 15px 10px; 11 | font-size: 12px; 12 | background: #fcfcfc; 13 | 14 | } 15 | 16 | .dndflow aside > * { 17 | margin-bottom: 10px; 18 | cursor: grab; 19 | } 20 | 21 | .dndflow aside .description { 22 | margin-bottom: 10px; 23 | } 24 | 25 | .dndflow .reactflow-wrapper { 26 | flex-grow: 1; 27 | height: 100%; 28 | width: 80%; 29 | z-index: 10000; 30 | } 31 | 32 | @media screen and (min-width: 768px) { 33 | .dndflow { 34 | flex-direction: row; 35 | } 36 | 37 | .dndflow aside { 38 | width: 20%; 39 | max-width: 180px; 40 | z-index: 10000; 41 | } 42 | } 43 | .react-flow{ 44 | position: static; 45 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | color: #111; 4 | } 5 | 6 | html, 7 | body, 8 | #root { 9 | margin: 0; 10 | height: 100%; 11 | } 12 | 13 | #root { 14 | display: flex; 15 | flex-direction: column; 16 | } 17 | 18 | header { 19 | padding: 10px; 20 | border-bottom: 1px solid #eee; 21 | display: flex; 22 | font-weight: 700; 23 | align-items: center; 24 | } 25 | 26 | .logo { 27 | text-decoration: none; 28 | display: block; 29 | line-height: 1; 30 | } 31 | 32 | header a, 33 | header a:focus, 34 | header a:active, 35 | header a:visited { 36 | color: #111; 37 | } 38 | 39 | header a:hover { 40 | color: #333; 41 | } 42 | 43 | header select { 44 | margin-left: 1em; 45 | } 46 | 47 | .overview-example__add { 48 | display: none; 49 | } 50 | 51 | .react-flow__node a { 52 | font-weight: 700; 53 | color: #111; 54 | } 55 | 56 | .react-flow__node.dark-node { 57 | background: #0041d0; 58 | color: #f8f8f8; 59 | } 60 | 61 | .react-flow__node.dark { 62 | background: #557; 63 | color: #f8f8f8; 64 | } 65 | 66 | .react-flow__node-selectorNode { 67 | font-size: 12px; 68 | background: #f0f2f3; 69 | border: 1px solid 555; 70 | border-radius: 5px; 71 | text-align: center; 72 | } 73 | 74 | .react-flow__node-selectorNode .react-flow__handle { 75 | border-color: #f0f2f3; 76 | } 77 | 78 | @media screen and (min-width: 768px) { 79 | .overview-example__add { 80 | display: block; 81 | } 82 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React template 5 | 6 | 7 | 13 | 17 | 18 | 22 | 26 | 30 | 36 | 37 | 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { render } from "react-dom"; 3 | import AppContainer from "./App"; 4 | 5 | function App() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | 13 | render(, document.getElementById("root")); 14 | -------------------------------------------------------------------------------- /src/tree.css: -------------------------------------------------------------------------------- 1 | .tree ul { 2 | padding-top: 20px; position: relative; 3 | 4 | transition: all 0.5s; 5 | -webkit-transition: all 0.5s; 6 | -moz-transition: all 0.5s; 7 | } 8 | 9 | .tree li { 10 | float: left; text-align: center; 11 | list-style-type: none; 12 | position: relative; 13 | padding: 20px 5px 0 5px; 14 | 15 | transition: all 0.5s; 16 | -webkit-transition: all 0.5s; 17 | -moz-transition: all 0.5s; 18 | } 19 | 20 | .tree li::before, .tree li::after{ 21 | content: ''; 22 | position: absolute; top: 0; right: 50%; 23 | border-top: 1px solid #ccc; 24 | width: 50%; height: 20px; 25 | } 26 | .tree li::after{ 27 | right: auto; left: 50%; 28 | border-left: 1px solid #ccc; 29 | } 30 | 31 | .tree li:only-child::after, .tree li:only-child::before { 32 | display: none; 33 | } 34 | 35 | .tree li:only-child{ padding-top: 0;} 36 | 37 | .tree li:first-child::before, .tree li:last-child::after{ 38 | border: 0 none; 39 | } 40 | .tree li:last-child::before{ 41 | border-right: 1px solid #ccc; 42 | border-radius: 0 5px 0 0; 43 | -webkit-border-radius: 0 5px 0 0; 44 | -moz-border-radius: 0 5px 0 0; 45 | } 46 | .tree li:first-child::after{ 47 | border-radius: 5px 0 0 0; 48 | -webkit-border-radius: 5px 0 0 0; 49 | -moz-border-radius: 5px 0 0 0; 50 | } 51 | .tree ul ul::before{ 52 | content: ''; 53 | position: absolute; top: 0; left: 50%; 54 | border-left: 1px solid #ccc; 55 | width: 0; height: 20px; 56 | } 57 | .tree li div{ 58 | border: 1px solid #ccc; 59 | padding: 5px 60px; 60 | text-decoration: none; 61 | color: #666; 62 | font-family: arial, verdana, tahoma; 63 | font-size: 11px; 64 | display: inline-block; 65 | 66 | border-radius: 5px; 67 | -webkit-border-radius: 5px; 68 | -moz-border-radius: 5px; 69 | 70 | transition: all 0.5s; 71 | -webkit-transition: all 0.5s; 72 | -moz-transition: all 0.5s; 73 | } 74 | .tree li div:hover, .tree li div:hover+ul li div { 75 | background: #c8e4f8; color: #000; border: 1px solid #94a0b4; 76 | } 77 | .tree li div:hover+ul li::after, 78 | .tree li div:hover+ul li::before, 79 | .tree li div:hover+ul::before, 80 | .tree li div:hover+ul ul::before{ 81 | border-color: #94a0b4; 82 | } -------------------------------------------------------------------------------- /src/utils/ApiContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext } from 'react'; 2 | import { db } from './firebase'; 3 | 4 | export const ApiContextProvider = createContext({}); 5 | 6 | const ApiContext = ({ children }) => { 7 | 8 | const addMessage = (sender, reciever, message) => { 9 | db.collection("chat").add({ 10 | sender : sender, 11 | reciever : reciever, 12 | message : message 13 | }) 14 | } 15 | 16 | const getChats = (reciever, sender) => { 17 | const all_chats = db.collection("chat").get({reciever:reciever, sender:sender}) 18 | } 19 | 20 | 21 | return ( 22 | 23 | {children} 24 | 25 | ) 26 | } 27 | 28 | export default ApiContext 29 | -------------------------------------------------------------------------------- /src/utils/firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import firebase from 'firebase' 3 | // import { getAnalytics } from "firebase/analytics"; 4 | // TODO: Add SDKs for Firebase products that you want to use 5 | // https://firebase.google.com/docs/web/setup#available-libraries 6 | 7 | // Your web app's Firebase configuration 8 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 9 | const firebaseConfig = { 10 | apiKey: "AIzaSyBrGixsKyAQBW0oZQl8S_7iVP51UdKQZLo", 11 | authDomain: "mychat-5fe8d.firebaseapp.com", 12 | projectId: "mychat-5fe8d", 13 | storageBucket: "mychat-5fe8d.appspot.com", 14 | messagingSenderId: "794609494151", 15 | appId: "1:794609494151:web:6b55bc7e904915a3573022", 16 | measurementId: "G-KB866EKF53" 17 | }; 18 | 19 | // Initialize Firebase 20 | const app = firebase.initializeApp(firebaseConfig); 21 | export const db = firebase.firestore(); 22 | export default app -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const TerserPlugin = require("terser-webpack-plugin"); 5 | 6 | module.exports = { 7 | mode: 'development', 8 | devtool: 'inline-source-map', 9 | devServer: { 10 | contentBase: './build', 11 | }, 12 | entry: './src/index.js', 13 | output: { 14 | path: path.resolve(__dirname, "build"), 15 | publicPath: '/', 16 | }, 17 | devtool: 'inline-source-map', 18 | devServer: { 19 | contentBase: './dist', 20 | }, 21 | plugins: [ 22 | new HtmlWebpackPlugin({ 23 | template: path.resolve(__dirname, "src", "index.html") 24 | }) 25 | ], 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(sa|sc|c)ss$/, 30 | use: ["style-loader","css-loader","sass-loader"] 31 | }, 32 | { 33 | test: /\.(png|jpe?g|gif)$/i, 34 | use: [ 35 | { 36 | loader: 'file-loader', 37 | }, 38 | ], 39 | }, 40 | { 41 | test: /\.js$/, 42 | exclude: /node_modules/, 43 | use: ["babel-loader"] 44 | }, 45 | ] 46 | }, 47 | optimization: { 48 | splitChunks: { chunks: "all" }, 49 | minimize: true, 50 | // minimizer: [ 51 | // new TerserPlugin({ 52 | // // sourceMap: true, // Must be set to true if using source-maps in production 53 | // terserOptions: { 54 | // compress: { 55 | // // drop_console: true, // << this needs only to remove console.log // 56 | // }, 57 | // }, 58 | // }), 59 | // ], 60 | }, 61 | } --------------------------------------------------------------------------------