├── .changeset ├── README.md ├── cold-drinks-unite.md └── config.json ├── .editorconfig ├── .envrc ├── .gitbook.yaml ├── .gitbook └── assets │ ├── example1.jpg │ ├── example2.jpg │ ├── example3.jpg │ └── logo.jpg ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── prettier.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── diagrams-demo-gallery ├── .babelrc.json ├── .storybook │ ├── main.js │ ├── manager.js │ ├── preview-head.html │ ├── preview.js │ └── theme.js ├── CHANGELOG.md ├── demos │ ├── 1_SimpleUsage.stories.tsx │ ├── 2_AdvancedUsage.stories.tsx │ ├── 3_Customization.stories.tsx │ ├── 4_Libraries.stories.tsx │ ├── demo-alternative-linking │ │ ├── CreateLinkState.ts │ │ ├── DefaultState.ts │ │ └── index.tsx │ ├── demo-animation │ │ └── index.tsx │ ├── demo-canvas-drag │ │ └── index.tsx │ ├── demo-cloning │ │ └── index.tsx │ ├── demo-custom-action │ │ └── index.tsx │ ├── demo-custom-link-label │ │ ├── EditableLabelFactory.tsx │ │ ├── EditableLabelModel.ts │ │ ├── EditableLabelWidget.tsx │ │ └── index.tsx │ ├── demo-custom-link1 │ │ └── index.tsx │ ├── demo-custom-link2 │ │ └── index.tsx │ ├── demo-custom-node1 │ │ ├── DiamondNodeFactory.tsx │ │ ├── DiamondNodeModel.ts │ │ ├── DiamondNodeWidget.tsx │ │ ├── DiamondPortModel.ts │ │ ├── SimplePortFactory.ts │ │ └── index.tsx │ ├── demo-custom_delete_keys │ │ └── index.tsx │ ├── demo-dagre │ │ └── index.tsx │ ├── demo-drag-and-drop │ │ ├── Application.ts │ │ ├── components │ │ │ ├── BodyWidget.tsx │ │ │ ├── TrayItemWidget.tsx │ │ │ └── TrayWidget.tsx │ │ └── index.tsx │ ├── demo-dynamic-ports │ │ └── index.tsx │ ├── demo-grid │ │ └── index.tsx │ ├── demo-labelled-links │ │ └── index.tsx │ ├── demo-listeners │ │ └── index.tsx │ ├── demo-locks │ │ └── index.tsx │ ├── demo-mutate-graph │ │ └── index.tsx │ ├── demo-pan-and-zoom │ │ └── index.tsx │ ├── demo-performance │ │ └── index.tsx │ ├── demo-right-angles-routing │ │ └── index.tsx │ ├── demo-serializing │ │ └── index.tsx │ ├── demo-simple-flow │ │ └── index.tsx │ ├── demo-simple │ │ └── index.tsx │ ├── demo-smart-routing │ │ └── index.tsx │ ├── demo-zoom-to-fit-nodes │ │ └── index.tsx │ ├── demo-zoom-to-fit │ │ └── index.tsx │ └── helpers │ │ ├── DemoCanvasWidget.tsx │ │ ├── DemoWorkspaceWidget.tsx │ │ ├── Helper.tsx │ │ └── index.css ├── package.json └── tsconfig.json ├── diagrams-demo-project ├── .babelrc ├── CHANGELOG.md ├── README.md ├── index.html ├── package.json ├── screenshot.png ├── src │ ├── BodyWidget.tsx │ ├── custom-node-js │ │ ├── JSCustomNodeFactory.jsx │ │ ├── JSCustomNodeModel.js │ │ └── JSCustomNodeWidget.jsx │ ├── custom-node-ts │ │ ├── TSCustomNodeFactory.tsx │ │ ├── TSCustomNodeModel.ts │ │ └── TSCustomNodeWidget.tsx │ ├── main.css │ └── main.tsx ├── tsconfig.json └── webpack.config.js ├── docs ├── README.md ├── about-the-project │ ├── architecture-questions.md │ └── testing.md ├── customizing │ ├── README.md │ ├── extending-default-links.md │ ├── images │ │ ├── custom-link.png │ │ └── diamond-node.png │ ├── nodes.md │ └── ports.md └── getting-started │ ├── README.md │ └── using-the-library.md ├── package.json ├── packages ├── geometry │ ├── .npmignore │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── BezierCurve.ts │ │ ├── Bounds.ts │ │ ├── Matrix.ts │ │ ├── Point.ts │ │ ├── Polygon.ts │ │ ├── Rectangle.ts │ │ ├── index.ts │ │ └── toolkit.ts │ ├── tsconfig.json │ └── webpack.config.js ├── react-canvas-core │ ├── .npmignore │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── CanvasEngine.ts │ │ ├── Toolkit.ts │ │ ├── actions │ │ │ ├── DeleteItemsAction.ts │ │ │ ├── PanAndZoomCanvasAction.ts │ │ │ └── ZoomCanvasAction.ts │ │ ├── core-actions │ │ │ ├── Action.ts │ │ │ └── ActionEventBus.ts │ │ ├── core-models │ │ │ ├── BaseEntity.ts │ │ │ ├── BaseModel.ts │ │ │ └── BasePositionModel.ts │ │ ├── core-state │ │ │ ├── AbstractDisplacementState.ts │ │ │ ├── State.ts │ │ │ └── StateMachine.ts │ │ ├── core │ │ │ ├── AbstractFactory.ts │ │ │ ├── AbstractModelFactory.ts │ │ │ ├── AbstractReactFactory.tsx │ │ │ ├── BaseObserver.ts │ │ │ ├── FactoryBank.ts │ │ │ └── ModelGeometryInterface.ts │ │ ├── entities │ │ │ ├── canvas │ │ │ │ ├── CanvasModel.ts │ │ │ │ └── CanvasWidget.tsx │ │ │ ├── layer │ │ │ │ ├── LayerModel.ts │ │ │ │ ├── SmartLayerWidget.tsx │ │ │ │ └── TransformLayerWidget.tsx │ │ │ └── selection │ │ │ │ ├── SelectionBoxLayerFactory.tsx │ │ │ │ ├── SelectionBoxWidget.tsx │ │ │ │ └── SelectionLayerModel.ts │ │ ├── index.ts │ │ ├── states │ │ │ ├── DefaultState.ts │ │ │ ├── DragCanvasState.ts │ │ │ ├── MoveItemsState.ts │ │ │ ├── SelectingState.ts │ │ │ └── SelectionBoxState.ts │ │ └── widgets │ │ │ └── PeformanceWidget.tsx │ ├── tsconfig.json │ └── webpack.config.js ├── react-diagrams-core │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── DiagramEngine.ts │ │ ├── entities │ │ │ ├── label │ │ │ │ ├── LabelModel.ts │ │ │ │ └── LabelWidget.tsx │ │ │ ├── link-layer │ │ │ │ ├── LinkLayerFactory.tsx │ │ │ │ ├── LinkLayerModel.ts │ │ │ │ └── LinkLayerWidget.tsx │ │ │ ├── link │ │ │ │ ├── LinkModel.ts │ │ │ │ ├── LinkWidget.tsx │ │ │ │ └── PointModel.ts │ │ │ ├── node-layer │ │ │ │ ├── NodeLayerFactory.tsx │ │ │ │ ├── NodeLayerModel.ts │ │ │ │ └── NodeLayerWidget.tsx │ │ │ ├── node │ │ │ │ ├── NodeModel.ts │ │ │ │ └── NodeWidget.tsx │ │ │ └── port │ │ │ │ ├── PortModel.ts │ │ │ │ └── PortWidget.tsx │ │ ├── index.ts │ │ ├── models │ │ │ └── DiagramModel.ts │ │ └── states │ │ │ ├── DefaultDiagramState.ts │ │ │ ├── DragDiagramItemsState.ts │ │ │ └── DragNewLinkState.ts │ ├── tsconfig.json │ └── webpack.config.js ├── react-diagrams-defaults │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── label │ │ │ ├── DefaultLabelFactory.tsx │ │ │ ├── DefaultLabelModel.tsx │ │ │ └── DefaultLabelWidget.tsx │ │ ├── link │ │ │ ├── DefaultLinkFactory.tsx │ │ │ ├── DefaultLinkModel.ts │ │ │ ├── DefaultLinkPointWidget.tsx │ │ │ ├── DefaultLinkSegmentWidget.tsx │ │ │ └── DefaultLinkWidget.tsx │ │ ├── node │ │ │ ├── DefaultNodeFactory.tsx │ │ │ ├── DefaultNodeModel.ts │ │ │ └── DefaultNodeWidget.tsx │ │ └── port │ │ │ ├── DefaultPortFactory.tsx │ │ │ ├── DefaultPortLabelWidget.tsx │ │ │ └── DefaultPortModel.ts │ ├── tsconfig.json │ └── webpack.config.js ├── react-diagrams-routing │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── dagre │ │ │ └── DagreEngine.ts │ │ ├── engine │ │ │ └── PathFinding.ts │ │ ├── index.ts │ │ └── link │ │ │ ├── PathFindingLinkFactory.tsx │ │ │ ├── PathFindingLinkModel.ts │ │ │ ├── PathFindingLinkWidget.tsx │ │ │ ├── RightAngleLinkFactory.tsx │ │ │ ├── RightAngleLinkModel.ts │ │ │ └── RightAngleLinkWidget.tsx │ ├── tests │ │ └── PathFinding.test.tsx │ ├── tsconfig.json │ └── webpack.config.js └── react-diagrams │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.base.json ├── tsconfig.json └── webpack.shared.js /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/cold-drinks-unite.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@projectstorm/react-diagrams-defaults': patch 3 | '@projectstorm/react-diagrams-routing': patch 4 | '@projectstorm/react-diagrams-core': patch 5 | '@projectstorm/react-canvas-core': patch 6 | '@projectstorm/react-diagrams-gallery': patch 7 | '@projectstorm/react-diagrams-demo': patch 8 | '@projectstorm/geometry': patch 9 | --- 10 | 11 | Updated packages to support React v19 12 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | 6 | # Some exceptions 7 | [{package.json,*.yml}] 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | PATH_add ./node_modules/.bin 2 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs/ 2 | 3 | structure: 4 | summary: README.md -------------------------------------------------------------------------------- /.gitbook/assets/example1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/.gitbook/assets/example1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/example2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/.gitbook/assets/example2.jpg -------------------------------------------------------------------------------- /.gitbook/assets/example3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/.gitbook/assets/example3.jpg -------------------------------------------------------------------------------- /.gitbook/assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/.gitbook/assets/logo.jpg -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Checklist 2 | 3 | - [ ] The tests pass 4 | - [ ] I have referenced the issue(s) or other PR(s) this fixes/relates-to 5 | - [ ] I have run ```pnpm changeset``` and followed the instructions 6 | - [ ] I have explained in this PR, what I did and why 7 | - [ ] I replaced the image below 8 | - [ ] Had a beer/coffee/tea because I did something cool today 9 | 10 | ## What, why and how? 11 | 12 | 13 | ## Feel good image: 14 | 15 | 16 |  -------------------------------------------------------------------------------- /.github/workflows/prettier.yml: -------------------------------------------------------------------------------- 1 | name: Prettier check 2 | 3 | # This action works with pull requests and pushes 4 | on: 5 | pull_request: 6 | 7 | jobs: 8 | prettier: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | # Make sure the actual branch is checked out when running on pull requests 16 | ref: ${{ github.head_ref }} 17 | 18 | 19 | - uses: actions/checkout@v2 # Check out the repository first. 20 | - uses: actionsx/prettier@v2 21 | with: 22 | # prettier CLI arguments. 23 | args: --check --ignore-path .prettierignore --config .prettierrc '**/*.{ts,tsx,js,jsx}' -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v2 17 | 18 | - name: Read .nvmrc 19 | run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" 20 | id: nvm 21 | 22 | - name: Use Node.js (.nvmrc) 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: "${{ steps.nvm.outputs.NVMRC }}" 26 | 27 | - name: Install PNPM 28 | uses: pnpm/action-setup@v2 29 | with: 30 | version: latest 31 | 32 | - name: Install Dependencies 33 | run: pnpm install 34 | 35 | - name: Create Release Pull Request 36 | uses: changesets/action@v1 37 | id: changesets 38 | with: 39 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 40 | publish: pnpm release 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | 45 | - name: publish storybook 46 | if: steps.changesets.outputs.published == 'true' 47 | run: pnpm release:storybook -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | on: 3 | pull_request: 4 | jobs: 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout Repo 10 | uses: actions/checkout@v2 11 | 12 | - name: Read .nvmrc 13 | run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" 14 | id: nvm 15 | 16 | - name: Use Node.js (.nvmrc) 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: "${{ steps.nvm.outputs.NVMRC }}" 20 | 21 | - name: Install PNPM 22 | uses: pnpm/action-setup@v2 23 | with: 24 | version: latest 25 | 26 | - name: Install Dependencies 27 | run: pnpm install 28 | 29 | - name: Build 30 | run: pnpm build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .DS_Store 3 | .idea 4 | .out 5 | *.zip 6 | .env 7 | node_modules 8 | tsconfig.tsbuildinfo 9 | .vscode -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .out 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "useTabs": true, 5 | "printWidth": 120, 6 | "trailingComma": "none" 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | See below for guidelines on house keeping: 4 | 5 | ### Always add a PR 6 | 7 | Since the project runs on GitHub, the best way to contribute is to fork and then submit a PR. 8 | You will find a template that you will need to fill out 9 | 10 | ### Adding new demos 11 | 12 | Add a new folder in the ./demos directory and make sure that it is named correctly like the other demos. 13 | A new demo should conform to the standard of either `demo-simple` in which it contains a markdown file that 14 | clearly explains 'whats going on', or the code sample should have very clear comments that almost always should ready 15 | like an instruction manual such as the simple demo. 16 | 17 | Finally, you should link up your demo to the __index.tsx__ file in the __demos__ directory. It should be quite 18 | self explanatory on how it works, but ultimately I have a helper method that makes it easy to link source code 19 | to the demo itself, hence the 'require' statements. The third parameter is if you want to place your demo 20 | inside a markdown guide (again: see simple demo for how that is done). 21 | 22 | ### Make the demo testable 23 | 24 | Similar procedure, except link your demo in the __index.tsx__ file sitting in the __tests__ directory. 25 | Running `yarn run test` will fire up jest (hopefully) and then it will render your demo to a snapshot directory 26 | which when run again (for a second time), should compare the output to the newely generated snapshot. Make sure 27 | to commit the updated snapshot file with your PR! 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Storm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "chrome": 100, 9 | "safari": 15, 10 | "firefox": 91 11 | } 12 | } 13 | ], 14 | "@babel/preset-react", 15 | "@babel/preset-typescript" 16 | ], 17 | "plugins": [] 18 | } -------------------------------------------------------------------------------- /diagrams-demo-gallery/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../demos/*.stories.tsx'], 3 | addons: ['@storybook/addon-actions', '@storybook/addon-webpack5-compiler-babel'], 4 | framework: '@storybook/react-webpack5' 5 | }; 6 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/manager-api'; 2 | 3 | import diagramsTheme from './theme'; 4 | 5 | addons.setConfig({ 6 | theme: diagramsTheme 7 | }); 8 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | layout: 'fullscreen' 3 | }; 4 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/.storybook/theme.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | 3 | export default create({ 4 | base: 'dark', 5 | brandTitle: 'STORM React Diagrams', 6 | brandUrl: 'https://github.com/projectstorm/react-diagrams' 7 | }); 8 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/1_SimpleUsage.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Toolkit } from '@projectstorm/react-canvas-core'; 2 | Toolkit.TESTING = true; 3 | 4 | export default { 5 | title: 'Simple Usage' 6 | }; 7 | 8 | import demo_simple from './demo-simple'; 9 | import demo_flow from './demo-simple-flow'; 10 | import demo_performance from './demo-performance'; 11 | import demo_locks from './demo-locks'; 12 | import demo_grid from './demo-grid'; 13 | import demo_listeners from './demo-listeners'; 14 | import demo_zoom from './demo-zoom-to-fit'; 15 | import demo_zoom_nodes from './demo-zoom-to-fit-nodes'; 16 | import demo_canvas_drag from './demo-canvas-drag'; 17 | import demo_pan_and_zoom from './demo-pan-and-zoom'; 18 | import demo_dynamic_ports from './demo-dynamic-ports'; 19 | import demo_labels from './demo-labelled-links'; 20 | 21 | export const DemoSimple = demo_simple; 22 | export const SimpleFlowExample = demo_flow; 23 | export const PerformanceDemo = demo_performance; 24 | export const LockedWidget = demo_locks; 25 | export const CanvasGridSize = demo_grid; 26 | export const EventsAndListeners = demo_listeners; 27 | export const ZoomToFit = demo_zoom; 28 | export const ZoomToFitSelectNodes = demo_zoom_nodes; 29 | export const CanvasDrag = demo_canvas_drag; 30 | export const CanvasPanAndZoom = demo_pan_and_zoom; 31 | export const DynamicPorts = demo_dynamic_ports; 32 | export const LinksWithLabels = demo_labels; 33 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/2_AdvancedUsage.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Toolkit } from '@projectstorm/react-canvas-core'; 2 | Toolkit.TESTING = true; 3 | 4 | export default { 5 | title: 'Advanced Usage' 6 | }; 7 | 8 | import demo_adv_clone_selected from './demo-cloning'; 9 | import demo_adv_ser_des from './demo-serializing'; 10 | import demo_adv_prog from './demo-mutate-graph'; 11 | import demo_adv_dnd from './demo-drag-and-drop'; 12 | import demo_smart_routing from './demo-smart-routing'; 13 | import demo_right_angles_routing from './demo-right-angles-routing'; 14 | import demo_alternative_linking from './demo-alternative-linking'; 15 | import demo_custom_delete_keys from './demo-custom_delete_keys'; 16 | 17 | export const CloneSelected = demo_adv_clone_selected; 18 | export const SerializingAndDeSerializing = demo_adv_ser_des; 19 | export const ProgramaticallyModifyingGraph = demo_adv_prog; 20 | export const DragAndDrop = demo_adv_dnd; 21 | export const SmartRouting = demo_smart_routing; 22 | export const RightAnglesRouting = demo_right_angles_routing; 23 | export const LinkingByClickingInsteadOfDragging = demo_alternative_linking; 24 | export const SettingCustomDeleteKeys = demo_custom_delete_keys; 25 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/3_Customization.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Toolkit } from '@projectstorm/react-canvas-core'; 2 | Toolkit.TESTING = true; 3 | 4 | export default { 5 | title: 'Customization' 6 | }; 7 | 8 | import demo_custom_link_label from './demo-custom-link-label'; 9 | import demo_custom_action from './demo-custom-action'; 10 | import demo_cust_nodes from './demo-custom-node1'; 11 | import demo_cust_links from './demo-custom-link1'; 12 | import demo_cust_links2 from './demo-custom-link2'; 13 | 14 | export const CustomDiamondNode = demo_cust_nodes; 15 | export const CustomAnimatedLinks = demo_cust_links; 16 | export const CustomLinkEndsWithArrows = demo_cust_links2; 17 | export const CustomLinkLabel = demo_custom_link_label; 18 | export const CustomEvent = demo_custom_action; 19 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/4_Libraries.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Toolkit } from '@projectstorm/react-canvas-core'; 2 | Toolkit.TESTING = true; 3 | 4 | export default { 5 | title: 'External Libs' 6 | }; 7 | 8 | import demo_3rd_dagre from './demo-dagre'; 9 | import demo_gsap from './demo-animation'; 10 | 11 | export const DagreDistribute = demo_3rd_dagre; 12 | export const GsapAnimation = demo_gsap; 13 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-alternative-linking/DefaultState.ts: -------------------------------------------------------------------------------- 1 | import { MouseEvent, TouchEvent } from 'react'; 2 | import { 3 | SelectingState, 4 | State, 5 | Action, 6 | InputType, 7 | ActionEvent, 8 | DragCanvasState 9 | } from '@projectstorm/react-canvas-core'; 10 | import { PortModel, DiagramEngine, DragDiagramItemsState } from '@projectstorm/react-diagrams-core'; 11 | import { CreateLinkState } from './CreateLinkState'; 12 | 13 | export class DefaultState extends State { 14 | dragCanvas: DragCanvasState; 15 | createLink: CreateLinkState; 16 | dragItems: DragDiagramItemsState; 17 | 18 | constructor() { 19 | super({ name: 'starting-state' }); 20 | this.childStates = [new SelectingState()]; 21 | this.dragCanvas = new DragCanvasState(); 22 | this.createLink = new CreateLinkState(); 23 | this.dragItems = new DragDiagramItemsState(); 24 | 25 | // determine what was clicked on 26 | this.registerAction( 27 | new Action({ 28 | type: InputType.MOUSE_DOWN, 29 | fire: (event: ActionEvent) => { 30 | const element = this.engine.getActionEventBus().getModelForEvent(event); 31 | 32 | // the canvas was clicked on, transition to the dragging canvas state 33 | if (!element) { 34 | this.transitionWithEvent(this.dragCanvas, event); 35 | } 36 | // initiate dragging a new link 37 | else if (element instanceof PortModel) { 38 | return; 39 | } 40 | // move the items (and potentially link points) 41 | else { 42 | this.transitionWithEvent(this.dragItems, event); 43 | } 44 | } 45 | }) 46 | ); 47 | 48 | // touch drags the canvas 49 | this.registerAction( 50 | new Action({ 51 | type: InputType.TOUCH_START, 52 | fire: (event: ActionEvent) => { 53 | this.transitionWithEvent(new DragCanvasState(), event); 54 | } 55 | }) 56 | ); 57 | 58 | this.registerAction( 59 | new Action({ 60 | type: InputType.MOUSE_UP, 61 | fire: (event: ActionEvent) => { 62 | const element = this.engine.getActionEventBus().getModelForEvent(event); 63 | 64 | if (element instanceof PortModel) this.transitionWithEvent(this.createLink, event); 65 | } 66 | }) 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-alternative-linking/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 3 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | import { DefaultState } from './DefaultState'; 6 | 7 | export default () => { 8 | const engine = createEngine(); 9 | const model = new DiagramModel(); 10 | 11 | const node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 12 | node1.addOutPort('Out'); 13 | node1.setPosition(100, 100); 14 | 15 | const node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 16 | node2.addInPort('In'); 17 | node2.setPosition(400, 100); 18 | 19 | model.addAll(node1, node2); 20 | 21 | engine.setModel(model); 22 | 23 | // Use this custom "DefaultState" instead of the actual default state we get with the engine 24 | engine.getStateMachine().pushState(new DefaultState()); 25 | 26 | return ( 27 | 28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-animation/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import gsap from 'gsap'; 4 | import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 5 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 6 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 7 | 8 | /** 9 | * Tests the grid size 10 | */ 11 | class NodeDelayedPosition extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | const { engine } = this.props; 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | } 27 | 28 | export default () => { 29 | //1) setup the diagram engine 30 | var engine = createEngine({ repaintDebounceMs: 12 }); 31 | 32 | //2) setup the diagram model 33 | var model = new DiagramModel(); 34 | 35 | //3-A) create a default node 36 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 37 | var port1 = node1.addOutPort('Out'); 38 | node1.setPosition(100, 100); 39 | 40 | //3-B) create another default node 41 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 42 | var port2 = node2.addInPort('In'); 43 | node2.setPosition(400, 100); 44 | 45 | //3-C) create another default node 46 | var node3 = new DefaultNodeModel('Node 3', 'rgb(192,255,0)'); 47 | node2.setPosition(200, 300); 48 | 49 | //3-D) create another default node 50 | var node4 = new DefaultNodeModel('Node 4', 'rgb(192,255,0)'); 51 | node2.setPosition(400, 400); 52 | 53 | //3-C) link the 2 nodes together 54 | var link1 = port1.link(port2); 55 | 56 | //4) add the models to the root graph 57 | model.addAll(node1, node2, link1, node3, node4); 58 | 59 | //5) load model into engine 60 | engine.setModel(model); 61 | 62 | var interval = setInterval(() => { 63 | [node1, node2, node3, node4].map((node) => { 64 | var obj = { x: 0, y: 0 }; 65 | gsap.fromTo( 66 | obj, 67 | { 68 | x: node.getPosition().x, 69 | y: node.getPosition().y 70 | }, 71 | { 72 | x: Math.floor(Math.random() * 500), 73 | y: Math.floor(Math.random() * 500), 74 | duration: 0.8, 75 | onUpdate: () => { 76 | node.setPosition(obj.x, obj.y); 77 | engine.repaintCanvas(); 78 | } 79 | } 80 | ); 81 | }); 82 | }, 2000); 83 | 84 | //6) render the diagram! 85 | return ; 86 | }; 87 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-canvas-drag/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 3 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 4 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 5 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 6 | 7 | /** 8 | * Tests the drag on/off 9 | */ 10 | class CanvasDragToggle extends React.Component { 11 | enableDrag = () => { 12 | const { engine } = this.props; 13 | const state = engine.getStateMachine().getCurrentState(); 14 | state.dragCanvas.config.allowDrag = true; 15 | }; 16 | 17 | disableDrag = () => { 18 | const { engine } = this.props; 19 | const state = engine.getStateMachine().getCurrentState(); 20 | state.dragCanvas.config.allowDrag = false; 21 | }; 22 | 23 | render() { 24 | const { engine } = this.props; 25 | return ( 26 | 29 | Enable canvas drag 30 | , 31 | 32 | Disable canvas drag 33 | 34 | ]} 35 | > 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default () => { 45 | //1) setup the diagram engine 46 | var engine = createEngine(); 47 | 48 | //2) setup the diagram model 49 | var model = new DiagramModel(); 50 | 51 | //3-A) create a default node 52 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 53 | var port1 = node1.addOutPort('Out'); 54 | node1.setPosition(100, 100); 55 | 56 | //3-B) create another default node 57 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 58 | var port2 = node2.addInPort('In'); 59 | node2.setPosition(400, 100); 60 | 61 | //3-C) link the 2 nodes together 62 | var link1 = port1.link(port2); 63 | 64 | //4) add the models to the root graph 65 | model.addAll(node1, node2, link1); 66 | 67 | //5) load model into engine 68 | engine.setModel(model); 69 | 70 | //6) render the diagram! 71 | return ; 72 | }; 73 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-cloning/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel, LinkModel, NodeModel } from '@projectstorm/react-diagrams'; 2 | import _forEach from 'lodash/forEach'; 3 | import * as React from 'react'; 4 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 5 | import { BaseModel, CanvasWidget } from '@projectstorm/react-canvas-core'; 6 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 7 | 8 | /** 9 | * Tests cloning 10 | */ 11 | class CloneSelected extends React.Component { 12 | constructor(props: any) { 13 | super(props); 14 | this.cloneSelected = this.cloneSelected.bind(this); 15 | } 16 | 17 | cloneSelected() { 18 | let { engine } = this.props; 19 | let offset = { x: 100, y: 100 }; 20 | let model = engine.getModel(); 21 | 22 | let itemMap = {}; 23 | _forEach(model.getSelectedEntities(), (item: BaseModel) => { 24 | let newItem = item.clone(itemMap); 25 | 26 | // offset the nodes slightly 27 | if (newItem instanceof NodeModel) { 28 | newItem.setPosition(newItem.getX() + offset.x, newItem.getY() + offset.y); 29 | model.addNode(newItem); 30 | } else if (newItem instanceof LinkModel) { 31 | // offset the link points 32 | newItem.getPoints().forEach((p) => { 33 | p.setPosition(p.getX() + offset.x, p.getY() + offset.y); 34 | }); 35 | model.addLink(newItem); 36 | } 37 | (newItem as BaseModel).setSelected(false); 38 | }); 39 | 40 | this.forceUpdate(); 41 | } 42 | 43 | render() { 44 | const { engine } = this.props; 45 | return ( 46 | Clone Selected}> 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default () => { 56 | //1) setup the diagram engine 57 | var engine = createEngine(); 58 | 59 | //2) setup the diagram model 60 | var model = new DiagramModel(); 61 | 62 | //3-A) create a default node 63 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 64 | let port = node1.addOutPort('Out'); 65 | node1.setPosition(100, 100); 66 | 67 | //3-B) create another default node 68 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 69 | let port2 = node2.addInPort('In'); 70 | node2.setPosition(400, 100); 71 | 72 | // link the ports 73 | let link1 = port.link(port2); 74 | 75 | //4) add the models to the root graph 76 | model.addAll(node1, node2, link1); 77 | 78 | //5) load model into engine 79 | engine.setModel(model); 80 | 81 | //6) render the diagram! 82 | return ; 83 | }; 84 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-action/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import _forEach from 'lodash/forEach'; 3 | import createEngine, { DiagramModel, DefaultNodeModel, DefaultLinkModel } from '@projectstorm/react-diagrams'; 4 | import { CanvasWidget, Action, ActionEvent, InputType } from '@projectstorm/react-canvas-core'; 5 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 6 | 7 | interface CustomDeleteItemsActionOptions { 8 | keyCodes?: number[]; 9 | } 10 | 11 | /** 12 | * Deletes all selected items, but asks for confirmation first 13 | */ 14 | class CustomDeleteItemsAction extends Action { 15 | constructor(options: CustomDeleteItemsActionOptions = {}) { 16 | options = { 17 | keyCodes: [46, 8], 18 | ...options 19 | }; 20 | super({ 21 | type: InputType.KEY_DOWN, 22 | fire: (event: ActionEvent) => { 23 | if (options.keyCodes.indexOf(event.event.keyCode) !== -1) { 24 | const selectedEntities = this.engine.getModel().getSelectedEntities(); 25 | if (selectedEntities.length > 0) { 26 | const confirm = window.confirm('Are you sure you want to delete?'); 27 | 28 | if (confirm) { 29 | _forEach(selectedEntities, (model) => { 30 | // only delete items which are not locked 31 | if (!model.isLocked()) { 32 | model.remove(); 33 | } 34 | }); 35 | this.engine.repaintCanvas(); 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | } 42 | } 43 | 44 | export default () => { 45 | // create an engine without registering DeleteItemsAction 46 | const engine = createEngine({ registerDefaultDeleteItemsAction: false }); 47 | const model = new DiagramModel(); 48 | 49 | const node1 = new DefaultNodeModel({ name: 'Node 1', color: 'rgb(0,192,255)' }); 50 | node1.setPosition(100, 100); 51 | const port1 = node1.addOutPort('Out'); 52 | 53 | const node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 54 | const port2 = node2.addInPort('In'); 55 | node2.setPosition(400, 100); 56 | 57 | const link1 = port1.link(port2); 58 | link1.getOptions().testName = 'Test'; 59 | link1.addLabel('Hello World!'); 60 | 61 | model.addAll(node1, node2, link1); 62 | 63 | engine.setModel(model); 64 | 65 | // register an DeleteItemsAction with custom keyCodes (in this case, only Delete key) 66 | engine.getActionEventBus().registerAction(new CustomDeleteItemsAction()); 67 | 68 | return ( 69 | 70 | 71 | 72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-link-label/EditableLabelFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AbstractReactFactory, GenerateWidgetEvent } from '@projectstorm/react-canvas-core'; 3 | import { DiagramEngine } from '@projectstorm/react-diagrams'; 4 | 5 | import { EditableLabelModel } from './EditableLabelModel'; 6 | import { EditableLabelWidget } from './EditableLabelWidget'; 7 | import { JSX } from 'react'; 8 | 9 | export class EditableLabelFactory extends AbstractReactFactory { 10 | constructor() { 11 | super('editable-label'); 12 | } 13 | 14 | generateModel(): EditableLabelModel { 15 | return new EditableLabelModel(); 16 | } 17 | 18 | generateReactWidget(event: GenerateWidgetEvent): JSX.Element { 19 | return ; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-link-label/EditableLabelModel.ts: -------------------------------------------------------------------------------- 1 | import { LabelModel } from '@projectstorm/react-diagrams'; 2 | import { BaseModelOptions, DeserializeEvent } from '@projectstorm/react-canvas-core'; 3 | 4 | export interface EditableLabelOptions extends BaseModelOptions { 5 | value?: string; 6 | } 7 | 8 | export class EditableLabelModel extends LabelModel { 9 | value: string; 10 | 11 | constructor(options: EditableLabelOptions = {}) { 12 | super({ 13 | ...options, 14 | type: 'editable-label' 15 | }); 16 | this.value = options.value || ''; 17 | } 18 | 19 | serialize() { 20 | return { 21 | ...super.serialize(), 22 | value: this.value 23 | }; 24 | } 25 | 26 | deserialize(event: DeserializeEvent): void { 27 | super.deserialize(event); 28 | this.value = event.data.value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-link-label/EditableLabelWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { EditableLabelModel } from './EditableLabelModel'; 4 | import styled from '@emotion/styled'; 5 | import { action } from '@storybook/addon-actions'; 6 | 7 | export interface FlowAliasLabelWidgetProps { 8 | model: EditableLabelModel; 9 | } 10 | 11 | namespace S { 12 | // NOTE: this CSS rules allows to interact with elements in label 13 | export const Label = styled.div` 14 | user-select: none; 15 | pointer-events: auto; 16 | `; 17 | } 18 | 19 | // now we can render all what we want in the label 20 | export const EditableLabelWidget: React.FunctionComponent = (props) => { 21 | const [str, setStr] = React.useState(props.model.value); 22 | 23 | return ( 24 | 25 | { 28 | const newVal = event.target.value; 29 | 30 | // update value both in internal component state 31 | setStr(newVal); 32 | // and in model object 33 | props.model.value = newVal; 34 | }} 35 | /> 36 | 37 | action('model eventDidFire')('You clicked the button')}>Click me! 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-link-label/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import createEngine, { DefaultNodeModel, DiagramModel } from '@projectstorm/react-diagrams'; 3 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | 6 | import { EditableLabelFactory } from './EditableLabelFactory'; 7 | import { EditableLabelModel } from './EditableLabelModel'; 8 | 9 | /** 10 | * @Author Shumaf Lovpache (aka Soarex16) 11 | */ 12 | 13 | export default () => { 14 | // engine setup 15 | const engine = createEngine(); 16 | 17 | // register our label factory 18 | engine.getLabelFactories().registerFactory(new EditableLabelFactory()); 19 | 20 | // setup diagram model 21 | const model = new DiagramModel(); 22 | 23 | // create some nodes 24 | const node1 = new DefaultNodeModel('Node1', 'red'); 25 | const port1 = node1.addOutPort('out'); 26 | node1.setPosition(250, 100); 27 | 28 | const node2 = new DefaultNodeModel('Node2', 'green'); 29 | const port2 = node2.addInPort('in'); 30 | node2.setPosition(800, 300); 31 | 32 | // link nodes together 33 | const link1 = port1.link(port2); 34 | 35 | // !!! 36 | // add our custom label to link 37 | link1.addLabel( 38 | new EditableLabelModel({ 39 | value: 'Hello, I am label!' 40 | }) 41 | ); 42 | 43 | // add models to the root graph 44 | model.addAll(node1, port1, node2, port2, link1); 45 | 46 | // load model into engine 47 | engine.setModel(model); 48 | 49 | // render diagram 50 | return ( 51 | 52 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-node1/DiamondNodeFactory.tsx: -------------------------------------------------------------------------------- 1 | import { DiamondNodeWidget } from './DiamondNodeWidget'; 2 | import { DiamondNodeModel } from './DiamondNodeModel'; 3 | import * as React from 'react'; 4 | import { AbstractReactFactory } from '@projectstorm/react-canvas-core'; 5 | import { DiagramEngine } from '@projectstorm/react-diagrams-core'; 6 | import { JSX } from 'react'; 7 | 8 | export class DiamondNodeFactory extends AbstractReactFactory { 9 | constructor() { 10 | super('diamond'); 11 | } 12 | 13 | generateReactWidget(event): JSX.Element { 14 | return ; 15 | } 16 | 17 | generateModel(event) { 18 | return new DiamondNodeModel(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-node1/DiamondNodeModel.ts: -------------------------------------------------------------------------------- 1 | import { NodeModel, NodeModelGenerics, PortModelAlignment } from '@projectstorm/react-diagrams'; 2 | import { DiamondPortModel } from './DiamondPortModel'; 3 | 4 | export interface DiamondNodeModelGenerics { 5 | PORT: DiamondPortModel; 6 | } 7 | 8 | export class DiamondNodeModel extends NodeModel { 9 | constructor() { 10 | super({ 11 | type: 'diamond' 12 | }); 13 | this.addPort(new DiamondPortModel(PortModelAlignment.TOP)); 14 | this.addPort(new DiamondPortModel(PortModelAlignment.LEFT)); 15 | this.addPort(new DiamondPortModel(PortModelAlignment.BOTTOM)); 16 | this.addPort(new DiamondPortModel(PortModelAlignment.RIGHT)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-node1/DiamondPortModel.ts: -------------------------------------------------------------------------------- 1 | import { LinkModel, PortModel, DefaultLinkModel, PortModelAlignment } from '@projectstorm/react-diagrams'; 2 | 3 | export class DiamondPortModel extends PortModel { 4 | constructor(alignment: PortModelAlignment) { 5 | super({ 6 | type: 'diamond', 7 | name: alignment, 8 | alignment: alignment 9 | }); 10 | } 11 | 12 | createLinkModel(): LinkModel { 13 | return new DefaultLinkModel(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-node1/SimplePortFactory.ts: -------------------------------------------------------------------------------- 1 | import { DiagramEngine, PortModel } from '@projectstorm/react-diagrams'; 2 | import { AbstractModelFactory } from '@projectstorm/react-canvas-core'; 3 | 4 | export class SimplePortFactory extends AbstractModelFactory { 5 | cb: (initialConfig?: any) => PortModel; 6 | 7 | constructor(type: string, cb: (initialConfig?: any) => PortModel) { 8 | super(type); 9 | this.cb = cb; 10 | } 11 | 12 | generateModel(event): PortModel { 13 | return this.cb(event.initialConfig); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom-node1/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DefaultNodeModel, DiagramModel, PortModelAlignment } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | // import the custom models 4 | import { DiamondNodeModel } from './DiamondNodeModel'; 5 | import { DiamondNodeFactory } from './DiamondNodeFactory'; 6 | import { SimplePortFactory } from './SimplePortFactory'; 7 | import { DiamondPortModel } from './DiamondPortModel'; 8 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 9 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 10 | 11 | /** 12 | * @Author Dylan Vorster 13 | */ 14 | export default () => { 15 | //1) setup the diagram engine 16 | var engine = createEngine(); 17 | 18 | // register some other factories as well 19 | engine 20 | .getPortFactories() 21 | .registerFactory(new SimplePortFactory('diamond', (config) => new DiamondPortModel(PortModelAlignment.LEFT))); 22 | engine.getNodeFactories().registerFactory(new DiamondNodeFactory()); 23 | 24 | //2) setup the diagram model 25 | var model = new DiagramModel(); 26 | 27 | //3-A) create a default node 28 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 29 | var port1 = node1.addOutPort('Out'); 30 | node1.setPosition(100, 200); 31 | 32 | //3-B) create our new custom node 33 | var node2 = new DiamondNodeModel(); 34 | node2.setPosition(250, 108); 35 | 36 | var node3 = new DefaultNodeModel('Node 3', 'red'); 37 | var port3 = node3.addInPort('In'); 38 | node3.setPosition(500, 100); 39 | 40 | //3-C) link the 2 nodes together 41 | var link1 = port1.link(node2.getPort(PortModelAlignment.LEFT)); 42 | var link2 = port3.link(node2.getPort(PortModelAlignment.RIGHT)); 43 | 44 | var node4 = new DefaultNodeModel('Node 4', 'rgb(0,192,255)'); 45 | var port4 = node4.addOutPort('Out'); 46 | node4.setPosition(200, 10); 47 | 48 | var link3 = port4.link(node2.getPort(PortModelAlignment.TOP)); 49 | 50 | var node5 = new DefaultNodeModel('Node 5', 'mediumpurple'); 51 | var port5 = node5.addInPort('In'); 52 | node5.setPosition(400, 300); 53 | 54 | var link4 = port5.link(node2.getPort(PortModelAlignment.BOTTOM)); 55 | 56 | //4) add the models to the root graph 57 | model.addAll(node1, node2, node3, link1, link2, node4, link3, link4, node5); 58 | 59 | //5) load model into engine 60 | engine.setModel(model); 61 | 62 | //6) render the diagram! 63 | return ( 64 | 65 | 66 | 67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-custom_delete_keys/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import createEngine, { DiagramModel, DefaultNodeModel, DefaultLinkModel } from '@projectstorm/react-diagrams'; 3 | import { CanvasWidget, DeleteItemsAction } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | 6 | export default () => { 7 | // create an engine without registering DeleteItemsAction 8 | const engine = createEngine({ registerDefaultDeleteItemsAction: false }); 9 | const model = new DiagramModel(); 10 | 11 | const node1 = new DefaultNodeModel({ name: 'Node 1', color: 'rgb(0,192,255)' }); 12 | node1.setPosition(100, 100); 13 | const port1 = node1.addOutPort('Out'); 14 | 15 | const node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 16 | const port2 = node2.addInPort('In'); 17 | node2.setPosition(400, 100); 18 | 19 | const link1 = port1.link(port2); 20 | link1.getOptions().testName = 'Test'; 21 | link1.addLabel('Hello World!'); 22 | 23 | model.addAll(node1, node2, link1); 24 | 25 | engine.setModel(model); 26 | 27 | // register an DeleteItemsAction with custom keyCodes (in this case, only Delete key) 28 | engine.getActionEventBus().registerAction(new DeleteItemsAction({ keyCodes: [46] })); 29 | 30 | return ( 31 | 32 | 33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-drag-and-drop/Application.ts: -------------------------------------------------------------------------------- 1 | import * as SRD from '@projectstorm/react-diagrams'; 2 | 3 | /** 4 | * @author Dylan Vorster 5 | */ 6 | export class Application { 7 | protected activeModel: SRD.DiagramModel; 8 | protected diagramEngine: SRD.DiagramEngine; 9 | 10 | constructor() { 11 | this.diagramEngine = SRD.default(); 12 | this.newModel(); 13 | } 14 | 15 | public newModel() { 16 | this.activeModel = new SRD.DiagramModel(); 17 | this.diagramEngine.setModel(this.activeModel); 18 | 19 | //3-A) create a default node 20 | var node1 = new SRD.DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 21 | let port = node1.addOutPort('Out'); 22 | node1.setPosition(100, 100); 23 | 24 | //3-B) create another default node 25 | var node2 = new SRD.DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 26 | let port2 = node2.addInPort('In'); 27 | node2.setPosition(400, 100); 28 | 29 | // link the ports 30 | let link1 = port.link(port2); 31 | 32 | this.activeModel.addAll(node1, node2, link1); 33 | } 34 | 35 | public getActiveDiagram(): SRD.DiagramModel { 36 | return this.activeModel; 37 | } 38 | 39 | public getDiagramEngine(): SRD.DiagramEngine { 40 | return this.diagramEngine; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-drag-and-drop/components/BodyWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import _keys from 'lodash/keys'; 3 | import { TrayWidget } from './TrayWidget'; 4 | import { Application } from '../Application'; 5 | import { TrayItemWidget } from './TrayItemWidget'; 6 | import { DefaultNodeModel } from '@projectstorm/react-diagrams'; 7 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 8 | import { DemoCanvasWidget } from '../../helpers/DemoCanvasWidget'; 9 | import styled from '@emotion/styled'; 10 | 11 | export interface BodyWidgetProps { 12 | app: Application; 13 | } 14 | 15 | namespace S { 16 | export const Body = styled.div` 17 | flex-grow: 1; 18 | display: flex; 19 | flex-direction: column; 20 | min-height: 100%; 21 | `; 22 | 23 | export const Header = styled.div` 24 | display: flex; 25 | background: rgb(30, 30, 30); 26 | flex-grow: 0; 27 | flex-shrink: 0; 28 | color: white; 29 | font-family: Helvetica, Arial, sans-serif; 30 | padding: 10px; 31 | align-items: center; 32 | `; 33 | 34 | export const Content = styled.div` 35 | display: flex; 36 | flex-grow: 1; 37 | `; 38 | 39 | export const Layer = styled.div` 40 | position: relative; 41 | flex-grow: 1; 42 | `; 43 | } 44 | 45 | export class BodyWidget extends React.Component { 46 | render() { 47 | return ( 48 | 49 | 50 | Storm React Diagrams - DnD demo 51 | 52 | 53 | 54 | 55 | 56 | 57 | { 59 | var data = JSON.parse(event.dataTransfer.getData('storm-diagram-node')); 60 | var nodesCount = _keys(this.props.app.getDiagramEngine().getModel().getNodes()).length; 61 | 62 | var node: DefaultNodeModel = null; 63 | if (data.type === 'in') { 64 | node = new DefaultNodeModel('Node ' + (nodesCount + 1), 'rgb(192,255,0)'); 65 | node.addInPort('In'); 66 | } else { 67 | node = new DefaultNodeModel('Node ' + (nodesCount + 1), 'rgb(0,192,255)'); 68 | node.addOutPort('Out'); 69 | } 70 | var point = this.props.app.getDiagramEngine().getRelativeMousePoint(event); 71 | node.setPosition(point); 72 | this.props.app.getDiagramEngine().getModel().addNode(node); 73 | this.forceUpdate(); 74 | }} 75 | onDragOver={(event) => { 76 | event.preventDefault(); 77 | }} 78 | > 79 | 80 | 81 | 82 | 83 | 84 | 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-drag-and-drop/components/TrayItemWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | export interface TrayItemWidgetProps { 5 | model: any; 6 | color?: string; 7 | name: string; 8 | } 9 | 10 | namespace S { 11 | export const Tray = styled.div<{ color: string }>` 12 | color: white; 13 | font-family: Helvetica, Arial; 14 | padding: 5px; 15 | margin: 0px 10px; 16 | border: solid 1px ${(p) => p.color}; 17 | border-radius: 5px; 18 | margin-bottom: 2px; 19 | cursor: pointer; 20 | `; 21 | } 22 | 23 | export class TrayItemWidget extends React.Component { 24 | render() { 25 | return ( 26 | { 30 | event.dataTransfer.setData('storm-diagram-node', JSON.stringify(this.props.model)); 31 | }} 32 | className="tray-item" 33 | > 34 | {this.props.name} 35 | 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-drag-and-drop/components/TrayWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | namespace S { 5 | export const Tray = styled.div` 6 | min-width: 200px; 7 | background: rgb(20, 20, 20); 8 | flex-grow: 0; 9 | flex-shrink: 0; 10 | `; 11 | } 12 | 13 | export class TrayWidget extends React.Component { 14 | render() { 15 | return {this.props.children}; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-drag-and-drop/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { BodyWidget } from './components/BodyWidget'; 4 | import { Application } from './Application'; 5 | 6 | export default () => { 7 | var app = new Application(); 8 | return ; 9 | }; 10 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-dynamic-ports/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel, DiagramEngine } from '@projectstorm/react-diagrams'; 2 | import _values from 'lodash/values'; 3 | import * as React from 'react'; 4 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 5 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 6 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 7 | 8 | class CloneSelected extends React.Component<{ model: DiagramModel; engine: DiagramEngine }, any> { 9 | addPorts = () => { 10 | const nodes: DefaultNodeModel[] = _values(this.props.model.getNodes()) as DefaultNodeModel[]; 11 | for (let node of nodes) { 12 | if (node.getOptions().name === 'Node 2') { 13 | node.addInPort(`in-${node.getInPorts().length + 1}`, false); 14 | } else { 15 | node.addOutPort(`out-${node.getOutPorts().length + 1}`, false); 16 | } 17 | } 18 | this.props.engine.repaintCanvas(); 19 | }; 20 | 21 | render() { 22 | const { engine } = this.props; 23 | return ( 24 | Add more ports}> 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | } 32 | 33 | export default () => { 34 | //1) setup the diagram engine 35 | var engine = createEngine(); 36 | 37 | //2) setup the diagram model 38 | var model = new DiagramModel(); 39 | 40 | //3-A) create a default node 41 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 42 | node1.setPosition(100, 100); 43 | 44 | //3-B) create another default node 45 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 46 | node2.setPosition(400, 100); 47 | 48 | // link the ports 49 | 50 | //4) add the models to the root graph 51 | model.addAll(node1, node2); 52 | 53 | //5) load model into engine 54 | engine.setModel(model); 55 | 56 | //6) render the diagram! 57 | return ; 58 | }; 59 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-grid/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | 6 | /** 7 | * Tests the grid size 8 | */ 9 | export default () => { 10 | //1) setup the diagram engine 11 | var engine = createEngine(); 12 | 13 | //2) setup the diagram model 14 | var model = new DiagramModel(); 15 | model.setGridSize(50); 16 | 17 | //3-A) create a default node 18 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 19 | let port = node1.addOutPort('Out'); 20 | node1.setPosition(100, 100); 21 | 22 | //3-B) create another default node 23 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 24 | let port2 = node2.addInPort('In'); 25 | node2.setPosition(400, 100); 26 | 27 | // link the ports 28 | let link1 = port.link(port2); 29 | 30 | //4) add the models to the root graph 31 | model.addAll(node1, node2, link1); 32 | 33 | //5) load model into engine 34 | engine.setModel(model); 35 | 36 | //6) render the diagram! 37 | return ( 38 | 39 | 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-labelled-links/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel, DefaultLinkModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 4 | import { action } from '@storybook/addon-actions'; 5 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 6 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 7 | 8 | export default () => { 9 | // setup the diagram engine 10 | const engine = createEngine(); 11 | 12 | // setup the diagram model 13 | const model = new DiagramModel(); 14 | 15 | // create four nodes 16 | const node1 = new DefaultNodeModel('Node A', 'rgb(0,192,255)'); 17 | const port1 = node1.addOutPort('Out'); 18 | node1.setPosition(100, 100); 19 | 20 | const node2 = new DefaultNodeModel('Node B', 'rgb(255,255,0)'); 21 | const port2 = node2.addInPort('In'); 22 | node2.setPosition(400, 50); 23 | 24 | const node3 = new DefaultNodeModel('Node C (no label)', 'rgb(192,255,255)'); 25 | const port3 = node3.addInPort('In'); 26 | node3.setPosition(450, 180); 27 | 28 | const node4 = new DefaultNodeModel('Node D', 'rgb(192,0,255)'); 29 | const port4 = node4.addInPort('In'); 30 | node4.setPosition(300, 250); 31 | 32 | // link node A and B together and give it a label 33 | const link1 = port1.link(port2); 34 | (link1 as DefaultLinkModel).addLabel('Custom label 1'); 35 | (link1 as DefaultLinkModel).addLabel('Custom label 2'); 36 | 37 | // no label for A and C, just a link 38 | const link2 = port1.link(port3); 39 | 40 | // also a label for A and D 41 | const link3 = port1.link(port4); 42 | (link3 as DefaultLinkModel).addLabel('Emoji label: 🎉'); 43 | 44 | // add all to the main model 45 | model.addAll(node1, node2, node3, node4, link1, link2, link3); 46 | 47 | // load model into engine and render 48 | engine.setModel(model); 49 | 50 | return ( 51 | { 55 | action('Serialized Graph')(JSON.stringify(model.serializeDiagram(), null, 2)); 56 | }} 57 | > 58 | Serialize Graph 59 | 60 | } 61 | > 62 | 63 | 64 | 65 | 66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-listeners/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { action } from '@storybook/addon-actions'; 3 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 4 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 5 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 6 | 7 | /** 8 | * Shows some of the events triggered when elements are selected 9 | */ 10 | export default () => { 11 | // setup the diagram engine 12 | var engine = createEngine(); 13 | 14 | var model = new DiagramModel(); 15 | 16 | // sample for link with simple line 17 | var node1 = new DefaultNodeModel('Node 1', 'rgb(255,99,66)'); 18 | var port1 = node1.addOutPort('Out'); 19 | node1.setPosition(100, 100); 20 | 21 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 22 | var port2 = node2.addInPort('In'); 23 | node2.setPosition(400, 40); 24 | 25 | var node3 = new DefaultNodeModel('Node 3', 'rgb(128,99,255)'); 26 | var port3 = node3.addInPort('In'); 27 | node3.setPosition(300, 160); 28 | 29 | //link the nodes 30 | let link1 = port1.link(port2); 31 | let link2 = port1.link(port3); 32 | 33 | // add all the models 34 | let models = model.addAll(node1, node2, node3, link1, link2); 35 | 36 | // add a selection listener to each 37 | models.forEach((item) => { 38 | item.registerListener({ 39 | eventDidFire: action('element eventDidFire') 40 | }); 41 | }); 42 | 43 | model.registerListener({ 44 | eventDidFire: action('model eventDidFire') 45 | }); 46 | 47 | engine.setModel(model); 48 | 49 | return ( 50 | 51 | 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-locks/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 3 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | 6 | /** 7 | * 8 | * Shows how you can lock down the system so that the entire scene cant be interacted with. 9 | * 10 | * @Author Dylan Vorster 11 | */ 12 | export default () => { 13 | //1) setup the diagram engine 14 | var engine = createEngine(); 15 | 16 | var model = new DiagramModel(); 17 | 18 | // sample for link with simple line (no additional points) 19 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 20 | var port1 = node1.addOutPort('Out'); 21 | node1.setPosition(100, 100); 22 | 23 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 24 | var port2 = node2.addInPort('In'); 25 | node2.setPosition(400, 100); 26 | 27 | let link1 = port1.link(port2); 28 | 29 | model.addAll(node1, node2, link1); 30 | 31 | // sample for link with complex line (additional points) 32 | var node3 = new DefaultNodeModel('Node 3', 'rgb(0,192,255)'); 33 | var port3 = node3.addOutPort('Out'); 34 | node3.setPosition(100, 250); 35 | 36 | var node4 = new DefaultNodeModel('Node 4', 'rgb(192,255,0)'); 37 | var port4 = node4.addInPort('In'); 38 | node4.setPosition(400, 250); 39 | 40 | var link2 = port3.link(port4); 41 | 42 | link2.point(350, 225); 43 | link2.point(200, 225); 44 | 45 | model.addAll(node3, node4, link2); 46 | 47 | engine.setModel(model); 48 | 49 | //!========================================= <<<<<<< 50 | 51 | model.setLocked(true); 52 | 53 | //!========================================= <<<<<<< 54 | 55 | return ( 56 | 57 | 58 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-pan-and-zoom/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 3 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 4 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 5 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 6 | 7 | /** 8 | * Tests the pan and zoom action, which is intended as a trackpad/mobile 9 | * alternative to the standard ZoomCanvasAction 10 | */ 11 | class CanvasPanAndZoomToggle extends React.Component { 12 | render() { 13 | const { engine } = this.props; 14 | return ( 15 | 16 | 17 | 18 | ); 19 | } 20 | } 21 | 22 | export default () => { 23 | /** 24 | * 1) setup the diagram engine 25 | * PandAndZoomCanvasAction and ZoomCanvasAction are mutually exclusive 26 | * If both are enabled, ZoomCanvasAction will override. 27 | */ 28 | var engine = createEngine({ 29 | registerDefaultPanAndZoomCanvasAction: true, 30 | registerDefaultZoomCanvasAction: false 31 | }); 32 | 33 | //2) setup the diagram model 34 | var model = new DiagramModel(); 35 | 36 | //3-A) create a default node 37 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 38 | var port1 = node1.addOutPort('Out'); 39 | node1.setPosition(100, 100); 40 | 41 | //3-B) create another default node 42 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 43 | var port2 = node2.addInPort('In'); 44 | node2.setPosition(400, 100); 45 | 46 | //3-C) link the 2 nodes together 47 | var link1 = port1.link(port2); 48 | 49 | //4) add the models to the root graph 50 | model.addAll(node1, node2, link1); 51 | 52 | //5) load model into engine 53 | engine.setModel(model); 54 | 55 | //6) render the diagram! 56 | return ; 57 | }; 58 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-performance/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | 6 | /** 7 | * 8 | * Simple stress test of the system, shows that it can handle many nodes, and 9 | * retain good performance 10 | * 11 | * @Author Dylan Vorster 12 | */ 13 | export default () => { 14 | //1) setup the diagram engine 15 | var engine = createEngine(); 16 | 17 | //2) setup the diagram model 18 | var model = new DiagramModel(); 19 | 20 | for (var i = 0; i < 8; i++) { 21 | for (var j = 0; j < 8; j++) { 22 | generateNodes(model, i * 200, j * 100); 23 | } 24 | } 25 | 26 | //5) load model into engine 27 | engine.setModel(model); 28 | 29 | //6) render the diagram! 30 | return ( 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) { 38 | //3-A) create a default node 39 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 40 | var port1 = node1.addOutPort('Out'); 41 | node1.setPosition(100 + offsetX, 100 + offsetY); 42 | 43 | //3-B) create another default node 44 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 45 | var port2 = node2.addInPort('In'); 46 | node2.setPosition(200 + offsetX, 100 + offsetY); 47 | 48 | //3-C) link the 2 nodes together 49 | var link1 = port1.link(port2); 50 | 51 | //4) add the models to the root graph 52 | model.addAll(node1, node2, link1); 53 | } 54 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-right-angles-routing/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { 2 | DiagramModel, 3 | DefaultNodeModel, 4 | DefaultPortModel, 5 | RightAngleLinkFactory, 6 | LinkModel, 7 | RightAngleLinkModel 8 | } from '@projectstorm/react-diagrams'; 9 | import * as React from 'react'; 10 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 11 | import { action } from '@storybook/addon-actions'; 12 | import { AbstractModelFactory, CanvasWidget } from '@projectstorm/react-canvas-core'; 13 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 14 | 15 | // When new link is created by clicking on port the RightAngleLinkModel needs to be returned. 16 | export class RightAnglePortModel extends DefaultPortModel { 17 | createLinkModel(factory?: AbstractModelFactory) { 18 | return new RightAngleLinkModel(); 19 | } 20 | } 21 | 22 | export default () => { 23 | // setup the diagram engine 24 | const engine = createEngine(); 25 | engine.getLinkFactories().registerFactory(new RightAngleLinkFactory()); 26 | 27 | // setup the diagram model 28 | const model = new DiagramModel(); 29 | 30 | // create four nodes in a way that straight links wouldn't work 31 | const node1 = new DefaultNodeModel('Node A', 'rgb(0,192,255)'); 32 | const port1 = node1.addPort(new RightAnglePortModel(false, 'out-1', 'Out')); 33 | node1.setPosition(340, 350); 34 | 35 | const node2 = new DefaultNodeModel('Node B', 'rgb(255,255,0)'); 36 | const port2 = node2.addPort(new RightAnglePortModel(false, 'out-1', 'Out')); 37 | node2.setPosition(240, 80); 38 | const node3 = new DefaultNodeModel('Node C', 'rgb(192,255,255)'); 39 | const port3 = node3.addPort(new RightAnglePortModel(true, 'in-1', 'In')); 40 | node3.setPosition(540, 180); 41 | const node4 = new DefaultNodeModel('Node D', 'rgb(192,0,255)'); 42 | const port4 = node4.addPort(new RightAnglePortModel(true, 'in-1', 'In')); 43 | node4.setPosition(95, 185); 44 | 45 | // linking things together 46 | const link1 = port1.link(port4); 47 | const link2 = port2.link(port3); 48 | 49 | // add all to the main model 50 | model.addAll(node1, node2, node3, node4, link1, link2); 51 | 52 | // load model into engine and render 53 | engine.setModel(model); 54 | 55 | return ( 56 | { 60 | action('Serialized Graph')(JSON.stringify(model.serialize(), null, 2)); 61 | }} 62 | > 63 | Serialize Graph 64 | 65 | } 66 | > 67 | 68 | 69 | 70 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-serializing/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel, DefaultLabelModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 4 | import { action } from '@storybook/addon-actions'; 5 | import * as beautify from 'json-beautify'; 6 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 7 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 8 | 9 | export default () => { 10 | //1) setup the diagram engine 11 | var engine = createEngine(); 12 | 13 | //2) setup the diagram model 14 | var model = new DiagramModel(); 15 | 16 | //3-A) create a default node 17 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 18 | var port1 = node1.addOutPort('Out'); 19 | node1.setPosition(100, 100); 20 | 21 | //3-B) create another default node 22 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 23 | var port2 = node2.addInPort('In'); 24 | node2.setPosition(400, 100); 25 | 26 | //3-C) link the 2 nodes together 27 | var link1 = port1.link(port2); 28 | link1.addLabel(new DefaultLabelModel({ label: 'Label' })); 29 | 30 | //4) add the models to the root graph 31 | model.addAll(node1, node2, link1); 32 | 33 | //5) load model into engine 34 | engine.setModel(model); 35 | 36 | //!------------- SERIALIZING ------------------ 37 | 38 | var str = JSON.stringify(model.serialize()); 39 | 40 | //!------------- DESERIALIZING ---------------- 41 | 42 | var model2 = new DiagramModel(); 43 | model2.deserializeModel(JSON.parse(str), engine); 44 | engine.setModel(model2); 45 | 46 | return ( 47 | { 51 | action('Serialized Graph')(beautify(model2.serialize(), null, 2, 80)); 52 | }} 53 | > 54 | Serialize Graph 55 | 56 | } 57 | > 58 | 59 | 60 | 61 | 62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-simple-flow/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel, DefaultDiagramState } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | 6 | export default () => { 7 | //1) setup the diagram engine 8 | var engine = createEngine(); 9 | 10 | // ############################################ MAGIC HAPPENS HERE 11 | const state = engine.getStateMachine().getCurrentState(); 12 | if (state instanceof DefaultDiagramState) { 13 | state.dragNewLink.config.allowLooseLinks = false; 14 | } 15 | // ############################################ MAGIC HAPPENS HERE 16 | 17 | //2) setup the diagram model 18 | var model = new DiagramModel(); 19 | 20 | //3-A) create a default node 21 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 22 | var port1 = node1.addOutPort('Out'); 23 | node1.setPosition(100, 100); 24 | 25 | //3-B) create another default node 26 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 27 | var port2 = node2.addInPort('In'); 28 | node2.setPosition(400, 100); 29 | 30 | //3-C) link the 2 nodes together 31 | var link1 = port1.link(port2); 32 | 33 | //3-D) create an orphaned node 34 | var node3 = new DefaultNodeModel('Node 3', 'rgb(0,192,255)'); 35 | node3.addOutPort('Out'); 36 | node3.setPosition(100, 200); 37 | 38 | //4) add the models to the root graph 39 | model.addAll(node1, node2, node3, link1); 40 | 41 | //5) load model into engine 42 | engine.setModel(model); 43 | 44 | //6) render the diagram! 45 | return ( 46 | 47 | 48 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-simple/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel, DefaultLinkModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 4 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 5 | 6 | export default () => { 7 | //1) setup the diagram engine 8 | var engine = createEngine(); 9 | 10 | //2) setup the diagram model 11 | var model = new DiagramModel(); 12 | 13 | //3-A) create a default node 14 | var node1 = new DefaultNodeModel({ 15 | name: 'Node 1', 16 | color: 'rgb(0,192,255)' 17 | }); 18 | node1.setPosition(100, 100); 19 | let port1 = node1.addOutPort('Out'); 20 | 21 | //3-B) create another default node 22 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 23 | let port2 = node2.addInPort('In'); 24 | node2.setPosition(400, 100); 25 | 26 | // link the ports 27 | let link1 = port1.link(port2); 28 | link1.getOptions().testName = 'Test'; 29 | link1.addLabel('Hello World!'); 30 | 31 | //4) add the models to the root graph 32 | model.addAll(node1, node2, link1); 33 | 34 | //5) load model into engine 35 | engine.setModel(model); 36 | 37 | //6) render the diagram! 38 | return ( 39 | 40 | 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-smart-routing/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { 2 | DiagramModel, 3 | DefaultNodeModel, 4 | DefaultPortModel, 5 | PathFindingLinkFactory, 6 | DefaultLabelModel 7 | } from '@projectstorm/react-diagrams'; 8 | import * as React from 'react'; 9 | import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget'; 10 | import { action } from '@storybook/addon-actions'; 11 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 12 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 13 | 14 | export default () => { 15 | // setup the diagram engine 16 | const engine = createEngine(); 17 | 18 | // setup the diagram model 19 | const model = new DiagramModel(); 20 | 21 | // create four nodes in a way that straight links wouldn't work 22 | const node1 = new DefaultNodeModel('Node A', 'rgb(0,192,255)'); 23 | const port1 = node1.addPort(new DefaultPortModel(false, 'out-1', 'Out')); 24 | node1.setPosition(340, 350); 25 | 26 | const node2 = new DefaultNodeModel('Node B', 'rgb(255,255,0)'); 27 | const port2 = node2.addPort(new DefaultPortModel(false, 'out-1', 'Out')); 28 | node2.setPosition(240, 80); 29 | const node3 = new DefaultNodeModel('Node C', 'rgb(192,255,255)'); 30 | const port3 = node3.addPort(new DefaultPortModel(true, 'in-1', 'In')); 31 | node3.setPosition(540, 180); 32 | const node4 = new DefaultNodeModel('Node D', 'rgb(192,0,255)'); 33 | const port4 = node4.addPort(new DefaultPortModel(true, 'in-1', 'In')); 34 | node4.setPosition(95, 185); 35 | const node5 = new DefaultNodeModel('Node E', 'rgb(192,255,0)'); 36 | node5.setPosition(250, 180); 37 | 38 | const pathfinding = engine.getLinkFactories().getFactory(PathFindingLinkFactory.NAME); 39 | 40 | // linking things together (specifically using the pathfinding link) 41 | const link1 = port1.link(port4, pathfinding); 42 | const link2 = port2.link(port3, pathfinding); 43 | 44 | link1.addLabel( 45 | new DefaultLabelModel({ 46 | label: 'I am a label!', 47 | offsetY: 20 48 | }) 49 | ); 50 | 51 | // add all to the main model 52 | model.addAll(node1, node2, node3, node4, node5, link1, link2); 53 | 54 | // load model into engine and render 55 | engine.setModel(model); 56 | 57 | return ( 58 | { 62 | action('Serialized Graph')(JSON.stringify(model.serialize(), null, 2)); 63 | }} 64 | > 65 | Serialize Graph 66 | 67 | } 68 | > 69 | 70 | 71 | 72 | 73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-zoom-to-fit-nodes/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { DemoWorkspaceWidget, DemoButton } from '../helpers/DemoWorkspaceWidget'; 4 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 5 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 6 | 7 | /** 8 | * 9 | * Simple stress test of the system plus zoom to fit function 10 | * 11 | * @Author Dylan Vorster 12 | */ 13 | export default () => { 14 | //1) setup the diagram engine 15 | var engine = createEngine(); 16 | 17 | //2) setup the diagram model 18 | var model = new DiagramModel(); 19 | 20 | for (var i = 0; i < 8; i++) { 21 | for (var j = 0; j < 8; j++) { 22 | generateNodes(model, i * 200, j * 100); 23 | } 24 | } 25 | 26 | //5) load model into engine 27 | engine.setModel(model); 28 | 29 | //6) render the diagram! 30 | return ( 31 | engine.zoomToFitSelectedNodes(50)}>Zoom to fit} 33 | > 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) { 42 | //3-A) create a default node 43 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 44 | var port1 = node1.addOutPort('Out'); 45 | node1.setPosition(100 + offsetX, 100 + offsetY); 46 | 47 | //3-B) create another default node 48 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 49 | var port2 = node2.addInPort('In'); 50 | node2.setPosition(200 + offsetX, 100 + offsetY); 51 | 52 | //3-C) link the 2 nodes together 53 | var link1 = port1.link(port2); 54 | 55 | //4) add the models to the root graph 56 | model.addAll(node1, node2, link1); 57 | } 58 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/demo-zoom-to-fit/index.tsx: -------------------------------------------------------------------------------- 1 | import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams'; 2 | import * as React from 'react'; 3 | import { DemoWorkspaceWidget, DemoButton } from '../helpers/DemoWorkspaceWidget'; 4 | import { CanvasWidget } from '@projectstorm/react-canvas-core'; 5 | import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget'; 6 | 7 | /** 8 | * 9 | * Simple stress test of the system plus zoom to fit function 10 | * 11 | * @Author Dylan Vorster 12 | */ 13 | export default () => { 14 | //1) setup the diagram engine 15 | var engine = createEngine(); 16 | 17 | //2) setup the diagram model 18 | var model = new DiagramModel(); 19 | 20 | for (var i = 0; i < 8; i++) { 21 | for (var j = 0; j < 8; j++) { 22 | generateNodes(model, i * 200, j * 100); 23 | } 24 | } 25 | 26 | //5) load model into engine 27 | engine.setModel(model); 28 | 29 | //6) render the diagram! 30 | return ( 31 | engine.zoomToFit()}>Zoom to fit}> 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) { 40 | //3-A) create a default node 41 | var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)'); 42 | var port1 = node1.addOutPort('Out'); 43 | node1.setPosition(100 + offsetX, 100 + offsetY); 44 | 45 | //3-B) create another default node 46 | var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)'); 47 | var port2 = node2.addInPort('In'); 48 | node2.setPosition(200 + offsetX, 100 + offsetY); 49 | 50 | //3-C) link the 2 nodes together 51 | var link1 = port1.link(port2); 52 | 53 | //4) add the models to the root graph 54 | model.addAll(node1, node2, link1); 55 | } 56 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/helpers/DemoCanvasWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { css, Global } from '@emotion/react'; 4 | 5 | export interface DemoCanvasWidgetProps { 6 | color?: string; 7 | background?: string; 8 | } 9 | 10 | namespace S { 11 | export const Container = styled.div<{ color: string; background: string }>` 12 | height: 100%; 13 | background-color: ${(p) => p.background}; 14 | background-size: 50px 50px; 15 | display: flex; 16 | 17 | > * { 18 | height: 100%; 19 | min-height: 100%; 20 | width: 100%; 21 | } 22 | 23 | background-image: linear-gradient( 24 | 0deg, 25 | transparent 24%, 26 | ${(p) => p.color} 25%, 27 | ${(p) => p.color} 26%, 28 | transparent 27%, 29 | transparent 74%, 30 | ${(p) => p.color} 75%, 31 | ${(p) => p.color} 76%, 32 | transparent 77%, 33 | transparent 34 | ), 35 | linear-gradient( 36 | 90deg, 37 | transparent 24%, 38 | ${(p) => p.color} 25%, 39 | ${(p) => p.color} 26%, 40 | transparent 27%, 41 | transparent 74%, 42 | ${(p) => p.color} 75%, 43 | ${(p) => p.color} 76%, 44 | transparent 77%, 45 | transparent 46 | ); 47 | `; 48 | 49 | export const Expand = css` 50 | html, 51 | body, 52 | #root { 53 | height: 100%; 54 | } 55 | `; 56 | } 57 | 58 | export class DemoCanvasWidget extends React.Component> { 59 | render() { 60 | return ( 61 | <> 62 | 63 | 67 | {this.props.children} 68 | 69 | > 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/helpers/DemoWorkspaceWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | export interface DemoWorkspaceWidgetProps { 5 | buttons?: any; 6 | } 7 | 8 | namespace S { 9 | export const Toolbar = styled.div` 10 | padding: 5px; 11 | display: flex; 12 | flex-shrink: 0; 13 | `; 14 | 15 | export const Content = styled.div` 16 | flex-grow: 1; 17 | height: 100%; 18 | `; 19 | 20 | export const Container = styled.div` 21 | background: black; 22 | display: flex; 23 | flex-direction: column; 24 | height: 100%; 25 | border-radius: 5px; 26 | overflow: hidden; 27 | `; 28 | } 29 | 30 | export const DemoButton = styled.button` 31 | background: rgb(60, 60, 60); 32 | font-size: 14px; 33 | padding: 5px 10px; 34 | border: none; 35 | color: white; 36 | outline: none; 37 | cursor: pointer; 38 | margin: 2px; 39 | border-radius: 3px; 40 | 41 | &:hover { 42 | background: rgb(0, 192, 255); 43 | } 44 | `; 45 | 46 | export class DemoWorkspaceWidget extends React.Component> { 47 | render() { 48 | return ( 49 | 50 | {this.props.buttons} 51 | {this.props.children} 52 | 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/helpers/Helper.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export class Helper { 4 | /** 5 | * Logs the mouse position in the console, but overlays a div that consumes all events 6 | * since the actual story book stories are rendered as an iFrame. 7 | */ 8 | static logMousePosition() { 9 | let element = window.parent.document.createElement('mouse-position'); 10 | element.style.position = 'absolute'; 11 | element.style.top = '0px'; 12 | element.style.left = '0px'; 13 | element.style.bottom = '0px'; 14 | element.style.right = '0px'; 15 | element.style.zIndex = '10'; 16 | window.parent.document.body.appendChild(element); 17 | 18 | window.parent.window.addEventListener('mousemove', (event) => { 19 | console.clear(); 20 | console.log(event.clientX, event.clientY); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/demos/helpers/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #storybook-root { 4 | height: 100%; 5 | padding: 0; 6 | margin: 0; 7 | } 8 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams-gallery", 3 | "version": "7.2.1", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "private": true, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/projectstorm/react-diagrams.git" 10 | }, 11 | "scripts": { 12 | "start": "pnpm storybook dev", 13 | "storybook:build": "pnpm storybook build -c .storybook -o .out" 14 | }, 15 | "keywords": [ 16 | "web", 17 | "diagram", 18 | "diagrams", 19 | "react", 20 | "typescript", 21 | "flowchart", 22 | "simple", 23 | "links", 24 | "nodes" 25 | ], 26 | "dependencies": { 27 | "@projectstorm/react-canvas-core": "workspace:*", 28 | "@projectstorm/react-diagrams": "workspace:*", 29 | "@projectstorm/react-diagrams-core": "workspace:*", 30 | "@projectstorm/react-diagrams-defaults": "workspace:*", 31 | "gsap": "^3.12.2", 32 | "json-beautify": "^1.1.1", 33 | "lodash": "^4.17.21", 34 | "react": "^19.0.0", 35 | "react-dom": "^19.0.0" 36 | }, 37 | "devDependencies": { 38 | "@babel/preset-env": "^7.26.9", 39 | "@babel/preset-react": "^7.26.3", 40 | "@babel/preset-typescript": "^7.27.0", 41 | "@storybook/addon-actions": "^8.6.9", 42 | "@storybook/addon-webpack5-compiler-babel": "^3.0.5", 43 | "@storybook/manager-api": "^8.6.10", 44 | "@storybook/preview-api": "^8.6.10", 45 | "@storybook/react": "^8.6.9", 46 | "@storybook/react-webpack5": "^8.6.9", 47 | "@storybook/storybook-deployer": "^2.8.16", 48 | "@storybook/theming": "^8.6.9", 49 | "@types/lodash": "^4.14.200", 50 | "@types/react": "^19.0.12", 51 | "@types/react-dom": "^19.0.4", 52 | "storybook": "^8.6.9" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /diagrams-demo-gallery/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "declaration": true, 6 | "composite": true, 7 | "incremental": true, 8 | "strictNullChecks": false, 9 | "sourceMap": true, 10 | "skipLibCheck": true, 11 | "jsx": "react", 12 | "target": "ES6", 13 | "module": "commonjs", 14 | "strict": false, 15 | "lib": [ 16 | "DOM", 17 | "ES6" 18 | ] 19 | }, 20 | "include": [ 21 | "demos" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /diagrams-demo-project/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env",{ 4 | "targets": { 5 | "node": true 6 | } 7 | }], 8 | "@babel/preset-react" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /diagrams-demo-project/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @projectstorm/react-diagrams-demo 2 | 3 | ## 7.0.4 4 | 5 | ### Patch Changes 6 | 7 | - @projectstorm/react-diagrams@7.0.4 8 | 9 | ## 7.0.3 10 | 11 | ### Patch Changes 12 | 13 | - 66c687a: Upgrade all dependencies and fix Storybook after upgrade 14 | - @projectstorm/react-diagrams@7.0.3 15 | 16 | ## 7.0.2 17 | 18 | ### Patch Changes 19 | 20 | - b8a4cbd: Inline sources in sourcemap 21 | - Updated dependencies [b8a4cbd] 22 | - @projectstorm/react-diagrams@7.0.2 23 | 24 | ## 7.0.1 25 | 26 | ### Patch Changes 27 | 28 | - @projectstorm/react-diagrams@7.0.1 29 | 30 | ## 7.0.0 31 | 32 | ### Major Changes 33 | 34 | - b051697: - [internal] moves to `Pnpm` (instead of yarn -\_-) 35 | - [internal]moves to `Changesets` for releases 36 | - [internal]removes `Lerna` 37 | - [internal] upgrades all dependencies 38 | - [internal] switches to workspace protocol syntax (Changesets will bake in the correct version when a publish occurs) 39 | - [internal] Changesets will open a release PR which can wrap up several changes in 1 go 40 | - [internal] Changesets will run the storybook deploy automatically upon merging the release PR 41 | - [internal] removes a lot of the stuff from the root package.json 42 | - [internal] cleans up the build and clean commands 43 | - [internal] remove E2E tests, they are a nightmare to maintain and the ROI is far too low 44 | - [fix] Wrong type name for react-canvas model listener 45 | - [fix] export more stuff form the main react-diagrams package 46 | - [fix] circular deps with Rectangle and Polygon (turns out this was a problem but only with UMD builds, sorry @everyone who I doubted, but this is also why I could never reproduce the issue) 47 | - [breaking change] compile both ES6 and UMD 48 | - [breaking change] moves dependencies back to each package. (After years of working on libraries, I've come to actually hate peer dependencies, and this is easily solved with build systems / package managers). 49 | - [breaking change] static methods on `Polygon` and `Rectangle` moved to standalone methods 50 | - [breaking change] static construction methods to rather deal with different Rectangle constructor overloads (I now consider this bad design) 51 | - [breaking change] introduce `Bounds` as a simpler point-array type to deal with boundary computation instead 52 | 53 | ### Patch Changes 54 | 55 | - Updated dependencies [b051697] 56 | - @projectstorm/react-diagrams@7.0.0 57 | -------------------------------------------------------------------------------- /diagrams-demo-project/README.md: -------------------------------------------------------------------------------- 1 | # Project STORM > React diagrams > Demo Project 2 | 3 |  4 | 5 | In this repo you will find a simple webpack-dev-server project 6 | that shows how to get started with the library. 7 | 8 | It contains an example of how to implement a custom node in both Vanilla ES6 as-well 9 | as typescript (the recommended way). 10 | 11 | Simply run `yarn start` which will also open your browser. 12 | 13 | -------------------------------------------------------------------------------- /diagrams-demo-project/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Project STORM | React Diagrams demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /diagrams-demo-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams-demo", 3 | "version": "7.0.4", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "private": true, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/projectstorm/react-diagrams.git" 10 | }, 11 | "scripts": { 12 | "start": "./node_modules/.bin/webpack serve --open" 13 | }, 14 | "keywords": [ 15 | "web", 16 | "diagram", 17 | "diagrams", 18 | "react", 19 | "typescript", 20 | "flowchart", 21 | "simple", 22 | "links", 23 | "nodes" 24 | ], 25 | "main": "./dist/index.js", 26 | "typings": "./dist/@types/index", 27 | "dependencies": { 28 | "@projectstorm/react-diagrams": "workspace:*", 29 | "react": "^19.0.0", 30 | "react-dom": "^19.0.0" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "^7.26.10", 34 | "@babel/preset-react": "^7.26.3", 35 | "@types/react": "^19.0.12", 36 | "@types/react-dom": "^19.0.4", 37 | "babel-loader": "^9.1.3", 38 | "css-loader": "^6.8.1", 39 | "html-webpack-plugin": "^5.5.3", 40 | "source-map-loader": "^4.0.1", 41 | "style-loader": "^3.3.3", 42 | "webpack": "^5.88.2", 43 | "webpack-cli": "^5.1.4", 44 | "webpack-dev-server": "^4.15.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /diagrams-demo-project/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/diagrams-demo-project/screenshot.png -------------------------------------------------------------------------------- /diagrams-demo-project/src/BodyWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DiagramEngine, CanvasWidget } from '@projectstorm/react-diagrams'; 3 | 4 | export interface BodyWidgetProps { 5 | engine: DiagramEngine; 6 | } 7 | 8 | export class BodyWidget extends React.Component { 9 | render() { 10 | return ; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/custom-node-js/JSCustomNodeFactory.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { JSCustomNodeModel } from './JSCustomNodeModel'; 3 | import { JSCustomNodeWidget } from './JSCustomNodeWidget'; 4 | import { AbstractReactFactory } from '@projectstorm/react-diagrams'; 5 | 6 | export class JSCustomNodeFactory extends AbstractReactFactory { 7 | constructor() { 8 | super('js-custom-node'); 9 | } 10 | 11 | generateModel(event) { 12 | return new JSCustomNodeModel(); 13 | } 14 | 15 | generateReactWidget(event) { 16 | return ; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/custom-node-js/JSCustomNodeModel.js: -------------------------------------------------------------------------------- 1 | import { DefaultPortModel, NodeModel } from '@projectstorm/react-diagrams'; 2 | 3 | /** 4 | * Example of a custom model using pure javascript 5 | */ 6 | export class JSCustomNodeModel extends NodeModel { 7 | constructor(options = {}) { 8 | super({ 9 | ...options, 10 | type: 'js-custom-node' 11 | }); 12 | this.color = options.color || { options: 'red' }; 13 | 14 | // setup an in and out port 15 | this.addPort( 16 | new DefaultPortModel({ 17 | in: true, 18 | name: 'in' 19 | }) 20 | ); 21 | this.addPort( 22 | new DefaultPortModel({ 23 | in: false, 24 | name: 'out' 25 | }) 26 | ); 27 | } 28 | 29 | serialize() { 30 | return { 31 | ...super.serialize(), 32 | color: this.color 33 | }; 34 | } 35 | 36 | deserialize(ob, engine) { 37 | super.deserialize(ob, engine); 38 | this.color = ob.color; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/custom-node-js/JSCustomNodeWidget.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PortWidget } from '@projectstorm/react-diagrams'; 3 | 4 | export class JSCustomNodeWidget extends React.Component { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/custom-node-ts/TSCustomNodeFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { TSCustomNodeModel } from './TSCustomNodeModel'; 3 | import { TSCustomNodeWidget } from './TSCustomNodeWidget'; 4 | import { AbstractReactFactory } from '@projectstorm/react-diagrams'; 5 | import { DiagramEngine } from '@projectstorm/react-diagrams'; 6 | import { JSX } from 'react'; 7 | 8 | export class TSCustomNodeFactory extends AbstractReactFactory { 9 | constructor() { 10 | super('ts-custom-node'); 11 | } 12 | 13 | generateModel(initialConfig) { 14 | return new TSCustomNodeModel(); 15 | } 16 | 17 | generateReactWidget(event): JSX.Element { 18 | return ; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/custom-node-ts/TSCustomNodeModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseModelOptions, DefaultPortModel, NodeModel } from '@projectstorm/react-diagrams'; 2 | 3 | export interface TSCustomNodeModelOptions extends BaseModelOptions { 4 | color?: string; 5 | } 6 | 7 | export class TSCustomNodeModel extends NodeModel { 8 | color: string; 9 | 10 | constructor(options: TSCustomNodeModelOptions = {}) { 11 | super({ 12 | ...options, 13 | type: 'ts-custom-node' 14 | }); 15 | this.color = options.color || 'red'; 16 | 17 | // setup an in and out port 18 | this.addPort( 19 | new DefaultPortModel({ 20 | in: true, 21 | name: 'in' 22 | }) 23 | ); 24 | this.addPort( 25 | new DefaultPortModel({ 26 | in: false, 27 | name: 'out' 28 | }) 29 | ); 30 | } 31 | 32 | serialize() { 33 | return { 34 | ...super.serialize(), 35 | color: this.color 36 | }; 37 | } 38 | 39 | deserialize(event): void { 40 | super.deserialize(event); 41 | this.color = event.data.color; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/custom-node-ts/TSCustomNodeWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DiagramEngine, PortWidget } from '@projectstorm/react-diagrams'; 3 | import { TSCustomNodeModel } from './TSCustomNodeModel'; 4 | 5 | export interface TSCustomNodeWidgetProps { 6 | node: TSCustomNodeModel; 7 | engine: DiagramEngine; 8 | } 9 | 10 | export interface TSCustomNodeWidgetState {} 11 | 12 | export class TSCustomNodeWidget extends React.Component { 13 | constructor(props: TSCustomNodeWidgetProps) { 14 | super(props); 15 | this.state = {}; 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/main.css: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body, #application{ 7 | height: 100%; 8 | overflow: hidden; 9 | } 10 | 11 | .diagram-container{ 12 | background: #333333; 13 | width: 100%; 14 | height: 100%; 15 | } 16 | 17 | .custom-node{ 18 | border: solid 2px gray; 19 | border-radius: 5px; 20 | width: 50px; 21 | height: 50px; 22 | display: flex; 23 | align-items: flex-start; 24 | justify-content: space-between; 25 | position: relative; 26 | } 27 | 28 | .custom-node-color{ 29 | position: absolute; 30 | top: 50%; 31 | left: 50%; 32 | width: 20px; 33 | height: 20px; 34 | transform: translate(-50%, -50%); 35 | border-radius: 10px; 36 | } 37 | 38 | .circle-port{ 39 | width: 12px; 40 | height: 12px; 41 | margin: 2px; 42 | border-radius: 4px; 43 | background: darkgray; 44 | cursor: pointer; 45 | } 46 | 47 | .circle-port:hover{ 48 | background: mediumpurple; 49 | } 50 | -------------------------------------------------------------------------------- /diagrams-demo-project/src/main.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import './main.css'; 4 | import createEngine, { DefaultLinkModel, DiagramModel } from '@projectstorm/react-diagrams'; 5 | import { JSCustomNodeFactory } from './custom-node-js/JSCustomNodeFactory'; 6 | import { TSCustomNodeFactory } from './custom-node-ts/TSCustomNodeFactory'; 7 | import { JSCustomNodeModel } from './custom-node-js/JSCustomNodeModel'; 8 | import { TSCustomNodeModel } from './custom-node-ts/TSCustomNodeModel'; 9 | import { BodyWidget } from './BodyWidget'; 10 | 11 | // create an instance of the engine 12 | const engine = createEngine(); 13 | 14 | // register the two engines 15 | engine.getNodeFactories().registerFactory(new JSCustomNodeFactory() as any); 16 | engine.getNodeFactories().registerFactory(new TSCustomNodeFactory()); 17 | 18 | // create a diagram model 19 | const model = new DiagramModel(); 20 | 21 | //#################################################### 22 | // now create two nodes of each type, and connect them 23 | 24 | const node1 = new JSCustomNodeModel({ color: 'rgb(192,255,0)' }); 25 | node1.setPosition(50, 50); 26 | 27 | const node2 = new TSCustomNodeModel({ color: 'rgb(0,192,255)' }); 28 | node2.setPosition(200, 50); 29 | 30 | const link1 = new DefaultLinkModel(); 31 | link1.setSourcePort(node1.getPort('out')); 32 | link1.setTargetPort(node2.getPort('in')); 33 | 34 | model.addAll(node1, node2, link1); 35 | 36 | //#################################################### 37 | 38 | // install the model into the engine 39 | engine.setModel(model); 40 | 41 | document.addEventListener('DOMContentLoaded', () => { 42 | const root = createRoot(document.querySelector('#application')); 43 | root.render(); 44 | }); 45 | -------------------------------------------------------------------------------- /diagrams-demo-project/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "jsx": "react", 6 | "allowJs": true, 7 | "target": "es6", 8 | "module": "CommonJS" 9 | }, 10 | "include": [ 11 | "./src" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /diagrams-demo-project/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const production = process.env.NODE_ENV === 'production'; 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | 6 | module.exports = { 7 | mode: production ? 'production' : 'development', 8 | devtool: 'inline-source-map', 9 | entry: './src/main.tsx', 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js' 13 | }, 14 | resolve: { 15 | extensions: ['.ts', '.tsx', '.js', '.jsx'] 16 | }, 17 | optimization: { 18 | minimizer: [ 19 | new TerserPlugin({ 20 | parallel: true, 21 | terserOptions: { 22 | ecma: 6 23 | } 24 | }) 25 | ] 26 | }, 27 | plugins: [ 28 | new HtmlWebpackPlugin({ 29 | template: 'index.html' 30 | }) 31 | ], 32 | module: { 33 | rules: [ 34 | { 35 | enforce: 'pre', 36 | test: /\.js$/, 37 | loader: 'source-map-loader' 38 | }, 39 | { 40 | test: /\.css$/, 41 | use: ['style-loader', 'css-loader'] 42 | }, 43 | { 44 | test: /\.jsx?$/, 45 | exclude: /node_modules/, 46 | use: ['babel-loader'] 47 | }, 48 | { 49 | test: /\.tsx?$/, 50 | loader: 'ts-loader' 51 | } 52 | ] 53 | }, 54 | devServer: { 55 | client: { 56 | overlay: true 57 | }, 58 | hot: false, 59 | compress: true 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Introduction](README.md) 4 | * [Getting Started](getting-started/README.md) 5 | * [Using the library](getting-started/using-the-library.md) 6 | * [Customizing](customizing/README.md) 7 | * [Extending DefaultLinkModel](customizing/extending-default-links.md) 8 | * [Custom Nodes](customizing/nodes.md) 9 | * [Custom Ports](customizing/ports.md) 10 | * [About the project](about-the-project/README.md) 11 | * [Testing](about-the-project/testing.md) 12 | * [Architecture Questions](about-the-project/architecture-questions.md) 13 | 14 | -------------------------------------------------------------------------------- /docs/about-the-project/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## End to end testing 4 | 5 | To test the functionality of the library, we make use of e2e tests \(end to end tests\). In this library, we spin up a headless chrome using puppeteer and interactively and programmatically tell the mouse pointer to click and drag on various elements while making assertions along the way. 6 | 7 | We use Jest for the assertions and the interactivity is handled by puppeteer. Due to the laborious nature of writing e2e tests, there is a helper method that is provided in each test that makes interacting with the diagrams a lot easier. Using this helper, you can easily tell the mouse to drag links between nodes, select them and also easily assert information about them. The important thing here, is that this helper does not touch the model in any way, but is purely a helper for writing the tests themselves. Please make use of this helper when writing tests, as it ensure that the tests are defensive in nature, and also reduces the overhead of physically writing them. 8 | 9 | -------------------------------------------------------------------------------- /docs/customizing/README.md: -------------------------------------------------------------------------------- 1 | # Customizing 2 | 3 | Almost all components in react-diagrams are customizable. While some customization is better documented than others, the best way to learn about customization is through the examples in the codebase and by looking at the type annotations that come with the library. 4 | 5 | Most UI customization can be done through extending existing base classes. While node, port, and link have different data models, they share the same customization pattern: 6 | 7 | - they need a **model factory** extended off `AbstractModelFactory`, and that factory needs to be registered with the engine under a different model type 8 | - optionally, if you data model is different from the default, you can extend existing base classes such as `NodeModel`, `PortModel`, `DefaultLinkModel`, etc. 9 | - they need to have a **custom component** which renders using its default or customized data model. Some component such as the port can also be extended with composition such as port if you want to simply change the appearance. 10 | 11 | ## Working with custom links 12 | 13 | This is the easiest way to get started: 14 | 15 | [Extending the default Link](./extending-default-links.md) 16 | 17 | ## Working with custom nodes 18 | 19 | [Working with Nodes](./nodes.md) 20 | 21 | [Working with Ports](./ports.md) 22 | -------------------------------------------------------------------------------- /docs/customizing/extending-default-links.md: -------------------------------------------------------------------------------- 1 | # Custom Links 2 | 3 | ## Extending the DefaultLinkModel 4 | 5 | Much like extending nodes, custom links can also be created. 6 | In the below example, we have created a link that renders a circle animating from the source port to the target port. 7 | 8 |  9 | 10 | In this specific example, we extended the `DefaultLinkModel` because we wanted to retain 11 | a lot of the functionality that it provides in the base class: 12 | 13 | ```typescript 14 | export class AdvancedLinkModel extends DefaultLinkModel { 15 | constructor() { 16 | super({ 17 | type: 'advanced', // <-- here we give it a new type 18 | width: 10 // we specifically want this to also be width 10 19 | }); 20 | } 21 | } 22 | ``` 23 | 24 | Now we need to create a new link factory to tell the system how our new link model fits into the core system. We specifically are going to extend the `DefaultLinkFactory` because we still want to render a `DefaultLinkWidget`. The only difference is that we want each __path segment__ to be a red line with an animating circle. Fortunately, the `DefaultLinkWidget` already uses the `generateLinkSegment()` method defined in the `DefaultLinkFactory` to accomplish this. The only thing we need to do, is provide a different type of segment: 25 | 26 | ```typescript 27 | export class AdvancedLinkFactory extends DefaultLinkFactory { 28 | constructor() { 29 | super('advanced'); // <-- this matches with the link model above 30 | } 31 | 32 | generateModel(): AdvancedLinkModel { 33 | return new AdvancedLinkModel(); // <-- this is how we get new instances 34 | } 35 | 36 | /** 37 | * @override the DefaultLinkWidget makes use of this, and it normally renders that 38 | * familiar gray line, so in this case we simply make it return a new advanced segment. 39 | */ 40 | generateLinkSegment(model: AdvancedLinkModel, selected: boolean, path: string) { 41 | return ( 42 | 43 | 44 | 45 | ); 46 | } 47 | } 48 | ``` 49 | 50 | The actual code for the `AdvancedLinkSegment` [can be found here](https://github.com/projectstorm/react-diagrams/tree/master/diagrams-demo-gallery/demos/demo-custom-link1) (it is in the `demo-custom-link1` folder in the demo gallery). 51 | 52 | This is the easiest and most simple way to get started with custom links. 53 | -------------------------------------------------------------------------------- /docs/customizing/images/custom-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/docs/customizing/images/custom-link.png -------------------------------------------------------------------------------- /docs/customizing/images/diamond-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/docs/customizing/images/diamond-node.png -------------------------------------------------------------------------------- /docs/getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Get the package 4 | 5 | The first thing you need to do, is grab the distribution files on NPM. 6 | 7 | **Via yarn:** 8 | 9 | ```text 10 | yarn add @projectstorm/react-diagrams 11 | ``` 12 | 13 | **Via npm:** 14 | 15 | ```text 16 | npm install @projectstorm/react-diagrams 17 | ``` 18 | 19 | **Via pnpm:** 20 | 21 | ```text 22 | pnpm add @projectstorm/react-diagrams 23 | ``` 24 | 25 | When you run this in your project directory, this will install the library into `./node_modules/@projectstorm/react-diagrams`. You will then find a **dist** folder that contains all the minified and production ready code. -------------------------------------------------------------------------------- /docs/getting-started/using-the-library.md: -------------------------------------------------------------------------------- 1 | # Using the library 2 | 3 | ## Using Typescript 4 | 5 | If you are using typescript, then you are in luck! The library is built in typescript, and includes advanced types for everything you need right out of the box. 6 | 7 | Lets start by including the things we are going to need: 8 | 9 | ```typescript 10 | import createEngine, { 11 | DefaultLinkModel, 12 | DefaultNodeModel, 13 | DiagramModel 14 | } from '@projectstorm/react-diagrams'; 15 | 16 | import { 17 | CanvasWidget 18 | } from '@projectstorm/react-canvas-core'; 19 | ``` 20 | 21 | Now we call `createEngine` which will bootstrap a **DiagramEngine** for us that contains all the defaults setup. 22 | 23 | ```typescript 24 | // create an instance of the engine with all the defaults 25 | const engine = createEngine(); 26 | ``` 27 | 28 | Next, we create two nodes: 29 | 30 | ```typescript 31 | // node 1 32 | const node1 = new DefaultNodeModel({ 33 | name: 'Node 1', 34 | color: 'rgb(0,192,255)', 35 | }); 36 | node1.setPosition(100, 100); 37 | let port1 = node1.addOutPort('Out'); 38 | 39 | // node 2 40 | const node2 = new DefaultNodeModel({ 41 | name: 'Node 1', 42 | color: 'rgb(0,192,255)', 43 | }); 44 | node2.setPosition(100, 100); 45 | let port2 = node2.addOutPort('Out'); 46 | ``` 47 | 48 | Now we link the two ports of both of the nodes: 49 | 50 | ```typescript 51 | // link them and add a label to the link 52 | const link = port1.link(port2); 53 | link.addLabel('Hello World!'); 54 | ``` 55 | 56 | Great! Now we have setup a simple diagram. All thats left to do, is create a **DiagramModel** to contain everything, add all the elements to it, and then add it to the engine. 57 | 58 | ```typescript 59 | const model = new DiagramModel(); 60 | model.addAll(node1, node2, link); 61 | engine.setModel(model); 62 | ``` 63 | 64 | And then we render with **React**! 65 | 66 | ```jsx 67 | 68 | ``` 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams", 3 | "author": "dylanvorster", 4 | "private": true, 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/projectstorm/react-diagrams.git" 8 | }, 9 | "keywords": [ 10 | "web", 11 | "diagram", 12 | "diagrams", 13 | "react", 14 | "typescript", 15 | "flowchart", 16 | "simple", 17 | "links", 18 | "nodes" 19 | ], 20 | "scripts": { 21 | "ncu": "ncu -u && pnpm recursive exec -- ncu -u", 22 | "format": "prettier --write \"**/*.{ts,tsx,js,jsx}\"", 23 | "clean": "rm -rf packages/*/dist", 24 | "test": "pnpm run -r test", 25 | "build": "tsc --build && pnpm run -r build", 26 | "build:prod": "NODE_ENV=production pnpm build", 27 | "release": "pnpm build:prod && pnpm changeset publish", 28 | "release:storybook": "tsc --build && cd diagrams-demo-gallery && pnpm storybook:build && ./node_modules/.bin/storybook-to-ghpages --existing-output-dir .out" 29 | }, 30 | "devDependencies": { 31 | "@changesets/cli": "^2.26.2", 32 | "@types/jest": "^29.5.5", 33 | "@types/node": "^20.6.3", 34 | "jest": "^29.7.0", 35 | "jest-cli": "^29.7.0", 36 | "prettier": "^3.0.3", 37 | "rimraf": "^5.0.1", 38 | "source-map-loader": "^4.0.1", 39 | "terser-webpack-plugin": "^5.3.9", 40 | "ts-jest": "^29.1.1", 41 | "ts-loader": "^9.4.4", 42 | "typescript": "^5.2.2", 43 | "webpack": "^5.88.2", 44 | "webpack-cli": "^5.1.4", 45 | "webpack-dev-server": "^4.15.1", 46 | "webpack-node-externals": "^3.0.0" 47 | }, 48 | "pnpm": { 49 | "overrides": { 50 | "react": "^19.0.0" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/geometry/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | dist/tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /packages/geometry/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @projectstorm/geometry 2 | 3 | ## 7.0.3 4 | 5 | ### Patch Changes 6 | 7 | - 80285fe: refactor: update lodash imports to use individual functions 8 | 9 | ## 7.0.2 10 | 11 | ### Patch Changes 12 | 13 | - 66c687a: Upgrade all dependencies and fix Storybook after upgrade 14 | 15 | ## 7.0.1 16 | 17 | ### Patch Changes 18 | 19 | - b8a4cbd: Inline sources in sourcemap 20 | 21 | ## 7.0.0 22 | 23 | ### Major Changes 24 | 25 | - b051697: - [internal] moves to `Pnpm` (instead of yarn -\_-) 26 | - [internal]moves to `Changesets` for releases 27 | - [internal]removes `Lerna` 28 | - [internal] upgrades all dependencies 29 | - [internal] switches to workspace protocol syntax (Changesets will bake in the correct version when a publish occurs) 30 | - [internal] Changesets will open a release PR which can wrap up several changes in 1 go 31 | - [internal] Changesets will run the storybook deploy automatically upon merging the release PR 32 | - [internal] removes a lot of the stuff from the root package.json 33 | - [internal] cleans up the build and clean commands 34 | - [internal] remove E2E tests, they are a nightmare to maintain and the ROI is far too low 35 | - [fix] Wrong type name for react-canvas model listener 36 | - [fix] export more stuff form the main react-diagrams package 37 | - [fix] circular deps with Rectangle and Polygon (turns out this was a problem but only with UMD builds, sorry @everyone who I doubted, but this is also why I could never reproduce the issue) 38 | - [breaking change] compile both ES6 and UMD 39 | - [breaking change] moves dependencies back to each package. (After years of working on libraries, I've come to actually hate peer dependencies, and this is easily solved with build systems / package managers). 40 | - [breaking change] static methods on `Polygon` and `Rectangle` moved to standalone methods 41 | - [breaking change] static construction methods to rather deal with different Rectangle constructor overloads (I now consider this bad design) 42 | - [breaking change] introduce `Bounds` as a simpler point-array type to deal with boundary computation instead 43 | -------------------------------------------------------------------------------- /packages/geometry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/geometry", 3 | "version": "7.0.3", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/projectstorm/react-diagrams.git" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf ./dist", 12 | "build": "../../node_modules/.bin/webpack" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "keywords": [ 18 | "web", 19 | "diagram", 20 | "diagrams", 21 | "react", 22 | "typescript", 23 | "flowchart", 24 | "simple", 25 | "links", 26 | "nodes" 27 | ], 28 | "main": "./dist/index.umd.js", 29 | "module": "./dist/index.js", 30 | "typings": "./dist/@types/index", 31 | "dependencies": { 32 | "lodash": "^4.17.21" 33 | }, 34 | "devDependencies": { 35 | "@types/lodash": "^4.14.200" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/geometry/src/BezierCurve.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './Point'; 2 | import { Polygon } from './Polygon'; 3 | 4 | export enum BezierCurvepPoints { 5 | SOURCE = 0, 6 | SOURCE_CONTROL = 1, 7 | TARGET_CONTROL = 2, 8 | TARGET = 3 9 | } 10 | 11 | export class BezierCurve extends Polygon { 12 | constructor() { 13 | super([new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]); 14 | } 15 | 16 | getSVGCurve(): string { 17 | return `M${this.getSource().toSVG()} C${this.getSourceControl().toSVG()}, ${this.getTargetControl().toSVG()}, ${this.getTarget().toSVG()}`; 18 | } 19 | 20 | setPoints(points: Point[]) { 21 | if (points.length !== 4) { 22 | throw new Error('BezierCurve must have extactly 4 points'); 23 | } 24 | super.setPoints(points); 25 | } 26 | 27 | getSource(): Point { 28 | return this.points[BezierCurvepPoints.SOURCE]; 29 | } 30 | 31 | getSourceControl(): Point { 32 | return this.points[BezierCurvepPoints.SOURCE_CONTROL]; 33 | } 34 | 35 | getTargetControl(): Point { 36 | return this.points[BezierCurvepPoints.TARGET_CONTROL]; 37 | } 38 | 39 | getTarget(): Point { 40 | return this.points[BezierCurvepPoints.TARGET]; 41 | } 42 | 43 | setSource(point: Point) { 44 | this.points[BezierCurvepPoints.SOURCE] = point; 45 | } 46 | 47 | setSourceControl(point: Point) { 48 | this.points[BezierCurvepPoints.SOURCE_CONTROL] = point; 49 | } 50 | 51 | setTargetControl(point: Point) { 52 | this.points[BezierCurvepPoints.TARGET_CONTROL] = point; 53 | } 54 | 55 | setTarget(point: Point) { 56 | this.points[BezierCurvepPoints.TARGET] = point; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/geometry/src/Bounds.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './Point'; 2 | 3 | export enum BoundsCorner { 4 | TOP_LEFT = 'TL', 5 | TOP_RIGHT = 'TR', 6 | BOTTOM_RIGHT = 'BR', 7 | BOTTOM_LEFT = 'BL' 8 | } 9 | 10 | export type Bounds = { [k in BoundsCorner]: Point }; 11 | 12 | export const boundsFromPositionAndSize = (x: number, y: number, width: number, height: number): Bounds => { 13 | return { 14 | [BoundsCorner.TOP_LEFT]: new Point(x, y), 15 | [BoundsCorner.TOP_RIGHT]: new Point(x + width, y), 16 | [BoundsCorner.BOTTOM_RIGHT]: new Point(x + width, y + height), 17 | [BoundsCorner.BOTTOM_LEFT]: new Point(x, y + height) 18 | }; 19 | }; 20 | 21 | export const createEmptyBounds = () => { 22 | return { 23 | [BoundsCorner.TOP_LEFT]: new Point(), 24 | [BoundsCorner.TOP_RIGHT]: new Point(), 25 | [BoundsCorner.BOTTOM_RIGHT]: new Point(), 26 | [BoundsCorner.BOTTOM_LEFT]: new Point() 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/geometry/src/Matrix.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './Point'; 2 | 3 | export class Matrix { 4 | matrix: number[][]; 5 | 6 | constructor(matrix: number[][]) { 7 | this.matrix = matrix; 8 | } 9 | 10 | mmul(matrix: Matrix): Matrix { 11 | this.matrix = this.matrix.map((row, i) => 12 | matrix.asArray()[0].map((_, j) => row.reduce((acc, _, n) => acc + this.matrix[i][n] * matrix.asArray()[n][j], 0)) 13 | ); 14 | return this; 15 | } 16 | 17 | asArray(): number[][] { 18 | return this.matrix; 19 | } 20 | 21 | get(rowIndex: number, columnIndex: number): number { 22 | return this.asArray()[rowIndex][columnIndex]; 23 | } 24 | 25 | public static multiply(...matrices: Matrix[]): Matrix { 26 | let m: Matrix = matrices[0]; 27 | for (let i = 1; i < matrices.length; i++) { 28 | m = m.mmul(matrices[i]); 29 | } 30 | return m; 31 | } 32 | 33 | public static scaleMatrix(x: number, y: number): Matrix { 34 | return new Matrix([ 35 | [x, 0, 0], 36 | [0, y, 0], 37 | [0, 0, 1] 38 | ]); 39 | } 40 | 41 | public static translateMatrix(x: number, y: number): Matrix { 42 | return new Matrix([ 43 | [1, 0, x], 44 | [0, 1, y], 45 | [0, 0, 1] 46 | ]); 47 | } 48 | 49 | public static rotateMatrix(deg: number): Matrix { 50 | return new Matrix([ 51 | [Math.cos(deg), -1 * Math.sin(deg), 0], 52 | [Math.sin(deg), Math.cos(deg), 0], 53 | [0, 0, 1] 54 | ]); 55 | } 56 | 57 | static createScaleMatrix(x, y, origin: Point): Matrix { 58 | return this.multiply( 59 | Matrix.translateMatrix(origin.x, origin.y), 60 | Matrix.scaleMatrix(x, y), 61 | Matrix.translateMatrix(-origin.x, -origin.y) 62 | ); 63 | } 64 | 65 | static createRotateMatrix(deg: number, origin: Point): Matrix { 66 | return this.multiply( 67 | Matrix.translateMatrix(origin.x, origin.y), 68 | Matrix.rotateMatrix(deg), 69 | Matrix.translateMatrix(-origin.x, -origin.y) 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/geometry/src/Point.ts: -------------------------------------------------------------------------------- 1 | import { Matrix } from './Matrix'; 2 | 3 | export class Point { 4 | x: number; 5 | y: number; 6 | 7 | constructor(x: number = 0, y: number = 0) { 8 | this.x = x; 9 | this.y = y; 10 | } 11 | 12 | translate(x: number, y: number) { 13 | this.x += x; 14 | this.y += y; 15 | } 16 | 17 | clone() { 18 | return new Point(this.x, this.y); 19 | } 20 | 21 | toSVG() { 22 | return this.x + ' ' + this.y; 23 | } 24 | 25 | asMatrix() { 26 | return new Matrix([[this.x], [this.y], [1]]); 27 | } 28 | 29 | transform(matrix: Matrix) { 30 | let final: Matrix = matrix.mmul(this.asMatrix()); 31 | this.x = final.get(0, 0); 32 | this.y = final.get(1, 0); 33 | } 34 | 35 | public static middlePoint(pointA: Point, pointB: Point): Point { 36 | return new Point((pointB.x + pointA.x) / 2, (pointB.y + pointA.y) / 2); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/geometry/src/Polygon.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './Point'; 2 | import _forEach from 'lodash/forEach'; 3 | import _map from 'lodash/map'; 4 | import { Matrix } from './Matrix'; 5 | import { boundingBoxFromPoints } from './toolkit'; 6 | import { Bounds, BoundsCorner } from './Bounds'; 7 | 8 | export class Polygon { 9 | protected points: Point[]; 10 | 11 | constructor(points: Point[] = []) { 12 | this.points = points; 13 | } 14 | 15 | serialize() { 16 | return _map(this.points, (point) => { 17 | return [point.x, point.y]; 18 | }); 19 | } 20 | 21 | deserialize(data: any) { 22 | this.points = _map(data, (point) => { 23 | return new Point(point[0], point[1]); 24 | }); 25 | } 26 | 27 | scale(x, y, origin: Point) { 28 | let matrix = Matrix.createScaleMatrix(x, y, origin); 29 | _forEach(this.points, (point) => { 30 | point.transform(matrix); 31 | }); 32 | } 33 | 34 | transform(matrix: Matrix) { 35 | _forEach(this.points, (point) => { 36 | point.transform(matrix); 37 | }); 38 | } 39 | 40 | setPoints(points: Point[]) { 41 | this.points = points; 42 | } 43 | 44 | getPoints(): Point[] { 45 | return this.points; 46 | } 47 | 48 | rotate(degrees: number) { 49 | this.transform(Matrix.createRotateMatrix(degrees / (180 / Math.PI), this.getOrigin())); 50 | } 51 | 52 | translate(offsetX: number, offsetY: number) { 53 | _forEach(this.points, (point) => { 54 | point.translate(offsetX, offsetY); 55 | }); 56 | } 57 | 58 | doClone(ob: this) { 59 | this.points = _map(ob.points, (point) => { 60 | return point.clone(); 61 | }); 62 | } 63 | 64 | clone(): this { 65 | let ob = Object.create(this); 66 | ob.doClone(this); 67 | return ob; 68 | } 69 | 70 | getOrigin(): Point { 71 | if (this.points.length === 0) { 72 | return null; 73 | } 74 | let dimensions = boundingBoxFromPoints(this.points); 75 | return Point.middlePoint(dimensions[BoundsCorner.TOP_LEFT], dimensions[BoundsCorner.BOTTOM_RIGHT]); 76 | } 77 | 78 | getBoundingBox(): Bounds { 79 | return boundingBoxFromPoints(this.points); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/geometry/src/Rectangle.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './Point'; 2 | import { Polygon } from './Polygon'; 3 | import { Bounds, BoundsCorner, boundsFromPositionAndSize, createEmptyBounds } from './Bounds'; 4 | 5 | export class Rectangle extends Polygon { 6 | static fromPositionAndSize(x: number, y: number, width: number, height: number) { 7 | return new Rectangle(boundsFromPositionAndSize(x, y, width, height)); 8 | } 9 | 10 | static fromPointAndSize(position: Point, width: number, height: number) { 11 | return new Rectangle(boundsFromPositionAndSize(position.x, position.y, width, height)); 12 | } 13 | 14 | constructor(points?: Bounds) { 15 | if (!points) { 16 | points = createEmptyBounds(); 17 | } 18 | 19 | super([ 20 | points[BoundsCorner.TOP_LEFT], 21 | points[BoundsCorner.TOP_RIGHT], 22 | points[BoundsCorner.BOTTOM_RIGHT], 23 | points[BoundsCorner.BOTTOM_LEFT] 24 | ]); 25 | } 26 | 27 | updateDimensions(x: number, y: number, width: number, height: number) { 28 | const points = boundsFromPositionAndSize(x, y, width, height); 29 | this.setPoints([ 30 | points[BoundsCorner.TOP_LEFT], 31 | points[BoundsCorner.TOP_RIGHT], 32 | points[BoundsCorner.BOTTOM_RIGHT], 33 | points[BoundsCorner.BOTTOM_LEFT] 34 | ]); 35 | } 36 | 37 | setPoints(points: Point[]) { 38 | if (points.length !== 4) { 39 | throw 'Rectangles must always have 4 points'; 40 | } 41 | super.setPoints(points); 42 | } 43 | 44 | containsPoint(point: Point) { 45 | const tl = this.getTopLeft(); 46 | const br = this.getBottomRight(); 47 | 48 | return point.x >= tl.x && point.x <= br.x && point.y >= tl.y && point.y <= br.y; 49 | } 50 | 51 | getWidth(): number { 52 | return Math.sqrt( 53 | Math.pow(this.getTopLeft().x - this.getTopRight().x, 2) + Math.pow(this.getTopLeft().y - this.getTopRight().y, 2) 54 | ); 55 | } 56 | 57 | getHeight(): number { 58 | return Math.sqrt( 59 | Math.pow(this.getBottomLeft().x - this.getTopLeft().x, 2) + 60 | Math.pow(this.getBottomLeft().y - this.getTopLeft().y, 2) 61 | ); 62 | } 63 | 64 | getTopMiddle(): Point { 65 | return Point.middlePoint(this.getTopLeft(), this.getTopRight()); 66 | } 67 | 68 | getBottomMiddle(): Point { 69 | return Point.middlePoint(this.getBottomLeft(), this.getBottomRight()); 70 | } 71 | 72 | getLeftMiddle(): Point { 73 | return Point.middlePoint(this.getBottomLeft(), this.getTopLeft()); 74 | } 75 | 76 | getRightMiddle(): Point { 77 | return Point.middlePoint(this.getBottomRight(), this.getTopRight()); 78 | } 79 | 80 | getTopLeft(): Point { 81 | return this.points[0]; 82 | } 83 | 84 | getTopRight(): Point { 85 | return this.points[1]; 86 | } 87 | 88 | getBottomRight(): Point { 89 | return this.points[2]; 90 | } 91 | 92 | getBottomLeft(): Point { 93 | return this.points[3]; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/geometry/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Point'; 2 | export * from './Matrix'; 3 | export * from './Polygon'; 4 | export * from './Rectangle'; 5 | export * from './BezierCurve'; 6 | export * from './toolkit'; 7 | export * from './Bounds'; 8 | -------------------------------------------------------------------------------- /packages/geometry/src/toolkit.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './Point'; 2 | import _flatMap from 'lodash/flatMap'; 3 | import { Polygon } from './Polygon'; 4 | import { Bounds, BoundsCorner, createEmptyBounds } from './Bounds'; 5 | 6 | export const boundingBoxFromPoints = (points: Point[]): Bounds => { 7 | if (points.length === 0) { 8 | return createEmptyBounds(); 9 | } 10 | 11 | let minX = points[0].x; 12 | let maxX = points[0].x; 13 | let minY = points[0].y; 14 | let maxY = points[0].y; 15 | 16 | for (let i = 1; i < points.length; i++) { 17 | if (points[i].x < minX) { 18 | minX = points[i].x; 19 | } 20 | if (points[i].x > maxX) { 21 | maxX = points[i].x; 22 | } 23 | if (points[i].y < minY) { 24 | minY = points[i].y; 25 | } 26 | if (points[i].y > maxY) { 27 | maxY = points[i].y; 28 | } 29 | } 30 | 31 | return { 32 | [BoundsCorner.TOP_LEFT]: new Point(minX, minY), 33 | [BoundsCorner.TOP_RIGHT]: new Point(maxX, minY), 34 | [BoundsCorner.BOTTOM_RIGHT]: new Point(maxX, maxY), 35 | [BoundsCorner.BOTTOM_LEFT]: new Point(minX, maxY) 36 | }; 37 | }; 38 | 39 | export const boundingBoxFromPolygons = (polygons: Polygon[]): Bounds => { 40 | return boundingBoxFromPoints( 41 | _flatMap(polygons, (polygon) => { 42 | return polygon.getPoints(); 43 | }) 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /packages/geometry/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "sourceMap": true, 8 | "declarationDir": "dist/@types", 9 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 10 | }, 11 | "include": ["./src"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/geometry/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: '@projectstorm/react-diagrams-geometry' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/react-canvas-core/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | dist/tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /packages/react-canvas-core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @projectstorm/react-canvas-core 2 | 3 | ## 7.0.3 4 | 5 | ### Patch Changes 6 | 7 | - 09ed60f: Allow more derived State classes to provide a generic type 8 | - 80285fe: refactor: update lodash imports to use individual functions 9 | - Updated dependencies [80285fe] 10 | - @projectstorm/geometry@7.0.3 11 | 12 | ## 7.0.2 13 | 14 | ### Patch Changes 15 | 16 | - 66c687a: Upgrade all dependencies and fix Storybook after upgrade 17 | - Updated dependencies [66c687a] 18 | - @projectstorm/geometry@7.0.2 19 | 20 | ## 7.0.1 21 | 22 | ### Patch Changes 23 | 24 | - b8a4cbd: Inline sources in sourcemap 25 | - Updated dependencies [b8a4cbd] 26 | - @projectstorm/geometry@7.0.1 27 | 28 | ## 7.0.0 29 | 30 | ### Major Changes 31 | 32 | - b051697: - [internal] moves to `Pnpm` (instead of yarn -\_-) 33 | - [internal]moves to `Changesets` for releases 34 | - [internal]removes `Lerna` 35 | - [internal] upgrades all dependencies 36 | - [internal] switches to workspace protocol syntax (Changesets will bake in the correct version when a publish occurs) 37 | - [internal] Changesets will open a release PR which can wrap up several changes in 1 go 38 | - [internal] Changesets will run the storybook deploy automatically upon merging the release PR 39 | - [internal] removes a lot of the stuff from the root package.json 40 | - [internal] cleans up the build and clean commands 41 | - [internal] remove E2E tests, they are a nightmare to maintain and the ROI is far too low 42 | - [fix] Wrong type name for react-canvas model listener 43 | - [fix] export more stuff form the main react-diagrams package 44 | - [fix] circular deps with Rectangle and Polygon (turns out this was a problem but only with UMD builds, sorry @everyone who I doubted, but this is also why I could never reproduce the issue) 45 | - [breaking change] compile both ES6 and UMD 46 | - [breaking change] moves dependencies back to each package. (After years of working on libraries, I've come to actually hate peer dependencies, and this is easily solved with build systems / package managers). 47 | - [breaking change] static methods on `Polygon` and `Rectangle` moved to standalone methods 48 | - [breaking change] static construction methods to rather deal with different Rectangle constructor overloads (I now consider this bad design) 49 | - [breaking change] introduce `Bounds` as a simpler point-array type to deal with boundary computation instead 50 | 51 | ### Patch Changes 52 | 53 | - Updated dependencies [b051697] 54 | - @projectstorm/geometry@7.0.0 55 | -------------------------------------------------------------------------------- /packages/react-canvas-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-canvas-core", 3 | "version": "7.0.3", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/projectstorm/react-diagrams.git" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf ./dist", 12 | "build": "../../node_modules/.bin/webpack" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "keywords": [ 18 | "web", 19 | "diagram", 20 | "diagrams", 21 | "react", 22 | "typescript", 23 | "flowchart", 24 | "simple", 25 | "links", 26 | "nodes" 27 | ], 28 | "main": "./dist/index.umd.js", 29 | "module": "./dist/index.js", 30 | "typings": "./dist/@types/index", 31 | "dependencies": { 32 | "@emotion/react": "^11.14.0", 33 | "@emotion/styled": "^11.11.0", 34 | "@projectstorm/geometry": "workspace:*", 35 | "lodash": "^4.17.21", 36 | "react": "^19.0.0" 37 | }, 38 | "devDependencies": { 39 | "@types/lodash": "^4.14.200", 40 | "@types/react": "^19.0.12" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/Toolkit.ts: -------------------------------------------------------------------------------- 1 | export class Toolkit { 2 | static TESTING: boolean = false; 3 | static TESTING_UID = 0; 4 | 5 | /** 6 | * Generats a unique ID (thanks Stack overflow :3) 7 | * @returns {String} 8 | */ 9 | public static UID(): string { 10 | if (Toolkit.TESTING) { 11 | Toolkit.TESTING_UID++; 12 | return `${Toolkit.TESTING_UID}`; 13 | } 14 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { 15 | const r = (Math.random() * 16) | 0; 16 | const v = c === 'x' ? r : (r & 0x3) | 0x8; 17 | return v.toString(16); 18 | }); 19 | } 20 | 21 | public static closest(element: Element, selector: string) { 22 | if (!Element.prototype.closest) { 23 | Element.prototype.closest = function (s) { 24 | var el = this; 25 | 26 | do { 27 | if (Element.prototype.matches.call(el, s)) return el; 28 | el = el.parentElement || el.parentNode; 29 | } while (el !== null && el.nodeType === 1); 30 | return null; 31 | }; 32 | } 33 | return element.closest(selector); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/actions/DeleteItemsAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionEvent, InputType } from '../core-actions/Action'; 2 | import { KeyboardEvent } from 'react'; 3 | import _forEach from 'lodash/forEach'; 4 | import _isEqual from 'lodash/isEqual'; 5 | 6 | export interface DeleteItemsActionOptions { 7 | keyCodes?: number[]; 8 | modifiers?: { 9 | ctrlKey?: boolean; 10 | shiftKey?: boolean; 11 | altKey?: boolean; 12 | metaKey?: boolean; 13 | }; 14 | } 15 | 16 | /** 17 | * Deletes all selected items 18 | */ 19 | export class DeleteItemsAction extends Action { 20 | constructor(options: DeleteItemsActionOptions = {}) { 21 | const keyCodes = options.keyCodes || [46, 8]; 22 | const modifiers = { 23 | ctrlKey: false, 24 | shiftKey: false, 25 | altKey: false, 26 | metaKey: false, 27 | ...options.modifiers 28 | }; 29 | 30 | super({ 31 | type: InputType.KEY_DOWN, 32 | fire: (event: ActionEvent) => { 33 | const { keyCode, ctrlKey, shiftKey, altKey, metaKey } = event.event; 34 | 35 | if (keyCodes.indexOf(keyCode) !== -1 && _isEqual({ ctrlKey, shiftKey, altKey, metaKey }, modifiers)) { 36 | _forEach(this.engine.getModel().getSelectedEntities(), (model) => { 37 | // only delete items which are not locked 38 | if (!model.isLocked()) { 39 | model.remove(); 40 | } 41 | }); 42 | this.engine.repaintCanvas(); 43 | } 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/actions/PanAndZoomCanvasAction.ts: -------------------------------------------------------------------------------- 1 | import { WheelEvent } from 'react'; 2 | import { Action, ActionEvent, InputType } from '../core-actions/Action'; 3 | 4 | export interface PanAndZoomCanvasActionOptions { 5 | inverseZoom?: boolean; 6 | } 7 | 8 | export class PanAndZoomCanvasAction extends Action { 9 | constructor(options: PanAndZoomCanvasActionOptions = {}) { 10 | super({ 11 | type: InputType.MOUSE_WHEEL, 12 | fire: (actionEvent: ActionEvent) => { 13 | const { event } = actionEvent; 14 | // we can block layer rendering because we are only targeting the transforms 15 | for (let layer of this.engine.getModel().getLayers()) { 16 | layer.allowRepaint(false); 17 | } 18 | 19 | const model = this.engine.getModel(); 20 | event.stopPropagation(); 21 | if (event.ctrlKey) { 22 | // Pinch and zoom gesture 23 | const oldZoomFactor = this.engine.getModel().getZoomLevel() / 100; 24 | 25 | let scrollDelta = options.inverseZoom ? event.deltaY : -event.deltaY; 26 | scrollDelta /= 3; 27 | 28 | if (model.getZoomLevel() + scrollDelta > 10) { 29 | model.setZoomLevel(model.getZoomLevel() + scrollDelta); 30 | } 31 | 32 | const zoomFactor = model.getZoomLevel() / 100; 33 | 34 | const boundingRect = event.currentTarget.getBoundingClientRect(); 35 | const clientWidth = boundingRect.width; 36 | const clientHeight = boundingRect.height; 37 | // compute difference between rect before and after scroll 38 | const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor; 39 | const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor; 40 | // compute mouse coords relative to canvas 41 | const clientX = event.clientX - boundingRect.left; 42 | const clientY = event.clientY - boundingRect.top; 43 | 44 | // compute width and height increment factor 45 | const xFactor = (clientX - model.getOffsetX()) / oldZoomFactor / clientWidth; 46 | const yFactor = (clientY - model.getOffsetY()) / oldZoomFactor / clientHeight; 47 | 48 | model.setOffset(model.getOffsetX() - widthDiff * xFactor, model.getOffsetY() - heightDiff * yFactor); 49 | } else { 50 | // Pan gesture 51 | let yDelta = options.inverseZoom ? -event.deltaY : event.deltaY; 52 | let xDelta = options.inverseZoom ? -event.deltaX : event.deltaX; 53 | model.setOffset(model.getOffsetX() - xDelta, model.getOffsetY() - yDelta); 54 | } 55 | this.engine.repaintCanvas(); 56 | 57 | // re-enable rendering 58 | for (let layer of this.engine.getModel().getLayers()) { 59 | layer.allowRepaint(true); 60 | } 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/actions/ZoomCanvasAction.ts: -------------------------------------------------------------------------------- 1 | import { WheelEvent } from 'react'; 2 | import { Action, ActionEvent, InputType } from '../core-actions/Action'; 3 | 4 | export interface ZoomCanvasActionOptions { 5 | inverseZoom?: boolean; 6 | } 7 | 8 | export class ZoomCanvasAction extends Action { 9 | constructor(options: ZoomCanvasActionOptions = {}) { 10 | super({ 11 | type: InputType.MOUSE_WHEEL, 12 | fire: (actionEvent: ActionEvent) => { 13 | const { event } = actionEvent; 14 | // we can block layer rendering because we are only targeting the transforms 15 | for (let layer of this.engine.getModel().getLayers()) { 16 | layer.allowRepaint(false); 17 | } 18 | 19 | const model = this.engine.getModel(); 20 | event.stopPropagation(); 21 | const oldZoomFactor = this.engine.getModel().getZoomLevel() / 100; 22 | let scrollDelta = options.inverseZoom ? -event.deltaY : event.deltaY; 23 | //check if it is pinch gesture 24 | if (event.ctrlKey && scrollDelta % 1 !== 0) { 25 | /* 26 | Chrome and Firefox sends wheel event with deltaY that 27 | have fractional part, also `ctrlKey` prop of the event is true 28 | though ctrl isn't pressed 29 | */ 30 | scrollDelta /= 3; 31 | } else { 32 | scrollDelta /= 60; 33 | } 34 | if (model.getZoomLevel() + scrollDelta > 10) { 35 | model.setZoomLevel(model.getZoomLevel() + scrollDelta); 36 | } 37 | 38 | const zoomFactor = model.getZoomLevel() / 100; 39 | 40 | const boundingRect = event.currentTarget.getBoundingClientRect(); 41 | const clientWidth = boundingRect.width; 42 | const clientHeight = boundingRect.height; 43 | // compute difference between rect before and after scroll 44 | const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor; 45 | const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor; 46 | // compute mouse coords relative to canvas 47 | const clientX = event.clientX - boundingRect.left; 48 | const clientY = event.clientY - boundingRect.top; 49 | 50 | // compute width and height increment factor 51 | const xFactor = (clientX - model.getOffsetX()) / oldZoomFactor / clientWidth; 52 | const yFactor = (clientY - model.getOffsetY()) / oldZoomFactor / clientHeight; 53 | 54 | model.setOffset(model.getOffsetX() - widthDiff * xFactor, model.getOffsetY() - heightDiff * yFactor); 55 | this.engine.repaintCanvas(); 56 | 57 | // re-enable rendering 58 | for (let layer of this.engine.getModel().getLayers()) { 59 | layer.allowRepaint(true); 60 | } 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core-actions/Action.ts: -------------------------------------------------------------------------------- 1 | import { MouseEvent, KeyboardEvent, WheelEvent, TouchEvent, SyntheticEvent } from 'react'; 2 | import { Toolkit } from '../Toolkit'; 3 | import { CanvasEngine } from '../CanvasEngine'; 4 | import { BaseModel } from '../core-models/BaseModel'; 5 | 6 | export enum InputType { 7 | MOUSE_DOWN = 'mouse-down', 8 | MOUSE_UP = 'mouse-up', 9 | MOUSE_MOVE = 'mouse-move', 10 | MOUSE_WHEEL = 'mouse-wheel', 11 | KEY_DOWN = 'key-down', 12 | KEY_UP = 'key-up', 13 | TOUCH_START = 'touch-start', 14 | TOUCH_END = 'touch-end', 15 | TOUCH_MOVE = 'touch-move' 16 | } 17 | 18 | export interface Mapping { 19 | [InputType.MOUSE_DOWN]: MouseEvent; 20 | [InputType.MOUSE_UP]: MouseEvent; 21 | [InputType.MOUSE_MOVE]: MouseEvent; 22 | [InputType.MOUSE_WHEEL]: WheelEvent; 23 | [InputType.KEY_DOWN]: KeyboardEvent; 24 | [InputType.KEY_UP]: KeyboardEvent; 25 | [InputType.TOUCH_START]: TouchEvent; 26 | [InputType.TOUCH_END]: TouchEvent; 27 | [InputType.TOUCH_MOVE]: TouchEvent; 28 | } 29 | 30 | export interface ActionEvent { 31 | event: Event; 32 | model?: Model; 33 | } 34 | 35 | export interface ActionOptions { 36 | type: InputType; 37 | fire: (event: ActionEvent) => void; 38 | } 39 | 40 | export class Action { 41 | options: ActionOptions; 42 | id: string; 43 | engine: T; 44 | 45 | constructor(options: ActionOptions) { 46 | this.options = options; 47 | this.id = Toolkit.UID(); 48 | } 49 | 50 | setEngine(engine: T) { 51 | this.engine = engine; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core-actions/ActionEventBus.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionEvent, InputType } from './Action'; 2 | import { KeyboardEvent, MouseEvent } from 'react'; 3 | import _filter from 'lodash/filter'; 4 | import _keys from 'lodash/keys'; 5 | import { CanvasEngine } from '../CanvasEngine'; 6 | import { BaseModel } from '../core-models/BaseModel'; 7 | 8 | export class ActionEventBus { 9 | protected actions: { [id: string]: Action }; 10 | protected engine: CanvasEngine; 11 | protected keys: { [key: string]: boolean }; 12 | 13 | constructor(engine: CanvasEngine) { 14 | this.actions = {}; 15 | this.engine = engine; 16 | this.keys = {}; 17 | } 18 | 19 | getKeys(): string[] { 20 | return _keys(this.keys); 21 | } 22 | 23 | registerAction(action: Action): () => void { 24 | action.setEngine(this.engine); 25 | this.actions[action.id] = action; 26 | return () => { 27 | this.deregisterAction(action); 28 | }; 29 | } 30 | 31 | deregisterAction(action: Action) { 32 | action.setEngine(null); 33 | delete this.actions[action.id]; 34 | } 35 | 36 | getActionsForType(type: InputType): Action[] { 37 | return _filter(this.actions, (action) => { 38 | return action.options.type === type; 39 | }); 40 | } 41 | 42 | getModelForEvent(actionEvent: ActionEvent): BaseModel { 43 | if (actionEvent.model) { 44 | return actionEvent.model; 45 | } 46 | return this.engine.getMouseElement(actionEvent.event); 47 | } 48 | 49 | getActionsForEvent(actionEvent: ActionEvent): Action[] { 50 | const { event } = actionEvent; 51 | if (event.type === 'mousedown') { 52 | return this.getActionsForType(InputType.MOUSE_DOWN); 53 | } else if (event.type === 'mouseup') { 54 | return this.getActionsForType(InputType.MOUSE_UP); 55 | } else if (event.type === 'keydown') { 56 | // store the recorded key 57 | this.keys[(event as KeyboardEvent).key.toLowerCase()] = true; 58 | return this.getActionsForType(InputType.KEY_DOWN); 59 | } else if (event.type === 'keyup') { 60 | // delete the recorded key 61 | delete this.keys[(event as KeyboardEvent).key.toLowerCase()]; 62 | return this.getActionsForType(InputType.KEY_UP); 63 | } else if (event.type === 'mousemove') { 64 | return this.getActionsForType(InputType.MOUSE_MOVE); 65 | } else if (event.type === 'wheel') { 66 | return this.getActionsForType(InputType.MOUSE_WHEEL); 67 | } else if (event.type === 'touchstart') { 68 | return this.getActionsForType(InputType.TOUCH_START); 69 | } else if (event.type === 'touchend') { 70 | return this.getActionsForType(InputType.TOUCH_END); 71 | } else if (event.type === 'touchmove') { 72 | return this.getActionsForType(InputType.TOUCH_MOVE); 73 | } 74 | 75 | return []; 76 | } 77 | 78 | fireAction(actionEvent: ActionEvent) { 79 | const actions = this.getActionsForEvent(actionEvent); 80 | for (let action of actions) { 81 | action.options.fire(actionEvent as any); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core-models/BaseModel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseEntity, 3 | BaseEntityEvent, 4 | BaseEntityGenerics, 5 | BaseEntityListener, 6 | BaseEntityOptions, 7 | DeserializeEvent 8 | } from './BaseEntity'; 9 | import { CanvasModel } from '../entities/canvas/CanvasModel'; 10 | 11 | export interface BaseModelListener extends BaseEntityListener { 12 | selectionChanged?(event: BaseEntityEvent & { isSelected: boolean }): void; 13 | 14 | entityRemoved?(event: BaseEntityEvent): void; 15 | } 16 | 17 | export interface BaseModelOptions extends BaseEntityOptions { 18 | type?: string; 19 | selected?: boolean; 20 | extras?: any; 21 | } 22 | 23 | export interface BaseModelGenerics extends BaseEntityGenerics { 24 | LISTENER: BaseModelListener; 25 | PARENT: BaseEntity; 26 | OPTIONS: BaseModelOptions; 27 | } 28 | 29 | export class BaseModel extends BaseEntity { 30 | protected parent: G['PARENT']; 31 | 32 | constructor(options: G['OPTIONS']) { 33 | super(options); 34 | } 35 | 36 | performanceTune() { 37 | return true; 38 | } 39 | 40 | getParentCanvasModel(): CanvasModel { 41 | if (!this.parent) { 42 | return null; 43 | } 44 | if (this.parent instanceof CanvasModel) { 45 | return this.parent; 46 | } else if (this.parent instanceof BaseModel) { 47 | return this.parent.getParentCanvasModel(); 48 | } 49 | return null; 50 | } 51 | 52 | getParent(): G['PARENT'] { 53 | return this.parent; 54 | } 55 | 56 | setParent(parent: G['PARENT']) { 57 | this.parent = parent; 58 | } 59 | 60 | getSelectionEntities(): Array { 61 | return [this]; 62 | } 63 | 64 | serialize() { 65 | return { 66 | ...super.serialize(), 67 | type: this.options.type, 68 | selected: this.options.selected, 69 | extras: this.options.extras 70 | }; 71 | } 72 | 73 | deserialize(event: DeserializeEvent) { 74 | super.deserialize(event); 75 | this.options.extras = event.data.extras; 76 | this.options.selected = event.data.selected; 77 | } 78 | 79 | getType(): string { 80 | return this.options.type; 81 | } 82 | 83 | isSelected(): boolean { 84 | return this.options.selected; 85 | } 86 | 87 | isLocked(): boolean { 88 | const locked = super.isLocked(); 89 | if (locked) { 90 | return true; 91 | } 92 | 93 | // delegate this call up to the parent 94 | if (this.parent) { 95 | return this.parent.isLocked(); 96 | } 97 | return false; 98 | } 99 | 100 | setSelected(selected: boolean = true) { 101 | if (this.options.selected !== selected) { 102 | this.options.selected = selected; 103 | 104 | this.fireEvent( 105 | { 106 | isSelected: selected 107 | }, 108 | 'selectionChanged' 109 | ); 110 | } 111 | } 112 | 113 | remove() { 114 | this.fireEvent({}, 'entityRemoved'); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core-models/BasePositionModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, BaseModelGenerics, BaseModelListener, BaseModelOptions } from './BaseModel'; 2 | import { BaseEntityEvent, DeserializeEvent } from './BaseEntity'; 3 | import { Point, Rectangle } from '@projectstorm/geometry'; 4 | import { ModelGeometryInterface } from '../core/ModelGeometryInterface'; 5 | 6 | export interface BasePositionModelListener extends BaseModelListener { 7 | positionChanged?(event: BaseEntityEvent): void; 8 | } 9 | 10 | export interface BasePositionModelOptions extends BaseModelOptions { 11 | position?: Point; 12 | } 13 | 14 | export interface BasePositionModelGenerics extends BaseModelGenerics { 15 | LISTENER: BasePositionModelListener; 16 | OPTIONS: BasePositionModelOptions; 17 | } 18 | 19 | export class BasePositionModel 20 | extends BaseModel 21 | implements ModelGeometryInterface 22 | { 23 | protected position: Point; 24 | 25 | constructor(options: G['OPTIONS']) { 26 | super(options); 27 | this.position = options.position || new Point(0, 0); 28 | } 29 | 30 | setPosition(point: Point): void; 31 | setPosition(x: number, y: number): void; 32 | setPosition(x: number | Point, y?: number): void { 33 | if (x instanceof Point) { 34 | this.position = x; 35 | } else { 36 | this.position = new Point(x, y); 37 | } 38 | this.fireEvent({}, 'positionChanged'); 39 | } 40 | 41 | getBoundingBox(): Rectangle { 42 | return Rectangle.fromPointAndSize(this.position, 0, 0); 43 | } 44 | 45 | deserialize(event: DeserializeEvent) { 46 | super.deserialize(event); 47 | this.position = new Point(event.data.x, event.data.y); 48 | } 49 | 50 | serialize() { 51 | return { 52 | ...super.serialize(), 53 | x: this.position.x, 54 | y: this.position.y 55 | }; 56 | } 57 | 58 | getPosition(): Point { 59 | return this.position; 60 | } 61 | 62 | getX() { 63 | return this.position.x; 64 | } 65 | 66 | getY() { 67 | return this.position.y; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core-state/StateMachine.ts: -------------------------------------------------------------------------------- 1 | import { State } from './State'; 2 | import _last from 'lodash/last'; 3 | import { CanvasEngine } from '../CanvasEngine'; 4 | import { BaseEvent, BaseListener, BaseObserver } from '../core/BaseObserver'; 5 | 6 | export interface StateMachineListener extends BaseListener { 7 | stateChanged?: (event: BaseEvent & { newState: State }) => any; 8 | } 9 | 10 | export class StateMachine extends BaseObserver { 11 | protected currentState: State; 12 | protected stateStack: State[]; 13 | protected engine: CanvasEngine; 14 | 15 | constructor(engine: CanvasEngine) { 16 | super(); 17 | this.engine = engine; 18 | this.stateStack = []; 19 | } 20 | 21 | getCurrentState() { 22 | return this.currentState; 23 | } 24 | 25 | pushState(state: State) { 26 | this.stateStack.push(state); 27 | this.setState(state); 28 | } 29 | 30 | popState() { 31 | this.stateStack.pop(); 32 | this.setState(_last(this.stateStack)); 33 | } 34 | 35 | setState(state: State) { 36 | state.setEngine(this.engine); 37 | 38 | // if no state object, get the initial state 39 | if (this.currentState) { 40 | this.currentState.deactivated(state); 41 | } 42 | const old = this.currentState; 43 | this.currentState = state; 44 | if (this.currentState) { 45 | this.currentState.activated(old); 46 | this.fireEvent<'stateChanged'>( 47 | { 48 | newState: state 49 | }, 50 | 'stateChanged' 51 | ); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core/AbstractFactory.ts: -------------------------------------------------------------------------------- 1 | import { CanvasEngine } from '../CanvasEngine'; 2 | import { FactoryBank } from './FactoryBank'; 3 | 4 | /** 5 | * Base factory for all the different types of entities. 6 | * Gets registered with the engine, and is used to generate models 7 | */ 8 | export abstract class AbstractFactory { 9 | /** 10 | * Couples the factory with the models it generates 11 | */ 12 | protected type: string; 13 | /** 14 | * The engine gets injected when the factory is registered 15 | */ 16 | protected engine: E; 17 | protected bank: FactoryBank; 18 | 19 | constructor(type: string) { 20 | this.type = type; 21 | } 22 | 23 | setDiagramEngine(engine: E) { 24 | this.engine = engine; 25 | } 26 | 27 | setFactoryBank(bank: FactoryBank) { 28 | this.bank = bank; 29 | } 30 | 31 | getType(): string { 32 | return this.type; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core/AbstractModelFactory.ts: -------------------------------------------------------------------------------- 1 | import { AbstractFactory } from './AbstractFactory'; 2 | import { BaseModel } from '../core-models/BaseModel'; 3 | import { CanvasEngine } from '../CanvasEngine'; 4 | 5 | export interface GenerateModelEvent { 6 | initialConfig?: any; 7 | } 8 | 9 | export abstract class AbstractModelFactory< 10 | T extends BaseModel = BaseModel, 11 | E extends CanvasEngine = CanvasEngine 12 | > extends AbstractFactory { 13 | /** 14 | * Generates new models (the core factory pattern) 15 | */ 16 | abstract generateModel(event: GenerateModelEvent): T; 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core/AbstractReactFactory.tsx: -------------------------------------------------------------------------------- 1 | import { BaseModel } from '../core-models/BaseModel'; 2 | import { AbstractModelFactory } from './AbstractModelFactory'; 3 | import { CanvasEngine } from '../CanvasEngine'; 4 | import { JSX } from 'react'; 5 | 6 | export interface GenerateWidgetEvent { 7 | model: T; 8 | } 9 | 10 | /** 11 | * Further extends the AbstractFactory to add widget generation capability. 12 | */ 13 | export abstract class AbstractReactFactory< 14 | T extends BaseModel = BaseModel, 15 | E extends CanvasEngine = CanvasEngine 16 | > extends AbstractModelFactory { 17 | /** 18 | * Generates React widgets from the model contained in the event object 19 | */ 20 | abstract generateReactWidget(event: GenerateWidgetEvent): JSX.Element; 21 | } 22 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core/FactoryBank.ts: -------------------------------------------------------------------------------- 1 | import { BaseEvent, BaseListener, BaseObserver } from './BaseObserver'; 2 | import { AbstractFactory } from './AbstractFactory'; 3 | import _values from 'lodash/values'; 4 | 5 | export interface FactoryBankListener extends BaseListener { 6 | /** 7 | * Factory as added to rhe bank 8 | */ 9 | factoryAdded?: (event: BaseEvent & { factory: F }) => any; 10 | 11 | /** 12 | * Factory was removed from the bank 13 | */ 14 | factoryRemoved?: (event: BaseEvent & { factory: F }) => any; 15 | } 16 | 17 | /** 18 | * Store and managed Factories that extend from Abstractfactory 19 | */ 20 | export class FactoryBank< 21 | F extends AbstractFactory = AbstractFactory, 22 | L extends FactoryBankListener = FactoryBankListener 23 | > extends BaseObserver { 24 | protected factories: { [type: string]: F }; 25 | 26 | constructor() { 27 | super(); 28 | this.factories = {}; 29 | } 30 | 31 | getFactories(): F[] { 32 | return _values(this.factories); 33 | } 34 | 35 | clearFactories() { 36 | for (let factory in this.factories) { 37 | this.deregisterFactory(factory); 38 | } 39 | } 40 | 41 | getFactory(type: string): T { 42 | if (!this.factories[type]) { 43 | throw new Error(`Cannot find factory with type [${type}]`); 44 | } 45 | return this.factories[type] as T; 46 | } 47 | 48 | registerFactory(factory: F) { 49 | factory.setFactoryBank(this); 50 | this.factories[factory.getType()] = factory; 51 | // todo fixme 52 | this.fireEvent<'factoryAdded'>({ factory } as any, 'factoryAdded'); 53 | } 54 | 55 | deregisterFactory(type: string) { 56 | const factory = this.factories[type]; 57 | factory.setFactoryBank(null); 58 | delete this.factories[type]; 59 | // todo fixme 60 | this.fireEvent<'factoryRemoved'>({ factory } as any, 'factoryRemoved'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/core/ModelGeometryInterface.ts: -------------------------------------------------------------------------------- 1 | import { Rectangle } from '@projectstorm/geometry'; 2 | 3 | export interface ModelGeometryInterface { 4 | getBoundingBox(): Rectangle; 5 | } 6 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/entities/layer/SmartLayerWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { LayerModel } from './LayerModel'; 3 | import { CanvasEngine } from '../../CanvasEngine'; 4 | 5 | export interface SmartLayerWidgetProps { 6 | layer: LayerModel; 7 | engine: CanvasEngine; 8 | } 9 | 10 | export class SmartLayerWidget extends React.Component { 11 | shouldComponentUpdate(): boolean { 12 | return this.props.layer.isRepaintEnabled(); 13 | } 14 | 15 | render() { 16 | return this.props.engine.getFactoryForLayer(this.props.layer).generateReactWidget({ model: this.props.layer }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/entities/layer/TransformLayerWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { CSSProperties } from 'react'; 4 | import { LayerModel } from './LayerModel'; 5 | import { css } from '@emotion/react'; 6 | 7 | export interface TransformLayerWidgetProps { 8 | layer: LayerModel; 9 | } 10 | 11 | namespace S { 12 | const shared = css` 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | bottom: 0; 17 | position: absolute; 18 | pointer-events: none; 19 | transform-origin: 0 0; 20 | width: 100%; 21 | height: 100%; 22 | overflow: visible; 23 | `; 24 | 25 | export const DivLayer = styled.div` 26 | ${shared} 27 | `; 28 | 29 | export const SvgLayer = styled.svg` 30 | ${shared} 31 | `; 32 | } 33 | 34 | export class TransformLayerWidget extends React.Component> { 35 | constructor(props: TransformLayerWidgetProps) { 36 | super(props); 37 | this.state = {}; 38 | } 39 | 40 | getTransform() { 41 | const model = this.props.layer.getParent(); 42 | return ` 43 | translate( 44 | ${model.getOffsetX()}px, 45 | ${model.getOffsetY()}px) 46 | scale( 47 | ${model.getZoomLevel() / 100.0} 48 | ) 49 | `; 50 | } 51 | 52 | getTransformStyle(): CSSProperties { 53 | if (this.props.layer.getOptions().transformed) { 54 | return { 55 | transform: this.getTransform() 56 | }; 57 | } 58 | return {}; 59 | } 60 | 61 | render() { 62 | if (this.props.layer.getOptions().isSvg) { 63 | return {this.props.children}; 64 | } 65 | return {this.props.children}; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/entities/selection/SelectionBoxLayerFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AbstractReactFactory, GenerateWidgetEvent } from '../../core/AbstractReactFactory'; 3 | import { SelectionLayerModel } from './SelectionLayerModel'; 4 | import { GenerateModelEvent } from '../../core/AbstractModelFactory'; 5 | import { SelectionBoxWidget } from './SelectionBoxWidget'; 6 | import { JSX } from 'react'; 7 | 8 | export class SelectionBoxLayerFactory extends AbstractReactFactory { 9 | constructor() { 10 | super('selection'); 11 | } 12 | 13 | generateModel(event: GenerateModelEvent): SelectionLayerModel { 14 | return new SelectionLayerModel(); 15 | } 16 | 17 | generateReactWidget(event: GenerateWidgetEvent): JSX.Element { 18 | return ; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/entities/selection/SelectionBoxWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { SimpleClientRect } from '../../states/SelectionBoxState'; 4 | 5 | export interface SelectionBoxWidgetProps { 6 | rect: SimpleClientRect; 7 | } 8 | 9 | namespace S { 10 | export const Container = styled.div` 11 | position: absolute; 12 | background-color: rgba(0, 192, 255, 0.2); 13 | border: solid 2px rgb(0, 192, 255); 14 | `; 15 | } 16 | 17 | export class SelectionBoxWidget extends React.Component { 18 | render() { 19 | const { rect } = this.props; 20 | 21 | if (!rect) return null; 22 | 23 | return ( 24 | 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/entities/selection/SelectionLayerModel.ts: -------------------------------------------------------------------------------- 1 | import { LayerModel } from '../layer/LayerModel'; 2 | import { FactoryBank } from '../../core/FactoryBank'; 3 | import { AbstractModelFactory } from '../../core/AbstractModelFactory'; 4 | import { BaseModel } from '../../core-models/BaseModel'; 5 | import { SimpleClientRect } from '../../states/SelectionBoxState'; 6 | 7 | export class SelectionLayerModel extends LayerModel { 8 | box: SimpleClientRect; 9 | 10 | constructor() { 11 | super({ 12 | transformed: false, 13 | isSvg: false, 14 | type: 'selection' 15 | }); 16 | } 17 | 18 | setBox(rect: SimpleClientRect) { 19 | this.box = rect; 20 | } 21 | 22 | getChildModelFactoryBank(): FactoryBank> { 23 | // is not used as it doesnt serialize 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CanvasEngine'; 2 | export * from './Toolkit'; 3 | export * from './entities/canvas/CanvasModel'; 4 | 5 | export * from './core/AbstractFactory'; 6 | export * from './core/AbstractModelFactory'; 7 | export * from './core/AbstractReactFactory'; 8 | export * from './core/BaseObserver'; 9 | export * from './core/FactoryBank'; 10 | export * from './core/ModelGeometryInterface'; 11 | 12 | export * from './core-actions/Action'; 13 | export * from './core-actions/ActionEventBus'; 14 | 15 | export * from './core-models/BaseEntity'; 16 | export * from './core-models/BaseModel'; 17 | export * from './core-models/BasePositionModel'; 18 | 19 | export * from './entities/canvas/CanvasModel'; 20 | export * from './entities/canvas/CanvasWidget'; 21 | 22 | export * from './entities/layer/LayerModel'; 23 | export * from './entities/layer/TransformLayerWidget'; 24 | export * from './entities/layer/SmartLayerWidget'; 25 | 26 | export * from './entities/selection/SelectionBoxLayerFactory'; 27 | export * from './entities/selection/SelectionBoxWidget'; 28 | export * from './entities/selection/SelectionLayerModel'; 29 | 30 | export * from './widgets/PeformanceWidget'; 31 | 32 | export * from './core-state/AbstractDisplacementState'; 33 | export * from './core-state/State'; 34 | export * from './core-state/StateMachine'; 35 | 36 | export * from './states/DefaultState'; 37 | export * from './states/DragCanvasState'; 38 | export * from './states/SelectingState'; 39 | export * from './states/SelectionBoxState'; 40 | export * from './states/MoveItemsState'; 41 | 42 | export * from './actions/DeleteItemsAction'; 43 | export * from './actions/ZoomCanvasAction'; 44 | export * from './actions/PanAndZoomCanvasAction'; 45 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/states/DefaultState.ts: -------------------------------------------------------------------------------- 1 | import { State } from '../core-state/State'; 2 | import { Action, ActionEvent, InputType } from '../core-actions/Action'; 3 | import { MouseEvent, TouchEvent } from 'react'; 4 | import { DragCanvasState } from './DragCanvasState'; 5 | import { SelectingState } from './SelectingState'; 6 | import { MoveItemsState } from './MoveItemsState'; 7 | 8 | export class DefaultState extends State { 9 | constructor() { 10 | super({ 11 | name: 'default' 12 | }); 13 | this.childStates = [new SelectingState()]; 14 | 15 | // determine what was clicked on 16 | this.registerAction( 17 | new Action({ 18 | type: InputType.MOUSE_DOWN, 19 | fire: (event: ActionEvent) => { 20 | const element = this.engine.getActionEventBus().getModelForEvent(event); 21 | 22 | // the canvas was clicked on, transition to the dragging canvas state 23 | if (!element) { 24 | this.transitionWithEvent(new DragCanvasState(), event); 25 | } else { 26 | this.transitionWithEvent(new MoveItemsState(), event); 27 | } 28 | } 29 | }) 30 | ); 31 | 32 | // touch drags the canvas 33 | this.registerAction( 34 | new Action({ 35 | type: InputType.TOUCH_START, 36 | fire: (event: ActionEvent) => { 37 | this.transitionWithEvent(new DragCanvasState(), event); 38 | } 39 | }) 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/states/DragCanvasState.ts: -------------------------------------------------------------------------------- 1 | import { CanvasEngine } from '../CanvasEngine'; 2 | import { AbstractDisplacementState, AbstractDisplacementStateEvent } from '../core-state/AbstractDisplacementState'; 3 | import { State } from '../core-state/State'; 4 | 5 | export interface DragCanvasStateOptions { 6 | /** 7 | * If enabled, the canvas is available to drag 8 | */ 9 | allowDrag?: boolean; 10 | } 11 | 12 | export class DragCanvasState extends AbstractDisplacementState { 13 | // store this as we drag the canvas 14 | initialCanvasX: number; 15 | initialCanvasY: number; 16 | config: DragCanvasStateOptions; 17 | 18 | constructor(options: DragCanvasStateOptions = {}) { 19 | super({ 20 | name: 'drag-canvas' 21 | }); 22 | this.config = { 23 | allowDrag: true, 24 | ...options 25 | }; 26 | } 27 | 28 | async activated(prev) { 29 | super.activated(prev); 30 | this.engine.getModel().clearSelection(); 31 | await this.engine.repaintCanvas(true); 32 | 33 | // we can block layer rendering because we are only targeting the transforms 34 | for (let layer of this.engine.getModel().getLayers()) { 35 | layer.allowRepaint(false); 36 | } 37 | 38 | this.initialCanvasX = this.engine.getModel().getOffsetX(); 39 | this.initialCanvasY = this.engine.getModel().getOffsetY(); 40 | } 41 | 42 | deactivated(next: State) { 43 | super.deactivated(next); 44 | for (let layer of this.engine.getModel().getLayers()) { 45 | layer.allowRepaint(true); 46 | } 47 | } 48 | 49 | fireMouseMoved(event: AbstractDisplacementStateEvent) { 50 | if (this.config.allowDrag) { 51 | this.engine 52 | .getModel() 53 | .setOffset(this.initialCanvasX + event.displacementX, this.initialCanvasY + event.displacementY); 54 | this.engine.repaintCanvas(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/states/MoveItemsState.ts: -------------------------------------------------------------------------------- 1 | import { AbstractDisplacementState, AbstractDisplacementStateEvent } from '../core-state/AbstractDisplacementState'; 2 | import { State } from '../core-state/State'; 3 | import { Action, ActionEvent, InputType } from '../core-actions/Action'; 4 | import { BasePositionModel } from '../core-models/BasePositionModel'; 5 | import { Point } from '@projectstorm/geometry'; 6 | import { CanvasEngine } from '../CanvasEngine'; 7 | 8 | export class MoveItemsState extends AbstractDisplacementState { 9 | initialPositions: { 10 | [id: string]: { 11 | point: Point; 12 | item: BasePositionModel; 13 | }; 14 | }; 15 | 16 | constructor() { 17 | super({ 18 | name: 'move-items' 19 | }); 20 | this.registerAction( 21 | new Action({ 22 | type: InputType.MOUSE_DOWN, 23 | fire: (event: ActionEvent) => { 24 | const element = this.engine.getActionEventBus().getModelForEvent(event); 25 | if (!element) { 26 | return; 27 | } 28 | if (!element.isSelected()) { 29 | this.engine.getModel().clearSelection(); 30 | } 31 | element.setSelected(true); 32 | this.engine.repaintCanvas(); 33 | } 34 | }) 35 | ); 36 | } 37 | 38 | activated(previous: State) { 39 | super.activated(previous); 40 | this.initialPositions = {}; 41 | } 42 | 43 | fireMouseMoved(event: AbstractDisplacementStateEvent) { 44 | const items = this.engine.getModel().getSelectedEntities(); 45 | const model = this.engine.getModel(); 46 | for (let item of items) { 47 | if (item instanceof BasePositionModel) { 48 | if (item.isLocked()) { 49 | continue; 50 | } 51 | if (!this.initialPositions[item.getID()]) { 52 | this.initialPositions[item.getID()] = { 53 | point: item.getPosition(), 54 | item: item 55 | }; 56 | } 57 | 58 | const pos = this.initialPositions[item.getID()].point; 59 | item.setPosition( 60 | model.getGridPosition(pos.x + event.virtualDisplacementX), 61 | model.getGridPosition(pos.y + event.virtualDisplacementY) 62 | ); 63 | } 64 | } 65 | this.engine.repaintCanvas(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/states/SelectingState.ts: -------------------------------------------------------------------------------- 1 | import { State } from '../core-state/State'; 2 | import { Action, ActionEvent, InputType } from '../core-actions/Action'; 3 | import { SelectionBoxState } from './SelectionBoxState'; 4 | import { MouseEvent } from 'react'; 5 | import { CanvasEngine } from '../CanvasEngine'; 6 | 7 | export class SelectingState extends State { 8 | constructor() { 9 | super({ 10 | name: 'selecting' 11 | }); 12 | this.keys = ['shift']; 13 | 14 | this.registerAction( 15 | new Action({ 16 | type: InputType.MOUSE_DOWN, 17 | fire: (event: ActionEvent) => { 18 | const element = this.engine.getActionEventBus().getModelForEvent(event); 19 | 20 | // go into a selection box on the canvas state 21 | if (!element) { 22 | this.transitionWithEvent(new SelectionBoxState(), event); 23 | } else { 24 | element.setSelected(true); 25 | this.engine.repaintCanvas(); 26 | } 27 | } 28 | }) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-canvas-core/src/widgets/PeformanceWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import _isEqual from 'lodash/isEqual'; 3 | import { BaseModel } from '../core-models/BaseModel'; 4 | import { JSX } from 'react'; 5 | 6 | export interface PeformanceWidgetProps { 7 | children: () => JSX.Element; 8 | serialized: object; 9 | model: BaseModel; 10 | } 11 | 12 | export interface PeformanceWidgetState {} 13 | 14 | export class PeformanceWidget extends React.Component { 15 | shouldComponentUpdate( 16 | nextProps: Readonly, 17 | nextState: Readonly, 18 | nextContext: any 19 | ): boolean { 20 | if (!this.props.model.performanceTune()) { 21 | return true; 22 | } 23 | // deserialization event 24 | if (this.props.model !== nextProps.model) { 25 | return true; 26 | } 27 | 28 | // change event 29 | return !_isEqual(this.props.serialized, nextProps.serialized); 30 | } 31 | 32 | render() { 33 | return this.props.children(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/react-canvas-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "declarationDir": "dist/@types", 8 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 9 | }, 10 | "references": [ 11 | { 12 | "path": "../geometry" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-canvas-core/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: 'projectstorm/react-canvas-core' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | dist/tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/README.md: -------------------------------------------------------------------------------- 1 | # Project STORM > React Diagrams > Core 2 | 3 | This workspace houses the default models 4 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams-core", 3 | "version": "7.0.3", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/projectstorm/react-diagrams.git" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf ./dist", 12 | "build": "../../node_modules/.bin/webpack", 13 | "build:es": "../../node_modules/.bin/tsc", 14 | "build:prod": "NODE_ENV=production ../../node_modules/.bin/webpack && NODE_ENV=production ../../node_modules/.bin/tsc" 15 | }, 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "keywords": [ 20 | "web", 21 | "diagram", 22 | "diagrams", 23 | "react", 24 | "typescript", 25 | "flowchart", 26 | "simple", 27 | "links", 28 | "nodes" 29 | ], 30 | "main": "./dist/index.umd.js", 31 | "module": "./dist/index.js", 32 | "typings": "./dist/@types/index", 33 | "dependencies": { 34 | "@emotion/styled": "^11.11.0", 35 | "@projectstorm/geometry": "workspace:*", 36 | "@projectstorm/react-canvas-core": "workspace:*", 37 | "lodash": "^4.17.21", 38 | "react": "^19.0.0", 39 | "resize-observer-polyfill": "^1.5.1" 40 | }, 41 | "devDependencies": { 42 | "@types/lodash": "^4.14.200", 43 | "@types/react": "^19.0.12" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/label/LabelModel.ts: -------------------------------------------------------------------------------- 1 | import { LinkModel } from '../link/LinkModel'; 2 | import { BaseModel, BaseModelGenerics, BaseModelOptions, DeserializeEvent } from '@projectstorm/react-canvas-core'; 3 | 4 | export interface LabelModelOptions extends BaseModelOptions { 5 | offsetX?: number; 6 | offsetY?: number; 7 | } 8 | 9 | export interface LabelModelGenerics extends BaseModelGenerics { 10 | PARENT: LinkModel; 11 | OPTIONS: LabelModelOptions; 12 | } 13 | 14 | export class LabelModel extends BaseModel { 15 | constructor(options: G['OPTIONS']) { 16 | super({ 17 | ...options, 18 | offsetX: options.offsetX || 0, 19 | offsetY: options.offsetY || 0 20 | }); 21 | } 22 | 23 | deserialize(event: DeserializeEvent) { 24 | super.deserialize(event); 25 | this.options.offsetX = event.data.offsetX; 26 | this.options.offsetY = event.data.offsetY; 27 | } 28 | 29 | serialize() { 30 | return { 31 | ...super.serialize(), 32 | offsetX: this.options.offsetX, 33 | offsetY: this.options.offsetY 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/link-layer/LinkLayerFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AbstractReactFactory, GenerateModelEvent, GenerateWidgetEvent } from '@projectstorm/react-canvas-core'; 3 | import { DiagramEngine } from '../../DiagramEngine'; 4 | import { LinkLayerModel } from './LinkLayerModel'; 5 | import { LinkLayerWidget } from './LinkLayerWidget'; 6 | import { JSX } from 'react'; 7 | 8 | export class LinkLayerFactory extends AbstractReactFactory { 9 | constructor() { 10 | super('diagram-links'); 11 | } 12 | 13 | generateModel(event: GenerateModelEvent): LinkLayerModel { 14 | return new LinkLayerModel(); 15 | } 16 | 17 | generateReactWidget(event: GenerateWidgetEvent): JSX.Element { 18 | return ; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/link-layer/LinkLayerModel.ts: -------------------------------------------------------------------------------- 1 | import { LayerModel, LayerModelGenerics } from '@projectstorm/react-canvas-core'; 2 | import { LinkModel } from '../link/LinkModel'; 3 | import { DiagramEngine } from '../../DiagramEngine'; 4 | import { DiagramModel } from '../../models/DiagramModel'; 5 | 6 | export interface LinkLayerModelGenerics extends LayerModelGenerics { 7 | CHILDREN: LinkModel; 8 | ENGINE: DiagramEngine; 9 | } 10 | 11 | export class LinkLayerModel extends LayerModel { 12 | constructor() { 13 | super({ 14 | type: 'diagram-links', 15 | isSvg: true, 16 | transformed: true 17 | }); 18 | } 19 | 20 | addModel(model: G['CHILDREN']): void { 21 | if (!(model instanceof LinkModel)) { 22 | throw new Error('Can only add links to this layer'); 23 | } 24 | model.registerListener({ 25 | entityRemoved: () => { 26 | (this.getParent() as DiagramModel).removeLink(model); 27 | } 28 | }); 29 | super.addModel(model); 30 | } 31 | 32 | getLinks() { 33 | return this.getModels(); 34 | } 35 | 36 | getChildModelFactoryBank(engine: G['ENGINE']) { 37 | return engine.getLinkFactories(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/link-layer/LinkLayerWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import _map from 'lodash/map'; 4 | import { LinkWidget } from '../link/LinkWidget'; 5 | import { LinkLayerModel } from './LinkLayerModel'; 6 | import { DiagramEngine } from '../../DiagramEngine'; 7 | 8 | export interface LinkLayerWidgetProps { 9 | layer: LinkLayerModel; 10 | engine: DiagramEngine; 11 | } 12 | 13 | namespace S { 14 | export const Container = styled.div``; 15 | } 16 | 17 | export class LinkLayerWidget extends React.Component { 18 | render() { 19 | return ( 20 | <> 21 | { 22 | //only perform these actions when we have a diagram 23 | _map(this.props.layer.getLinks(), (link) => { 24 | return ; 25 | }) 26 | } 27 | > 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/link/PointModel.ts: -------------------------------------------------------------------------------- 1 | import { LinkModel } from './LinkModel'; 2 | import { 3 | BaseModelListener, 4 | BasePositionModel, 5 | BasePositionModelGenerics, 6 | BasePositionModelOptions 7 | } from '@projectstorm/react-canvas-core'; 8 | 9 | export interface PointModelOptions extends Omit { 10 | link: LinkModel; 11 | } 12 | 13 | export interface PointModelGenerics { 14 | PARENT: LinkModel; 15 | OPTIONS: PointModelOptions; 16 | LISTENER: BaseModelListener; 17 | } 18 | 19 | export class PointModel extends BasePositionModel< 20 | G & BasePositionModelGenerics 21 | > { 22 | constructor(options: G['OPTIONS']) { 23 | super({ 24 | ...options, 25 | type: 'point' 26 | }); 27 | this.parent = options.link; 28 | } 29 | 30 | isConnectedToPort(): boolean { 31 | return this.parent.getPortForPoint(this) !== null; 32 | } 33 | 34 | getLink(): LinkModel { 35 | return this.getParent(); 36 | } 37 | 38 | remove() { 39 | //clear references 40 | if (this.parent) { 41 | this.parent.removePoint(this); 42 | } 43 | super.remove(); 44 | } 45 | 46 | isLocked() { 47 | return super.isLocked() || this.getParent().isLocked(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/node-layer/NodeLayerFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AbstractReactFactory, GenerateModelEvent, GenerateWidgetEvent } from '@projectstorm/react-canvas-core'; 3 | import { DiagramEngine } from '../../DiagramEngine'; 4 | import { NodeLayerModel } from './NodeLayerModel'; 5 | import { NodeLayerWidget } from './NodeLayerWidget'; 6 | import { JSX } from 'react'; 7 | 8 | export class NodeLayerFactory extends AbstractReactFactory { 9 | constructor() { 10 | super('diagram-nodes'); 11 | } 12 | 13 | generateModel(event: GenerateModelEvent): NodeLayerModel { 14 | return new NodeLayerModel(); 15 | } 16 | 17 | generateReactWidget(event: GenerateWidgetEvent): JSX.Element { 18 | return ; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/node-layer/NodeLayerModel.ts: -------------------------------------------------------------------------------- 1 | import { LayerModel, LayerModelGenerics } from '@projectstorm/react-canvas-core'; 2 | import { NodeModel } from '../node/NodeModel'; 3 | import { DiagramEngine } from '../../DiagramEngine'; 4 | import { DiagramModel } from '../../models/DiagramModel'; 5 | 6 | export interface NodeLayerModelGenerics extends LayerModelGenerics { 7 | CHILDREN: NodeModel; 8 | ENGINE: DiagramEngine; 9 | } 10 | 11 | export class NodeLayerModel extends LayerModel { 12 | constructor() { 13 | super({ 14 | type: 'diagram-nodes', 15 | isSvg: false, 16 | transformed: true 17 | }); 18 | } 19 | 20 | addModel(model: G['CHILDREN']): void { 21 | if (!(model instanceof NodeModel)) { 22 | throw new Error('Can only add nodes to this layer'); 23 | } 24 | model.registerListener({ 25 | entityRemoved: () => { 26 | (this.getParent() as DiagramModel).removeNode(model); 27 | } 28 | }); 29 | super.addModel(model); 30 | } 31 | 32 | getChildModelFactoryBank(engine: G['ENGINE']) { 33 | return engine.getNodeFactories(); 34 | } 35 | 36 | getNodes() { 37 | return this.getModels(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/node-layer/NodeLayerWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import _map from 'lodash/map'; 3 | import { NodeModel } from '../node/NodeModel'; 4 | import { NodeWidget } from '../node/NodeWidget'; 5 | import { NodeLayerModel } from './NodeLayerModel'; 6 | import { DiagramEngine } from '../../DiagramEngine'; 7 | 8 | export interface NodeLayerWidgetProps { 9 | layer: NodeLayerModel; 10 | engine: DiagramEngine; 11 | } 12 | 13 | export class NodeLayerWidget extends React.Component { 14 | render() { 15 | return ( 16 | <> 17 | {_map(this.props.layer.getNodes(), (node: NodeModel) => { 18 | return ; 19 | })} 20 | > 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/entities/port/PortWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import _keys from 'lodash/keys'; 3 | import { PortModel } from './PortModel'; 4 | import { DiagramEngine } from '../../DiagramEngine'; 5 | import { ListenerHandle, Toolkit } from '@projectstorm/react-canvas-core'; 6 | 7 | export interface PortProps { 8 | port: PortModel; 9 | engine: DiagramEngine; 10 | className?; 11 | style?; 12 | } 13 | 14 | export class PortWidget extends React.Component> { 15 | ref: React.RefObject; 16 | engineListenerHandle: ListenerHandle; 17 | 18 | constructor(props: PortProps) { 19 | super(props); 20 | this.ref = React.createRef(); 21 | } 22 | 23 | report() { 24 | this.props.port.updateCoords(this.props.engine.getPortCoords(this.props.port, this.ref.current)); 25 | } 26 | 27 | componentWillUnmount(): void { 28 | this.engineListenerHandle && this.engineListenerHandle.deregister(); 29 | } 30 | 31 | componentDidUpdate(prevProps: Readonly, prevState, snapshot?: any): void { 32 | if (!this.props.port.reportedPosition) { 33 | this.report(); 34 | } 35 | } 36 | 37 | componentDidMount(): void { 38 | this.engineListenerHandle = this.props.engine.registerListener({ 39 | canvasReady: () => { 40 | this.report(); 41 | } 42 | }); 43 | if (this.props.engine.getCanvas()) { 44 | this.report(); 45 | } 46 | } 47 | 48 | getExtraProps() { 49 | if (Toolkit.TESTING) { 50 | const links = _keys(this.props.port.getNode().getPort(this.props.port.getName()).links).join(','); 51 | return { 52 | 'data-links': links 53 | }; 54 | } 55 | return {}; 56 | } 57 | 58 | render() { 59 | return ( 60 | 68 | {this.props.children} 69 | 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './models/DiagramModel'; 2 | export * from './entities/label/LabelModel'; 3 | 4 | export * from './entities/link/LinkModel'; 5 | export * from './entities/link/PointModel'; 6 | export * from './entities/link/LinkWidget'; 7 | 8 | export * from './entities/link-layer/LinkLayerModel'; 9 | export * from './entities/link-layer/LinkLayerWidget'; 10 | export * from './entities/link-layer/LinkLayerFactory'; 11 | 12 | export * from './entities/node-layer/NodeLayerModel'; 13 | export * from './entities/node-layer/NodeLayerWidget'; 14 | export * from './entities/node-layer/NodeLayerFactory'; 15 | 16 | export * from './entities/node/NodeModel'; 17 | export * from './entities/node/NodeWidget'; 18 | export * from './entities/port/PortModel'; 19 | export * from './entities/port/PortWidget'; 20 | 21 | export * from './states/DefaultDiagramState'; 22 | export * from './states/DragDiagramItemsState'; 23 | export * from './states/DragNewLinkState'; 24 | 25 | export * from './DiagramEngine'; 26 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/states/DefaultDiagramState.ts: -------------------------------------------------------------------------------- 1 | import { MouseEvent, TouchEvent } from 'react'; 2 | import { 3 | SelectingState, 4 | State, 5 | Action, 6 | InputType, 7 | ActionEvent, 8 | DragCanvasState 9 | } from '@projectstorm/react-canvas-core'; 10 | import { PortModel } from '../entities/port/PortModel'; 11 | import { DragNewLinkState } from './DragNewLinkState'; 12 | import { DiagramEngine } from '../DiagramEngine'; 13 | import { DragDiagramItemsState } from './DragDiagramItemsState'; 14 | 15 | export class DefaultDiagramState extends State { 16 | dragCanvas: DragCanvasState; 17 | dragNewLink: DragNewLinkState; 18 | dragItems: DragDiagramItemsState; 19 | 20 | constructor() { 21 | super({ 22 | name: 'default-diagrams' 23 | }); 24 | this.childStates = [new SelectingState()]; 25 | this.dragCanvas = new DragCanvasState(); 26 | this.dragNewLink = new DragNewLinkState(); 27 | this.dragItems = new DragDiagramItemsState(); 28 | 29 | // determine what was clicked on 30 | this.registerAction( 31 | new Action({ 32 | type: InputType.MOUSE_DOWN, 33 | fire: (event: ActionEvent) => { 34 | const element = this.engine.getActionEventBus().getModelForEvent(event); 35 | 36 | // the canvas was clicked on, transition to the dragging canvas state 37 | if (!element) { 38 | this.transitionWithEvent(this.dragCanvas, event); 39 | } 40 | // initiate dragging a new link 41 | else if (element instanceof PortModel) { 42 | this.transitionWithEvent(this.dragNewLink, event); 43 | } 44 | // move the items (and potentially link points) 45 | else { 46 | this.transitionWithEvent(this.dragItems, event); 47 | } 48 | } 49 | }) 50 | ); 51 | 52 | // touch drags the canvas 53 | this.registerAction( 54 | new Action({ 55 | type: InputType.TOUCH_START, 56 | fire: (event: ActionEvent) => { 57 | this.transitionWithEvent(this.dragCanvas, event); 58 | } 59 | }) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/src/states/DragDiagramItemsState.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionEvent, InputType, MoveItemsState } from '@projectstorm/react-canvas-core'; 2 | import _forEach from 'lodash/forEach'; 3 | import { PointModel } from '../entities/link/PointModel'; 4 | import { DiagramEngine } from '../DiagramEngine'; 5 | import { PortModel } from '../entities/port/PortModel'; 6 | import { MouseEvent } from 'react'; 7 | import { LinkModel } from '../entities/link/LinkModel'; 8 | 9 | export class DragDiagramItemsState extends MoveItemsState { 10 | constructor() { 11 | super(); 12 | this.registerAction( 13 | new Action({ 14 | type: InputType.MOUSE_UP, 15 | fire: (event: ActionEvent) => { 16 | const item = this.engine.getMouseElement(event.event); 17 | if (item instanceof PortModel) { 18 | _forEach(this.initialPositions, (position) => { 19 | if (position.item instanceof PointModel) { 20 | const link = position.item.getParent() as LinkModel; 21 | 22 | // only care about the last links 23 | if (link.getLastPoint() !== position.item) { 24 | return; 25 | } 26 | if (link.getSourcePort().canLinkToPort(item)) { 27 | link.setTargetPort(item); 28 | item.reportPosition(); 29 | this.engine.repaintCanvas(); 30 | } 31 | } 32 | }); 33 | } 34 | } 35 | }) 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "declarationDir": "dist/@types", 8 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 9 | }, 10 | "references": [ 11 | { 12 | "path": "../react-canvas-core" 13 | }, 14 | { 15 | "path": "../geometry" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-diagrams-core/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: 'projectstorm/react-canvas-core' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | dist/tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/README.md: -------------------------------------------------------------------------------- 1 | # Project STORM > React Diagrams > Defaults 2 | 3 | This workspace houses the default models 4 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams-defaults", 3 | "version": "7.1.3", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/projectstorm/react-diagrams.git" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf ./dist", 12 | "build": "../../node_modules/.bin/webpack" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "keywords": [ 18 | "web", 19 | "diagram", 20 | "diagrams", 21 | "react", 22 | "typescript", 23 | "flowchart", 24 | "simple", 25 | "links", 26 | "nodes" 27 | ], 28 | "main": "./dist/index.umd.js", 29 | "module": "./dist/index.js", 30 | "typings": "./dist/@types/index", 31 | "dependencies": { 32 | "@emotion/react": "^11.14.0", 33 | "@emotion/styled": "^11.*", 34 | "@projectstorm/geometry": "workspace:*", 35 | "@projectstorm/react-canvas-core": "workspace:*", 36 | "@projectstorm/react-diagrams-core": "workspace:*", 37 | "lodash": "^4.17.21", 38 | "react": "^19.0.0" 39 | }, 40 | "devDependencies": { 41 | "@types/lodash": "^4.14.200", 42 | "@types/react": "^19.0.12" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './label/DefaultLabelFactory'; 2 | export * from './label/DefaultLabelModel'; 3 | export * from './label/DefaultLabelWidget'; 4 | 5 | export * from './link/DefaultLinkFactory'; 6 | export * from './link/DefaultLinkModel'; 7 | export * from './link/DefaultLinkWidget'; 8 | export * from './link/DefaultLinkSegmentWidget'; 9 | export * from './link/DefaultLinkPointWidget'; 10 | 11 | export * from './node/DefaultNodeFactory'; 12 | export * from './node/DefaultNodeModel'; 13 | export * from './node/DefaultNodeWidget'; 14 | 15 | export * from './port/DefaultPortFactory'; 16 | export * from './port/DefaultPortLabelWidget'; 17 | export * from './port/DefaultPortModel'; 18 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/label/DefaultLabelFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DefaultLabelModel } from './DefaultLabelModel'; 3 | import { DefaultLabelWidget } from './DefaultLabelWidget'; 4 | import { AbstractReactFactory } from '@projectstorm/react-canvas-core'; 5 | import { DiagramEngine } from '@projectstorm/react-diagrams-core'; 6 | import { JSX } from 'react'; 7 | 8 | /** 9 | * @author Dylan Vorster 10 | */ 11 | export class DefaultLabelFactory extends AbstractReactFactory { 12 | constructor() { 13 | super('default'); 14 | } 15 | 16 | generateReactWidget(event): JSX.Element { 17 | return ; 18 | } 19 | 20 | generateModel(event): DefaultLabelModel { 21 | return new DefaultLabelModel(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/label/DefaultLabelModel.tsx: -------------------------------------------------------------------------------- 1 | import { LabelModel, LabelModelGenerics, LabelModelOptions } from '@projectstorm/react-diagrams-core'; 2 | import { DeserializeEvent } from '@projectstorm/react-canvas-core'; 3 | 4 | export interface DefaultLabelModelOptions extends LabelModelOptions { 5 | label?: string; 6 | } 7 | 8 | export interface DefaultLabelModelGenerics extends LabelModelGenerics { 9 | OPTIONS: DefaultLabelModelOptions; 10 | } 11 | 12 | export class DefaultLabelModel extends LabelModel { 13 | constructor(options: DefaultLabelModelOptions = {}) { 14 | super({ 15 | offsetY: options.offsetY == null ? -23 : options.offsetY, 16 | type: 'default', 17 | ...options 18 | }); 19 | } 20 | 21 | setLabel(label: string) { 22 | this.options.label = label; 23 | } 24 | 25 | deserialize(event: DeserializeEvent) { 26 | super.deserialize(event); 27 | this.options.label = event.data.label; 28 | } 29 | 30 | serialize() { 31 | return { 32 | ...super.serialize(), 33 | label: this.options.label 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/label/DefaultLabelWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DefaultLabelModel } from './DefaultLabelModel'; 3 | import styled from '@emotion/styled'; 4 | 5 | export interface DefaultLabelWidgetProps { 6 | model: DefaultLabelModel; 7 | } 8 | 9 | namespace S { 10 | export const Label = styled.div` 11 | background: rgba(0, 0, 0, 0.8); 12 | border-radius: 5px; 13 | color: white; 14 | font-size: 12px; 15 | padding: 4px 8px; 16 | font-family: sans-serif; 17 | user-select: none; 18 | `; 19 | } 20 | 21 | export class DefaultLabelWidget extends React.Component { 22 | render() { 23 | return {this.props.model.getOptions().label}; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/link/DefaultLinkFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DefaultLinkModel } from './DefaultLinkModel'; 3 | import { DefaultLinkWidget } from './DefaultLinkWidget'; 4 | import styled from '@emotion/styled'; 5 | import { AbstractReactFactory } from '@projectstorm/react-canvas-core'; 6 | import { DiagramEngine } from '@projectstorm/react-diagrams-core'; 7 | import { css, keyframes } from '@emotion/react'; 8 | import { JSX } from 'react'; 9 | 10 | namespace S { 11 | export const Keyframes = keyframes` 12 | from { 13 | stroke-dashoffset: 24; 14 | } 15 | to { 16 | stroke-dashoffset: 0; 17 | } 18 | `; 19 | 20 | const selected = css` 21 | stroke-dasharray: 10, 2; 22 | animation: ${Keyframes} 1s linear infinite; 23 | `; 24 | 25 | export const Path = styled.path<{ selected: boolean }>` 26 | ${(p) => p.selected && selected}; 27 | fill: none; 28 | pointer-events: auto; 29 | `; 30 | } 31 | 32 | export class DefaultLinkFactory extends AbstractReactFactory< 33 | Link, 34 | DiagramEngine 35 | > { 36 | constructor(type = 'default') { 37 | super(type); 38 | } 39 | 40 | generateReactWidget(event): JSX.Element { 41 | return ; 42 | } 43 | 44 | generateModel(event): Link { 45 | return new DefaultLinkModel() as Link; 46 | } 47 | 48 | generateLinkSegment(model: Link, selected: boolean, path: string) { 49 | return ( 50 | 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/link/DefaultLinkPointWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PointModel } from '@projectstorm/react-diagrams-core'; 3 | import styled from '@emotion/styled'; 4 | 5 | export interface DefaultLinkPointWidgetProps { 6 | point: PointModel; 7 | color?: string; 8 | colorSelected: string; 9 | } 10 | 11 | export interface DefaultLinkPointWidgetState { 12 | selected: boolean; 13 | } 14 | 15 | namespace S { 16 | export const PointTop = styled.circle` 17 | pointer-events: all; 18 | `; 19 | } 20 | 21 | export class DefaultLinkPointWidget extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | selected: false 26 | }; 27 | } 28 | 29 | render() { 30 | const { point } = this.props; 31 | return ( 32 | 33 | 39 | { 42 | this.setState({ selected: false }); 43 | }} 44 | onMouseEnter={() => { 45 | this.setState({ selected: true }); 46 | }} 47 | data-id={point.getID()} 48 | data-linkid={point.getLink().getID()} 49 | cx={point.getPosition().x} 50 | cy={point.getPosition().y} 51 | r={15} 52 | opacity={0.0} 53 | /> 54 | 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/link/DefaultLinkSegmentWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DefaultLinkFactory } from './DefaultLinkFactory'; 3 | import { DiagramEngine } from '@projectstorm/react-diagrams-core'; 4 | import { DefaultLinkModel } from './DefaultLinkModel'; 5 | 6 | export interface DefaultLinkSegmentWidgetProps { 7 | path: string; 8 | link: DefaultLinkModel; 9 | selected: boolean; 10 | forwardRef: React.RefObject; 11 | factory: DefaultLinkFactory; 12 | diagramEngine: DiagramEngine; 13 | onSelection: (selected: boolean) => any; 14 | extras: object; 15 | } 16 | 17 | export class DefaultLinkSegmentWidget extends React.Component { 18 | render() { 19 | const Bottom = React.cloneElement( 20 | this.props.factory.generateLinkSegment( 21 | this.props.link, 22 | this.props.selected || this.props.link.isSelected(), 23 | this.props.path 24 | ), 25 | { 26 | ref: this.props.forwardRef 27 | } 28 | ); 29 | 30 | const Top = React.cloneElement(Bottom, { 31 | strokeLinecap: 'round', 32 | onMouseLeave: () => { 33 | this.props.onSelection(false); 34 | }, 35 | onMouseEnter: () => { 36 | this.props.onSelection(true); 37 | }, 38 | ...this.props.extras, 39 | ref: null, 40 | 'data-linkid': this.props.link.getID(), 41 | strokeOpacity: this.props.selected ? 0.1 : 0, 42 | strokeWidth: 20, 43 | fill: 'none', 44 | onContextMenu: () => { 45 | if (!this.props.link.isLocked()) { 46 | event.preventDefault(); 47 | this.props.link.remove(); 48 | } 49 | } 50 | }); 51 | 52 | return ( 53 | 54 | {Bottom} 55 | {Top} 56 | 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/node/DefaultNodeFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DefaultNodeModel } from './DefaultNodeModel'; 3 | import { DefaultNodeWidget } from './DefaultNodeWidget'; 4 | import { AbstractReactFactory } from '@projectstorm/react-canvas-core'; 5 | import { DiagramEngine } from '@projectstorm/react-diagrams-core'; 6 | import { JSX } from 'react'; 7 | 8 | export class DefaultNodeFactory extends AbstractReactFactory { 9 | constructor() { 10 | super('default'); 11 | } 12 | 13 | generateReactWidget(event): JSX.Element { 14 | return ; 15 | } 16 | 17 | generateModel(event): DefaultNodeModel { 18 | return new DefaultNodeModel(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/node/DefaultNodeWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import _map from 'lodash/map'; 3 | import { DiagramEngine } from '@projectstorm/react-diagrams-core'; 4 | import { DefaultNodeModel } from './DefaultNodeModel'; 5 | import { DefaultPortLabel } from '../port/DefaultPortLabelWidget'; 6 | import styled from '@emotion/styled'; 7 | 8 | namespace S { 9 | export const Node = styled.div<{ background: string; selected: boolean }>` 10 | background-color: ${(p) => p.background}; 11 | border-radius: 5px; 12 | font-family: sans-serif; 13 | color: white; 14 | border: solid 2px black; 15 | overflow: visible; 16 | font-size: 11px; 17 | border: solid 2px ${(p) => (p.selected ? 'rgb(0,192,255)' : 'black')}; 18 | `; 19 | 20 | export const Title = styled.div` 21 | background: rgba(0, 0, 0, 0.3); 22 | display: flex; 23 | white-space: nowrap; 24 | justify-items: center; 25 | `; 26 | 27 | export const TitleName = styled.div` 28 | flex-grow: 1; 29 | padding: 5px 5px; 30 | `; 31 | 32 | export const Ports = styled.div` 33 | display: flex; 34 | background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.2)); 35 | `; 36 | 37 | export const PortsContainer = styled.div` 38 | flex-grow: 1; 39 | display: flex; 40 | flex-direction: column; 41 | 42 | &:first-of-type { 43 | margin-right: 10px; 44 | } 45 | 46 | &:only-child { 47 | margin-right: 0px; 48 | } 49 | `; 50 | } 51 | 52 | export interface DefaultNodeProps { 53 | node: DefaultNodeModel; 54 | engine: DiagramEngine; 55 | } 56 | 57 | /** 58 | * Default node that models the DefaultNodeModel. It creates two columns 59 | * for both all the input ports on the left, and the output ports on the right. 60 | */ 61 | export class DefaultNodeWidget extends React.Component { 62 | generatePort = (port) => { 63 | return ; 64 | }; 65 | 66 | render() { 67 | return ( 68 | 73 | 74 | {this.props.node.getOptions().name} 75 | 76 | 77 | {_map(this.props.node.getInPorts(), this.generatePort)} 78 | {_map(this.props.node.getOutPorts(), this.generatePort)} 79 | 80 | 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/port/DefaultPortFactory.tsx: -------------------------------------------------------------------------------- 1 | import { DefaultPortModel } from './DefaultPortModel'; 2 | import { AbstractModelFactory } from '@projectstorm/react-canvas-core'; 3 | import { DiagramEngine } from '@projectstorm/react-diagrams-core'; 4 | 5 | export class DefaultPortFactory extends AbstractModelFactory { 6 | constructor() { 7 | super('default'); 8 | } 9 | 10 | generateModel(): DefaultPortModel { 11 | return new DefaultPortModel({ 12 | name: 'unknown' 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/port/DefaultPortLabelWidget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DiagramEngine, PortWidget } from '@projectstorm/react-diagrams-core'; 3 | import { DefaultPortModel } from './DefaultPortModel'; 4 | import styled from '@emotion/styled'; 5 | 6 | export interface DefaultPortLabelProps { 7 | port: DefaultPortModel; 8 | engine: DiagramEngine; 9 | } 10 | 11 | namespace S { 12 | export const PortLabel = styled.div` 13 | display: flex; 14 | margin-top: 1px; 15 | align-items: center; 16 | `; 17 | 18 | export const Label = styled.div` 19 | padding: 0 5px; 20 | flex-grow: 1; 21 | `; 22 | 23 | export const Port = styled.div` 24 | width: 15px; 25 | height: 15px; 26 | background: rgba(255, 255, 255, 0.1); 27 | 28 | &:hover { 29 | background: rgb(192, 255, 0); 30 | } 31 | `; 32 | } 33 | 34 | export class DefaultPortLabel extends React.Component { 35 | render() { 36 | const port = ( 37 | 38 | 39 | 40 | ); 41 | const label = {this.props.port.getOptions().label}; 42 | 43 | return ( 44 | 45 | {this.props.port.getOptions().in ? port : label} 46 | {this.props.port.getOptions().in ? label : port} 47 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/src/port/DefaultPortModel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LinkModel, 3 | PortModel, 4 | PortModelAlignment, 5 | PortModelGenerics, 6 | PortModelOptions 7 | } from '@projectstorm/react-diagrams-core'; 8 | import { DefaultLinkModel } from '../link/DefaultLinkModel'; 9 | import { AbstractModelFactory, DeserializeEvent } from '@projectstorm/react-canvas-core'; 10 | 11 | export interface DefaultPortModelOptions extends PortModelOptions { 12 | label?: string; 13 | in?: boolean; 14 | type?: string; 15 | } 16 | 17 | export interface DefaultPortModelGenerics extends PortModelGenerics { 18 | OPTIONS: DefaultPortModelOptions; 19 | } 20 | 21 | export class DefaultPortModel extends PortModel { 22 | constructor(isIn: boolean, name?: string, label?: string); 23 | constructor(options: DefaultPortModelOptions); 24 | constructor(options: DefaultPortModelOptions | boolean, name?: string, label?: string) { 25 | if (!!name) { 26 | options = { 27 | in: !!options, 28 | name: name, 29 | label: label 30 | }; 31 | } 32 | options = options as DefaultPortModelOptions; 33 | super({ 34 | label: options.label || options.name, 35 | alignment: options.in ? PortModelAlignment.LEFT : PortModelAlignment.RIGHT, 36 | type: 'default', 37 | ...options 38 | }); 39 | } 40 | 41 | deserialize(event: DeserializeEvent) { 42 | super.deserialize(event); 43 | this.options.in = event.data.in; 44 | this.options.label = event.data.label; 45 | } 46 | 47 | serialize() { 48 | return { 49 | ...super.serialize(), 50 | in: this.options.in, 51 | label: this.options.label 52 | }; 53 | } 54 | 55 | link(port: PortModel, factory?: AbstractModelFactory): T { 56 | let link = this.createLinkModel(factory); 57 | link.setSourcePort(this); 58 | link.setTargetPort(port); 59 | return link as T; 60 | } 61 | 62 | canLinkToPort(port: PortModel): boolean { 63 | if (port instanceof DefaultPortModel) { 64 | return this.options.in !== port.getOptions().in; 65 | } 66 | return true; 67 | } 68 | 69 | createLinkModel(factory?: AbstractModelFactory): LinkModel { 70 | let link = super.createLinkModel(); 71 | if (!link && factory) { 72 | return factory.generateModel({}); 73 | } 74 | return link || new DefaultLinkModel(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "declarationDir": "dist/@types", 8 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 9 | }, 10 | "references": [ 11 | { 12 | "path": "../react-diagrams-core" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-diagrams-defaults/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: 'projectstorm/react-diagrams-defaults' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | dist/tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /packages/react-diagrams-routing/README.md: -------------------------------------------------------------------------------- 1 | # Project STORM > React Diagrams > Dagre 2 | 3 | This package adds dagre integration for laying out nodes and links 4 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports = { 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | roots: [path.join(__dirname, 'tests')], 7 | testMatch: ['**/*.test.{ts,tsx}'] 8 | }; 9 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams-routing", 3 | "version": "7.1.3", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/projectstorm/react-diagrams.git" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf ./dist", 12 | "build": "../../node_modules/.bin/webpack", 13 | "test": "../../node_modules/.bin/jest" 14 | }, 15 | "publishConfig": { 16 | "access": "public" 17 | }, 18 | "keywords": [ 19 | "web", 20 | "diagram", 21 | "diagrams", 22 | "react", 23 | "typescript", 24 | "flowchart", 25 | "simple", 26 | "links", 27 | "nodes" 28 | ], 29 | "main": "./dist/index.umd.js", 30 | "module": "./dist/index.js", 31 | "typings": "./dist/@types/index", 32 | "dependencies": { 33 | "@projectstorm/geometry": "workspace:*", 34 | "@projectstorm/react-canvas-core": "workspace:*", 35 | "@projectstorm/react-diagrams-core": "workspace:*", 36 | "@projectstorm/react-diagrams-defaults": "workspace:*", 37 | "dagre": "^0.8.5", 38 | "lodash": "^4.17.21", 39 | "pathfinding": "^0.4.18", 40 | "paths-js": "^0.4.11", 41 | "react": "^19.0.0" 42 | }, 43 | "devDependencies": { 44 | "@types/dagre": "^0.7.50", 45 | "@types/lodash": "^4.14.200", 46 | "@types/react": "^19.0.12" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './link/PathFindingLinkFactory'; 2 | export * from './link/PathFindingLinkModel'; 3 | export * from './link/PathFindingLinkWidget'; 4 | export * from './link/RightAngleLinkWidget'; 5 | export * from './link/RightAngleLinkFactory'; 6 | export * from './link/RightAngleLinkModel'; 7 | 8 | export * from './engine/PathFinding'; 9 | export * from './dagre/DagreEngine'; 10 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/src/link/PathFindingLinkModel.ts: -------------------------------------------------------------------------------- 1 | import { PathFindingLinkFactory } from './PathFindingLinkFactory'; 2 | import { DefaultLinkModel, DefaultLinkModelOptions } from '@projectstorm/react-diagrams-defaults'; 3 | 4 | export class PathFindingLinkModel extends DefaultLinkModel { 5 | constructor(options: DefaultLinkModelOptions = {}) { 6 | super({ 7 | type: PathFindingLinkFactory.NAME, 8 | ...options 9 | }); 10 | } 11 | 12 | performanceTune() { 13 | return false; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/src/link/RightAngleLinkFactory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RightAngleLinkWidget } from './RightAngleLinkWidget'; 3 | import { DefaultLinkFactory } from '@projectstorm/react-diagrams-defaults'; 4 | import { RightAngleLinkModel } from './RightAngleLinkModel'; 5 | import { JSX } from 'react'; 6 | 7 | /** 8 | * @author Daniel Lazar 9 | */ 10 | export class RightAngleLinkFactory extends DefaultLinkFactory { 11 | static NAME = 'rightAngle'; 12 | 13 | constructor() { 14 | super(RightAngleLinkFactory.NAME); 15 | } 16 | 17 | generateModel(event): RightAngleLinkModel { 18 | return new RightAngleLinkModel(); 19 | } 20 | 21 | generateReactWidget(event): JSX.Element { 22 | return ; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/src/link/RightAngleLinkModel.ts: -------------------------------------------------------------------------------- 1 | import { DefaultLinkModel, DefaultLinkModelOptions } from '@projectstorm/react-diagrams-defaults'; 2 | import { RightAngleLinkFactory } from './RightAngleLinkFactory'; 3 | import { PointModel } from '@projectstorm/react-diagrams-core'; 4 | import { DeserializeEvent } from '@projectstorm/react-canvas-core'; 5 | 6 | export class RightAngleLinkModel extends DefaultLinkModel { 7 | lastHoverIndexOfPath: number; 8 | private _lastPathXdirection: boolean; 9 | private _firstPathXdirection: boolean; 10 | constructor(options: DefaultLinkModelOptions = {}) { 11 | super({ 12 | type: RightAngleLinkFactory.NAME, 13 | ...options 14 | }); 15 | this.lastHoverIndexOfPath = 0; 16 | this._lastPathXdirection = false; 17 | this._firstPathXdirection = false; 18 | } 19 | 20 | setFirstAndLastPathsDirection() { 21 | let points = this.getPoints(); 22 | for (let i = 1; i < points.length; i += points.length - 2) { 23 | let dx = Math.abs(points[i].getX() - points[i - 1].getX()); 24 | let dy = Math.abs(points[i].getY() - points[i - 1].getY()); 25 | if (i - 1 === 0) { 26 | this._firstPathXdirection = dx > dy; 27 | } else { 28 | this._lastPathXdirection = dx > dy; 29 | } 30 | } 31 | } 32 | 33 | // @ts-ignore 34 | addPoint(pointModel: P, index: number = 1): P { 35 | // @ts-ignore 36 | super.addPoint(pointModel, index); 37 | this.setFirstAndLastPathsDirection(); 38 | return pointModel; 39 | } 40 | 41 | deserialize(event: DeserializeEvent) { 42 | super.deserialize(event); 43 | this.setFirstAndLastPathsDirection(); 44 | } 45 | 46 | setManuallyFirstAndLastPathsDirection(first, last) { 47 | this._firstPathXdirection = first; 48 | this._lastPathXdirection = last; 49 | } 50 | 51 | getLastPathXdirection(): boolean { 52 | return this._lastPathXdirection; 53 | } 54 | getFirstPathXdirection(): boolean { 55 | return this._firstPathXdirection; 56 | } 57 | 58 | setWidth(width: number) { 59 | this.options.width = width; 60 | this.fireEvent({ width }, 'widthChanged'); 61 | } 62 | 63 | setColor(color: string) { 64 | this.options.color = color; 65 | this.fireEvent({ color }, 'colorChanged'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/tests/PathFinding.test.tsx: -------------------------------------------------------------------------------- 1 | import { PathFinding } from '../src/engine/PathFinding'; 2 | 3 | describe('calculating start and end points', function () { 4 | let pathFinding: PathFinding = new PathFinding(null); 5 | 6 | beforeEach(() => { 7 | pathFinding = new PathFinding(null); 8 | }); 9 | 10 | test('return correct object for valid walkable input', () => { 11 | const matrix = [ 12 | [0, 0, 0, 0, 1, 1], 13 | [0, 0, 0, 0, 1, 1], 14 | [0, 0, 0, 0, 0, 0], 15 | [0, 0, 0, 0, 0, 0], 16 | [1, 1, 0, 0, 0, 0], 17 | [1, 1, 0, 0, 0, 0] 18 | ]; 19 | const path = [ 20 | [0, 5], 21 | [1, 4], 22 | [2, 3], 23 | [3, 2], 24 | [4, 1], 25 | [5, 0] 26 | ]; 27 | 28 | const result = pathFinding.calculateLinkStartEndCoords(matrix, path); 29 | 30 | expect(result.start).toEqual({ 31 | x: 2, 32 | y: 3 33 | }); 34 | expect(result.end).toEqual({ 35 | x: 3, 36 | y: 2 37 | }); 38 | expect(result.pathToStart).toEqual([ 39 | [0, 5], 40 | [1, 4] 41 | ]); 42 | expect(result.pathToEnd).toEqual([ 43 | [3, 2], 44 | [4, 1], 45 | [5, 0] 46 | ]); 47 | }); 48 | 49 | test('undefined is returned when no walkable path exists', () => { 50 | const matrix = [ 51 | [0, 0, 1, 1], 52 | [0, 0, 1, 1], 53 | [1, 1, 0, 0], 54 | [1, 1, 0, 0] 55 | ]; 56 | const path = [ 57 | [0, 3], 58 | [1, 2], 59 | [2, 1], 60 | [3, 0] 61 | ]; 62 | 63 | const result = pathFinding.calculateLinkStartEndCoords(matrix, path); 64 | 65 | expect(result).toBeUndefined(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "declarationDir": "dist/@types", 8 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 9 | }, 10 | "include": ["./src"], 11 | "references": [ 12 | { 13 | "path": "../geometry" 14 | }, 15 | { 16 | "path": "../react-canvas-core" 17 | }, 18 | { 19 | "path": "../react-diagrams-defaults" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: 'projectstorm/react-diagrams-routing' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/react-diagrams/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | dist/tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /packages/react-diagrams/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/packages/react-diagrams/README.md -------------------------------------------------------------------------------- /packages/react-diagrams/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams", 3 | "version": "7.0.4", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/projectstorm/react-diagrams.git" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf ./dist", 12 | "build": "../../node_modules/.bin/webpack" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "keywords": [ 18 | "web", 19 | "diagram", 20 | "diagrams", 21 | "react", 22 | "typescript", 23 | "flowchart", 24 | "simple", 25 | "links", 26 | "nodes" 27 | ], 28 | "main": "./dist/index.umd.js", 29 | "module": "./dist/index.js", 30 | "typings": "./dist/@types/index", 31 | "dependencies": { 32 | "@projectstorm/react-canvas-core": "workspace:*", 33 | "@projectstorm/react-diagrams-core": "workspace:*", 34 | "@projectstorm/react-diagrams-defaults": "workspace:*", 35 | "@projectstorm/react-diagrams-routing": "workspace:*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/react-diagrams/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultDiagramState, 3 | DiagramEngine, 4 | LinkLayerFactory, 5 | NodeLayerFactory 6 | } from '@projectstorm/react-diagrams-core'; 7 | import { 8 | DefaultLabelFactory, 9 | DefaultLinkFactory, 10 | DefaultNodeFactory, 11 | DefaultPortFactory 12 | } from '@projectstorm/react-diagrams-defaults'; 13 | import { PathFindingLinkFactory } from '@projectstorm/react-diagrams-routing'; 14 | import { SelectionBoxLayerFactory, CanvasEngineOptions } from '@projectstorm/react-canvas-core'; 15 | 16 | export * from '@projectstorm/react-canvas-core'; 17 | export * from '@projectstorm/react-diagrams-core'; 18 | export * from '@projectstorm/react-diagrams-defaults'; 19 | export * from '@projectstorm/react-diagrams-routing'; 20 | 21 | /** 22 | * Construct an engine with the defaults installed 23 | */ 24 | export default (options: CanvasEngineOptions = {}): DiagramEngine => { 25 | const engine = new DiagramEngine(options); 26 | 27 | // register model factories 28 | engine.getLayerFactories().registerFactory(new NodeLayerFactory()); 29 | engine.getLayerFactories().registerFactory(new LinkLayerFactory()); 30 | engine.getLayerFactories().registerFactory(new SelectionBoxLayerFactory()); 31 | 32 | engine.getLabelFactories().registerFactory(new DefaultLabelFactory()); 33 | engine.getNodeFactories().registerFactory(new DefaultNodeFactory()); // i cant figure out why 34 | engine.getLinkFactories().registerFactory(new DefaultLinkFactory()); 35 | engine.getLinkFactories().registerFactory(new PathFindingLinkFactory()); 36 | engine.getPortFactories().registerFactory(new DefaultPortFactory()); 37 | 38 | // register the default interaction behaviours 39 | engine.getStateMachine().pushState(new DefaultDiagramState()); 40 | return engine; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/react-diagrams/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "declarationDir": "dist/@types", 7 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 8 | }, 9 | "references": [ 10 | { 11 | "path": "../react-canvas-core" 12 | }, 13 | { 14 | "path": "../react-diagrams-defaults" 15 | }, 16 | { 17 | "path": "../react-diagrams-routing" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-diagrams/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: 'projectstorm/react-diagrams' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'diagrams-demo-gallery' 4 | - 'diagrams-demo-project' -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": true, 5 | "composite": true, 6 | "incremental": true, 7 | "strictNullChecks": false, 8 | "sourceMap": true, 9 | "inlineSources": true, 10 | "skipLibCheck": true, 11 | "jsx": "react", 12 | "target": "ES6", 13 | "moduleResolution": "Node", 14 | "module": "es6", 15 | "strict": false, 16 | "lib": [ 17 | "DOM", 18 | "ES6" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./packages/geometry" 6 | }, 7 | { 8 | "path": "./packages/react-canvas-core" 9 | }, 10 | { 11 | "path": "./packages/react-diagrams" 12 | }, 13 | { 14 | "path": "./packages/react-diagrams-core" 15 | }, 16 | { 17 | "path": "./packages/react-diagrams-defaults" 18 | }, 19 | { 20 | "path": "./packages/react-diagrams-routing" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /webpack.shared.js: -------------------------------------------------------------------------------- 1 | const production = process.env.NODE_ENV === 'production'; 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | const path = require('path'); 5 | 6 | module.exports = (directory) => { 7 | return { 8 | entry: path.join(directory, './dist/index.js'), 9 | output: { 10 | filename: 'index.umd.js', 11 | path: path.join(directory, 'dist'), 12 | libraryTarget: 'umd' 13 | }, 14 | externals: [ 15 | nodeExternals({ modulesDir: path.join(directory, 'node_modules') }), 16 | nodeExternals({ modulesDir: path.join(__dirname, 'node_modules') }) 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | enforce: 'pre', 22 | test: /\.js$/, 23 | loader: 'source-map-loader' 24 | } 25 | ] 26 | }, 27 | resolve: { 28 | extensions: ['.tsx', '.ts', '.js'] 29 | }, 30 | devtool: production ? 'source-map' : 'cheap-module-source-map', 31 | mode: production ? 'production' : 'development', 32 | optimization: { 33 | minimizer: [new TerserPlugin()] 34 | } 35 | }; 36 | }; 37 | --------------------------------------------------------------------------------
(pointModel: P, index: number = 1): P { 35 | // @ts-ignore 36 | super.addPoint(pointModel, index); 37 | this.setFirstAndLastPathsDirection(); 38 | return pointModel; 39 | } 40 | 41 | deserialize(event: DeserializeEvent) { 42 | super.deserialize(event); 43 | this.setFirstAndLastPathsDirection(); 44 | } 45 | 46 | setManuallyFirstAndLastPathsDirection(first, last) { 47 | this._firstPathXdirection = first; 48 | this._lastPathXdirection = last; 49 | } 50 | 51 | getLastPathXdirection(): boolean { 52 | return this._lastPathXdirection; 53 | } 54 | getFirstPathXdirection(): boolean { 55 | return this._firstPathXdirection; 56 | } 57 | 58 | setWidth(width: number) { 59 | this.options.width = width; 60 | this.fireEvent({ width }, 'widthChanged'); 61 | } 62 | 63 | setColor(color: string) { 64 | this.options.color = color; 65 | this.fireEvent({ color }, 'colorChanged'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/tests/PathFinding.test.tsx: -------------------------------------------------------------------------------- 1 | import { PathFinding } from '../src/engine/PathFinding'; 2 | 3 | describe('calculating start and end points', function () { 4 | let pathFinding: PathFinding = new PathFinding(null); 5 | 6 | beforeEach(() => { 7 | pathFinding = new PathFinding(null); 8 | }); 9 | 10 | test('return correct object for valid walkable input', () => { 11 | const matrix = [ 12 | [0, 0, 0, 0, 1, 1], 13 | [0, 0, 0, 0, 1, 1], 14 | [0, 0, 0, 0, 0, 0], 15 | [0, 0, 0, 0, 0, 0], 16 | [1, 1, 0, 0, 0, 0], 17 | [1, 1, 0, 0, 0, 0] 18 | ]; 19 | const path = [ 20 | [0, 5], 21 | [1, 4], 22 | [2, 3], 23 | [3, 2], 24 | [4, 1], 25 | [5, 0] 26 | ]; 27 | 28 | const result = pathFinding.calculateLinkStartEndCoords(matrix, path); 29 | 30 | expect(result.start).toEqual({ 31 | x: 2, 32 | y: 3 33 | }); 34 | expect(result.end).toEqual({ 35 | x: 3, 36 | y: 2 37 | }); 38 | expect(result.pathToStart).toEqual([ 39 | [0, 5], 40 | [1, 4] 41 | ]); 42 | expect(result.pathToEnd).toEqual([ 43 | [3, 2], 44 | [4, 1], 45 | [5, 0] 46 | ]); 47 | }); 48 | 49 | test('undefined is returned when no walkable path exists', () => { 50 | const matrix = [ 51 | [0, 0, 1, 1], 52 | [0, 0, 1, 1], 53 | [1, 1, 0, 0], 54 | [1, 1, 0, 0] 55 | ]; 56 | const path = [ 57 | [0, 3], 58 | [1, 2], 59 | [2, 1], 60 | [3, 0] 61 | ]; 62 | 63 | const result = pathFinding.calculateLinkStartEndCoords(matrix, path); 64 | 65 | expect(result).toBeUndefined(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "declarationDir": "dist/@types", 8 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 9 | }, 10 | "include": ["./src"], 11 | "references": [ 12 | { 13 | "path": "../geometry" 14 | }, 15 | { 16 | "path": "../react-canvas-core" 17 | }, 18 | { 19 | "path": "../react-diagrams-defaults" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-diagrams-routing/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: 'projectstorm/react-diagrams-routing' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/react-diagrams/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | !package.json 4 | dist/tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /packages/react-diagrams/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectstorm/react-diagrams/cd3bdd119376836a0c675be5e3e58ee807cfd0c1/packages/react-diagrams/README.md -------------------------------------------------------------------------------- /packages/react-diagrams/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@projectstorm/react-diagrams", 3 | "version": "7.0.4", 4 | "author": "dylanvorster", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/projectstorm/react-diagrams.git" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf ./dist", 12 | "build": "../../node_modules/.bin/webpack" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "keywords": [ 18 | "web", 19 | "diagram", 20 | "diagrams", 21 | "react", 22 | "typescript", 23 | "flowchart", 24 | "simple", 25 | "links", 26 | "nodes" 27 | ], 28 | "main": "./dist/index.umd.js", 29 | "module": "./dist/index.js", 30 | "typings": "./dist/@types/index", 31 | "dependencies": { 32 | "@projectstorm/react-canvas-core": "workspace:*", 33 | "@projectstorm/react-diagrams-core": "workspace:*", 34 | "@projectstorm/react-diagrams-defaults": "workspace:*", 35 | "@projectstorm/react-diagrams-routing": "workspace:*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/react-diagrams/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultDiagramState, 3 | DiagramEngine, 4 | LinkLayerFactory, 5 | NodeLayerFactory 6 | } from '@projectstorm/react-diagrams-core'; 7 | import { 8 | DefaultLabelFactory, 9 | DefaultLinkFactory, 10 | DefaultNodeFactory, 11 | DefaultPortFactory 12 | } from '@projectstorm/react-diagrams-defaults'; 13 | import { PathFindingLinkFactory } from '@projectstorm/react-diagrams-routing'; 14 | import { SelectionBoxLayerFactory, CanvasEngineOptions } from '@projectstorm/react-canvas-core'; 15 | 16 | export * from '@projectstorm/react-canvas-core'; 17 | export * from '@projectstorm/react-diagrams-core'; 18 | export * from '@projectstorm/react-diagrams-defaults'; 19 | export * from '@projectstorm/react-diagrams-routing'; 20 | 21 | /** 22 | * Construct an engine with the defaults installed 23 | */ 24 | export default (options: CanvasEngineOptions = {}): DiagramEngine => { 25 | const engine = new DiagramEngine(options); 26 | 27 | // register model factories 28 | engine.getLayerFactories().registerFactory(new NodeLayerFactory()); 29 | engine.getLayerFactories().registerFactory(new LinkLayerFactory()); 30 | engine.getLayerFactories().registerFactory(new SelectionBoxLayerFactory()); 31 | 32 | engine.getLabelFactories().registerFactory(new DefaultLabelFactory()); 33 | engine.getNodeFactories().registerFactory(new DefaultNodeFactory()); // i cant figure out why 34 | engine.getLinkFactories().registerFactory(new DefaultLinkFactory()); 35 | engine.getLinkFactories().registerFactory(new PathFindingLinkFactory()); 36 | engine.getPortFactories().registerFactory(new DefaultPortFactory()); 37 | 38 | // register the default interaction behaviours 39 | engine.getStateMachine().pushState(new DefaultDiagramState()); 40 | return engine; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/react-diagrams/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "declarationDir": "dist/@types", 7 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" 8 | }, 9 | "references": [ 10 | { 11 | "path": "../react-canvas-core" 12 | }, 13 | { 14 | "path": "../react-diagrams-defaults" 15 | }, 16 | { 17 | "path": "../react-diagrams-routing" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-diagrams/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../webpack.shared')(__dirname); 2 | module.exports = { 3 | ...config, 4 | output: { 5 | ...config.output, 6 | library: 'projectstorm/react-diagrams' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'diagrams-demo-gallery' 4 | - 'diagrams-demo-project' -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": true, 5 | "composite": true, 6 | "incremental": true, 7 | "strictNullChecks": false, 8 | "sourceMap": true, 9 | "inlineSources": true, 10 | "skipLibCheck": true, 11 | "jsx": "react", 12 | "target": "ES6", 13 | "moduleResolution": "Node", 14 | "module": "es6", 15 | "strict": false, 16 | "lib": [ 17 | "DOM", 18 | "ES6" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./packages/geometry" 6 | }, 7 | { 8 | "path": "./packages/react-canvas-core" 9 | }, 10 | { 11 | "path": "./packages/react-diagrams" 12 | }, 13 | { 14 | "path": "./packages/react-diagrams-core" 15 | }, 16 | { 17 | "path": "./packages/react-diagrams-defaults" 18 | }, 19 | { 20 | "path": "./packages/react-diagrams-routing" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /webpack.shared.js: -------------------------------------------------------------------------------- 1 | const production = process.env.NODE_ENV === 'production'; 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | const path = require('path'); 5 | 6 | module.exports = (directory) => { 7 | return { 8 | entry: path.join(directory, './dist/index.js'), 9 | output: { 10 | filename: 'index.umd.js', 11 | path: path.join(directory, 'dist'), 12 | libraryTarget: 'umd' 13 | }, 14 | externals: [ 15 | nodeExternals({ modulesDir: path.join(directory, 'node_modules') }), 16 | nodeExternals({ modulesDir: path.join(__dirname, 'node_modules') }) 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | enforce: 'pre', 22 | test: /\.js$/, 23 | loader: 'source-map-loader' 24 | } 25 | ] 26 | }, 27 | resolve: { 28 | extensions: ['.tsx', '.ts', '.js'] 29 | }, 30 | devtool: production ? 'source-map' : 'cheap-module-source-map', 31 | mode: production ? 'production' : 'development', 32 | optimization: { 33 | minimizer: [new TerserPlugin()] 34 | } 35 | }; 36 | }; 37 | --------------------------------------------------------------------------------