The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .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 | ![LOL](https://i.pinimg.com/originals/7f/1b/c3/7f1bc3fb2e123dd3255a85c04db22f19.jpg)


--------------------------------------------------------------------------------
/.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 | <style>
2 | 	html, body, #storybook-root {
3 | 		height: 100%;
4 | 	}
5 | </style>


--------------------------------------------------------------------------------
/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<DiagramEngine> {
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<MouseEvent>) => {
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<TouchEvent>) => {
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<MouseEvent>) => {
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 | 		<DemoCanvasWidget>
28 | 			<CanvasWidget engine={engine} />
29 | 		</DemoCanvasWidget>
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<any, any> {
12 | 	constructor(props) {
13 | 		super(props);
14 | 	}
15 | 
16 | 	render() {
17 | 		const { engine } = this.props;
18 | 		return (
19 | 			<DemoWorkspaceWidget>
20 | 				<DemoCanvasWidget>
21 | 					<CanvasWidget engine={engine} />
22 | 				</DemoCanvasWidget>
23 | 			</DemoWorkspaceWidget>
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 <NodeDelayedPosition engine={engine} model={model} />;
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<any, any> {
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 | 			<DemoWorkspaceWidget
27 | 				buttons={[
28 | 					<DemoButton key={1} onClick={this.enableDrag}>
29 | 						Enable canvas drag
30 | 					</DemoButton>,
31 | 					<DemoButton key={2} onClick={this.disableDrag}>
32 | 						Disable canvas drag
33 | 					</DemoButton>
34 | 				]}
35 | 			>
36 | 				<DemoCanvasWidget>
37 | 					<CanvasWidget engine={engine} />
38 | 				</DemoCanvasWidget>
39 | 			</DemoWorkspaceWidget>
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 <CanvasDragToggle engine={engine} model={model} />;
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<any, any> {
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<any>) => {
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 | 			<DemoWorkspaceWidget buttons={<DemoButton onClick={this.cloneSelected}>Clone Selected</DemoButton>}>
47 | 				<DemoCanvasWidget>
48 | 					<CanvasWidget engine={engine} />
49 | 				</DemoCanvasWidget>
50 | 			</DemoWorkspaceWidget>
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 <CloneSelected engine={engine} model={model} />;
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<React.KeyboardEvent>) => {
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<DefaultLinkModel>(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 | 		<DemoCanvasWidget>
70 | 			<CanvasWidget engine={engine} />
71 | 		</DemoCanvasWidget>
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<EditableLabelModel, DiagramEngine> {
10 | 	constructor() {
11 | 		super('editable-label');
12 | 	}
13 | 
14 | 	generateModel(): EditableLabelModel {
15 | 		return new EditableLabelModel();
16 | 	}
17 | 
18 | 	generateReactWidget(event: GenerateWidgetEvent<EditableLabelModel>): JSX.Element {
19 | 		return <EditableLabelWidget model={event.model} />;
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<this>): 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<FlowAliasLabelWidgetProps> = (props) => {
21 | 	const [str, setStr] = React.useState(props.model.value);
22 | 
23 | 	return (
24 | 		<S.Label>
25 | 			<input
26 | 				value={str}
27 | 				onChange={(event) => {
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 | 			<button onClick={() => action('model eventDidFire')('You clicked the button')}>Click me!</button>
38 | 		</S.Label>
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 | 		<DemoCanvasWidget>
52 | 			<CanvasWidget engine={engine} />
53 | 		</DemoCanvasWidget>
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<DiamondNodeModel, DiagramEngine> {
 9 | 	constructor() {
10 | 		super('diamond');
11 | 	}
12 | 
13 | 	generateReactWidget(event): JSX.Element {
14 | 		return <DiamondNodeWidget engine={this.engine} size={50} node={event.model} />;
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<NodeModelGenerics & DiamondNodeModelGenerics> {
 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<PortModel, DiagramEngine> {
 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 | 		<DemoCanvasWidget>
65 | 			<CanvasWidget engine={engine} />
66 | 		</DemoCanvasWidget>
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<DefaultLinkModel>(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 | 		<DemoCanvasWidget>
32 | 			<CanvasWidget engine={engine} />
33 | 		</DemoCanvasWidget>
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<BodyWidgetProps> {
46 | 	render() {
47 | 		return (
48 | 			<S.Body>
49 | 				<S.Header>
50 | 					<div className="title">Storm React Diagrams - DnD demo</div>
51 | 				</S.Header>
52 | 				<S.Content>
53 | 					<TrayWidget>
54 | 						<TrayItemWidget model={{ type: 'in' }} name="In Node" color="rgb(192,255,0)" />
55 | 						<TrayItemWidget model={{ type: 'out' }} name="Out Node" color="rgb(0,192,255)" />
56 | 					</TrayWidget>
57 | 					<S.Layer
58 | 						onDrop={(event) => {
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 | 						<DemoCanvasWidget>
80 | 							<CanvasWidget engine={this.props.app.getDiagramEngine()} />
81 | 						</DemoCanvasWidget>
82 | 					</S.Layer>
83 | 				</S.Content>
84 | 			</S.Body>
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<TrayItemWidgetProps> {
24 | 	render() {
25 | 		return (
26 | 			<S.Tray
27 | 				color={this.props.color}
28 | 				draggable={true}
29 | 				onDragStart={(event) => {
30 | 					event.dataTransfer.setData('storm-diagram-node', JSON.stringify(this.props.model));
31 | 				}}
32 | 				className="tray-item"
33 | 			>
34 | 				{this.props.name}
35 | 			</S.Tray>
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 <S.Tray>{this.props.children}</S.Tray>;
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 <BodyWidget app={app} />;
 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 | 			<DemoWorkspaceWidget buttons={<DemoButton onClick={this.addPorts}>Add more ports</DemoButton>}>
25 | 				<DemoCanvasWidget>
26 | 					<CanvasWidget engine={engine} />
27 | 				</DemoCanvasWidget>
28 | 			</DemoWorkspaceWidget>
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 <CloneSelected engine={engine} model={model} />;
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 | 		<DemoCanvasWidget>
39 | 			<CanvasWidget engine={engine} />
40 | 		</DemoCanvasWidget>
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 | 		<DemoWorkspaceWidget
52 | 			buttons={
53 | 				<DemoButton
54 | 					onClick={() => {
55 | 						action('Serialized Graph')(JSON.stringify(model.serializeDiagram(), null, 2));
56 | 					}}
57 | 				>
58 | 					Serialize Graph
59 | 				</DemoButton>
60 | 			}
61 | 		>
62 | 			<DemoCanvasWidget>
63 | 				<CanvasWidget engine={engine} />
64 | 			</DemoCanvasWidget>
65 | 		</DemoWorkspaceWidget>
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 | 		<DemoCanvasWidget>
51 | 			<CanvasWidget engine={engine} />
52 | 		</DemoCanvasWidget>
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 | 		<DemoCanvasWidget>
57 | 			<CanvasWidget engine={engine} />
58 | 		</DemoCanvasWidget>
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<any, any> {
12 | 	render() {
13 | 		const { engine } = this.props;
14 | 		return (
15 | 			<DemoCanvasWidget>
16 | 				<CanvasWidget engine={engine} />
17 | 			</DemoCanvasWidget>
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 <CanvasPanAndZoomToggle engine={engine} model={model} />;
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 | 		<DemoCanvasWidget>
32 | 			<CanvasWidget engine={engine} />
33 | 		</DemoCanvasWidget>
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<LinkModel>) {
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 | 		<DemoWorkspaceWidget
57 | 			buttons={
58 | 				<DemoButton
59 | 					onClick={() => {
60 | 						action('Serialized Graph')(JSON.stringify(model.serialize(), null, 2));
61 | 					}}
62 | 				>
63 | 					Serialize Graph
64 | 				</DemoButton>
65 | 			}
66 | 		>
67 | 			<DemoCanvasWidget>
68 | 				<CanvasWidget engine={engine} />
69 | 			</DemoCanvasWidget>
70 | 		</DemoWorkspaceWidget>
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 | 		<DemoWorkspaceWidget
48 | 			buttons={
49 | 				<DemoButton
50 | 					onClick={() => {
51 | 						action('Serialized Graph')(beautify(model2.serialize(), null, 2, 80));
52 | 					}}
53 | 				>
54 | 					Serialize Graph
55 | 				</DemoButton>
56 | 			}
57 | 		>
58 | 			<DemoCanvasWidget>
59 | 				<CanvasWidget engine={engine} />
60 | 			</DemoCanvasWidget>
61 | 		</DemoWorkspaceWidget>
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 | 		<DemoCanvasWidget>
47 | 			<CanvasWidget engine={engine} />
48 | 		</DemoCanvasWidget>
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<DefaultLinkModel>(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 | 		<DemoCanvasWidget>
40 | 			<CanvasWidget engine={engine} />
41 | 		</DemoCanvasWidget>
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>(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 | 		<DemoWorkspaceWidget
59 | 			buttons={
60 | 				<DemoButton
61 | 					onClick={() => {
62 | 						action('Serialized Graph')(JSON.stringify(model.serialize(), null, 2));
63 | 					}}
64 | 				>
65 | 					Serialize Graph
66 | 				</DemoButton>
67 | 			}
68 | 		>
69 | 			<DemoCanvasWidget>
70 | 				<CanvasWidget engine={engine} />
71 | 			</DemoCanvasWidget>
72 | 		</DemoWorkspaceWidget>
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 | 		<DemoWorkspaceWidget
32 | 			buttons={<DemoButton onClick={() => engine.zoomToFitSelectedNodes(50)}>Zoom to fit</DemoButton>}
33 | 		>
34 | 			<DemoCanvasWidget>
35 | 				<CanvasWidget engine={engine} />
36 | 			</DemoCanvasWidget>
37 | 		</DemoWorkspaceWidget>
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 | 		<DemoWorkspaceWidget buttons={<DemoButton onClick={() => engine.zoomToFit()}>Zoom to fit</DemoButton>}>
32 | 			<DemoCanvasWidget>
33 | 				<CanvasWidget engine={engine} />
34 | 			</DemoCanvasWidget>
35 | 		</DemoWorkspaceWidget>
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<React.PropsWithChildren<DemoCanvasWidgetProps>> {
59 | 	render() {
60 | 		return (
61 | 			<>
62 | 				<Global styles={S.Expand} />
63 | 				<S.Container
64 | 					background={this.props.background || 'rgb(60, 60, 60)'}
65 | 					color={this.props.color || 'rgba(255,255,255, 0.05)'}
66 | 				>
67 | 					{this.props.children}
68 | 				</S.Container>
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<React.PropsWithChildren<DemoWorkspaceWidgetProps>> {
47 | 	render() {
48 | 		return (
49 | 			<S.Container>
50 | 				<S.Toolbar>{this.props.buttons}</S.Toolbar>
51 | 				<S.Content>{this.props.children}</S.Content>
52 | 			</S.Container>
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 | ![](./screenshot.png)
 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 | <!DOCTYPE html>
 2 | <html lang="en">
 3 | <head>
 4 | 	<meta charset="UTF-8"/>
 5 | 	<meta name="viewport" content="width=device-width, initial-scale=1"/>
 6 | 	<title>Project STORM | React Diagrams demo</title>
 7 | 	<script src="bundle.js"></script>
 8 | </head>
 9 | <body>
10 | <div id="application"/>
11 | </body>
12 | </html>
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<BodyWidgetProps> {
 9 | 	render() {
10 | 		return <CanvasWidget className="diagram-container" engine={this.props.engine} />;
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 <JSCustomNodeWidget engine={this.engine} node={event.model} />;
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 | 			<div className="custom-node">
 8 | 				<PortWidget engine={this.props.engine} port={this.props.node.getPort('in')}>
 9 | 					<div className="circle-port" />
10 | 				</PortWidget>
11 | 				<PortWidget engine={this.props.engine} port={this.props.node.getPort('out')}>
12 | 					<div className="circle-port" />
13 | 				</PortWidget>
14 | 				<div className="custom-node-color" style={{ backgroundColor: this.props.node.color }} />
15 | 			</div>
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<TSCustomNodeModel, DiagramEngine> {
 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 <TSCustomNodeWidget engine={this.engine as DiagramEngine} node={event.model} />;
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<TSCustomNodeWidgetProps, TSCustomNodeWidgetState> {
13 | 	constructor(props: TSCustomNodeWidgetProps) {
14 | 		super(props);
15 | 		this.state = {};
16 | 	}
17 | 
18 | 	render() {
19 | 		return (
20 | 			<div className="custom-node">
21 | 				<PortWidget engine={this.props.engine} port={this.props.node.getPort('in')}>
22 | 					<div className="circle-port" />
23 | 				</PortWidget>
24 | 				<PortWidget engine={this.props.engine} port={this.props.node.getPort('out')}>
25 | 					<div className="circle-port" />
26 | 				</PortWidget>
27 | 				<div className="custom-node-color" style={{ backgroundColor: this.props.node.color }} />
28 | 			</div>
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(<BodyWidget engine={engine} />);
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 | ![](./images/custom-link.png)
 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 | 			<g>
43 | 				<AdvancedLinkSegment model={model} path={path} />
44 | 			</g>
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<DefaultLinkModel>(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 | <CanvasWidget engine={engine} />
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<KeyboardEvent>) => {
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<WheelEvent>) => {
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<WheelEvent>) => {
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<Event extends SyntheticEvent = SyntheticEvent, Model extends BaseModel = BaseModel> {
31 | 	event: Event;
32 | 	model?: Model;
33 | }
34 | 
35 | export interface ActionOptions {
36 | 	type: InputType;
37 | 	fire: (event: ActionEvent<Mapping[this['type']]>) => void;
38 | }
39 | 
40 | export class Action<T extends CanvasEngine = CanvasEngine> {
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<MouseEvent>): 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<BaseModel> & { isSelected: boolean }): void;
 13 | 
 14 | 	entityRemoved?(event: BaseEntityEvent<BaseModel>): 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<G extends BaseModelGenerics = BaseModelGenerics> extends BaseEntity<G> {
 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<BaseModel> {
 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<this>) {
 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<BasePositionModel>): 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<G extends BasePositionModelGenerics = BasePositionModelGenerics>
20 | 	extends BaseModel<G>
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<this>) {
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<StateMachineListener> {
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<E extends CanvasEngine = CanvasEngine> {
 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<E> {
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<T extends BaseModel> {
 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<T, E> {
17 | 	/**
18 | 	 * Generates React widgets from the model contained in the event object
19 | 	 */
20 | 	abstract generateReactWidget(event: GenerateWidgetEvent<T>): 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<F extends AbstractFactory = AbstractFactory> 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<F> = FactoryBankListener<F>
23 | > extends BaseObserver<L> {
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<T extends F = F>(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<SmartLayerWidgetProps> {
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<React.PropsWithChildren<TransformLayerWidgetProps>> {
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 <S.SvgLayer style={this.getTransformStyle()}>{this.props.children}</S.SvgLayer>;
64 | 		}
65 | 		return <S.DivLayer style={this.getTransformStyle()}>{this.props.children}</S.DivLayer>;
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<SelectionLayerModel> {
 9 | 	constructor() {
10 | 		super('selection');
11 | 	}
12 | 
13 | 	generateModel(event: GenerateModelEvent): SelectionLayerModel {
14 | 		return new SelectionLayerModel();
15 | 	}
16 | 
17 | 	generateReactWidget(event: GenerateWidgetEvent<SelectionLayerModel>): JSX.Element {
18 | 		return <SelectionBoxWidget rect={event.model.box} />;
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<SelectionBoxWidgetProps> {
18 | 	render() {
19 | 		const { rect } = this.props;
20 | 
21 | 		if (!rect) return null;
22 | 
23 | 		return (
24 | 			<S.Container
25 | 				style={{
26 | 					top: rect.top,
27 | 					left: rect.left,
28 | 					width: rect.width,
29 | 					height: rect.height
30 | 				}}
31 | 			/>
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<AbstractModelFactory<BaseModel>> {
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<MouseEvent>) => {
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<TouchEvent>) => {
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<E extends CanvasEngine = CanvasEngine> extends AbstractDisplacementState<E> {
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<E extends CanvasEngine = CanvasEngine> extends AbstractDisplacementState<E> {
 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<React.MouseEvent>) => {
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<E extends CanvasEngine = CanvasEngine> extends State<E> {
 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<MouseEvent>) => {
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<PeformanceWidgetProps, PeformanceWidgetState> {
15 | 	shouldComponentUpdate(
16 | 		nextProps: Readonly<PeformanceWidgetProps>,
17 | 		nextState: Readonly<PeformanceWidgetState>,
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<G extends LabelModelGenerics = LabelModelGenerics> extends BaseModel<G> {
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<this>) {
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<LinkLayerModel, DiagramEngine> {
 9 | 	constructor() {
10 | 		super('diagram-links');
11 | 	}
12 | 
13 | 	generateModel(event: GenerateModelEvent): LinkLayerModel {
14 | 		return new LinkLayerModel();
15 | 	}
16 | 
17 | 	generateReactWidget(event: GenerateWidgetEvent<LinkLayerModel>): JSX.Element {
18 | 		return <LinkLayerWidget layer={event.model} engine={this.engine} />;
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<G extends LinkLayerModelGenerics = LinkLayerModelGenerics> extends LayerModel<G> {
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<LinkLayerWidgetProps> {
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 <LinkWidget key={link.getID()} link={link} diagramEngine={this.props.engine} />;
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<BasePositionModelOptions, 'type'> {
10 | 	link: LinkModel;
11 | }
12 | 
13 | export interface PointModelGenerics {
14 | 	PARENT: LinkModel;
15 | 	OPTIONS: PointModelOptions;
16 | 	LISTENER: BaseModelListener;
17 | }
18 | 
19 | export class PointModel<G extends PointModelGenerics = PointModelGenerics> 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<NodeLayerModel, DiagramEngine> {
 9 | 	constructor() {
10 | 		super('diagram-nodes');
11 | 	}
12 | 
13 | 	generateModel(event: GenerateModelEvent): NodeLayerModel {
14 | 		return new NodeLayerModel();
15 | 	}
16 | 
17 | 	generateReactWidget(event: GenerateWidgetEvent<NodeLayerModel>): JSX.Element {
18 | 		return <NodeLayerWidget layer={event.model} engine={this.engine} />;
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<G extends NodeLayerModelGenerics = NodeLayerModelGenerics> extends LayerModel<G> {
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<NodeLayerWidgetProps> {
14 | 	render() {
15 | 		return (
16 | 			<>
17 | 				{_map(this.props.layer.getNodes(), (node: NodeModel) => {
18 | 					return <NodeWidget key={node.getID()} diagramEngine={this.props.engine} node={node} />;
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<React.PropsWithChildren<PortProps>> {
15 | 	ref: React.RefObject<HTMLDivElement>;
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<PortProps>, 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 | 			<div
61 | 				style={this.props.style}
62 | 				ref={this.ref}
63 | 				className={`port ${this.props.className || ''}`}
64 | 				data-name={this.props.port.getName()}
65 | 				data-nodeid={this.props.port.getNode().getID()}
66 | 				{...this.getExtraProps()}
67 | 			>
68 | 				{this.props.children}
69 | 			</div>
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<DiagramEngine> {
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<MouseEvent>) => {
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<TouchEvent>) => {
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<E extends DiagramEngine = DiagramEngine> extends MoveItemsState<E> {
10 | 	constructor() {
11 | 		super();
12 | 		this.registerAction(
13 | 			new Action({
14 | 				type: InputType.MOUSE_UP,
15 | 				fire: (event: ActionEvent<MouseEvent>) => {
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<DefaultLabelModel, DiagramEngine> {
12 | 	constructor() {
13 | 		super('default');
14 | 	}
15 | 
16 | 	generateReactWidget(event): JSX.Element {
17 | 		return <DefaultLabelWidget model={event.model} />;
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<DefaultLabelModelGenerics> {
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<this>) {
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<DefaultLabelWidgetProps> {
22 | 	render() {
23 | 		return <S.Label>{this.props.model.getOptions().label}</S.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<Link extends DefaultLinkModel = DefaultLinkModel> extends AbstractReactFactory<
33 | 	Link,
34 | 	DiagramEngine
35 | > {
36 | 	constructor(type = 'default') {
37 | 		super(type);
38 | 	}
39 | 
40 | 	generateReactWidget(event): JSX.Element {
41 | 		return <DefaultLinkWidget link={event.model} diagramEngine={this.engine} />;
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 | 			<S.Path
51 | 				selected={selected}
52 | 				stroke={selected ? model.getOptions().selectedColor : model.getOptions().color}
53 | 				strokeWidth={model.getOptions().width}
54 | 				d={path}
55 | 			/>
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<DefaultLinkPointWidgetProps, DefaultLinkPointWidgetState> {
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 | 			<g>
33 | 				<circle
34 | 					cx={point.getPosition().x}
35 | 					cy={point.getPosition().y}
36 | 					r={5}
37 | 					fill={this.state.selected || this.props.point.isSelected() ? this.props.colorSelected : this.props.color}
38 | 				/>
39 | 				<S.PointTop
40 | 					className="point"
41 | 					onMouseLeave={() => {
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 | 			</g>
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<SVGPathElement | null>;
11 | 	factory: DefaultLinkFactory;
12 | 	diagramEngine: DiagramEngine;
13 | 	onSelection: (selected: boolean) => any;
14 | 	extras: object;
15 | }
16 | 
17 | export class DefaultLinkSegmentWidget extends React.Component<DefaultLinkSegmentWidgetProps> {
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 | 			<g>
54 | 				{Bottom}
55 | 				{Top}
56 | 			</g>
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<DefaultNodeModel, DiagramEngine> {
 9 | 	constructor() {
10 | 		super('default');
11 | 	}
12 | 
13 | 	generateReactWidget(event): JSX.Element {
14 | 		return <DefaultNodeWidget engine={this.engine} node={event.model} />;
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<DefaultNodeProps> {
62 | 	generatePort = (port) => {
63 | 		return <DefaultPortLabel engine={this.props.engine} port={port} key={port.getID()} />;
64 | 	};
65 | 
66 | 	render() {
67 | 		return (
68 | 			<S.Node
69 | 				data-default-node-name={this.props.node.getOptions().name}
70 | 				selected={this.props.node.isSelected()}
71 | 				background={this.props.node.getOptions().color}
72 | 			>
73 | 				<S.Title>
74 | 					<S.TitleName>{this.props.node.getOptions().name}</S.TitleName>
75 | 				</S.Title>
76 | 				<S.Ports>
77 | 					<S.PortsContainer>{_map(this.props.node.getInPorts(), this.generatePort)}</S.PortsContainer>
78 | 					<S.PortsContainer>{_map(this.props.node.getOutPorts(), this.generatePort)}</S.PortsContainer>
79 | 				</S.Ports>
80 | 			</S.Node>
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<DefaultPortModel, DiagramEngine> {
 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<DefaultPortLabelProps> {
35 | 	render() {
36 | 		const port = (
37 | 			<PortWidget engine={this.props.engine} port={this.props.port}>
38 | 				<S.Port />
39 | 			</PortWidget>
40 | 		);
41 | 		const label = <S.Label>{this.props.port.getOptions().label}</S.Label>;
42 | 
43 | 		return (
44 | 			<S.PortLabel>
45 | 				{this.props.port.getOptions().in ? port : label}
46 | 				{this.props.port.getOptions().in ? label : port}
47 | 			</S.PortLabel>
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<DefaultPortModelGenerics> {
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<this>) {
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<T extends LinkModel>(port: PortModel, factory?: AbstractModelFactory<T>): 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>): 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?
#39;: '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<RightAngleLinkModel> {
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 <RightAngleLinkWidget diagramEngine={this.engine} link={event.model} factory={this} />;
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<P extends PointModel>(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<this>) {
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 | 


--------------------------------------------------------------------------------