Compare commits

..

78 Commits

Author SHA1 Message Date
Dylan Vorster
e59edefbeb v6.0.1-beta.1 2019-08-17 11:30:44 +02:00
Dylan Vorster
27022d2965 Fixed up link selecting 2019-08-17 11:27:54 +02:00
Dylan Vorster
f0242c7924 add listeners to root model in demo 2019-08-17 11:05:48 +02:00
Dylan Vorster
cf9f9cb01d Merge pull request #401 from zacharyzhy/patch-1
Export DragDiagramItemsState
2019-08-17 10:52:40 +02:00
Dylan Vorster
4c90895bcd Merge pull request #398 from flomllr/patch-2
Update using-the-library.md
2019-08-17 10:52:26 +02:00
zacharyzhy
3f6d04521d Export DragDiagramItemsState
Export DragDiagramItemsState in the react-diagrams-core's index
2019-08-15 16:03:25 -07:00
Florian Müller
3a46047ee8 Update using-the-library.md 2019-08-15 18:58:50 +02:00
Dylan Vorster
77418ee8e7 v6.0.1-beta.0 2019-08-13 22:46:26 +02:00
Dylan Vorster
07c74d850f v6.0.0-beta.2 2019-08-13 22:10:22 +02:00
Dylan Vorster
75d1b6bb5d Merge pull request #387 from DanieLazarLDAPPS/features/labview-style-of-routes
Features/labview style of routes
2019-08-13 00:07:26 +02:00
Daniel Lazar
4cae28c808 of course there must be || to check positions if moving is too fast 2019-08-12 19:45:43 +02:00
Daniel Lazar
cff54426c9 update for new links ... port model needs RightAngleLinkModel 2019-08-12 12:57:04 +02:00
Daniel Lazar
3dd329b209 migrate to new six version 2019-08-12 11:59:50 +02:00
Daniel Lazar
6396abc17c remove node5 2019-08-12 09:31:44 +02:00
Daniel Lazar
16f6b484b5 renamed due to restriction with labview 2019-08-12 09:31:44 +02:00
Daniel Lazar
68c2c76d72 Right angles routes added 2019-08-12 09:31:30 +02:00
Daniel Lazar
16033a658c issue-277 few null checks to avoid crash when node went under bottom of screen 2019-08-12 09:31:08 +02:00
Dylan Vorster
96655baff2 v6.0.0-beta.1 2019-08-11 17:20:32 +02:00
Dylan Vorster
630467cf97 small display issue with ports 2019-08-11 17:19:15 +02:00
Dylan Vorster
23b174736c more info on ports 2019-08-11 15:49:09 +02:00
Dylan Vorster
9de0708535 more custom link docs 2019-08-11 15:41:02 +02:00
Dylan Vorster
4786208fe6 more port docs 2019-08-11 13:25:35 +02:00
Dylan Vorster
98888f6d96 more fixes 2019-08-11 13:20:02 +02:00
Dylan Vorster
f017873cdc docs 2019-08-11 13:17:09 +02:00
Dylan Vorster
d14f1a3bb3 Fixed doc issue 2019-08-11 11:16:53 +02:00
Dylan Vorster
cdda07b026 trying to fix gitbook 2019-08-11 11:12:30 +02:00
Dylan Vorster
fa6bebce6f upset 2019-08-11 11:07:22 +02:00
Dylan Vorster
04362b8da8 lets try this again -_- 2019-08-11 10:52:53 +02:00
Dylan Vorster
0dbdee9f18 gitbook y 2019-08-11 10:50:17 +02:00
Dylan Vorster
a5c4d917eb Revert "gitbook"
This reverts commit 962883ce4a.
2019-08-11 10:47:41 +02:00
Dylan Vorster
93cc419767 GitBook: [master] 2 pages modified 2019-08-11 08:30:40 +00:00
Dylan Vorster
7a7fef85d0 GitBook: [master] 7 pages and 4 assets modified 2019-08-11 08:24:24 +00:00
Dylan Vorster
4eccdbdc8a restructure 2019-08-11 10:20:22 +02:00
Dylan Vorster
962883ce4a gitbook 2019-08-11 10:18:43 +02:00
Dylan Vorster
6571dbe159 GitBook: [master] 12 pages and 4 assets modified 2019-08-11 08:14:28 +00:00
Dylan Vorster
8fbd87df8c Readme 2019-08-11 09:47:02 +02:00
Dylan Vorster
ce38f77027 Merge pull request #373 from projectstorm/lerna
Version 6 [Lerna, rearchitect, better design]
2019-08-11 01:11:21 +02:00
Dylan Vorster
bec82cd92c more readme 2019-08-11 01:06:15 +02:00
Dylan Vorster
d2c5bac65c v6.0.0-beta.0 2019-08-11 00:57:16 +02:00
Dylan Vorster
5fc17b6217 changelog 2019-08-11 00:53:52 +02:00
Dylan Vorster
d2e1cf2dd1 yaml is a piece of s*** 2019-08-11 00:35:56 +02:00
Dylan Vorster
9e2f6ffb0f sigh 2019-08-11 00:35:00 +02:00
Dylan Vorster
cc900b2a90 sigh 2019-08-11 00:32:39 +02:00
Dylan Vorster
51c6bd2391 fix tests 2019-08-11 00:31:49 +02:00
Dylan Vorster
a8c6031166 more restructuring 2019-08-10 22:33:06 +02:00
Dylan Vorster
a6655dbf2d more restructuring 2019-08-10 22:30:01 +02:00
Dylan Vorster
37532edd01 more readme 2019-08-10 22:24:46 +02:00
Dylan Vorster
4538884a96 Readme 2019-08-10 21:59:49 +02:00
Dylan Vorster
c9f7d2c516 ITS DONE 2019-08-10 21:18:29 +02:00
Dylan Vorster
5c66c84a31 I think we done 2019-08-10 16:58:56 +02:00
Dylan Vorster
4a57ecdc97 lik 99% there 2019-08-10 16:37:40 +02:00
Dylan Vorster
9fc65b7d7f added some nice apis to improve rendering and such 2019-08-10 15:04:43 +02:00
Dylan Vorster
2573c5d8cc Fixed up the custom node demo 2019-08-10 14:27:54 +02:00
Dylan Vorster
a46e17306b serialization is almost working again 2019-08-10 14:08:28 +02:00
Dylan Vorster
7045d75ae7 reworked deserialization 2019-08-10 12:46:59 +02:00
Dylan Vorster
47e4a14520 fixing serialization 2019-08-10 12:01:08 +02:00
Dylan Vorster
58eee117e9 we have parity with dragging and creating new links 2019-08-09 00:03:42 +02:00
Dylan Vorster
09d5b65e59 got more parity with moving points and such 2019-08-07 21:50:41 +02:00
Dylan Vorster
6512ca0eb0 Merge pull request #385 from flomllr/patch-1
Update Getting Started.md
2019-08-07 12:36:07 +02:00
Florian Müller
bfde4226ec Update Getting Started.md
storm-react-diagrams to @projectstorm/react-diagrams
2019-08-07 12:01:52 +02:00
Dylan Vorster
ea6adbe61b more parity 2019-08-06 23:56:17 +02:00
Dylan Vorster
5f74600157 some more parity work 2019-08-06 23:50:50 +02:00
Dylan Vorster
3590245f6c moving items works again 2019-08-05 00:24:43 +02:00
Dylan Vorster
46aa99b2ea selections are now event driven 2019-08-04 18:12:24 +02:00
Dylan Vorster
80f86867fd dragging and stuff is working again 2019-08-04 17:51:03 +02:00
Dylan Vorster
52f9105b1b state machines yay 2019-08-04 17:25:56 +02:00
Dylan Vorster
e9558919fb pretty 2019-08-04 13:28:38 +02:00
Dylan Vorster
dd5a0b7d8d break it up further into canvas 2019-08-04 13:27:55 +02:00
Dylan Vorster
0f7930e132 some minor things 2019-08-02 23:55:28 +02:00
Dylan Vorster
3823adc8e8 v6.0.0-alpha.4.2 2019-07-31 23:58:04 +02:00
Dylan Vorster
fce011a11b Merge pull request #379 from dudenamedjune/lerna
fixed path issue in lib-routing/src/link/PathFindingLinkWidget.tsx
2019-07-31 18:39:50 +02:00
dudenamedjune
4962343b54 fixed path issue in lib-routing/src/link/PathFindingLinkWidget.tsx 2019-07-31 11:22:29 -05:00
Dylan Vorster
45649146c9 v6.0.0-alpha.4.1 2019-07-30 22:53:42 +02:00
Dylan Vorster
74e24331ed fix flow demo 2019-07-30 22:48:57 +02:00
Dylan Vorster
0cc469fa44 storybook 2019-07-30 22:43:29 +02:00
Dylan Vorster
0b78ba69c6 fix up the builds 2019-07-30 22:36:44 +02:00
Dylan Vorster
ca301b9d4d v6.0.0-alpha.4.0 2019-07-30 22:23:18 +02:00
Dylan Vorster
ad6c77da28 v6.0.0-alpha.3.0 2019-07-30 22:15:19 +02:00
249 changed files with 4456 additions and 3243 deletions

View File

@@ -8,7 +8,7 @@ jobs:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "yarn.lock" }}
- v1-dependencies-{{ checksum "yarn.lock" }}
- run: yarn install
- save_cache:
paths:
@@ -19,4 +19,5 @@ jobs:
- run: yarn run build
# test e2e tests and jest snapshots
- run: yarn run test:ci
- run: cd packages/diagrams-demo-gallery && yarn run test --ci
- run: cd packages/react-diagrams-routing && yarn run test --ci

View File

@@ -4,6 +4,6 @@ indent_size = 2
trim_trailing_whitespace = true
# Some exceptions
[{package.json}]
[{package.json,*.yml}]
indent_style = space
indent_size = 2

4
.gitbook.yaml Normal file
View File

@@ -0,0 +1,4 @@
root: ./docs/
structure:
summary: README.md

View File

Before

Width:  |  Height:  |  Size: 438 KiB

After

Width:  |  Height:  |  Size: 438 KiB

View File

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 313 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

BIN
.gitbook/assets/logo.jpg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -1,7 +1,34 @@
__6.0.0__
* (maintenance) move to Lerna and break up the library
* (api)(breaking) smart routing is now a Link Factory
Note: This is a complete rewrite of the library, a good place to start to see how the new system works
is with the new demo project which illustrates the new capability.
I would also recommend taking a look at the new updated DiamondPort widget which shows more capability.
* Break up library into monorepo
* Introduce react-canvas-core as a new framework
* Use geometry classes instead of raw X and Y primitives so we can do matrix stuff in the future
* move testing framework to a name based system instead of ID's
* Introduce multiple layers (can now have multiple node and link layers)
* Rewrote the deserialization system to be promise based
* Completely overhauled the observer framework on the models
* Moved all the logic in the DiagramWidget into a a new hierarchical state machine
* Introduces new states for editing
* Introduced faster layout rendering when transforming the canvas directly
* Moved all canvas smart routing into its own link-type under routing package
* Broke up link rendering into a much more modular system that is much easier to extend
* Introduced port alignment allowing the developer to specify how enter it
* Improved generics throughout the entire model system with Mapped Types
* Rewrote all the styles using emotion instead of sass
* Fixed up all the demos to use the new API
* Introduced a demo project that illustrates how to use the library with ES6 as well as with Typescript
* Improved the grid rendering system to allow graphical elements to specify how they get transformed
* Introduced a performance widget for improving performance in a more deterministic way by comparing the serialization of the model (with a way of opting out)
* Renamed a bunch of methods to be more consistent and more understandable
* Completely removed the double render state system that required nodes to render before links, this is done when ports report their new positions
* Ports can now dynamically be added and removed without having to tell the system it happeend
* Port widgets are now containers dumb containers for you own ports
* Port widgets report new sizing information to their target links when they change position, you no longer need to invalidate them
__5.3.2__

View File

@@ -1,78 +1,94 @@
# STORM React Diagrams
# Introduction
__PSA 2018__: React Diagrams ~is currently~ was getting a bit of a rewrite to enable much more advanced features. To see the new foundation WIP visit [https://github.com/projectstorm/react-canvas](https://github.com/projectstorm/react-canvas).
[![Join the chat at https://gitter.im/projectstorm/react-diagrams](https://badges.gitter.im/projectstorm/react-diagrams.svg)](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![NPM](https://img.shields.io/npm/v/@projectstorm/react-diagrams.svg)](https://npmjs.org/package/@projectstorm/react-diagrams) [![Package Quality](https://npm.packagequality.com/shield/storm-react-diagrams.svg)](https://packagequality.com/#?package=storm-react-diagrams) [![CircleCI](https://circleci.com/gh/projectstorm/react-diagrams/tree/master.svg?style=svg)](https://circleci.com/gh/projectstorm/react-diagrams/tree/master) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)
__PSA 2019__: I still want to jump onto the rewrite, but it is a much larger project than anticipated, so going to try maintain this one in the mean time.
![](.gitbook/assets/logo.jpg)
---
[pssst! Looking for the old version 5?](https://github.com/projectstorm/react-diagrams/tree/v5.3.2)
**DEMO**: [http://projectstorm.cloud/react-diagrams](http://projectstorm.cloud/react-diagrams)
**(SOME) DOCS:** [https://projectstorm.gitbooks.io/react-diagrams](https://projectstorm.gitbooks.io/react-diagrams)
**DOCS \(wip\)** [https://projectstorm.gitbook.io/react-diagrams](https://projectstorm.gitbook.io/react-diagrams)
**RELEASE NOTES** : [http://dylanv.blog/2018/03/03/storm-react-diagrams-5-0-0/](http://dylanv.blog/2018/03/03/storm-react-diagrams-5-0-0/)
## \[! BETA NOTICE !\]
A super simple, no-nonsense diagramming library written in React that just works.
Version 6 is now in Beta, please let us know if you find anything inconsistent so we can try and get ready for a final release. Everything in master here represents 6.0, [if you are looking for the stable and old version 5, please go here](https://github.com/projectstorm/react-diagrams/tree/v5.3.2) :\)
[![Join the chat at https://gitter.im/projectstorm/react-diagrams](https://badges.gitter.im/projectstorm/react-diagrams.svg)](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![NPM](https://img.shields.io/npm/v/@projectstorm/react-diagrams.svg)](https://npmjs.org/package/@projectstorm/react-diagrams) [![NPM](https://img.shields.io/npm/dt/storm-react-diagrams.svg)](https://npmjs.org/package/storm-react-diagrams) [![Package Quality](http://npm.packagequality.com/shield/storm-react-diagrams.svg)](http://packagequality.com/#?package=storm-react-diagrams) [![CircleCI](https://circleci.com/gh/projectstorm/react-diagrams/tree/master.svg?style=svg)](https://circleci.com/gh/projectstorm/react-diagrams/tree/master) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)
Docs are currently being worked on, along with a migration path.
## What
Example implementation using custom models: (Dylan's personal code)
A flow & process orientated diagramming library inspired by **Blender**, **Labview** and **Unreal engine**.
* **Modern Codebase** written entirely in Typescript and React, the library makes use of powerful generics, advanced software engineering principles and is broken up into multiple modules.
* **Hackable and extensible** the entire library including its core can be extended, rewired and re-assembled into fundamentally different software to suit your own software needs.
* **HTML nodes as a first class citizen** the library was originally written to represent advanced dynamic nodes, that are difficult to represent as SVG's due to complex input requirements ux requirements.
* **Designed for process** the library is aimed for software engineers that want to rewire their programs at runtime, and that want to make their software more dynamic.
* **Fast diagram editing** the defaults provided give the heighest priority to editing diagrams as fast as possible.
## Gallery
Example implementation using custom models: \(Dylan's personal code\)
![Personal Project](.gitbook/assets/example1.jpg)
![](.gitbook/assets/example2.jpg)
![Personal Project](./docs/images/example1.jpg)
![](./docs/images/example2.jpg)
Get started with the default models right out of the box:
![](./docs/images/example3.jpg)
## Introduction
A no-nonsense diagramming library written entirely in React with the help of a few small libraries. It aims to be:
* Simple, and void of any fuss/complications when implementing it into your own application
* Customizable without having to hack the core \(adapters/factories etc..\)
* Simple to operate and understand without sugar and magic
* Fast and optimized to handle large diagrams with hundreds of nodes/links
* Use HTML to create nodes, instead of SVG's
* Super easy to use, and should work as you expect it to
* Perfect for creating declarative systems such as programmatic pipelines and visual programming languages (Labview, Symlink etc..)
![](docs/images/example3.jpg)
## Installing
For all the bells and whistles:
yarn add @projectstorm/react-diagrams
```text
yarn add @projectstorm/react-diagrams@next
```
This includes all the packages listed below (and works like it used to before version 6.0)
This includes all the packages listed below \(and works \(mostly and conceptually\) like it used to in version 5.0\)
### A more modular approach
This library now has a more modular design and you can import just the core (contains no default factories or routing)
This library now has a more modular design and you can import just the core \(contains no default factories or routing\)
yarn add @projectstorm/react-diagrams-core
```text
yarn add @projectstorm/react-diagrams-core@next
```
and add some extras:
this is built ontop of the evolving **react-canvas-core** library
```text
yarn add @projectstorm/react-diagrams-core@next
```
which makes use of
```text
yarn add @projectstorm/react-geometry@next
```
and of course, you can add some extras:
```text
yarn add @projectstorm/react-diagrams-defaults@next
yarn add @projectstorm/react-diagrams-routing@next
```
yarn add @projectstorm/react-diagrams-defaults
yarn add @projectstorm/react-diagrams-routing
## How to use
Take a look at the demos [lib-demo-gallery/demos](https://github.com/projectstorm/react-diagrams/tree/lerna/lib-demo-gallery/demos)
Take a look at the [diagram demos](https://github.com/projectstorm/react-diagrams/tree/8fbd87df8c074e0263fd86a64b05a56687a631cf/packages/diagrams-demo-gallery/demos/README.md)
__or__
**or**
Take a look at the demo project: [lib-demo-project](https://github.com/projectstorm/react-diagrams/tree/lerna/lib-demo-project)
Take a look at the [demo project](https://github.com/projectstorm/react-diagrams/tree/8fbd87df8c074e0263fd86a64b05a56687a631cf/packages/diagrams-demo-project/README.md) which contains an example for ES6 as well as Typescript
## Run the demos
After running `yarn install` you must then run: `yarn run storybook`
After running `yarn install` you must then run: `cd packages/diagrams-demo-gallery && yarn run start`
## Building from source
Simply run `yarn build` in the root directory \(or `NODE_ENV=production yarn build` if you want a production build\) and it will spit out the transpiled code and typescript definitions into the dist directory as a single file.
We use webpack for this because TSC cannot compile a single UMD file \(TSC can currently only output multiple UMD files\).
## [Checkout the docs](https://projectstorm.gitbooks.io/react-diagrams)
Simply run `yarn build` or `yarn build:prod` in the root directory and it will spit out the transpiled code and typescript definitions into the dist directory as a single file.
## [Checkout the docs](https://projectstorm.gitbook.io/react-diagrams/)

View File

@@ -1,11 +0,0 @@
# Summary
* [Introduction](README.md)
* [Interacting with diagrams](/docs/Interactive Usage.md)
* [Getting Started](/docs/Getting Started.md)
* [About the project](about-the-project.md)
* [Testing](/docs/Testing.md)
* [Architecture Questions](/docs/Architecture Questions.md)

View File

@@ -1,76 +0,0 @@
# Architecture Questions
Here I will try to answer any questions relating to the design of the system
### What was the inspiration for this library?
Joint JS (a fantastic library) + my need for rich HTML nodes + LabView + Blender Composite sub system
### Why render the nodes as HTML Elements and not SVG's?
My original requirement for this library stemmed from the requirement of wanting HTML nodes that would allow me to
embed rich controls such as input fields, drop downs and have the system treat such nodes as first class citizens.
I originally tried to make this work in JointJS, but ran into a number of problems of which this was a relatively big one.
JointJS does allow you to do this, but at the time of writing this library originally, I was having a lot of trouble
to make it work exactly like I needed it, and therefore decided from the very beginning that I would attempt this
with an HTML first mindset.
### Why Typescript?
Firstly, because it can transpile into any level of ECMAScript. This means that I don't need to break our the refactor tractor
every time ECMAScript decides it wants to add features which it should have done years ago.
I also ported it to Typescript to accommodate the heavy architectural changes I was starting to make. Since porting
the library to typescript, and seeing the project explode in size and complexity, I consider this the best decision
made with regards to this library so far.
Porting to typescript also afforded us a set of powerful features such as generics and static analysis that
all the project contributors have made exclusive use of.
Typescript is <3 typescript is life.
### Why not Flow instead of Typescript?
At the time when I first started evaluating languages that could transpile to ECMAScript, I was not so sold
on the supporting environment surrounding flow, and and found that there was better tooling to support
typescript, they are ultimately trying to do the same thing though, and I guess in the end, typescript just made
more sense.
### Why React ?
React is really efficient at rendering and managing HTML in a declarative manner. React has also become
one of the bigger industry standards and has a rich eco system that plays really well with typescript.
Apart from these notable points, I am really fond of React and wanted a diagramming library that takes full advantage of it,
and makes it easy for engineers to use its power as well, when extending this library.
### Why cant the Default models and widgets do this or that ?
They are intended to illustrate __how__ to use this library and act as a good starting point
to extend and show the capability. Ultimately I designed this library to be completely
pluggable in a way that you can use it as a library and not a framework. If the default widgets
are not good enough, then a good place to start is with creating your own models/factories/widgets.
### Model vs Widget
For those that are new to [Scene Graphs](https://en.wikipedia.org/wiki/Scene_graph) or are not familiar with concepts
such as [MVC](https://en.wikipedia.org/wiki/Modelviewcontroller), this library represents your entire graph as a model.
The model is a traversable graph that represents the nodes and links between them in a virtual manner. Your program (aka the business logic/layer)
can mutate this model imperatively or store snapshots decoratively of the complete model (via serialization) and then the engine and react
widgets will take care of the rendering. For this reason every model in the library is represented by a widget, and the factories glue it all together.
### How do I make my own elements?
Take a look at the __demos__ directory, with specific attention to the __DefaultNodeWidget__
That being said, the demos directory is an _example_ of how you can create your own elements.
A number of people want to use the defaults as is, which is cool,
but is recommended to create your own models/factories/widgets.
### How do I use the library?
Take a look at the demo folders, they have simple and complex examples of the complete usage.
A good example of a real-world example is Demo 5

View File

@@ -1,142 +0,0 @@
# Getting started
## Installation via NPM
The first thing you need to do, is grab the distribution files on NPM. You can do this either using yarn or npm
**Via yarn:**
```
yarn add storm-react-diagrams
```
**Via npm:**
```
npm install storm-react-diagrams
```
When you run this in your project directory, this will install the library into node\_modules/storm-react-diagrams. You will then find a dist folder that contains all the minified and production ready code.
This will also install React and a few other dependencies that you need in order to use this library.
## Including the library
When including the library you will need both the javascript files as well as the raw BEM styles. Both are included in the dist folder and there are numerous ways to integrate them into your project:
#### Getting the javascript files
**Using Typescript / ES6: \(recommended\)**
```js
import * as SRD from "storm-react-diagrams"
```
**Using RequireJS:**
```js
var SRD = require("storm-react-diagrams)
```
**As a script tag \(not recommended\)**
```html
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="node_modules/storm-react-diagrams/dist/main.js">
```
#### Getting the CSS
**Using RequireJS / Typescript/ ES6 \(recommended\)**
Make sure you have the style-loader enabled and then:
```js
require("storm-react-diagrams/dist/style.min.css");
```
or make sure you have the sass-loader enabled and then:
```js
require("storm-react-diagrams/src/sass/main.scss");
```
If you are using typescript and get a "require function not found" then make sure to
```
yarn add @types/node
```
which will give you the typescript definition files for requireJS
**Using SASS:**
setup your include paths on webpack or lib sass using the following option
```
includePaths: ["node_modules"]
```
and then if you want the raw sass source code:
```sass
@import "~storm-react-diagrams/src/sass/main";
```
or if you want the minified css
```sass
@import "~storm-react-diagrams/dist/style.min";
```
**Using a style tag**
or if you want the minified css
```html
<link rel="stylesheet" href="node_modules/dist/style.min.css">
```
## Render your first diagram
In your library code
```js
// 1) setup the diagram engine
var engine = new SRD.DiagramEngine();
engine.installDefaultFactories();
// 2) setup the diagram model
var model = new SRD.DiagramModel();
// 3) create a default node
var node1 = new SRD.DefaultNodeModel("Node 1", "rgb(0,192,255)");
let port1 = node1.addOutPort("Out");
node1.setPosition(100, 100);
// 4) create another default node
var node2 = new SRD.DefaultNodeModel("Node 2", "rgb(192,255,0)");
let port2 = node2.addInPort("In");
node2.setPosition(400, 100);
// 5) link the ports
let link1 = port1.link(port2);
// 6) add the models to the root graph
model.addAll(node1, node2, link1);
// 7) load model into engine
engine.setDiagramModel(model);
```
And then create an instance of the diagram widget. An example of the simplest possible react widget to do this would be:
```jsx
function SimpleDiagramWidget(props) {
return <SRD.DiagramWidget diagramEngine={props.engine} />;
}
```

View File

@@ -1,22 +0,0 @@
# End user usage
__Delete__ removes any selected items
![__Delete__](../images/rjdDelete.gif)
__Shift + Mouse Drag__ triggers a multi-selection box
![Shift + Mouse Drag](../images/mouseDrag.gif)
__Shift + Mouse Click__ selects the item (items can be multi-selected)
![Shift + Mouse Click](../images/shiftClick.gif)
__Mouse Drag__ drags the entire diagram
![Mouse Drag](../images/canvasDrag.gif)
__Mouse Wheel__ zooms the diagram in / out
![Mouse Wheel](../images/mouseWheel.gif)
__Click Link + Drag__ creates a new link point
![Click Link + Drag](../images/createPoint.gif)
__Click Node Port + Drag__ creates a new link
![Click Node Port + Drag](../images/createLink.gif)

12
docs/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Table of contents
* [Introduction](README.md)
* [Getting Started](getting-started/README.md)
* [Using the library](getting-started/using-the-library.md)
* [Customizing](customizing/README.md)
* [Extending DefaultLinkModel](customizing/extending-default-links.md)
* [Custom Ports](customizing/ports.md)
* [About the project](about-the-project/README.md)
* [Testing](about-the-project/testing.md)
* [Architecture Questions](about-the-project/architecture-questions.md)

View File

@@ -1,33 +0,0 @@
# Testing
STORM React diagrams is tested two main ways.
## JEST Snapshot testing
With Jest snapshots, we render all the demos in the demo folder by automatically
looking into each `demo-*` folder and searching for an __index.tsx__ file.
For each file we find, we dynamically include it as a storybook story and assemble one big test.
This test then renders each demo in a deterministic way and compares it to the snapshot file
situated in __snapshots__. If the snapshots don't match, then something has changed and either the snapshot
needs to be updated, or the test is failing in which case we need to fix the code.
Snapshot testing does not test the functionality of the program but it is a first important step
to make sure that changes aren't having a drastic effect on the overall system.
In the event that the snapshot needs to be updated, please run `yarn run test -u` to overwrite
the snapshot file, and then make sure to your branch.
## End to end testing
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 pupeteer and interactively and programmatically
tell the mouse pointer to click and drag on various elements while making assertions along the way.
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.

View File

@@ -0,0 +1,52 @@
# Architecture Questions
Here I will try to answer any questions relating to the design of the system
## What was the inspiration for this library?
Joint JS \(a fantastic library\) + my need for rich HTML nodes + LabView + Blender Composite sub system
## Why render the nodes as HTML Elements and not SVG's?
My original requirement for this library stemmed from the requirement of wanting HTML nodes that would allow me to embed rich controls such as input fields, drop downs and have the system treat such nodes as first class citizens. I originally tried to make this work in JointJS, but ran into a number of problems of which this was a relatively big one.
JointJS does allow you to do this, but at the time of writing this library originally, I was having a lot of trouble to make it work exactly like I needed it, and therefore decided from the very beginning that I would attempt this with an HTML first mindset.
## Why Typescript?
Firstly, because it can transpile into any level of ECMAScript. This means that I don't need to break our the refactor tractor every time ECMAScript decides it wants to add features which it should have done years ago.
I also ported it to Typescript to accommodate the heavy architectural changes I was starting to make. Since porting the library to typescript, and seeing the project explode in size and complexity, I consider this the best decision made with regards to this library so far.
Porting to typescript also afforded us a set of powerful features such as generics and static analysis that all the project contributors have made exclusive use of.
Typescript is &lt;3 typescript is life.
## Why not Flow instead of Typescript?
At the time when I first started evaluating languages that could transpile to ECMAScript, I was not so sold on the supporting environment surrounding flow, and and found that there was better tooling to support typescript, they are ultimately trying to do the same thing though, and I guess in the end, typescript just made more sense.
## Why React ?
React is really efficient at rendering and managing HTML in a declarative manner. React has also become one of the bigger industry standards and has a rich eco system that plays really well with typescript. Apart from these notable points, I am really fond of React and wanted a diagramming library that takes full advantage of it, and makes it easy for engineers to use its power as well, when extending this library.
## Why cant the Default models and widgets do this or that ?
They are intended to illustrate **how** to use this library and act as a good starting point to extend and show the capability. Ultimately I designed this library to be completely pluggable in a way that you can use it as a library and not a framework. If the default widgets are not good enough, then a good place to start is with creating your own models/factories/widgets.
## Model vs Widget
For those that are new to [Scene Graphs](https://en.wikipedia.org/wiki/Scene_graph) or are not familiar with concepts such as [MVC](https://en.wikipedia.org/wiki/Modelviewcontroller), this library represents your entire graph as a model. The model is a traversable graph that represents the nodes and links between them in a virtual manner. Your program \(aka the business logic/layer\) can mutate this model imperatively or store snapshots decoratively of the complete model \(via serialization\) and then the engine and react widgets will take care of the rendering. For this reason every model in the library is represented by a widget, and the factories glue it all together.
## How do I make my own elements?
Take a look at the **demos** directory, with specific attention to the **DefaultNodeWidget**
That being said, the demos directory is an _example_ of how you can create your own elements. A number of people want to use the defaults as is, which is cool, but is recommended to create your own models/factories/widgets.
## How do I use the library?
Take a look at the demo folders, they have simple and complex examples of the complete usage.
A good example of a real-world example is Demo 5

View File

@@ -0,0 +1,8 @@
# Testing
## End to end testing
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 pupeteer and interactively and programmatically tell the mouse pointer to click and drag on various elements while making assertions along the way.
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.

View File

@@ -0,0 +1,11 @@
# Customizing
## Working with custom links
This is the easiest way to get started:
[Extending the default Link](./extending-default-links.md)
## Working with custom nodes
[Working with Ports](./ports.md)

View File

@@ -0,0 +1,52 @@
# Custom Links
## Extending the DefaultLinkModel
Much like extending nodes, custom links can also be created.
In the below example, we have created a link that renders a circle animating from the source port to the target port.
![](./images/custom-link.png)
In this specific example, we extended the `DefaultLinkModel` because we wanted to retain
a lot of the functionality that it provides in the base class:
```typescript
export class AdvancedLinkModel extends DefaultLinkModel {
constructor() {
super({
type: 'advanced', // <-- here we give it a new type
width: 10 // we specifically want this to also be width 10
});
}
}
```
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:
```typescript
export class AdvancedLinkFactory extends DefaultLinkFactory {
constructor() {
super('advanced'); // <-- this matches with the link model above
}
generateModel(): AdvancedLinkModel {
return new AdvancedLinkModel(); // <-- this is how we get new instances
}
/**
* @override the DefaultLinkWidget makes use of this, and it normally renders that
* familiar gray line, so in this case we simply make it return a new advanced segment.
*/
generateLinkSegment(model: AdvancedLinkModel, selected: boolean, path: string) {
return (
<g>
<AdvancedLinkSegment model={model} path={path} />
</g>
);
}
}
```
The actual code for the `AdvancedLinkSegment` [can be found here](https://github.com/projectstorm/react-diagrams/blob/master/packages/diagrams-demo-gallery/demos/demo-custom-link1/index.tsx) (it is in the `demo-custom-link1` folder in the demo gallery).
This is the easiest and most simple way to get started with custom links.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

95
docs/customizing/ports.md Normal file
View File

@@ -0,0 +1,95 @@
# Ports
Ports allow links to connect to your nodes. Each port that is rendered in a node must also have a corresponding PortModel in the corresponding NodeModel (as is the case with essentially all of the models and widgets in this library).
## Custom port widgets
If you want to create a custom port that looks entirely different (much like in the image below), then you simply need to create your own widget and wrap it in a `PortWidget`:
```jsx
<PortWidget
port={this.props.node.getPort("in"}
engine={this.props.engine} >
<div
style={{
width: 40,
height: 40,
background: 'orange'
}}
/>
</PortWidget>
```
Obviously, you can create the React widgets in any way you like. Whether you use __Emotion__, __BEM__ or plain old __CSS__, the only important thing is that your custom port is wrapped inside a `PortWidget`
## Specifying alignment
When links enter ports, depending on the alignment specified, they can help the links render differently. Take the following example:
![](./images/diamond-node.png)
In the above example, the 4 ports on the diamond node model are setup with different alignment:
```typescript
this.addPort(new DiamondPortModel(PortModelAlignment.TOP));
this.addPort(new DiamondPortModel(PortModelAlignment.LEFT));
this.addPort(new DiamondPortModel(PortModelAlignment.BOTTOM));
this.addPort(new DiamondPortModel(PortModelAlignment.RIGHT));
```
Each of the custom `DiamondPortModel` models forwards this through to the base `PortModel` class:
```typescript
export class DiamondPortModel extends PortModel {
...
constructor(alignment: PortModelAlignment) {
super({
type: 'diamond',
name: alignment,
alignment: alignment // <-- here
});
}
...
}
```
## Specifying if a link can be connected
A port is directly responsible for specifying if a link is allowed to connect to it. When you drag an un-connected link end-point to a target port, the target port lets the link know if it is allowed to connect.
```typescript
class PortModel{
...
canLinkToPort(port: PortModel): boolean;
}
```
In the above definition, the port argument is the source port that the incoming link is connected to. By default, the method returns true, but you can extend this and overide this method to do more advanced checks.
The `DefaultPortModel` provided in the defaults package, makes use of this principle to only allow `Out` ports to connect to `In` ports:
```typescript
class DefaultPortModel extends PortModel{
...
canLinkToPort(port: PortModel): boolean {
if (port instanceof DefaultPortModel) {
return this.options.in !== port.getOptions().in;
}
return true;
}
}
```
## Specifying what type of link is generated from a port
When a user drags on a port to generate a link, the port is also responsible for specifying
what link is created. This happens through the `createLinkModel()` method:
```typescript
class DefaultPortModel extends PortModel{
...
createLinkModel(): LinkModel{
return new DefaultLinkModel(); // <-- here we generate a DefaultLinkModel
}
}
```

View File

@@ -0,0 +1,32 @@
# Getting Started
## Get the package
The first thing you need to do, is grab the distribution files on NPM. You can do this either using **yarn** or **npm.**
**Via yarn:**
```text
yarn add @projectstorm/react-diagrams@next
```
**Via npm:**
```text
npm install @projectstorm/react-diagrams@next
```
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.
## Install the peer dependencies
The library includes it's dependencies as peer-dependencies, so yarn will output warnings letting you know which ones are missing. Simple install them, specifically these ones:
```text
yarn add closest lodash react mathjs dagre pathfinding paths-js
```
We do this, so that you can better control the versions of these libraries yourself since you might make use of `Lodash` in other parts of your software.

View File

@@ -0,0 +1,68 @@
# Using the library
## Using Typescript
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.
Lets start by including the things we are going to need:
```typescript
import createEngine, {
DefaultLinkModel,
DefaultNodeModel,
DiagramModel
} from '@projectstorm/react-diagrams';
import {
CanvasWidget
} from '@projectstorm/react-canvas-core';
```
Now we call `createEngine` which will bootstrap a **DiagramEngine** for us that contains all the defaults setup.
```typescript
// create an instance of the engine with all the defaults
const engine = createEngine();
```
Next, we create two nodes:
```typescript
// node 1
const node1 = new DefaultNodeModel({
name: 'Node 1',
color: 'rgb(0,192,255)',
});
node1.setPosition(100, 100);
let port1 = node1.addOutPort('Out');
// node 2
const node2 = new DefaultNodeModel({
name: 'Node 1',
color: 'rgb(0,192,255)',
});
node2.setPosition(100, 100);
let port2 = node2.addOutPort('Out');
```
Now we link the two ports of both of the nodes:
```typescript
// link them and add a label to the link
const link = port1.link<DefaultLinkModel>(port2);
link.addLabel('Hello World!');
```
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.
```typescript
const model = new DiagramModel();
model.addAll(node1, node2, link);
engine.setModel(model);
```
And then we render with **React**!
```jsx
<CanvasWidget engine={engine} />
```

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "6.0.0-y.0"
"version": "6.0.1-beta.1"
}

View File

@@ -1,43 +0,0 @@
import './sass/main.scss';
export * from './src/core-actions/AbstractAction';
export * from './src/core-actions/AbstractActionFactory';
export * from './src/core-actions/AbstractMouseAction';
export * from './src/actions/move-canvas/MoveCanvasActionFactory';
export * from './src/actions/move-canvas/MoveCanvasAction';
export * from './src/actions/selecting-items/SelectingAction';
export * from './src/actions/selecting-items/SelectingItemsActionFactory';
export * from './src/actions/move-items/MoveItemsAction';
export * from './src/actions/move-items/MoveItemsActionFactory';
export * from './src/core/BaseObserver';
export * from './src/core/FactoryBank';
export * from './src/core/AbstractFactory';
export * from './src/core/AbstractModelFactory';
export * from './src/core/AbstractReactFactory';
export * from './src/core-models/BaseModel';
export * from './src/core-models/BasePositionModel';
export * from './src/core-models/BaseEntity';
export * from './src/models/DiagramModel';
export * from './src/models/LabelModel';
export * from './src/models/LinkModel';
export * from './src/models/PointModel';
export * from './src/models/PortModel';
export * from './src/models/SelectionModel';
export * from './src/models/NodeModel';
export * from './src/widgets/BaseWidget';
export * from './src/widgets/DiagramWidget';
export * from './src/widgets/layers/LinkLayerWidget';
export * from './src/widgets/layers/NodeLayerWidget';
export * from './src/widgets/LinkWidget';
export * from './src/widgets/NodeWidget';
export * from './src/widgets/PortWidget';
export * from './src/DiagramEngine';
export * from './src/Toolkit';

View File

@@ -1,13 +0,0 @@
.srd-diagram {
position: relative;
flex-grow: 1;
display: flex;
cursor: move;
overflow: hidden;
&__selector {
position: absolute;
background-color: rgba(0, 192, 255, 0.2);
border: solid 2px rgb(0, 192, 255);
}
}

View File

@@ -1,11 +0,0 @@
.srd-link-layer {
position: absolute;
height: 100%;
width: 100%;
transform-origin: 0 0;
overflow: visible !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

View File

@@ -1,11 +0,0 @@
.srd-node-layer {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
pointer-events: none;
transform-origin: 0 0;
width: 100%;
height: 100%;
}

View File

@@ -1,14 +0,0 @@
.srd-node {
position: absolute;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
user-select: none;
cursor: move;
pointer-events: all;
&--selected {
> * {
border-color: rgb(0, 192, 255) !important;
}
}
}

View File

@@ -1,10 +0,0 @@
.srd-port {
width: 15px;
height: 15px;
background: rgba(white, 0.1);
&:hover,
&.selected {
background: rgb(192, 255, 0);
}
}

View File

@@ -1,5 +0,0 @@
@import 'DiagramWidget';
@import 'LinkLayerWidget';
@import 'NodeLayerWidget';
@import 'NodeWidget';
@import 'PortWidget';

View File

@@ -1,336 +0,0 @@
import { BaseEntity } from './core-models/BaseEntity';
import { DiagramModel } from './models/DiagramModel';
import { BaseModel } from './core-models/BaseModel';
import { NodeModel } from './models/NodeModel';
import { PortModel } from './models/PortModel';
import { LinkModel } from './models/LinkModel';
import { LabelModel } from './models/LabelModel';
import { FactoryBank } from './core/FactoryBank';
import { AbstractReactFactory } from './core/AbstractReactFactory';
import { BaseListener, BaseObserver } from './core/BaseObserver';
import { Point } from '@projectstorm/react-diagrams-geometry';
import { Toolkit } from './Toolkit';
import { MouseEvent } from 'react';
import { AbstractActionFactory } from './core-actions/AbstractActionFactory';
import { AbstractModelFactory } from './core/AbstractModelFactory';
export interface DiagramEngineListener extends BaseListener {
canvasReady?(): void;
repaintCanvas?(): void;
rendered?(): void;
}
/**
* Passed as a parameter to the DiagramWidget
*/
export class DiagramEngine extends BaseObserver<DiagramEngineListener> {
protected nodeFactories: FactoryBank<AbstractReactFactory<NodeModel>>;
protected linkFactories: FactoryBank<AbstractReactFactory<LinkModel>>;
protected portFactories: FactoryBank<AbstractModelFactory<PortModel>>;
protected labelFactories: FactoryBank<AbstractReactFactory<LabelModel>>;
protected actionFactories: FactoryBank<AbstractActionFactory>;
diagramModel: DiagramModel;
canvas: HTMLDivElement;
maxNumberPointsPerLink: number;
constructor() {
super();
this.maxNumberPointsPerLink = 1000;
this.diagramModel = new DiagramModel();
// create banks for the different factory types
this.nodeFactories = new FactoryBank();
this.linkFactories = new FactoryBank();
this.portFactories = new FactoryBank();
this.labelFactories = new FactoryBank();
this.actionFactories = new FactoryBank();
const setup = (factory: FactoryBank) => {
factory.registerListener({
factoryAdded: event => {
event.factory.setDiagramEngine(this);
},
factoryRemoved: event => {
event.factory.setDiagramEngine(null);
}
});
};
setup(this.nodeFactories);
setup(this.linkFactories);
setup(this.portFactories);
setup(this.labelFactories);
setup(this.actionFactories);
this.canvas = null;
// this.linksThatHaveInitiallyRendered = {};
}
repaintCanvas() {
this.iterateListeners(listener => {
if (listener.repaintCanvas) {
listener.repaintCanvas();
}
});
}
/**
* Gets a model and element under the mouse cursor
*/
getMouseElement(event: MouseEvent): { model: BaseModel; element: Element } {
var target = event.target as Element;
var diagramModel = this.diagramModel;
//is it a port
var element = Toolkit.closest(target, '.port[data-name]');
if (element) {
var nodeElement = Toolkit.closest(target, '.node[data-nodeid]') as HTMLElement;
return {
model: diagramModel.getNode(nodeElement.getAttribute('data-nodeid')).getPort(element.getAttribute('data-name')),
element: element
};
}
//look for a point
element = Toolkit.closest(target, '.point[data-id]');
if (element) {
return {
model: diagramModel.getLink(element.getAttribute('data-linkid')).getPointModel(element.getAttribute('data-id')),
element: element
};
}
//look for a link
element = Toolkit.closest(target, '[data-linkid]');
if (element) {
return {
model: diagramModel.getLink(element.getAttribute('data-linkid')),
element: element
};
}
//look for a node
element = Toolkit.closest(target, '.node[data-nodeid]');
if (element) {
return {
model: diagramModel.getNode(element.getAttribute('data-nodeid')),
element: element
};
}
return null;
}
/**
* Checks to see if a model is locked by running through
* its parents to see if they are locked first
*/
isModelLocked(model: BaseEntity) {
//always check the diagram model
if (this.diagramModel.isLocked()) {
return true;
}
return model.isLocked();
}
setCanvas(canvas?: HTMLDivElement) {
if (this.canvas !== canvas) {
this.canvas = canvas;
if (canvas) {
this.fireEvent({}, 'canvasReady');
}
}
}
setDiagramModel(model: DiagramModel) {
this.diagramModel = model;
}
getDiagramModel(): DiagramModel {
return this.diagramModel;
}
//!-------------- FACTORIES ------------
getNodeFactories() {
return this.nodeFactories;
}
getLinkFactories() {
return this.linkFactories;
}
getLabelFactories() {
return this.labelFactories;
}
getPortFactories() {
return this.portFactories;
}
getActionFactories() {
return this.actionFactories;
}
getFactoryForNode<F extends AbstractReactFactory<NodeModel>>(node: NodeModel | string) {
if (typeof node === 'string') {
return this.nodeFactories.getFactory(node);
}
return this.nodeFactories.getFactory(node.getType());
}
getFactoryForLink<F extends AbstractReactFactory<LinkModel>>(link: LinkModel | string) {
if (typeof link === 'string') {
return this.linkFactories.getFactory<F>(link);
}
return this.linkFactories.getFactory<F>(link.getType());
}
getFactoryForLabel<F extends AbstractReactFactory<LabelModel>>(label: LabelModel) {
if (typeof label === 'string') {
return this.labelFactories.getFactory(label);
}
return this.labelFactories.getFactory(label.getType());
}
getFactoryForPort<F extends AbstractModelFactory<PortModel>>(port: PortModel) {
if (typeof port === 'string') {
return this.portFactories.getFactory<F>(port);
}
return this.portFactories.getFactory<F>(port.getType());
}
generateWidgetForLink(link: LinkModel): JSX.Element {
return this.getFactoryForLink(link).generateReactWidget({ model: link });
}
generateWidgetForNode(node: NodeModel): JSX.Element {
return this.getFactoryForNode(node).generateReactWidget({ model: node });
}
getRelativeMousePoint(event): Point {
var point = this.getRelativePoint(event.clientX, event.clientY);
return new Point(
(point.x - this.diagramModel.getOffsetX()) / (this.diagramModel.getZoomLevel() / 100.0),
(point.y - this.diagramModel.getOffsetY()) / (this.diagramModel.getZoomLevel() / 100.0)
);
}
getRelativePoint(x, y): Point {
var canvasRect = this.canvas.getBoundingClientRect();
return new Point(x - canvasRect.left, y - canvasRect.top);
}
getNodeElement(node: NodeModel): Element {
const selector = this.canvas.querySelector(`.node[data-nodeid="${node.getID()}"]`);
if (selector === null) {
throw new Error('Cannot find Node element with nodeID: [' + node.getID() + ']');
}
return selector;
}
getNodePortElement(port: PortModel): any {
var selector = this.canvas.querySelector(
`.port[data-name="${port.getName()}"][data-nodeid="${port.getParent().getID()}"]`
);
if (selector === null) {
throw new Error(
'Cannot find Node Port element with nodeID: [' +
port.getParent().getID() +
'] and name: [' +
port.getName() +
']'
);
}
return selector;
}
getPortCenter(port: PortModel): Point {
var sourceElement = this.getNodePortElement(port);
var sourceRect = sourceElement.getBoundingClientRect();
var rel = this.getRelativePoint(sourceRect.left, sourceRect.top);
return new Point(
sourceElement.offsetWidth / 2 +
(rel.x - this.diagramModel.getOffsetX()) / (this.diagramModel.getZoomLevel() / 100.0),
sourceElement.offsetHeight / 2 +
(rel.y - this.diagramModel.getOffsetY()) / (this.diagramModel.getZoomLevel() / 100.0)
);
}
/**
* Calculate rectangular coordinates of the port passed in.
*/
getPortCoords(
port: PortModel,
element?: HTMLDivElement
): {
x: number;
y: number;
width: number;
height: number;
} {
if (!this.canvas) {
throw new Error('Canvas needs to be set first');
}
if (!element) {
element = this.getNodePortElement(port);
}
const sourceRect = element.getBoundingClientRect();
const canvasRect = this.canvas.getBoundingClientRect() as ClientRect;
return {
x:
(sourceRect.left - this.diagramModel.getOffsetX()) / (this.diagramModel.getZoomLevel() / 100.0) -
canvasRect.left,
y:
(sourceRect.top - this.diagramModel.getOffsetY()) / (this.diagramModel.getZoomLevel() / 100.0) - canvasRect.top,
width: sourceRect.width,
height: sourceRect.height
};
}
/**
* Determine the width and height of the node passed in.
* It currently assumes nodes have a rectangular shape, can be overriden for customised shapes.
*/
getNodeDimensions(node: NodeModel): { width: number; height: number } {
if (!this.canvas) {
return {
width: 0,
height: 0
};
}
const nodeElement = this.getNodeElement(node);
const nodeRect = nodeElement.getBoundingClientRect();
return {
width: nodeRect.width,
height: nodeRect.height
};
}
getMaxNumberPointsPerLink(): number {
return this.maxNumberPointsPerLink;
}
setMaxNumberPointsPerLink(max: number) {
this.maxNumberPointsPerLink = max;
}
zoomToFit() {
const xFactor = this.canvas.clientWidth / this.canvas.scrollWidth;
const yFactor = this.canvas.clientHeight / this.canvas.scrollHeight;
const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
this.diagramModel.setZoomLevel(this.diagramModel.getZoomLevel() * zoomFactor);
this.diagramModel.setOffset(0, 0);
this.repaintCanvas();
}
}

View File

@@ -1,28 +0,0 @@
import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction';
import { MouseEvent } from 'react';
import { DiagramEngine } from '../../DiagramEngine';
export class MoveCanvasAction extends AbstractMouseAction {
initialOffsetX: number;
initialOffsetY: number;
constructor(mouseX: number, mouseY: number, engine: DiagramEngine) {
super(mouseX, mouseY, engine);
this.initialOffsetX = this.model.getOffsetX();
this.initialOffsetY = this.model.getOffsetY();
}
fireMouseMove(event: MouseEvent) {
//translate the actual canvas
this.model.setOffset(
this.initialOffsetX + (event.clientX - this.mouseX),
this.initialOffsetY + (event.clientY - this.mouseY)
);
}
fireMouseUp(event) {}
fireMouseDown(event) {
this.model.clearSelection();
}
}

View File

@@ -1,17 +0,0 @@
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
import { MoveCanvasAction } from './MoveCanvasAction';
import { MouseEvent } from 'react';
export class MoveCanvasActionFactory extends AbstractActionFactory<MoveCanvasAction> {
constructor() {
super('move-canvas');
}
generateAction(event: MouseEvent): MoveCanvasAction {
return new MoveCanvasAction(event.clientX, event.clientY, this.engine);
}
activate(event: ActionFactoryActivationEvent): boolean {
return !event.selectedEntity && !event.mouseEvent.shiftKey;
}
}

View File

@@ -1,177 +0,0 @@
import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction';
import { SelectionModel } from '../../models/SelectionModel';
import { PointModel } from '../../models/PointModel';
import { NodeModel } from '../../models/NodeModel';
import { DiagramEngine } from '../../DiagramEngine';
import { BasePositionModel } from '../../core-models/BasePositionModel';
import * as _ from 'lodash';
import { PortModel } from '../../models/PortModel';
import { LinkModel } from '../../models/LinkModel';
import { MouseEvent } from 'react';
import { ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
export class MoveItemsAction extends AbstractMouseAction {
selectionModels: SelectionModel[];
moved: boolean;
allowLooseLinks: boolean;
constructor(mouseX: number, mouseY: number, diagramEngine: DiagramEngine, allowLooseLinks: boolean) {
super(mouseX, mouseY, diagramEngine);
this.allowLooseLinks = allowLooseLinks;
this.moved = false;
}
fireMouseMove(event: MouseEvent) {
let amountX = event.clientX - this.mouseX;
let amountY = event.clientY - this.mouseY;
let amountZoom = this.model.getZoomLevel() / 100;
_.forEach(this.selectionModels, model => {
// in this case we need to also work out the relative grid position
if (model.model instanceof NodeModel || (model.model instanceof PointModel && !model.model.isConnectedToPort())) {
model.model.setPosition(
this.model.getGridPosition(model.initialX + amountX / amountZoom),
this.model.getGridPosition(model.initialY + amountY / amountZoom)
);
} else if (model.model instanceof PointModel) {
// we want points that are connected to ports, to not necessarily snap to grid
// this stuff needs to be pixel perfect, dont touch it
model.model.setPosition(
model.initialX + this.model.getGridPosition(amountX / amountZoom),
model.initialY + this.model.getGridPosition(amountY / amountZoom)
);
}
});
this.moved = true;
}
fireMouseUp(event: MouseEvent) {
const element = this.engine.getMouseElement(event);
_.forEach(this.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
if (element && element.model instanceof PortModel && !this.engine.isModelLocked(element.model)) {
let link = model.model.getLink();
//if this was a valid link already and we are adding a node in the middle, create 2 links from the original
if (link.getTargetPort()) {
if (link.getTargetPort() !== element.model && link.getSourcePort() !== element.model) {
const targetPort = link.getTargetPort();
let newLink = link.clone({});
newLink.setSourcePort(element.model);
newLink.setTargetPort(targetPort);
link.setTargetPort(element.model);
link.getLastPoint().setPosition(this.engine.getPortCenter(element.model));
targetPort.removeLink(link);
newLink.removePointsBefore(newLink.getPoints()[link.getPointIndex(model.model)]);
link.removePointsAfter(model.model);
this.engine.getDiagramModel().addLink(newLink);
//if we are connecting to the same target or source, remove tweener points
} else if (link.getTargetPort() === element.model) {
link.removePointsAfter(model.model);
} else if (link.getSourcePort() === element.model) {
link.removePointsBefore(model.model);
}
}
// set the target port
else {
link.setTargetPort(element.model);
link.getLastPoint().setPosition(this.engine.getPortCenter(element.model));
}
}
});
//check for / remove any loose links in any models which have been moved
if (!this.allowLooseLinks && this.moved) {
_.forEach(this.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
let selectedPoint: PointModel = model.model;
let link: LinkModel = selectedPoint.getLink();
if (link.getSourcePort() === null || link.getTargetPort() === null) {
link.remove();
}
});
}
//remove any invalid links
_.forEach(this.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
let link: LinkModel = model.model.getLink();
let sourcePort: PortModel = link.getSourcePort();
let targetPort: PortModel = link.getTargetPort();
if (sourcePort !== null && targetPort !== null) {
if (!sourcePort.canLinkToPort(targetPort)) {
//link not allowed
link.remove();
} else if (
_.some(
_.values(targetPort.getLinks()),
(l: LinkModel) => l !== link && (l.getSourcePort() === sourcePort || l.getTargetPort() === sourcePort)
)
) {
//link is a duplicate
link.remove();
}
}
});
}
fireMouseDown(event: ActionFactoryActivationEvent) {
// clear selection first?
if (!event.selectedModel.isSelected()) {
this.model.clearSelection();
}
if (event.selectedModel instanceof PortModel) {
//its a port element, we want to drag a link
if (!this.engine.isModelLocked(event.selectedModel)) {
const portCenter = this.engine.getPortCenter(event.selectedModel);
const sourcePort = event.selectedModel;
const link = sourcePort.createLinkModel();
link.setSourcePort(sourcePort);
if (link) {
link.removeMiddlePoints();
if (link.getSourcePort() !== sourcePort) {
link.setSourcePort(sourcePort);
}
link.setTargetPort(null);
link.getFirstPoint().setPosition(portCenter);
link.getLastPoint().setPosition(portCenter);
this.model.clearSelection();
link.getLastPoint().setSelected(true);
this.model.addLink(link);
}
}
} else {
event.selectedModel.setSelected(true);
}
const selectedItems = this.model.getSelectedItems().filter(item => {
if (!(item instanceof BasePositionModel)) {
return false;
}
return !this.engine.isModelLocked(item);
});
this.selectionModels = selectedItems.map((item: PointModel | NodeModel) => {
return {
model: item,
initialX: item.getX(),
initialY: item.getY()
};
});
}
}

View File

@@ -1,32 +0,0 @@
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
import { MouseEvent } from 'react';
import { MoveItemsAction } from './MoveItemsAction';
export interface MoveItemsActionFactoryOptions {
allowLooseLinks?: boolean;
}
export class MoveItemsActionFactory extends AbstractActionFactory<MoveItemsAction> {
options: MoveItemsActionFactoryOptions;
static NAME = 'move-items';
constructor(options: MoveItemsActionFactoryOptions = {}) {
super(MoveItemsActionFactory.NAME);
this.options = {
...options,
allowLooseLinks: options.allowLooseLinks == null ? true : options.allowLooseLinks
};
}
generateAction(event: MouseEvent): MoveItemsAction {
return new MoveItemsAction(event.clientX, event.clientY, this.engine, this.options.allowLooseLinks);
}
activate(event: ActionFactoryActivationEvent): boolean {
if (event.selectedModel) {
return !this.engine.isModelLocked(event.selectedModel);
}
return false;
}
}

View File

@@ -1,72 +0,0 @@
import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction';
import { DiagramModel } from '../../models/DiagramModel';
import * as _ from 'lodash';
import { DiagramEngine } from '../../DiagramEngine';
import { MouseEvent } from 'react';
export class SelectingAction extends AbstractMouseAction {
mouseX2: number;
mouseY2: number;
constructor(mouseX: number, mouseY: number, engine: DiagramEngine) {
super(mouseX, mouseY, engine);
this.mouseX2 = mouseX;
this.mouseY2 = mouseY;
}
getBoxDimensions() {
return {
left: this.mouseX2 > this.mouseX ? this.mouseX : this.mouseX2,
top: this.mouseY2 > this.mouseY ? this.mouseY : this.mouseY2,
width: Math.abs(this.mouseX2 - this.mouseX),
height: Math.abs(this.mouseY2 - this.mouseY),
right: this.mouseX2 < this.mouseX ? this.mouseX : this.mouseX2,
bottom: this.mouseY2 < this.mouseY ? this.mouseY : this.mouseY2
};
}
containsElement(x: number, y: number, diagramModel: DiagramModel): boolean {
var z = diagramModel.getZoomLevel() / 100.0;
let dimensions = this.getBoxDimensions();
return (
x * z + diagramModel.getOffsetX() > dimensions.left &&
x * z + diagramModel.getOffsetX() < dimensions.right &&
y * z + diagramModel.getOffsetY() > dimensions.top &&
y * z + diagramModel.getOffsetY() < dimensions.bottom
);
}
fireMouseMove(event: MouseEvent) {
var relative = this.engine.getRelativePoint(event.clientX, event.clientY);
_.forEach(this.model.getNodes(), node => {
// TODO use geometry instead
if (this.containsElement(node.getX(), node.getY(), this.model)) {
node.setSelected(true);
}
});
_.forEach(this.model.getLinks(), link => {
var allSelected = true;
_.forEach(link.getPoints(), point => {
if (this.containsElement(point.getX(), point.getY(), this.model)) {
point.setSelected(true);
} else {
allSelected = false;
}
});
if (allSelected) {
link.setSelected(true);
}
});
this.mouseX2 = relative.x;
this.mouseY2 = relative.y;
}
fireMouseUp(event) {}
fireMouseDown(event) {}
}

View File

@@ -1,17 +0,0 @@
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
import { MouseEvent } from 'react';
import { SelectingAction } from './SelectingAction';
export class SelectingItemsActionFactory extends AbstractActionFactory<SelectingAction> {
constructor() {
super('select-items');
}
generateAction(event: MouseEvent): SelectingAction {
return new SelectingAction(event.clientX, event.clientY, this.engine);
}
activate(event: ActionFactoryActivationEvent): boolean {
return !event.selectedEntity && event.mouseEvent.shiftKey;
}
}

View File

@@ -1,12 +0,0 @@
import { DiagramEngine } from '../DiagramEngine';
import { DiagramModel } from '../models/DiagramModel';
export class AbstractAction {
engine: DiagramEngine;
model: DiagramModel;
constructor(engine: DiagramEngine) {
this.engine = engine;
this.model = engine.getDiagramModel();
}
}

View File

@@ -1,16 +0,0 @@
import { AbstractAction } from './AbstractAction';
import { AbstractFactory } from '../core/AbstractFactory';
import { MouseEvent } from 'react';
import { BaseModel } from '../core-models/BaseModel';
export interface ActionFactoryActivationEvent {
selectedModel: BaseModel;
selectedEntity: HTMLElement;
mouseEvent: MouseEvent;
}
export abstract class AbstractActionFactory<T extends AbstractAction = AbstractAction> extends AbstractFactory {
abstract activate(event: ActionFactoryActivationEvent): boolean;
abstract generateAction(event: MouseEvent): T;
}

View File

@@ -1,21 +0,0 @@
import { MouseEvent } from 'react';
import { AbstractAction } from './AbstractAction';
import { DiagramEngine } from '../DiagramEngine';
import { ActionFactoryActivationEvent } from './AbstractActionFactory';
export abstract class AbstractMouseAction extends AbstractAction {
protected mouseX: number;
protected mouseY: number;
constructor(mouseX: number, mouseY: number, engine: DiagramEngine) {
super(engine);
this.mouseX = mouseX;
this.mouseY = mouseY;
}
abstract fireMouseDown(event: ActionFactoryActivationEvent);
abstract fireMouseMove(event: MouseEvent);
abstract fireMouseUp(event: MouseEvent);
}

View File

@@ -1,285 +0,0 @@
import {
BaseEntity,
BaseEntityEvent,
BaseEntityGenerics,
BaseEntityListener,
BaseEntityOptions,
BaseEntityType
} from '../core-models/BaseEntity';
import * as _ from 'lodash';
import { DiagramEngine } from '../DiagramEngine';
import { LinkModel } from './LinkModel';
import { NodeModel } from './NodeModel';
import { PortModel } from './PortModel';
import { BaseModel } from '../core-models/BaseModel';
import { PointModel } from './PointModel';
export interface DiagramListener extends BaseEntityListener {
nodesUpdated?(event: BaseEntityEvent & { node: NodeModel; isCreated: boolean }): void;
linksUpdated?(event: BaseEntityEvent & { link: LinkModel; isCreated: boolean }): void;
offsetUpdated?(event: BaseEntityEvent<DiagramModel> & { offsetX: number; offsetY: number }): void;
zoomUpdated?(event: BaseEntityEvent<DiagramModel> & { zoom: number }): void;
gridUpdated?(event: BaseEntityEvent<DiagramModel> & { size: number }): void;
}
export interface DiagramModelOptions extends BaseEntityOptions {
offsetX?: number;
offsetY?: number;
zoom?: number;
gridSize?: number;
}
export interface DiagramModelGenerics extends BaseEntityGenerics {
LISTENER: DiagramListener;
OPTIONS: DiagramModelOptions;
}
export class DiagramModel<G extends DiagramModelGenerics = DiagramModelGenerics> extends BaseEntity<G> {
//models
protected links: { [s: string]: LinkModel };
protected nodes: { [s: string]: NodeModel };
rendered: boolean;
constructor(options: G['OPTIONS'] = {}) {
super({
zoom: 100,
gridSize: 0,
offsetX: 0,
offsetY: 0,
...options
});
this.links = {};
this.nodes = {};
this.rendered = false;
}
setGridSize(size: number = 0) {
this.options.gridSize = size;
this.fireEvent({ size: size }, 'gridUpdated');
}
getGridPosition(pos) {
if (this.options.gridSize === 0) {
return pos;
}
return this.options.gridSize * Math.floor((pos + this.options.gridSize / 2) / this.options.gridSize);
}
deSerializeDiagram(object: any, diagramEngine: DiagramEngine) {
this.deSerialize(object, diagramEngine);
this.options.offsetX = object.offsetX;
this.options.offsetY = object.offsetY;
this.options.zoom = object.zoom;
this.options.gridSize = object.gridSize;
// deserialize nodes
_.forEach(object.nodes, (node: any) => {
let nodeOb = diagramEngine.getFactoryForNode(node.type).generateModel({ initialConfig: node });
nodeOb.setParent(this);
nodeOb.deSerialize(node, diagramEngine);
this.addNode(nodeOb);
});
// deserialze links
_.forEach(object.links, (link: any) => {
let linkOb = diagramEngine.getFactoryForLink(link.type).generateModel({ initialConfig: link });
linkOb.setParent(this);
linkOb.deSerialize(link, diagramEngine);
this.addLink(linkOb);
});
}
serializeDiagram() {
return {
...this.serialize(),
offsetX: this.options.offsetX,
offsetY: this.options.offsetY,
zoom: this.options.zoom,
gridSize: this.options.gridSize,
links: _.map(this.links, link => {
return link.serialize();
}),
nodes: _.map(this.nodes, node => {
return node.serialize();
})
};
}
clearSelection(ignore: BaseModel | null = null) {
_.forEach(this.getSelectedItems(), element => {
if (ignore && ignore.getID() === element.getID()) {
return;
}
element.setSelected(false); //TODO dont fire the listener
});
}
getSelectedItems(...filters: BaseEntityType[]): BaseModel[] {
if (!Array.isArray(filters)) {
filters = [filters];
}
var items = [];
// run through nodes
items = items.concat(
_.flatMap(this.nodes, node => {
return node.getSelectedEntities();
})
);
// find all the links
items = items.concat(
_.flatMap(this.links, link => {
return link.getSelectedEntities();
})
);
//find all points
items = items.concat(
_.flatMap(this.links, link => {
return _.flatMap(link.getPoints(), point => {
return point.getSelectedEntities();
});
})
);
items = _.uniq(items);
if (filters.length > 0) {
items = _.filter(_.uniq(items), (item: BaseModel<any>) => {
if (_.includes(filters, 'node') && item instanceof NodeModel) {
return true;
}
if (_.includes(filters, 'link') && item instanceof LinkModel) {
return true;
}
if (_.includes(filters, 'port') && item instanceof PortModel) {
return true;
}
if (_.includes(filters, 'point') && item instanceof PointModel) {
return true;
}
return false;
});
}
return items;
}
setZoomLevel(zoom: number) {
this.options.zoom = zoom;
this.fireEvent({ zoom }, 'zoomUpdated');
}
setOffset(offsetX: number, offsetY: number) {
this.options.offsetX = offsetX;
this.options.offsetY = offsetY;
this.fireEvent({ offsetX, offsetY }, 'offsetUpdated');
}
setOffsetX(offsetX: number) {
this.setOffset(offsetX, this.options.offsetY);
}
setOffsetY(offsetY: number) {
this.setOffset(this.options.offsetX, offsetY);
}
getOffsetY() {
return this.options.offsetY;
}
getOffsetX() {
return this.options.offsetX;
}
getZoomLevel() {
return this.options.zoom;
}
getNode(node: string | NodeModel): NodeModel | null {
if (node instanceof NodeModel) {
return node;
}
if (!this.nodes[node]) {
return null;
}
return this.nodes[node];
}
getLink(link: string | LinkModel): LinkModel | null {
if (link instanceof LinkModel) {
return link;
}
if (!this.links[link]) {
return null;
}
return this.links[link];
}
addAll(...models: BaseModel[]): BaseModel[] {
_.forEach(models, model => {
if (model instanceof LinkModel) {
this.addLink(model);
} else if (model instanceof NodeModel) {
this.addNode(model);
}
});
return models;
}
addLink(link: LinkModel): LinkModel {
link.registerListener({
entityRemoved: () => {
this.removeLink(link);
}
});
this.links[link.getID()] = link;
this.fireEvent(
{
link,
isCreated: true
},
'linksUpdated'
);
return link;
}
addNode(node: NodeModel): NodeModel {
node.registerListener({
entityRemoved: () => {
this.removeNode(node);
}
});
this.nodes[node.getID()] = node;
this.fireEvent({ node, isCreated: true }, 'nodesUpdated');
return node;
}
removeLink(link: LinkModel | string) {
link = this.getLink(link);
delete this.links[link.getID()];
this.fireEvent({ link, isCreated: false }, 'linksUpdated');
}
removeNode(node: NodeModel | string) {
node = this.getNode(node);
delete this.nodes[node.getID()];
this.fireEvent({ node, isCreated: false }, 'nodesUpdated');
}
getLinks(): { [s: string]: LinkModel } {
return this.links;
}
getNodes(): { [s: string]: NodeModel } {
return this.nodes;
}
}

View File

@@ -1,7 +0,0 @@
import { BaseModel } from '../core-models/BaseModel';
export interface SelectionModel {
model: BaseModel;
initialX: number;
initialY: number;
}

View File

@@ -1 +0,0 @@
export class ActionManager {}

View File

@@ -1,41 +0,0 @@
import * as React from 'react';
export interface BaseWidgetProps {
/**
* Override the base class name
*/
baseClass?: string;
/**
* append additional classes
*/
className?: string;
/**
* Additional props to add
*/
extraProps?: any;
}
export class BaseWidget<P extends BaseWidgetProps = BaseWidgetProps, S = any> extends React.Component<P, S> {
className: string;
constructor(name: string, props: P) {
super(props);
this.className = name;
}
bem(selector: string): string {
return (this.props.baseClass || this.className) + selector + ' ';
}
getClassName(): string {
return (this.props.baseClass || this.className) + ' ' + (this.props.className ? this.props.className + ' ' : '');
}
getProps(): any {
return {
...((this.props.extraProps as any) || {}),
className: this.getClassName()
};
}
}

View File

@@ -1,288 +0,0 @@
import * as React from 'react';
import { DiagramEngine } from '../DiagramEngine';
import * as _ from 'lodash';
import { LinkLayerWidget } from './layers/LinkLayerWidget';
import { NodeLayerWidget } from './layers/NodeLayerWidget';
import { AbstractMouseAction } from '../core-actions/AbstractMouseAction';
import { MoveItemsAction } from '../actions/move-items/MoveItemsAction';
import { SelectingAction } from '../actions/selecting-items/SelectingAction';
import { PointModel } from '../models/PointModel';
import { BaseWidget, BaseWidgetProps } from './BaseWidget';
import { MouseEvent } from 'react';
import { ActionFactoryActivationEvent } from '../core-actions/AbstractActionFactory';
import { AbstractAction } from '../core-actions/AbstractAction';
import { MoveItemsActionFactory } from '../actions/move-items/MoveItemsActionFactory';
export interface DiagramProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
// zoom
allowCanvasZoom?: boolean;
inverseZoom?: boolean;
actionStartedFiring?: (action: AbstractAction) => boolean;
actionStillFiring?: (action: AbstractAction) => void;
actionStoppedFiring?: (action: AbstractAction) => void;
deleteKeys?: number[];
}
export interface DiagramState {
action: AbstractAction;
diagramEngineListener: any;
}
export class DiagramWidget extends BaseWidget<DiagramProps, DiagramState> {
onKeyUpPointer: (this: Window, ev: KeyboardEvent) => void = null;
ref: React.RefObject<HTMLDivElement>;
constructor(props: DiagramProps) {
super('srd-diagram', props);
this.ref = React.createRef();
this.state = {
action: null,
diagramEngineListener: null
};
}
componentWillUnmount() {
this.props.diagramEngine.deregisterListener(this.state.diagramEngineListener);
this.props.diagramEngine.setCanvas(null);
window.removeEventListener('keyup', this.onKeyUpPointer);
window.removeEventListener('mouseUp', this.onMouseUp);
window.removeEventListener('mouseMove', this.onMouseMove);
}
componentWillReceiveProps(nextProps: DiagramProps) {
if (this.props.diagramEngine !== nextProps.diagramEngine) {
this.props.diagramEngine.deregisterListener(this.state.diagramEngineListener);
const diagramEngineListener = nextProps.diagramEngine.registerListener({
repaintCanvas: () => this.forceUpdate()
});
this.setState({ diagramEngineListener });
}
}
registerCanvas() {
this.props.diagramEngine.setCanvas(this.ref.current);
this.props.diagramEngine.iterateListeners(list => {
list.rendered && list.rendered();
});
}
componentDidUpdate() {
this.registerCanvas();
}
componentDidMount() {
this.onKeyUpPointer = this.onKeyUp.bind(this);
//add a keyboard listener
this.setState({
diagramEngineListener: this.props.diagramEngine.registerListener({
repaintCanvas: () => {
this.forceUpdate();
}
})
});
window.addEventListener('keyup', this.onKeyUpPointer, false);
// dont focus the window when in test mode - jsdom fails
if (process.env.NODE_ENV !== 'test') {
window.focus();
}
this.registerCanvas();
}
fireAction() {
if (this.state.action && this.props.actionStillFiring) {
this.props.actionStillFiring(this.state.action);
}
}
stopFiringAction(shouldSkipEvent?: boolean) {
if (this.props.actionStoppedFiring && !shouldSkipEvent) {
this.props.actionStoppedFiring(this.state.action);
}
this.setState({ action: null });
}
startFiringAction(action: AbstractAction) {
var setState = true;
if (this.props.actionStartedFiring) {
setState = this.props.actionStartedFiring(action);
}
if (setState) {
this.setState({ action: action });
}
}
onMouseUp = event => {
if (this.state.action && this.state.action instanceof AbstractMouseAction) {
this.state.action.fireMouseUp(event);
}
this.stopFiringAction();
document.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('mouseup', this.onMouseUp);
};
onMouseMove = event => {
//select items so draw a bounding box
if (this.state.action) {
if (this.state.action && this.state.action instanceof AbstractMouseAction) {
this.state.action.fireMouseMove(event);
}
this.fireAction();
this.forceUpdate();
}
};
onKeyUp = event => {
//delete all selected
if ((this.props.deleteKeys || [46, 8]).indexOf(event.keyCode) !== -1) {
_.forEach(this.props.diagramEngine.getDiagramModel().getSelectedItems(), element => {
//only delete items which are not locked
if (!this.props.diagramEngine.isModelLocked(element)) {
element.remove();
}
});
this.forceUpdate();
}
};
drawSelectionBox() {
let dimensions = (this.state.action as SelectingAction).getBoxDimensions();
return (
<div
className={this.bem('__selector')}
style={{
top: dimensions.top,
left: dimensions.left,
width: dimensions.width,
height: dimensions.height
}}
/>
);
}
getActionForEvent(event: MouseEvent): AbstractAction {
event.persist();
const { diagramEngine } = this.props;
const model = diagramEngine.getMouseElement(event);
const activateEvent: ActionFactoryActivationEvent = {
selectedModel: model && model.model,
selectedEntity: model && (model.element as HTMLElement),
mouseEvent: event
};
for (let factory of diagramEngine.getActionFactories().getFactories()) {
if (factory.activate(activateEvent)) {
return factory.generateAction(event);
}
}
return null;
}
render() {
const diagramEngine = this.props.diagramEngine;
const diagramModel = diagramEngine.getDiagramModel();
return (
<div
{...this.getProps()}
ref={this.ref}
onWheel={event => {
const allow = this.props.allowCanvasZoom == null ? true : this.props.allowCanvasZoom;
if (allow) {
event.stopPropagation();
const oldZoomFactor = diagramModel.getZoomLevel() / 100;
let scrollDelta = this.props.inverseZoom ? -event.deltaY : event.deltaY;
//check if it is pinch gesture
if (event.ctrlKey && scrollDelta % 1 !== 0) {
/*Chrome and Firefox sends wheel event with deltaY that
have fractional part, also `ctrlKey` prop of the event is true
though ctrl isn't pressed
*/
scrollDelta /= 3;
} else {
scrollDelta /= 60;
}
if (diagramModel.getZoomLevel() + scrollDelta > 10) {
diagramModel.setZoomLevel(diagramModel.getZoomLevel() + scrollDelta);
}
const zoomFactor = diagramModel.getZoomLevel() / 100;
const boundingRect = event.currentTarget.getBoundingClientRect();
const clientWidth = boundingRect.width;
const clientHeight = boundingRect.height;
// compute difference between rect before and after scroll
const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor;
const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor;
// compute mouse coords relative to canvas
const clientX = event.clientX - boundingRect.left;
const clientY = event.clientY - boundingRect.top;
// compute width and height increment factor
const xFactor = (clientX - diagramModel.getOffsetX()) / oldZoomFactor / clientWidth;
const yFactor = (clientY - diagramModel.getOffsetY()) / oldZoomFactor / clientHeight;
diagramModel.setOffset(
diagramModel.getOffsetX() - widthDiff * xFactor,
diagramModel.getOffsetY() - heightDiff * yFactor
);
this.forceUpdate();
}
}}
onMouseDown={event => {
// try and get an action for this event
const action = this.getActionForEvent(event);
if (action) {
if (action instanceof AbstractMouseAction) {
const selected = diagramEngine.getMouseElement(event);
action.fireMouseDown({
mouseEvent: event,
selectedEntity: selected && (selected.element as HTMLElement),
selectedModel: selected && selected.model
});
}
this.startFiringAction(action);
}
document.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('mouseup', this.onMouseUp);
}}>
<LinkLayerWidget
diagramEngine={diagramEngine}
pointAdded={(point: PointModel, event) => {
document.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('mouseup', this.onMouseUp);
event.stopPropagation();
// TODO implement this better and more generic
let action: MoveItemsAction = null;
let fac: MoveItemsActionFactory = null;
try {
fac = diagramEngine.getActionFactories().getFactory<MoveItemsActionFactory>(MoveItemsActionFactory.NAME);
} catch (e) {}
if (fac) {
action = fac.generateAction(event);
action.fireMouseDown({
selectedModel: point,
selectedEntity: event.target as HTMLElement,
mouseEvent: event
});
this.startFiringAction(action);
}
}}
/>
<NodeLayerWidget diagramEngine={diagramEngine} />
{this.state.action instanceof SelectingAction && this.drawSelectionBox()}
</div>
);
}
}

View File

@@ -1,118 +0,0 @@
import * as React from 'react';
import { DiagramEngine } from '../DiagramEngine';
import { LinkModel } from '../models/LinkModel';
import { ListenerHandle } from '../core/BaseObserver';
import { BaseEntityEvent } from '../core-models/BaseEntity';
import { BasePositionModel } from '../core-models/BasePositionModel';
import { PointModel } from '../models/PointModel';
import { PortModel } from '../models/PortModel';
import { MouseEvent } from 'react';
import * as _ from 'lodash';
import { LabelWidget } from './LabelWidget';
import { PeformanceWidget } from './PeformanceWidget';
export interface LinkProps {
link: LinkModel;
diagramEngine: DiagramEngine;
pointAdded: (point: PointModel, event: MouseEvent) => any;
}
export interface LinkState {
sourceID: PortModel;
targetID: PortModel;
}
export class LinkWidget extends React.Component<LinkProps, LinkState> {
sourceListener: ListenerHandle;
targetListener: ListenerHandle;
constructor(props) {
super(props);
this.state = {
sourceID: null,
targetID: null
};
}
componentWillUnmount(): void {
if (this.sourceListener) {
this.sourceListener.deregister();
}
if (this.targetListener) {
this.targetListener.deregister();
}
}
static getDerivedStateFromProps(nextProps: LinkProps, prevState: LinkState): LinkState {
return {
sourceID: nextProps.link.getSourcePort(),
targetID: nextProps.link.getTargetPort()
};
}
ensureInstalled(installSource: boolean, installTarget: boolean) {
if (installSource) {
this.sourceListener = this.props.link.getSourcePort().registerListener({
reportInitialPosition: (event: BaseEntityEvent<BasePositionModel>) => {
this.forceUpdate();
}
});
}
if (installTarget) {
this.targetListener = this.props.link.getTargetPort().registerListener({
reportInitialPosition: (event: BaseEntityEvent<BasePositionModel>) => {
this.forceUpdate();
}
});
}
}
componentDidUpdate(prevProps: Readonly<LinkProps>, prevState: Readonly<LinkState>, snapshot?: any): void {
let installSource = false;
let installTarget = false;
if (this.state.sourceID !== prevState.sourceID) {
this.sourceListener && this.sourceListener.deregister();
installSource = true;
}
if (this.state.targetID !== prevState.targetID) {
this.targetListener && this.targetListener.deregister();
installTarget = true;
}
this.ensureInstalled(installSource, installTarget);
}
componentDidMount(): void {
this.ensureInstalled(!!this.props.link.getSourcePort(), !!this.props.link.getTargetPort());
}
render() {
const { link } = this.props;
// only draw the link when we have reported positions
if (link.getSourcePort() && !link.getSourcePort().reportedPosition) {
return null;
}
if (link.getTargetPort() && !link.getTargetPort().reportedPosition) {
return null;
}
//generate links
return (
<PeformanceWidget serialized={this.props.link.serialize()}>
{() => {
return (
<g>
{React.cloneElement(this.props.diagramEngine.generateWidgetForLink(link), {
pointAdded: this.props.pointAdded
})}
{_.map(this.props.link.getLabels(), (labelModel, index) => {
return <LabelWidget engine={this.props.diagramEngine} label={labelModel} index={index} />;
})}
</g>
);
}}
</PeformanceWidget>
);
}
}

View File

@@ -1,48 +0,0 @@
import * as React from 'react';
import { DiagramEngine } from '../../DiagramEngine';
import { LinkWidget } from '../LinkWidget';
import * as _ from 'lodash';
import { PointModel } from '../../models/PointModel';
import { BaseWidget, BaseWidgetProps } from '../BaseWidget';
import { MouseEvent } from 'react';
export interface LinkLayerProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
pointAdded: (point: PointModel, event: MouseEvent) => any;
}
export class LinkLayerWidget extends BaseWidget<LinkLayerProps> {
constructor(props: LinkLayerProps) {
super('srd-link-layer', props);
}
render() {
var diagramModel = this.props.diagramEngine.getDiagramModel();
return (
<svg
{...this.getProps()}
style={{
transform:
'translate(' +
diagramModel.getOffsetX() +
'px,' +
diagramModel.getOffsetY() +
'px) scale(' +
diagramModel.getZoomLevel() / 100.0 +
')'
}}>
{//only perform these actions when we have a diagram
_.map(diagramModel.getLinks(), link => {
return (
<LinkWidget
pointAdded={this.props.pointAdded}
key={link.getID()}
link={link}
diagramEngine={this.props.diagramEngine}
/>
);
})}
</svg>
);
}
}

View File

@@ -1,38 +0,0 @@
import * as React from 'react';
import { DiagramEngine } from '../../DiagramEngine';
import * as _ from 'lodash';
import { NodeWidget } from '../NodeWidget';
import { NodeModel } from '../../models/NodeModel';
import { BaseWidget, BaseWidgetProps } from '../BaseWidget';
export interface NodeLayerProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
}
export class NodeLayerWidget extends BaseWidget<NodeLayerProps> {
constructor(props: NodeLayerProps) {
super('srd-node-layer', props);
}
render() {
var diagramModel = this.props.diagramEngine.getDiagramModel();
return (
<div
{...this.getProps()}
style={{
transform:
'translate(' +
diagramModel.getOffsetX() +
'px,' +
diagramModel.getOffsetY() +
'px) scale(' +
diagramModel.getZoomLevel() / 100.0 +
')'
}}>
{_.map(diagramModel.getNodes(), (node: NodeModel) => {
return <NodeWidget key={node.getID()} diagramEngine={this.props.diagramEngine} node={node} />;
})}
</div>
);
}
}

View File

@@ -1,8 +0,0 @@
const config = require('../webpack.shared')(__dirname);
module.exports = {
...config,
output: {
...config.output,
library: 'projectstorm/react-diagrams-core'
}
};

View File

@@ -1,14 +0,0 @@
import { AbstractFactory, PortModel } from '@projectstorm/react-diagrams';
export class SimplePortFactory extends AbstractFactory {
cb: (initialConfig?: any) => PortModel;
constructor(type: string, cb: (initialConfig?: any) => PortModel) {
super(type);
this.cb = cb;
}
generateModel(event): PortModel {
return this.cb(event.initialConfig);
}
}

View File

@@ -1,21 +0,0 @@
import * as React from 'react';
export interface TrayWidgetProps {}
export interface TrayWidgetState {}
/**
* @author Dylan Vorster
*/
export class TrayWidget extends React.Component<TrayWidgetProps, TrayWidgetState> {
public static defaultProps: TrayWidgetProps = {};
constructor(props: TrayWidgetProps) {
super(props);
this.state = {};
}
render() {
return <div className="tray">{this.props.children}</div>;
}
}

View File

@@ -1,47 +0,0 @@
.body {
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 100%;
.header {
display: flex;
background: rgb(30, 30, 30);
flex-grow: 0;
flex-shrink: 0;
color: white;
font-family: Helvetica, Arial;
padding: 10px;
> * {
align-self: center;
}
}
.content {
display: flex;
flex-grow: 1;
.diagram-layer {
position: relative;
flex-grow: 1;
}
.tray {
min-width: 200px;
background: rgb(20, 20, 20);
flex-grow: 0;
flex-shrink: 0;
.tray-item {
color: white;
font-family: Helvetica, Arial;
padding: 5px;
margin: 0px 10px;
border: solid 1px;
border-radius: 5px;
margin-bottom: 2px;
cursor: pointer;
}
}
}
}

View File

@@ -1,46 +0,0 @@
import * as React from 'react';
import createEngine, {
DiagramModel,
DefaultNodeModel,
DiagramWidget,
DiagramProps
} from '@projectstorm/react-diagrams';
/**
* Shows that a limit of points can be set for links
*/
export default () => {
// setup the diagram engine
var engine = createEngine();
var model = new DiagramModel();
//3-A) create a default node
var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)');
let port = node1.addOutPort('Out');
node1.setPosition(100, 100);
//3-B) create another default node
var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)');
let port2 = node2.addInPort('In');
node2.setPosition(400, 100);
// link the ports
let link1 = port.link(port2);
model.addAll(node1, node2, link1);
engine.setDiagramModel(model);
var props = {
diagramEngine: engine,
maxNumberPointsPerLink: 5
} as DiagramProps;
return (
<div>
<p>A maximum of 5 points can be created per link.</p>
<DiagramWidget className="srd-demo-canvas" {...props} />
</div>
);
};

View File

@@ -1,23 +0,0 @@
import * as React from 'react';
export interface DemoWorkspaceWidgetProps {
buttons?: any;
}
export interface DemoWorkspaceWidgetState {}
export class DemoWorkspaceWidget extends React.Component<DemoWorkspaceWidgetProps, DemoWorkspaceWidgetState> {
constructor(props: DemoWorkspaceWidgetProps) {
super(props);
this.state = {};
}
render() {
return (
<div className="srd-demo-workspace">
<div className="srd-demo-workspace__toolbar">{this.props.buttons}</div>
<div className="srd-demo-workspace__content">{this.props.children}</div>
</div>
);
}
}

View File

@@ -1,79 +0,0 @@
html,
body,
#root {
height: 100%;
padding: 0;
margin: 0;
}
.srd-demo-workspace {
background: black;
display: flex;
flex-direction: column;
height: 100%;
border-radius: 5px;
overflow: hidden;
&__toolbar {
padding: 5px;
display: flex;
flex-shrink: 0;
button {
background: rgb(60, 60, 60);
font-size: 14px;
padding: 5px 10px;
border: none;
color: white;
outline: none;
cursor: pointer;
margin: 2px;
border-radius: 3px;
&:hover {
background: rgb(0, 192, 255);
}
}
}
&__content {
flex-grow: 1;
height: 100%;
}
}
.srd-demo-canvas {
height: 100%;
min-height: 300px;
background-color: rgb(60, 60, 60) !important;
$color: rgba(white, 0.05);
background-image: linear-gradient(
0deg,
transparent 24%,
$color 25%,
$color 26%,
transparent 27%,
transparent 74%,
$color 75%,
$color 76%,
transparent 77%,
transparent
),
linear-gradient(
90deg,
transparent 24%,
$color 25%,
$color 26%,
transparent 27%,
transparent 74%,
$color 75%,
$color 76%,
transparent 77%,
transparent
);
background-size: 50px 50px;
.pointui {
fill: rgba(white, 0.5);
}
}

View File

@@ -1,28 +0,0 @@
{
"name": "@projectstorm/react-diagrams-gallery",
"version": "6.0.0-y.0",
"author": "dylanvorster",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/projectstorm/react-diagrams.git"
},
"scripts": {
"start": "../node_modules/.bin/start-storybook",
"build": "../node_modules/.bin/build-storybook -c .storybook -o .out",
"github": "../node_modules/.bin/storybook-to-ghpages",
"test:run": "../node_modules/.bin/jest --no-cache",
"test": "yarn build && yarn test:run"
},
"keywords": [
"web",
"diagram",
"diagrams",
"react",
"typescript",
"flowchart",
"simple",
"links",
"nodes"
]
}

View File

@@ -1,135 +0,0 @@
import { ElementHandle, FrameBase } from 'puppeteer';
import * as _ from 'lodash';
export abstract class E2EElement {
id: string;
constructor(id: string) {
this.id = id;
}
async getSelector(): Promise<FrameBase> {
return page;
}
async getElement(): Promise<ElementHandle | null> {
if (!(await this.getSelector())) {
return null;
}
return (await this.getSelector()).$(this.selector());
}
async waitForElement(): Promise<ElementHandle | null> {
return (await this.getSelector()).waitForSelector(this.selector(), {
timeout: 1000
});
}
protected abstract selector(): string;
}
export class E2ENode extends E2EElement {
async port(id: string): Promise<E2EPort> {
return new E2EPort(id, this);
}
protected selector(): string {
return `div[data-nodeid="${this.id}"]`;
}
}
export class E2EPort extends E2EElement {
parent: E2ENode;
constructor(id: string, parent: E2ENode) {
super(id);
this.parent = parent;
}
async getLinks(): Promise<E2ELink[]> {
const element = await this.getElement();
const attribute = await page.evaluate(obj => {
return obj.getAttribute('data-links');
}, element);
if (attribute.trim() === '') {
return [];
}
return _.map(attribute.split(','), id => {
return new E2ELink(id);
});
}
async link(port: E2EPort): Promise<E2ELink> {
let currentLinks = _.map(await this.getLinks(), 'id');
let bounds = await (await this.getElement()).boundingBox();
// click on this port
page.mouse.move(bounds.x, bounds.y);
page.mouse.down();
//
let bounds2 = await (await port.getElement()).boundingBox();
// drag to other port
page.mouse.move(bounds2.x, bounds2.y);
page.mouse.up();
let newLinks = _.map(await this.getLinks(), 'id');
// get the parent to get the link
return new E2ELink(_.difference(newLinks, currentLinks)[0]);
}
async linkToPoint(x: number, y: number): Promise<E2ELink> {
let currentLinks = _.map(await this.getLinks(), 'id');
let bounds = await (await this.getElement()).boundingBox();
// click on this port
page.mouse.move(bounds.x, bounds.y);
page.mouse.down();
// drag to point
page.mouse.move(x, y);
page.mouse.up();
let newLinks = _.map(await this.getLinks(), 'id');
const link = _.difference(newLinks, currentLinks)[0];
if (!link) {
return null;
}
// get the parent to get the link
return new E2ELink(link);
}
async getSelector(): Promise<FrameBase> {
return (await this.parent.getElement()) as any;
}
protected selector(): string {
return `div[data-name="${this.id}"]`;
}
}
export class E2ELink extends E2EElement {
async select(): Promise<any> {
const point = await page.evaluate(id => {
const path = document.querySelector(`path[data-linkid="${id}"]`) as SVGPathElement;
const point = path.getPointAtLength(path.getTotalLength() / 2);
return {
x: point.x,
y: point.y
};
}, this.id);
await page.keyboard.down('Shift');
await page.mouse.move(point.x, point.y);
await page.mouse.down();
await page.keyboard.up('Shift');
}
protected selector(): string {
return `path[data-linkid="${this.id}"]`;
}
}

View File

@@ -1,12 +0,0 @@
import * as React from 'react';
import { DiagramEngine, DiagramWidget } from '@projectstorm/react-diagrams';
export interface BodyWidgetProps {
engine: DiagramEngine;
}
export class BodyWidget extends React.Component<BodyWidgetProps> {
render() {
return <DiagramWidget className="diagram-container" diagramEngine={this.props.engine} />;
}
}

View File

@@ -7,15 +7,7 @@
"type": "git",
"url": "https://github.com/projectstorm/react-diagrams.git"
},
"workspaces": [
"lib-all",
"lib-core",
"lib-defaults",
"lib-routing",
"lib-geometry",
"lib-demo-project",
"lib-demo-gallery"
],
"workspaces": ["packages/*"],
"keywords": [
"web",
"diagram",
@@ -29,12 +21,14 @@
],
"scripts": {
"build": "yarn build:clean && lerna run build --stream",
"build:prod": "yarn build:clean && lerna run build:prod --stream",
"watch": "yarn build:clean && lerna run watch --stream",
"publish:npm": "lerna publish",
"publish:dev": "yarn build:prod && lerna publish --force-publish --dist-tag=next",
"publish:prod": "yarn build:prod && lerna publish --force-publish",
"publish:storybook": "cd packages/diagrams-demo-gallery && yarn storybook:build && ../../node_modules/.bin/storybook-to-ghpages --existing-output-dir .out",
"build:clean": "lerna run clean --stream",
"test": "lerna run test --stream",
"test:ci": "lerna run test --stream -- --runInBand --ci ",
"pretty": "prettier --write \"lib-*/**/*.{ts,tsx,scss,js,jsx}\""
"pretty": "prettier --write \"packages/**/*.{ts,tsx,scss,js,jsx}\""
},
"peerDependencies": {
"closest": "^0.0.1",
@@ -55,10 +49,10 @@
"@storybook/storybook-deployer": "^2.8.1",
"@storybook/theming": "^5.1.9",
"@types/dagre": "^0.7.42",
"@types/jest": "^24.0.15",
"@types/jest": "^24.0.16",
"@types/jest-environment-puppeteer": "^4.0.0",
"@types/lodash": "^4.14.136",
"@types/mathjs": "^6.0.0",
"@types/mathjs": "^6.0.1",
"@types/node": "^12.6.8",
"@types/promise": "^7.1.30",
"@types/puppeteer": "^1.19.0",
@@ -79,7 +73,6 @@
"lerna": "^3.16.4",
"lodash": "4.*",
"mathjs": "^6.0.3",
"node-sass": "^4.12.0",
"pathfinding": "^0.4.18",
"paths-js": "^0.4.9",
"prettier": "^1.18.2",
@@ -90,7 +83,6 @@
"react-dom": "^16.8.6",
"react-syntax-highlighter": "^11.0.2",
"react-test-renderer": "^16.8.6",
"sass-loader": "^7.1.0",
"source-map-loader": "^0.2.4",
"storybook-host": "^5.1.0",
"storybook-readme": "^5.0.5",

View File

@@ -19,8 +19,7 @@ module.exports = async ({ config, mode }) => {
{
loader: 'postcss-loader',
options: { config: { path: path.join(__dirname, '..') } }
},
'sass-loader'
}
]
},
{

View File

@@ -1,14 +1,9 @@
import createEngine, {
DiagramModel,
DefaultNodeModel,
LinkModel,
NodeModel,
DiagramWidget,
BaseModel
} from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel, LinkModel, NodeModel } from '@projectstorm/react-diagrams';
import * as _ from 'lodash';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { BaseModel, CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
* Tests cloning
@@ -22,10 +17,10 @@ class CloneSelected extends React.Component<any, any> {
cloneSelected() {
let { engine } = this.props;
let offset = { x: 100, y: 100 };
let model = engine.getDiagramModel();
let model = engine.getModel();
let itemMap = {};
_.forEach(model.getSelectedItems(), (item: BaseModel<any>) => {
_.forEach(model.getSelectedEntities(), (item: BaseModel<any>) => {
let newItem = item.clone(itemMap);
// offset the nodes slightly
@@ -39,7 +34,7 @@ class CloneSelected extends React.Component<any, any> {
});
model.addLink(newItem);
}
newItem.selected = false;
(newItem as BaseModel).setSelected(false);
});
this.forceUpdate();
@@ -48,8 +43,10 @@ class CloneSelected extends React.Component<any, any> {
render() {
const { engine } = this.props;
return (
<DemoWorkspaceWidget buttons={<button onClick={this.cloneSelected}>Clone Selected</button>}>
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
<DemoWorkspaceWidget buttons={<DemoButton onClick={this.cloneSelected}>Clone Selected</DemoButton>}>
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
}
@@ -79,7 +76,7 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <CloneSelected engine={engine} model={model} />;

View File

@@ -2,13 +2,12 @@ import createEngine, {
DiagramModel,
DefaultNodeModel,
DefaultPortModel,
DiagramWidget,
DefaultLinkWidget,
DefaultLinkFactory,
LinkModel,
DefaultLinkModel
} from '@projectstorm/react-diagrams';
import * as React from 'react';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
export class AdvancedLinkModel extends DefaultLinkModel {
constructor() {
@@ -70,6 +69,7 @@ export class AdvancedLinkSegment extends React.Component<{ model: AdvancedLinkMo
return (
<>
<path
fill="none"
ref={ref => {
this.path = ref;
}}
@@ -91,15 +91,14 @@ export class AdvancedLinkSegment extends React.Component<{ model: AdvancedLinkMo
export class AdvancedLinkFactory extends DefaultLinkFactory {
constructor() {
super();
this.type = 'advanced';
super('advanced');
}
generateModel(): AdvancedLinkModel {
return new AdvancedLinkModel();
}
generateLinkSegment(model: AdvancedLinkModel, widget: DefaultLinkWidget, selected: boolean, path: string) {
generateLinkSegment(model: AdvancedLinkModel, selected: boolean, path: string) {
return (
<g>
<AdvancedLinkSegment model={model} path={path} />
@@ -147,8 +146,12 @@ export default () => {
model.addAll(node1, node2, node3, node4);
// load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
// render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};

View File

@@ -1,9 +1,10 @@
import { DiamonNodeWidget } from './DiamondNodeWidget';
import { DiamondNodeModel } from './DiamondNodeModel';
import * as React from 'react';
import { AbstractReactFactory } from '@projectstorm/react-diagrams';
import { AbstractReactFactory } from '@projectstorm/react-canvas-core';
import { DiagramEngine } from '@projectstorm/react-diagrams-core';
export class DiamondNodeFactory extends AbstractReactFactory<DiamondNodeModel> {
export class DiamondNodeFactory extends AbstractReactFactory<DiamondNodeModel, DiagramEngine> {
constructor() {
super('diamond');
}

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { DiamondNodeModel } from './DiamondNodeModel';
import { DiagramEngine, PortModelAlignment, PortWidget } from '@projectstorm/react-diagrams';
import styled from '@emotion/styled';
export interface DiamonNodeWidgetProps {
node: DiamondNodeModel;
@@ -8,17 +9,25 @@ export interface DiamonNodeWidgetProps {
size?: number;
}
export interface DiamonNodeWidgetState {}
namespace S {
export const Port = styled.div`
width: 16px;
height: 16px;
z-index: 10;
background: rgba(0, 0, 0, 0.5);
border-radius: 8px;
cursor: pointer;
&:hover {
background: rgba(0, 0, 0, 1);
}
`;
}
/**
* @author Dylan Vorster
*/
export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, DiamonNodeWidgetState> {
constructor(props: DiamonNodeWidgetProps) {
super(props);
this.state = {};
}
export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps> {
render() {
return (
<div
@@ -37,7 +46,9 @@ export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, Dia
<g id="Layer_1">
</g>
<g id="Layer_2">
<polygon fill="mediumpurple" stroke="#000000" stroke-width="3" stroke-miterlimit="10" points="10,` +
<polygon fill="mediumpurple" stroke="${
this.props.node.isSelected() ? 'white' : '#000000'
}" stroke-width="3" stroke-miterlimit="10" points="10,` +
this.props.size / 2 +
` ` +
this.props.size / 2 +
@@ -54,42 +65,46 @@ export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, Dia
`
}}
/>
<div
<PortWidget
style={{
position: 'absolute',
zIndex: 10,
top: this.props.size / 2 - 8,
left: -8
}}>
<PortWidget port={this.props.node.getPort(PortModelAlignment.LEFT)} engine={this.props.engine} />
</div>
<div
left: -8,
position: 'absolute'
}}
port={this.props.node.getPort(PortModelAlignment.LEFT)}
engine={this.props.engine}>
<S.Port />
</PortWidget>
<PortWidget
style={{
position: 'absolute',
zIndex: 10,
left: this.props.size / 2 - 8,
top: -8
}}>
<PortWidget port={this.props.node.getPort(PortModelAlignment.TOP)} engine={this.props.engine} />
</div>
<div
top: -8,
position: 'absolute'
}}
port={this.props.node.getPort(PortModelAlignment.TOP)}
engine={this.props.engine}>
<S.Port />
</PortWidget>
<PortWidget
style={{
position: 'absolute',
zIndex: 10,
left: this.props.size - 8,
top: this.props.size / 2 - 8
}}>
<PortWidget port={this.props.node.getPort(PortModelAlignment.RIGHT)} engine={this.props.engine} />
</div>
<div
top: this.props.size / 2 - 8,
position: 'absolute'
}}
port={this.props.node.getPort(PortModelAlignment.RIGHT)}
engine={this.props.engine}>
<S.Port />
</PortWidget>
<PortWidget
style={{
position: 'absolute',
zIndex: 10,
left: this.props.size / 2 - 8,
top: this.props.size - 8
}}>
<PortWidget port={this.props.node.getPort(PortModelAlignment.BOTTOM)} engine={this.props.engine} />
</div>
top: this.props.size - 8,
position: 'absolute'
}}
port={this.props.node.getPort(PortModelAlignment.BOTTOM)}
engine={this.props.engine}>
<S.Port />
</PortWidget>
</div>
);
}

View File

@@ -0,0 +1,15 @@
import { DiagramEngine, PortModel } from '@projectstorm/react-diagrams';
import { AbstractModelFactory } from '@projectstorm/react-canvas-core';
export class SimplePortFactory extends AbstractModelFactory<PortModel, DiagramEngine> {
cb: (initialConfig?: any) => PortModel;
constructor(type: string, cb: (initialConfig?: any) => PortModel) {
super(type);
this.cb = cb;
}
generateModel(event): PortModel {
return this.cb(event.initialConfig);
}
}

View File

@@ -1,15 +1,12 @@
import createEngine, {
DefaultNodeModel,
DiagramModel,
DiagramWidget,
PortModelAlignment
} from '@projectstorm/react-diagrams';
import createEngine, { DefaultNodeModel, DiagramModel, PortModelAlignment } from '@projectstorm/react-diagrams';
import * as React from 'react';
// import the custom models
import { DiamondNodeModel } from './DiamondNodeModel';
import { DiamondNodeFactory } from './DiamondNodeFactory';
import { SimplePortFactory } from './SimplePortFactory';
import { DiamondPortModel } from './DiamondPortModel';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
* @Author Dylan Vorster
@@ -60,8 +57,12 @@ export default () => {
model.addAll(node1, node2, node3, link1, link2, node4, link3, link4, node5);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};

View File

@@ -2,14 +2,15 @@ import createEngine, {
DiagramModel,
DefaultNodeModel,
DefaultPortModel,
DiagramWidget,
NodeModel,
DagreEngine,
DiagramEngine,
PathFindingLinkFactory
} from '@projectstorm/react-diagrams';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
function createNode(name): any {
return new DefaultNodeModel(name, 'rgb(0,192,255)');
@@ -32,7 +33,7 @@ function connectNodes(nodeFrom, nodeTo, engine: DiagramEngine) {
/**
* Tests auto distribution
*/
class Demo8Widget extends React.Component<{ model: DiagramModel; engine: DiagramEngine }, any> {
class DemoWidget extends React.Component<{ model: DiagramModel; engine: DiagramEngine }, any> {
engine: DagreEngine;
constructor(props) {
@@ -69,15 +70,10 @@ class Demo8Widget extends React.Component<{ model: DiagramModel; engine: Diagram
render() {
return (
<DemoWorkspaceWidget buttons={<button onClick={this.autoDistribute}>Re-distribute</button>}>
<DiagramWidget
className="srd-demo-canvas"
diagramEngine={this.props.engine}
actionStoppedFiring={() => {
// only happens if pathfing is enabled (check line 25)
this.reroute();
}}
/>
<DemoWorkspaceWidget buttons={<DemoButton onClick={this.autoDistribute}>Re-distribute</DemoButton>}>
<DemoCanvasWidget>
<CanvasWidget engine={this.props.engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
}
@@ -127,7 +123,7 @@ export default () => {
model.addLink(link);
});
engine.setDiagramModel(model);
engine.setModel(model);
return <Demo8Widget model={model} engine={engine} />;
return <DemoWidget model={model} engine={engine} />;
};

View File

@@ -14,7 +14,7 @@ export class Application {
public newModel() {
this.activeModel = new SRD.DiagramModel();
this.diagramEngine.setDiagramModel(this.activeModel);
this.diagramEngine.setModel(this.activeModel);
//3-A) create a default node
var node1 = new SRD.DefaultNodeModel('Node 1', 'rgb(0,192,255)');

View File

@@ -3,42 +3,64 @@ import * as _ from 'lodash';
import { TrayWidget } from './TrayWidget';
import { Application } from '../Application';
import { TrayItemWidget } from './TrayItemWidget';
import { DefaultNodeModel, DiagramWidget } from '@projectstorm/react-diagrams';
import { DefaultNodeModel } from '@projectstorm/react-diagrams';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../../helpers/DemoCanvasWidget';
import styled from '@emotion/styled';
export interface BodyWidgetProps {
app: Application;
}
export interface BodyWidgetState {}
namespace S {
export const Body = styled.div`
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 100%;
`;
/**
* @author Dylan Vorster
*/
export class BodyWidget extends React.Component<BodyWidgetProps, BodyWidgetState> {
constructor(props: BodyWidgetProps) {
super(props);
this.state = {};
}
export const Header = styled.div`
display: flex;
background: rgb(30, 30, 30);
flex-grow: 0;
flex-shrink: 0;
color: white;
font-family: Helvetica, Arial, sans-serif;
padding: 10px;
align-items: center;
`;
export const Content = styled.div`
display: flex;
flex-grow: 1;
`;
export const Layer = styled.div`
position: relative;
flex-grow: 1;
`;
}
export class BodyWidget extends React.Component<BodyWidgetProps> {
render() {
return (
<div className="body">
<div className="header">
<div className="title">Storm React Diagrams - Demo 5</div>
</div>
<div className="content">
<S.Body>
<S.Header>
<div className="title">Storm React Diagrams - DnD demo</div>
</S.Header>
<S.Content>
<TrayWidget>
<TrayItemWidget model={{ type: 'in' }} name="In Node" color="rgb(192,255,0)" />
<TrayItemWidget model={{ type: 'out' }} name="Out Node" color="rgb(0,192,255)" />
</TrayWidget>
<div
className="diagram-layer"
<S.Layer
onDrop={event => {
var data = JSON.parse(event.dataTransfer.getData('storm-diagram-node'));
var nodesCount = _.keys(
this.props.app
.getDiagramEngine()
.getDiagramModel()
.getModel()
.getNodes()
).length;
@@ -54,17 +76,19 @@ export class BodyWidget extends React.Component<BodyWidgetProps, BodyWidgetState
node.setPosition(point);
this.props.app
.getDiagramEngine()
.getDiagramModel()
.getModel()
.addNode(node);
this.forceUpdate();
}}
onDragOver={event => {
event.preventDefault();
}}>
<DiagramWidget className="srd-demo-canvas" diagramEngine={this.props.app.getDiagramEngine()} />
</div>
</div>
</div>
<DemoCanvasWidget>
<CanvasWidget engine={this.props.app.getDiagramEngine()} />
</DemoCanvasWidget>
</S.Layer>
</S.Content>
</S.Body>
);
}
}

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import styled from '@emotion/styled';
export interface TrayItemWidgetProps {
model: any;
@@ -6,25 +7,31 @@ export interface TrayItemWidgetProps {
name: string;
}
export interface TrayItemWidgetState {}
export class TrayItemWidget extends React.Component<TrayItemWidgetProps, TrayItemWidgetState> {
constructor(props: TrayItemWidgetProps) {
super(props);
this.state = {};
}
namespace S {
export const Tray = styled.div<{ color: string }>`
color: white;
font-family: Helvetica, Arial;
padding: 5px;
margin: 0px 10px;
border: solid 1px ${p => p.color};
border-radius: 5px;
margin-bottom: 2px;
cursor: pointer;
`;
}
export class TrayItemWidget extends React.Component<TrayItemWidgetProps> {
render() {
return (
<div
style={{ borderColor: this.props.color }}
<S.Tray
color={this.props.color}
draggable={true}
onDragStart={event => {
event.dataTransfer.setData('storm-diagram-node', JSON.stringify(this.props.model));
}}
className="tray-item">
{this.props.name}
</div>
</S.Tray>
);
}
}

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import styled from '@emotion/styled';
namespace S {
export const Tray = styled.div`
min-width: 200px;
background: rgb(20, 20, 20);
flex-grow: 0;
flex-shrink: 0;
`;
}
export class TrayWidget extends React.Component {
render() {
return <S.Tray>{this.props.children}</S.Tray>;
}
}

View File

@@ -3,10 +3,7 @@ import * as React from 'react';
import { BodyWidget } from './components/BodyWidget';
import { Application } from './Application';
import './sass/main.scss';
export default () => {
var app = new Application();
return <BodyWidget app={app} />;
};

View File

@@ -1,12 +1,9 @@
import createEngine, {
DiagramModel,
DefaultNodeModel,
DiagramWidget,
DiagramEngine
} from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel, DiagramEngine } from '@projectstorm/react-diagrams';
import * as _ from 'lodash';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
class CloneSelected extends React.Component<{ model: DiagramModel; engine: DiagramEngine }, any> {
addPorts = () => {
@@ -24,8 +21,10 @@ class CloneSelected extends React.Component<{ model: DiagramModel; engine: Diagr
render() {
const { engine } = this.props;
return (
<DemoWorkspaceWidget buttons={<button onClick={this.addPorts}>Add more ports</button>}>
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
<DemoWorkspaceWidget buttons={<DemoButton onClick={this.addPorts}>Add more ports</DemoButton>}>
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
}
@@ -52,7 +51,7 @@ export default () => {
model.addAll(node1, node2);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <CloneSelected engine={engine} model={model} />;

View File

@@ -1,5 +1,7 @@
import createEngine, { DiagramModel, DefaultNodeModel, DiagramWidget } from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
* Tests the grid size
@@ -29,8 +31,12 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};

View File

@@ -1,12 +1,9 @@
import createEngine, {
DiagramModel,
DefaultNodeModel,
DiagramWidget,
DefaultLinkModel
} from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel, DefaultLinkModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { action } from '@storybook/addon-actions';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
export default () => {
// setup the diagram engine
@@ -48,19 +45,21 @@ export default () => {
model.addAll(node1, node2, node3, node4, link1, link2, link3);
// load model into engine and render
engine.setDiagramModel(model);
engine.setModel(model);
return (
<DemoWorkspaceWidget
buttons={
<button
<DemoButton
onClick={() => {
action('Serialized Graph')(JSON.stringify(model.serializeDiagram(), null, 2));
}}>
Serialize Graph
</button>
</DemoButton>
}>
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
};

View File

@@ -1,11 +1,8 @@
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import createEngine, {
DiagramModel,
DiagramProps,
DefaultNodeModel,
DiagramWidget
} from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
* Shows some of the events triggered when elements are selected
@@ -39,21 +36,19 @@ export default () => {
// add a selection listener to each
models.forEach(item => {
item.registerListener({
selectionChanged: action('selectionChanged')
eventDidFire: action('element eventDidFire')
});
});
engine.setDiagramModel(model);
model.registerListener({
eventDidFire: action('model eventDidFire')
});
var props = {
diagramEngine: engine,
maxNumberPointsPerLink: 0 // no extra points so link selection is fired straight away
} as DiagramProps;
engine.setModel(model);
return (
<div>
<p>Click the diagram elements to inspect some of the possible events.</p>
<DiagramWidget className="srd-demo-canvas" {...props} />
</div>
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};

View File

@@ -1,10 +1,7 @@
import * as React from 'react';
import createEngine, {
DiagramModel,
DefaultNodeModel,
DiagramWidget,
DiagramProps
} from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
*
@@ -47,19 +44,17 @@ export default () => {
model.addAll(node3, node4, link2);
engine.setDiagramModel(model);
engine.setModel(model);
//!========================================= <<<<<<<
model.setLocked(true);
var props = {
diagramEngine: engine,
allowLooseLinks: false,
allowCanvasTranslation: false,
allowCanvasZoom: false
} as DiagramProps;
//!========================================= <<<<<<<
return <DiagramWidget className="srd-demo-canvas" {...props} />;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};

View File

@@ -1,6 +1,9 @@
import createEngine, { DiagramModel, DefaultNodeModel, DiagramWidget } from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel, NodeModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import * as _ from 'lodash';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
* Tests the grid size
@@ -14,25 +17,25 @@ class NodeDelayedPosition extends React.Component<any, any> {
updatePosition() {
const { engine } = this.props;
let model = engine.getDiagramModel();
let model = engine.getModel();
const nodes = model.getNodes();
let node = nodes[Object.keys(nodes)[0]];
node.setPosition(node.x + 30, node.y + 30);
this.forceUpdate();
node.setPosition(node.getX() + 30, node.getY() + 30);
engine.repaintCanvas();
}
updatePositionViaSerialize() {
let { engine } = this.props;
let model = engine.getDiagramModel();
let str = JSON.stringify(model.serializeDiagram());
let model = engine.getModel();
let str = JSON.stringify(model.serialize());
let model2 = new DiagramModel();
let obj = JSON.parse(str);
let node = obj.nodes[0];
let obj: ReturnType<DiagramModel['serialize']> = JSON.parse(str);
let node: ReturnType<NodeModel['serialize']> = _.values(obj.layers[1].models)[0] as any;
node.x += 30;
node.y += 30;
model2.deSerializeDiagram(obj, engine);
engine.setDiagramModel(model2);
this.forceUpdate();
model2.deserializeModel(obj, engine);
engine.setModel(model2);
}
render() {
@@ -40,14 +43,16 @@ class NodeDelayedPosition extends React.Component<any, any> {
return (
<DemoWorkspaceWidget
buttons={[
<button key={1} onClick={this.updatePosition}>
<DemoButton key={1} onClick={this.updatePosition}>
Update position
</button>,
<button key={2} onClick={this.updatePositionViaSerialize}>
</DemoButton>,
<DemoButton key={2} onClick={this.updatePositionViaSerialize}>
Update position via serialize
</button>
</DemoButton>
]}>
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
}
@@ -77,7 +82,7 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <NodeDelayedPosition engine={engine} model={model} />;

View File

@@ -1,5 +1,7 @@
import createEngine, { DiagramModel, DefaultNodeModel, DiagramWidget } from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
*
@@ -22,10 +24,14 @@ export default () => {
}
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {

View File

@@ -0,0 +1,70 @@
import createEngine, {
DiagramModel,
DefaultNodeModel,
DefaultPortModel,
RightAngleLinkFactory,
LinkModel,
RightAngleLinkModel
} from '@projectstorm/react-diagrams';
import * as React from 'react';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { action } from '@storybook/addon-actions';
import { AbstractModelFactory, CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
// When new link is created by clicking on port the RightAngleLinkModel needs to be returned.
export class RightAnglePortModel extends DefaultPortModel {
createLinkModel(factory?: AbstractModelFactory<LinkModel>) {
return new RightAngleLinkModel();
}
}
export default () => {
// setup the diagram engine
const engine = createEngine();
engine.getLinkFactories().registerFactory(new RightAngleLinkFactory());
// setup the diagram model
const model = new DiagramModel();
// create four nodes in a way that straight links wouldn't work
const node1 = new DefaultNodeModel('Node A', 'rgb(0,192,255)');
const port1 = node1.addPort(new RightAnglePortModel(false, 'out-1', 'Out'));
node1.setPosition(340, 350);
const node2 = new DefaultNodeModel('Node B', 'rgb(255,255,0)');
const port2 = node2.addPort(new RightAnglePortModel(false, 'out-1', 'Out'));
node2.setPosition(240, 80);
const node3 = new DefaultNodeModel('Node C', 'rgb(192,255,255)');
const port3 = node3.addPort(new RightAnglePortModel(true, 'in-1', 'In'));
node3.setPosition(540, 180);
const node4 = new DefaultNodeModel('Node D', 'rgb(192,0,255)');
const port4 = node4.addPort(new RightAnglePortModel(true, 'in-1', 'In'));
node4.setPosition(95, 185);
// linking things together
const link1 = port1.link(port4);
const link2 = port2.link(port3);
// add all to the main model
model.addAll(node1, node2, node3, node4, link1, link2);
// load model into engine and render
engine.setModel(model);
return (
<DemoWorkspaceWidget
buttons={
<DemoButton
onClick={() => {
action('Serialized Graph')(JSON.stringify(model.serialize(), null, 2));
}}>
Serialize Graph
</DemoButton>
}>
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
};

View File

@@ -1,8 +1,10 @@
import createEngine, { DiagramModel, DefaultNodeModel, DiagramWidget } from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { action } from '@storybook/addon-actions';
import * as beautify from 'json-beautify';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
export default () => {
//1) setup the diagram engine
@@ -28,30 +30,31 @@ export default () => {
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//!------------- SERIALIZING ------------------
var str = JSON.stringify(model.serializeDiagram());
console.log(model.serializeDiagram());
var str = JSON.stringify(model.serialize());
//!------------- DESERIALIZING ----------------
var model2 = new DiagramModel();
model2.deSerializeDiagram(JSON.parse(str), engine);
engine.setDiagramModel(model2);
model2.deserializeModel(JSON.parse(str), engine);
engine.setModel(model2);
return (
<DemoWorkspaceWidget
buttons={
<button
<DemoButton
onClick={() => {
action('Serialized Graph')(beautify(model2.serializeDiagram(), null, 2, 80));
action('Serialized Graph')(beautify(model2.serialize(), null, 2, 80));
}}>
Serialize Graph
</button>
</DemoButton>
}>
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
};

View File

@@ -1,10 +1,19 @@
import createEngine, { DiagramModel, DefaultNodeModel, DiagramWidget } from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel, DefaultDiagramState } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
export default () => {
//1) setup the diagram engine
var engine = createEngine();
// ############################################ MAGIC HAPPENS HERE
const state = engine.getStateMachine().getCurrentState();
if (state instanceof DefaultDiagramState) {
state.dragNewLink.config.allowLooseLinks = false;
}
// ############################################ MAGIC HAPPENS HERE
//2) setup the diagram model
var model = new DiagramModel();
@@ -30,8 +39,12 @@ export default () => {
model.addAll(node1, node2, node3, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} allowLooseLinks={false} />;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};

View File

@@ -1,10 +1,7 @@
import createEngine, {
DiagramModel,
DefaultNodeModel,
DiagramWidget,
DefaultLinkModel
} from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel, DefaultLinkModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
export default () => {
//1) setup the diagram engine
@@ -14,9 +11,12 @@ export default () => {
var model = new DiagramModel();
//3-A) create a default node
var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)');
let port1 = node1.addOutPort('Out');
var node1 = new DefaultNodeModel({
name: 'Node 1',
color: 'rgb(0,192,255)'
});
node1.setPosition(100, 100);
let port1 = node1.addOutPort('Out');
//3-B) create another default node
var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)');
@@ -24,15 +24,20 @@ export default () => {
node2.setPosition(400, 100);
// link the ports
let link1 = port1.link(port2);
(link1 as DefaultLinkModel).addLabel('Hello World!');
let link1 = port1.link<DefaultLinkModel>(port2);
link1.getOptions().testName = 'Test';
link1.addLabel('Hello World!');
//4) add the models to the root graph
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
};

View File

@@ -2,13 +2,14 @@ import createEngine, {
DiagramModel,
DefaultNodeModel,
DefaultPortModel,
DiagramWidget,
PathFindingLinkFactory,
DefaultLabelModel
} from '@projectstorm/react-diagrams';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { action } from '@storybook/addon-actions';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
export default () => {
// setup the diagram engine
@@ -51,25 +52,21 @@ export default () => {
model.addAll(node1, node2, node3, node4, node5, link1, link2);
// load model into engine and render
engine.setDiagramModel(model);
engine.setModel(model);
return (
<DemoWorkspaceWidget
buttons={
<button
<DemoButton
onClick={() => {
action('Serialized Graph')(JSON.stringify(model.serializeDiagram(), null, 2));
action('Serialized Graph')(JSON.stringify(model.serialize(), null, 2));
}}>
Serialize Graph
</button>
</DemoButton>
}>
<DiagramWidget
actionStoppedFiring={() => {
pathfinding.calculateRoutingMatrix();
}}
className="srd-demo-canvas"
diagramEngine={engine}
/>
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
};

View File

@@ -1,6 +1,8 @@
import createEngine, { DiagramModel, DefaultNodeModel, DiagramWidget } from '@projectstorm/react-diagrams';
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import * as React from 'react';
import { DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { DemoWorkspaceWidget, DemoButton } from '../helpers/DemoWorkspaceWidget';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
*
@@ -22,12 +24,14 @@ export default () => {
}
//5) load model into engine
engine.setDiagramModel(model);
engine.setModel(model);
//6) render the diagram!
return (
<DemoWorkspaceWidget buttons={<button onClick={() => engine.zoomToFit()}>Zoom to fit</button>}>
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
<DemoWorkspaceWidget buttons={<DemoButton onClick={() => engine.zoomToFit()}>Zoom to fit</DemoButton>}>
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
</DemoWorkspaceWidget>
);
};

Some files were not shown because too many files have changed in this diff Show More