mirror of
https://github.com/projectstorm/react-diagrams.git
synced 2026-03-13 09:50:09 +08:00
Compare commits
286 Commits
v2.5.0
...
react_canv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c730a9fd1 | ||
|
|
a086bc785e | ||
|
|
a7b6012a50 | ||
|
|
4117af8a33 | ||
|
|
eb7f0642a5 | ||
|
|
a8c73115ac | ||
|
|
404e12c4e8 | ||
|
|
aa6d1c336c | ||
|
|
204e05a2a1 | ||
|
|
d12220baa0 | ||
|
|
91c1ee0169 | ||
|
|
4806805037 | ||
|
|
22f4062f56 | ||
|
|
32a3c33916 | ||
|
|
75ef02dd4d | ||
|
|
665c8b3443 | ||
|
|
ccf425676f | ||
|
|
1ff3abf3ef | ||
|
|
0b1dab0de6 | ||
|
|
0988e625b1 | ||
|
|
dbaf03662f | ||
|
|
55f62587bd | ||
|
|
98bcd60396 | ||
|
|
8467d8e7ca | ||
|
|
40b4e14f15 | ||
|
|
7a78aaa9fd | ||
|
|
47214df76b | ||
|
|
cc40c398b7 | ||
|
|
188f63979b | ||
|
|
468bcea8b2 | ||
|
|
54b9feb62e | ||
|
|
9f397b1453 | ||
|
|
445702dd43 | ||
|
|
bb6a6227e2 | ||
|
|
afff63e46a | ||
|
|
03e8348a14 | ||
|
|
2c6d02f101 | ||
|
|
327dcc190d | ||
|
|
c40c5c5919 | ||
|
|
388b9931b1 | ||
|
|
ae04b5a07f | ||
|
|
9d2f450440 | ||
|
|
45d2ea0c70 | ||
|
|
94fe0fc854 | ||
|
|
adf268697a | ||
|
|
2c478db23f | ||
|
|
3ea6375011 | ||
|
|
64ec2dd0a4 | ||
|
|
e84c2e4e3e | ||
|
|
5f5f13a818 | ||
|
|
2b1a39f236 | ||
|
|
4402068a93 | ||
|
|
ed50438744 | ||
|
|
9a651c3ecc | ||
|
|
1af2f8cbe9 | ||
|
|
2c74a5cf9a | ||
|
|
140817f8c6 | ||
|
|
1debc9c891 | ||
|
|
2ee0211994 | ||
|
|
86047f69fd | ||
|
|
036d8dddcf | ||
|
|
08b81fff56 | ||
|
|
fce1e0c7fe | ||
|
|
d69f61e39d | ||
|
|
eb6fac30e0 | ||
|
|
446cc8cdff | ||
|
|
b8fd5a0690 | ||
|
|
30195af8ca | ||
|
|
8566c12ccc | ||
|
|
ac741d63a6 | ||
|
|
9b4f7d28d6 | ||
|
|
bee07d1be2 | ||
|
|
259782ecfa | ||
|
|
ec3e022ca3 | ||
|
|
60e620539e | ||
|
|
06510c208c | ||
|
|
ff8a9c2e40 | ||
|
|
1854130a99 | ||
|
|
e2285cec16 | ||
|
|
070ed985f6 | ||
|
|
3f27f99a8f | ||
|
|
23b4a9cb2d | ||
|
|
fd33ab44c3 | ||
|
|
67ac9d1923 | ||
|
|
4d3ad389df | ||
|
|
9b3ad7a47f | ||
|
|
942a0c3894 | ||
|
|
6980fa091e | ||
|
|
a318b71ef1 | ||
|
|
6dea182ab8 | ||
|
|
20675cbf38 | ||
|
|
9f78dbba19 | ||
|
|
1b4bbf6493 | ||
|
|
317eabb42a | ||
|
|
32fd0000d2 | ||
|
|
29ca66989c | ||
|
|
76fb35f6d1 | ||
|
|
85f97a63fa | ||
|
|
f811f12206 | ||
|
|
183390ab60 | ||
|
|
b243d661c4 | ||
|
|
724b1d3e56 | ||
|
|
088230d100 | ||
|
|
afa34ae4cf | ||
|
|
4ea968da26 | ||
|
|
2fdf944e0f | ||
|
|
de40b3841d | ||
|
|
7d867dac97 | ||
|
|
372af4341d | ||
|
|
16523dac85 | ||
|
|
181b2f8122 | ||
|
|
a365d9eb23 | ||
|
|
f66c7a6011 | ||
|
|
6dc3a45851 | ||
|
|
4d6097a1d0 | ||
|
|
b7698ca53a | ||
|
|
e22d9929e5 | ||
|
|
f1e5a657ba | ||
|
|
c447103f0f | ||
|
|
c6ac076204 | ||
|
|
266eb85436 | ||
|
|
070696724c | ||
|
|
929790e807 | ||
|
|
54c4be43d3 | ||
|
|
cebd81f000 | ||
|
|
b92641a113 | ||
|
|
72adca100b | ||
|
|
455f46ada0 | ||
|
|
8fadbb5d39 | ||
|
|
dbdcd4a7ce | ||
|
|
e6506d673a | ||
|
|
2c0c5ffaff | ||
|
|
4c7daa223b | ||
|
|
74d172f904 | ||
|
|
75412dfb2e | ||
|
|
e103b55e7d | ||
|
|
518f481776 | ||
|
|
e47a929ff6 | ||
|
|
a7ac7d1c1c | ||
|
|
6b55811312 | ||
|
|
3a39137b82 | ||
|
|
fb5a7f9bf2 | ||
|
|
eebafbef64 | ||
|
|
16bb31770c | ||
|
|
32738688a6 | ||
|
|
ecd7202fb5 | ||
|
|
5bb9591478 | ||
|
|
ad9b98516b | ||
|
|
4598855145 | ||
|
|
2f048d6a94 | ||
|
|
0291bd41fd | ||
|
|
0be7157cd2 | ||
|
|
83d5d50a3b | ||
|
|
d41b523480 | ||
|
|
bbf88ca951 | ||
|
|
c057168a56 | ||
|
|
c9523a107a | ||
|
|
3e3b284ed5 | ||
|
|
f258371bc6 | ||
|
|
ee64e037b1 | ||
|
|
97a92f6d33 | ||
|
|
760c37e20c | ||
|
|
139442cbf3 | ||
|
|
b71f82bc73 | ||
|
|
0fd20981bf | ||
|
|
a744b26c29 | ||
|
|
73724ef55a | ||
|
|
bb7bff529f | ||
|
|
f41a842807 | ||
|
|
9a90a5e8f5 | ||
|
|
7a0898f189 | ||
|
|
c24c05597c | ||
|
|
94388f4aef | ||
|
|
83f130c013 | ||
|
|
6aced36e5b | ||
|
|
a034b5f950 | ||
|
|
a57fa33a24 | ||
|
|
8e6ebcf44f | ||
|
|
a3ef7a089f | ||
|
|
f040d46e53 | ||
|
|
9b047ca4e4 | ||
|
|
a1369453c1 | ||
|
|
23b5689ef4 | ||
|
|
f7aa626783 | ||
|
|
3d3a977037 | ||
|
|
dff4ea4c1e | ||
|
|
5ba8b42a77 | ||
|
|
985c22df2a | ||
|
|
9694f99827 | ||
|
|
ceff0cb74e | ||
|
|
a92ea0da97 | ||
|
|
a9e47be079 | ||
|
|
0ef0584ed3 | ||
|
|
45eaafffa2 | ||
|
|
42642d6070 | ||
|
|
a131680dc3 | ||
|
|
1fd302a4e1 | ||
|
|
7372ad075d | ||
|
|
9965bd1066 | ||
|
|
9523d64113 | ||
|
|
cd7306e0d8 | ||
|
|
e68e1474ea | ||
|
|
13d828e18c | ||
|
|
c1ee66d192 | ||
|
|
75e52e4508 | ||
|
|
6cb9181408 | ||
|
|
7f06610356 | ||
|
|
a8727f7f22 | ||
|
|
beff431b6a | ||
|
|
e1c5014207 | ||
|
|
bbe1e19710 | ||
|
|
4e102a109e | ||
|
|
600a386f32 | ||
|
|
8b7b32f4ed | ||
|
|
e93bf76167 | ||
|
|
0ac691910a | ||
|
|
0cf0cee501 | ||
|
|
4eca7ad122 | ||
|
|
e9307ffe97 | ||
|
|
1a1b47e085 | ||
|
|
bf9a20adbc | ||
|
|
55030b9358 | ||
|
|
a2dc9c77f5 | ||
|
|
e5f6877a77 | ||
|
|
c1a4273302 | ||
|
|
5f7f8bcad3 | ||
|
|
59fa06debf | ||
|
|
47fcae619a | ||
|
|
05f7c06227 | ||
|
|
3a52d4a852 | ||
|
|
ad7ccf07e5 | ||
|
|
c8116e12ca | ||
|
|
5c07f1cd17 | ||
|
|
a22f8814cf | ||
|
|
44d46fa029 | ||
|
|
f5b2d8662c | ||
|
|
404b73eeba | ||
|
|
bb1f4e1c4c | ||
|
|
3db0fcb85c | ||
|
|
4920523c48 | ||
|
|
955f9d8a58 | ||
|
|
babac6efc0 | ||
|
|
57a0d63d9c | ||
|
|
55eb201850 | ||
|
|
a6cc3b32dd | ||
|
|
89df144775 | ||
|
|
6996f8c6cd | ||
|
|
0903ccd5cf | ||
|
|
52f82a47c1 | ||
|
|
d5112c2218 | ||
|
|
b976e28c8b | ||
|
|
d1b551c370 | ||
|
|
0b761b14ab | ||
|
|
fbdcbc0ad1 | ||
|
|
6182bbc1d1 | ||
|
|
2cb0b53bd9 | ||
|
|
d452f10906 | ||
|
|
ad6e37450b | ||
|
|
8ae521a585 | ||
|
|
d27567af70 | ||
|
|
bc8e91682c | ||
|
|
b6737514bf | ||
|
|
341615a2ff | ||
|
|
10e68c8fb7 | ||
|
|
9c234fd97b | ||
|
|
c721a8ba01 | ||
|
|
5955df954b | ||
|
|
780c2b5528 | ||
|
|
efb26a899e | ||
|
|
0e49a6ec1d | ||
|
|
c411325f45 | ||
|
|
98b58cca5b | ||
|
|
e2d2a8e884 | ||
|
|
92111b8f1c | ||
|
|
ab6b93c12d | ||
|
|
892706af3c | ||
|
|
e76df52232 | ||
|
|
d5673bfa05 | ||
|
|
98c38c688b | ||
|
|
6d653aa0b6 | ||
|
|
9a14dc668c | ||
|
|
a2efd631a9 | ||
|
|
62ff649007 | ||
|
|
e600da1895 | ||
|
|
4d7c6fdaa2 | ||
|
|
caadbf4a9a |
31
.circleci/config.yml
Normal file
31
.circleci/config.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: projectstorm/react-diagrams-ci
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "package.json" }}
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1-dependencies-{{ checksum "package.json" }}
|
||||
|
||||
# test building project
|
||||
- run: yarn run prepublishOnly
|
||||
|
||||
# test building storybook
|
||||
- run: yarn run storybook:build
|
||||
|
||||
# test e2e tests and jest snapshots
|
||||
- run: yarn run test:ci
|
||||
27
.circleci/images/Dockerfile
Normal file
27
.circleci/images/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM node:8-slim
|
||||
|
||||
# Install latest chrome dev package.
|
||||
# Note: this installs the necessary libs to make the bundled version of Chromium that Pupppeteer
|
||||
# installs, work.
|
||||
RUN apt-get update && apt-get install -y wget --no-install-recommends \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-unstable \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get purge --auto-remove -y curl \
|
||||
&& rm -rf /src/*.deb
|
||||
|
||||
RUN yarn add puppeteer
|
||||
|
||||
# Add pptr user.
|
||||
RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
|
||||
&& mkdir -p /home/pptruser/Downloads \
|
||||
&& chown -R pptruser:pptruser /home/pptruser \
|
||||
&& chown -R pptruser:pptruser /node_modules
|
||||
|
||||
# Run user as non privileged.
|
||||
USER pptruser
|
||||
|
||||
CMD ["google-chrome-unstable"]
|
||||
27
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
27
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Checklist
|
||||
|
||||
- [ ] The code has been run through pretty `yarn run pretty`
|
||||
- [ ] The tests pass on CircleCI
|
||||
- [ ] You have referenced the issue(s) or other PR(s) this fixes/relates-to
|
||||
- [ ] The PR Template has been filled out (see below)
|
||||
- [ ] Had a beer because you are awesome
|
||||
|
||||
## What?
|
||||
|
||||
(My awesome new feature does this really cool thing.)
|
||||
|
||||
## Why?
|
||||
|
||||
(Because obviously it could not do it before)
|
||||
|
||||
## How?
|
||||
|
||||
(Basically I did this and that because im a super 1337 hacker)
|
||||
|
||||
## Feel-Good "programming lol" image:
|
||||
|
||||
(Add your own one below :])
|
||||
|
||||

|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,10 @@
|
||||
dist/
|
||||
dist/main.js
|
||||
dist/main.js.map
|
||||
/package
|
||||
*.tgz
|
||||
@types/
|
||||
|
||||
.out
|
||||
|
||||
# Created by https://www.gitignore.io/api/net,netbeans,sublimetext,phpstorm,windows,osx,node
|
||||
|
||||
16
.npmignore
16
.npmignore
@@ -1,11 +1,11 @@
|
||||
demos/demo1/bundle.js
|
||||
demos/demo1/bundle.js.map
|
||||
demos/demo2/bundle.js
|
||||
demos/demo2/bundle.js.map
|
||||
demos/demo3/bundle.js
|
||||
demos/demo3/bundle.js.map
|
||||
demos/demo4/bundle.js
|
||||
demos/demo4/bundle.js.map
|
||||
demos
|
||||
images
|
||||
docs
|
||||
.out
|
||||
.storybook
|
||||
.circleci
|
||||
tests
|
||||
*.md
|
||||
|
||||
# Created by https://www.gitignore.io/api/net,netbeans,sublimetext,phpstorm,windows,osx,node
|
||||
|
||||
|
||||
14
.storybook/addon-code/react.js
vendored
Normal file
14
.storybook/addon-code/react.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
export class WithCode extends React.Component {
|
||||
render() {
|
||||
const { children, code } = this.props;
|
||||
const channel = addons.getChannel();
|
||||
|
||||
// send the notes to the channel.
|
||||
channel.emit('storybook/code/set_code', code);
|
||||
// return children elements.
|
||||
return children;
|
||||
}
|
||||
}
|
||||
69
.storybook/addon-code/register.js
Normal file
69
.storybook/addon-code/register.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { github } from 'react-syntax-highlighter/styles/hljs';
|
||||
|
||||
/**
|
||||
* @author Dylan
|
||||
*
|
||||
* Simple little addon for displaying code, might make this a seperate project at some point
|
||||
*/
|
||||
export class CodePreview extends React.Component {
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
code: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { channel, api } = this.props;
|
||||
// Listen to the notes and render it.
|
||||
channel.on('storybook/code/set_code', (code) => {
|
||||
this.onAddCode(code);
|
||||
});
|
||||
|
||||
// Clear the current notes on every story change.
|
||||
this.stopListeningOnStory = api.onStory(() => {
|
||||
this.onAddCode('');
|
||||
});
|
||||
}
|
||||
|
||||
// This is some cleanup tasks when the Notes panel is unmounting.
|
||||
componentWillUnmount() {
|
||||
if (this.stopListeningOnStory) {
|
||||
this.stopListeningOnStory();
|
||||
}
|
||||
|
||||
this.unmounted = true;
|
||||
const { channel } = this.props;
|
||||
channel.removeListener('storybook/notes/add_notes', this.onAddCode);
|
||||
}
|
||||
|
||||
onAddCode(code) {
|
||||
this.setState({ code: code });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SyntaxHighlighter
|
||||
customStyle={{width: '100%', overflowX:'hidden', tabSize: 4}}
|
||||
showLineNumbers={true}
|
||||
language='language-tsx'
|
||||
style={github}
|
||||
>
|
||||
{this.state.code}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Register the addon with a unique name.
|
||||
addons.register('storybook/code', api => {
|
||||
// Also need to set a unique name to the panel.
|
||||
addons.addPanel('storybook/code/panel', {
|
||||
title: 'Code preview',
|
||||
render: () => <CodePreview channel={addons.getChannel()} api={api} />,
|
||||
});
|
||||
});
|
||||
3
.storybook/addons.js
Normal file
3
.storybook/addons.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import './addon-code/register';
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-options/register';
|
||||
@@ -7,18 +7,33 @@ module.exports = {
|
||||
loaders: ["style-loader", "css-loader", "sass-loader"],
|
||||
include: path.resolve(__dirname, '../')
|
||||
},
|
||||
{
|
||||
test: /\.css/,
|
||||
loaders: ["style-loader", "css-loader"],
|
||||
include: path.resolve(__dirname, '../')
|
||||
},
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.js$/,
|
||||
loader: "source-map-loader"
|
||||
loader: "source-map-loader",
|
||||
exclude: [
|
||||
/node_modules\//
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'awesome-typescript-loader',
|
||||
loader: 'awesome-typescript-loader?declaration=false',
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
|
||||
loader: "file-loader"
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'storm-react-diagrams': path.join(__dirname, "..", "src", "main")
|
||||
},
|
||||
extensions: [".tsx", ".ts", ".js"]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
76
CHANGELOG.md
Normal file
76
CHANGELOG.md
Normal file
@@ -0,0 +1,76 @@
|
||||
__5.1.0__
|
||||
|
||||
* [api] Rename XXXFactory into AbstractXXXFactory
|
||||
* [refactor] tslint and prettier are now the same
|
||||
* [refactor] Each class now explicitely has its own class file (consistency)
|
||||
* [feature] Smooth vertical links (no longer limited to horizontal)
|
||||
* [feature] Dedicated documentation via gitbook
|
||||
* [bug] forgot to export some
|
||||
* [refactor] consistently use lodash where possible
|
||||
* [maintenance] upgrade node modules
|
||||
|
||||
__5.0.0__ http://dylanv.blog/2018/03/03/storm-react-diagrams-5-0-0/
|
||||
|
||||
PR: https://github.com/projectstorm/react-diagrams/pull/145
|
||||
|
||||
* [refactor] Links completely overhauled
|
||||
* [feature] Smart Routing
|
||||
* [feature] Flow support
|
||||
* [demo] Smart Routing
|
||||
* [demo] Animated links
|
||||
* [api] Bootstrapping Improvements
|
||||
* [feature] add custom properties to all widgets
|
||||
* [refactor] use BEM for all css
|
||||
* [feature] Default Link factory hooks
|
||||
* [tests] e2e tests + helper framework
|
||||
* [tests] automatically load JEST Snapshots
|
||||
* [feature] Link labels!
|
||||
|
||||
__4.0.0__ http://dylanv.blog/2018/01/18/storm-react-diagrams-v4-0-0/
|
||||
|
||||
* [refactor] Events system was completely overhauled
|
||||
* [demo] Custom Link Sizes
|
||||
* [refactor] Demos are now much more verbose and better managed
|
||||
* [update] node packages
|
||||
* [bug] Fix #129
|
||||
* [feature] Control link creation through ports
|
||||
* [refactor] Models are now in seperate files
|
||||
* [refactor] Merged the concept of instance factories and widget factories into one
|
||||
* [feature] Models can now be cloned at various parts of the model graph
|
||||
* [demo] Cloning
|
||||
* [feature] models control isLocked
|
||||
|
||||
__3.2.0__ http://dylanv.blog/2017/11/22/storm-react-diagrams-3-2-0/
|
||||
* [feature] zoom to fit
|
||||
* added Circle CI tests
|
||||
* [demo] dagre automatic layouts
|
||||
* [demo] zoom to fit
|
||||
* [demo] selection events
|
||||
* [demo] limit number of points
|
||||
* [demo] programmatic node updating
|
||||
* updated dependencies
|
||||
* [bugs] swapping diagram models in engines
|
||||
* [bugs] issues with the rendering pipeline #107
|
||||
* added ci badge to Readme
|
||||
|
||||
__3.1.3__
|
||||
* Refactor links slightly
|
||||
* use min extension for css
|
||||
* bump package versions
|
||||
* export more classes
|
||||
|
||||
__3.1.2__
|
||||
* Hotfix: fix zooming when canvas not in the top left corner
|
||||
(https://github.com/projectstorm/react-diagrams/pull/88)
|
||||
|
||||
__3.1.0__ http://dylanv.blog/2017/09/15/storm-react-diagrams-3-1-0/
|
||||
* Zoom relative to mouse location
|
||||
* Fixed links not connecting when grid is larger than port size
|
||||
* Prevented points from dragging when connected to a port and the node itself is not selected
|
||||
* API fixes
|
||||
* Code cleanup
|
||||
|
||||
__3.0.0__ http://dylanv.blog/2017/09/13/storm-react-diagrams-v3/
|
||||
* Massive performance updates
|
||||
* Complete rewrite
|
||||
* Started a changelog and design documents for each revision
|
||||
27
CONTRIBUTING.md
Normal file
27
CONTRIBUTING.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Contributing
|
||||
|
||||
See below for guidelines on house keeping:
|
||||
|
||||
### Always add a PR
|
||||
|
||||
Since the project runs on GitHub, the best way to contribute is to fork and then submit a PR.
|
||||
You will find a template that you will need to fill out
|
||||
|
||||
### Adding new demos
|
||||
|
||||
Add a new folder in the ./demos directory and make sure that it is named correctly like the other demos.
|
||||
A new demo should conform to the standard of either `demo-simple` in which it contains a markdown file that
|
||||
clearly explains 'whats going on', or the code sample should have very clear comments that almost always should ready
|
||||
like an instruction manual such as the simple demo.
|
||||
|
||||
Finally, you should link up your demo to the __index.tsx__ file in the __demos__ directory. It should be quite
|
||||
self explanatory on how it works, but ultimately I have a helper method that makes it easy to link source code
|
||||
to the demo itself, hence the 'require' statements. The third parameter is if you want to place your demo
|
||||
inside a markdown guide (again: see simple demo for how that is done).
|
||||
|
||||
### Make the demo testable
|
||||
|
||||
Similar procedure, except link your demo in the __index.tsx__ file sitting in the __tests__ directory.
|
||||
Running `yarn run test` will fire up jest (hopefully) and then it will render your demo to a snapshot directory
|
||||
which when run again (for a second time), should compare the output to the newely generated snapshot. Make sure
|
||||
to commit the updated snapshot file with your PR!
|
||||
94
README.md
94
README.md
@@ -1,96 +1,46 @@
|
||||
# STORM React Diagrams
|
||||
|
||||
__DEMO__: http://www.projectstorm.io/react-diagrams
|
||||
**PSA**: React Diagrams is currently 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).
|
||||
|
||||
---
|
||||
|
||||
**DEMO**: [http://www.projectstorm.io/react-diagrams](http://www.projectstorm.io/react-diagrams)
|
||||
|
||||
**DOCS:** [https://projectstorm.gitbooks.io/react-diagrams](https://projectstorm.gitbooks.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/)
|
||||
|
||||
A super simple, no-nonsense diagramming library written in React that just works.
|
||||
|
||||
[](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://npmjs.org/package/storm-react-diagrams)
|
||||
[](https://npmjs.org/package/storm-react-diagrams)
|
||||
[](https://gitter.im/projectstorm/react-diagrams?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://npmjs.org/package/storm-react-diagrams) [](https://npmjs.org/package/storm-react-diagrams) [](http://packagequality.com/#?package=storm-react-diagrams) [](https://circleci.com/gh/projectstorm/react-diagrams/tree/master)
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## Introduction
|
||||
|
||||
A no-nonsense diagramming library written entirely in React with the help of Lodash, and a single polyfill. It aims to be:
|
||||
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..)
|
||||
* 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
|
||||
* 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
|
||||
|
||||
## Roadmap
|
||||
#### Run the demos
|
||||
|
||||
* Move path logic associated with links into a base link class so we can easily extend links
|
||||
* Add more fault tolerance
|
||||
After running `yarn install` you must then run: `yarn run storybook`
|
||||
|
||||
## How to install
|
||||
#### Building from source
|
||||
|
||||
```
|
||||
npm install storm-react-diagrams
|
||||
```
|
||||
or
|
||||
```
|
||||
yarn add storm-react-diagrams
|
||||
```
|
||||
Simply run `webpack` in the root directory \(or `export NODE_ENV=production && webpack` 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\).
|
||||
|
||||
## How to build
|
||||
## [Checkout the docs](https://projectstorm.gitbooks.io/react-diagrams)
|
||||
|
||||
Simply run ```webpack``` in the root directory (or ```export NODE_ENV=production && webpack``` 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. __It will also compile the code for the demos__ .We use webpack for this because TSC cannot compile a single UMD file (TSC can currently only output multiple UMD files).
|
||||
|
||||
_NOTE:_ We turn off name mangeling in production builds because we require class names to be left intact when serializing.
|
||||
|
||||
## Make your own nodes
|
||||
|
||||
To see how to create your own nodes like the one below, take a look at __demo3__:
|
||||
|
||||

|
||||
|
||||
## How does it work
|
||||
|
||||
The library uses a Model Graph to represent the virtual diagram and then renders the diagram using
|
||||
2 layers:
|
||||
* Node Layer -> which is responsible for rendering nodes as HTML components
|
||||
* Link Layer -> which renders the links as SVG paths
|
||||
|
||||
Each node and link is fed into a factory that then generates the corresponding node or link react widget.
|
||||
Therefore, to create custom nodes and links, register your own factories that return your own widgets.
|
||||
|
||||
As long as a node contains at least one port and the corresponding NodeWidget contains at least one PortWidget,
|
||||
a link can be connected to it.
|
||||
|
||||
## Events
|
||||
|
||||
[Event System](docs/Events.md)
|
||||
|
||||
## Questions
|
||||
|
||||
[Questions](docs/Questions.md)
|
||||
|
||||
## Usage
|
||||
|
||||
__Delete__ removes any selected items
|
||||

|
||||
|
||||
__Shift + Mouse Drag__ triggers a multi-selection box
|
||||

|
||||
|
||||
__Shift + Mouse Click__ selects the item (items can be multi-selected)
|
||||

|
||||
|
||||
__Mouse Drag__ drags the entire diagram
|
||||

|
||||
|
||||
__Mouse Wheel__ zooms the diagram in / out
|
||||

|
||||
|
||||
__Click Link + Drag__ creates a new link point
|
||||

|
||||
|
||||
__Click Node Port + Drag__ creates a new link
|
||||

|
||||
|
||||
11
SUMMARY.md
Normal file
11
SUMMARY.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
0
about-the-project.md
Normal file
0
about-the-project.md
Normal file
23
demos/.helpers/DemoWorkspaceWidget.tsx
Normal file
23
demos/.helpers/DemoWorkspaceWidget.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
37
demos/.helpers/Helper.tsx
Normal file
37
demos/.helpers/Helper.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from "react";
|
||||
import { withDocs } from "storybook-readme";
|
||||
import { WithCode } from "../../.storybook/addon-code/react.js";
|
||||
|
||||
export class Helper {
|
||||
/**
|
||||
* Logs the mouse position in the console, but overlays a div that consumes all events
|
||||
* since the actual story book stories are rendered as an iFrame.
|
||||
*/
|
||||
static logMousePosition() {
|
||||
let element = window.parent.document.createElement("mouse-position");
|
||||
element.style.position = "absolute";
|
||||
element.style.top = "0px";
|
||||
element.style.left = "0px";
|
||||
element.style.bottom = "0px";
|
||||
element.style.right = "0px";
|
||||
element.style.zIndex = "10";
|
||||
window.parent.document.body.appendChild(element);
|
||||
|
||||
window.parent.window.addEventListener("mousemove", event => {
|
||||
console.clear();
|
||||
console.log(event.clientX, event.clientY);
|
||||
});
|
||||
}
|
||||
|
||||
static makeDemo(widget, code, markdown?) {
|
||||
let container = () => <WithCode code={code}>{widget}</WithCode>;
|
||||
if (markdown) {
|
||||
return withDocs({
|
||||
PreviewComponent: ({ children }) => {
|
||||
return <div className="docs-preview-wrapper">{children}</div>;
|
||||
}
|
||||
})(markdown, container);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
}
|
||||
80
demos/.helpers/demo.scss
Normal file
80
demos/.helpers/demo.scss
Normal file
@@ -0,0 +1,80 @@
|
||||
@import "../../src/sass/main";
|
||||
|
||||
.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%;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-preview-wrapper{
|
||||
background: rgb(60,60,60);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.srd-demo-canvas{
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
background-color: rgb(60,60,60) !important;
|
||||
$color: rgba(white, .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);
|
||||
}
|
||||
|
||||
}
|
||||
88
demos/demo-cloning/index.tsx
Normal file
88
demos/demo-cloning/index.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
NodeModel,
|
||||
DiagramWidget,
|
||||
BaseModel
|
||||
} from "storm-react-diagrams";
|
||||
import * as _ from "lodash";
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
|
||||
/**
|
||||
* Tests cloning
|
||||
*/
|
||||
class CloneSelected extends React.Component<any, any> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.cloneSelected = this.cloneSelected.bind(this);
|
||||
}
|
||||
|
||||
cloneSelected() {
|
||||
let { engine } = this.props;
|
||||
let offset = { x: 100, y: 100 };
|
||||
let model = engine.getDiagramModel();
|
||||
|
||||
let itemMap = {};
|
||||
_.forEach(model.getSelectedItems(), (item: BaseModel<any>) => {
|
||||
let newItem = item.clone(itemMap);
|
||||
|
||||
// offset the nodes slightly
|
||||
if (newItem instanceof NodeModel) {
|
||||
newItem.setPosition(newItem.x + offset.x, newItem.y + offset.y);
|
||||
model.addNode(newItem);
|
||||
} else if (newItem instanceof LinkModel) {
|
||||
// offset the link points
|
||||
newItem.getPoints().forEach(p => {
|
||||
p.updateLocation({ x: p.getX() + offset.x, y: p.getY() + offset.y });
|
||||
});
|
||||
model.addLink(newItem);
|
||||
}
|
||||
newItem.selected = false;
|
||||
});
|
||||
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { engine } = this.props;
|
||||
return (
|
||||
<DemoWorkspaceWidget buttons={<button onClick={this.cloneSelected}>Clone Selected</button>}>
|
||||
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
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);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <CloneSelected engine={engine} model={model} />;
|
||||
};
|
||||
157
demos/demo-custom-link1/index.tsx
Normal file
157
demos/demo-custom-link1/index.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget,
|
||||
LinkWidget,
|
||||
LinkProps,
|
||||
DefaultLinkWidget,
|
||||
DefaultLinkModel,
|
||||
DefaultLinkFactory
|
||||
} from "storm-react-diagrams";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import * as React from "react";
|
||||
|
||||
export class AdvancedLinkModel extends DefaultLinkModel {
|
||||
constructor() {
|
||||
super("advanced");
|
||||
this.width = 10;
|
||||
}
|
||||
}
|
||||
|
||||
export class AdvancedPortModel extends DefaultPortModel {
|
||||
createLinkModel(): AdvancedLinkModel | null {
|
||||
return new AdvancedLinkModel();
|
||||
}
|
||||
}
|
||||
|
||||
export class AdvancedLinkSegment extends React.Component<{ model: AdvancedLinkModel; path: string }> {
|
||||
path: SVGPathElement;
|
||||
circle: SVGCircleElement;
|
||||
callback: () => any;
|
||||
percent: number;
|
||||
handle: any;
|
||||
mounted: boolean;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.percent = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.callback = () => {
|
||||
if (!this.circle || !this.path) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.percent += 2;
|
||||
if (this.percent > 100) {
|
||||
this.percent = 0;
|
||||
}
|
||||
|
||||
let point = this.path.getPointAtLength(this.path.getTotalLength() * (this.percent / 100.0));
|
||||
|
||||
this.circle.setAttribute("cx", "" + point.x);
|
||||
this.circle.setAttribute("cy", "" + point.y);
|
||||
|
||||
if (this.mounted) {
|
||||
requestAnimationFrame(this.callback);
|
||||
}
|
||||
};
|
||||
requestAnimationFrame(this.callback);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<path
|
||||
ref={ref => {
|
||||
this.path = ref;
|
||||
}}
|
||||
strokeWidth={this.props.model.width}
|
||||
stroke="rgba(255,0,0,0.5)"
|
||||
d={this.props.path}
|
||||
/>
|
||||
<circle
|
||||
ref={ref => {
|
||||
this.circle = ref;
|
||||
}}
|
||||
r={10}
|
||||
fill="orange"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class AdvancedLinkFactory extends DefaultLinkFactory {
|
||||
constructor() {
|
||||
super();
|
||||
this.type = "advanced";
|
||||
}
|
||||
|
||||
getNewInstance(initialConfig?: any): AdvancedLinkModel {
|
||||
return new AdvancedLinkModel();
|
||||
}
|
||||
|
||||
generateLinkSegment(model: AdvancedLinkModel, widget: DefaultLinkWidget, selected: boolean, path: string) {
|
||||
return (
|
||||
<g>
|
||||
<AdvancedLinkSegment model={model} path={path} />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Simple link styling demo
|
||||
*
|
||||
* @Author kfrajtak
|
||||
*/
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
engine.registerLinkFactory(new AdvancedLinkFactory());
|
||||
|
||||
// create some nodes
|
||||
var node1 = new DefaultNodeModel("Source", "rgb(0,192,255)");
|
||||
let port1 = node1.addPort(new AdvancedPortModel(false, "out-1", "Out thick"));
|
||||
let port2 = node1.addPort(new DefaultPortModel(false, "out-2", "Out default"));
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
var node2 = new DefaultNodeModel("Target", "rgb(192,255,0)");
|
||||
var port3 = node2.addPort(new AdvancedPortModel(true, "in-1", "In thick"));
|
||||
var port4 = node2.addPort(new DefaultPortModel(true, "in-2", "In default"));
|
||||
node2.setPosition(300, 100);
|
||||
|
||||
var node3 = new DefaultNodeModel("Source", "rgb(0,192,255)");
|
||||
node3.addPort(new AdvancedPortModel(false, "out-1", "Out thick"));
|
||||
node3.addPort(new DefaultPortModel(false, "out-2", "Out default"));
|
||||
node3.setPosition(100, 200);
|
||||
|
||||
var node4 = new DefaultNodeModel("Target", "rgb(192,255,0)");
|
||||
node4.addPort(new AdvancedPortModel(true, "in-1", "In thick"));
|
||||
node4.addPort(new DefaultPortModel(true, "in-2", "In default"));
|
||||
node4.setPosition(300, 200);
|
||||
|
||||
var model = new DiagramModel();
|
||||
|
||||
model.addAll(port1.link(port3), port2.link(port4));
|
||||
|
||||
// add everything else
|
||||
model.addAll(node1, node2, node3, node4);
|
||||
|
||||
// load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
// render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
|
||||
};
|
||||
18
demos/demo-custom-node1/DiamondNodeFactory.tsx
Normal file
18
demos/demo-custom-node1/DiamondNodeFactory.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as SRD from "storm-react-diagrams";
|
||||
import { DiamonNodeWidget } from "./DiamondNodeWidget";
|
||||
import { DiamondNodeModel } from "./DiamondNodeModel";
|
||||
import * as React from "react";
|
||||
|
||||
export class DiamondNodeFactory extends SRD.AbstractNodeFactory {
|
||||
constructor() {
|
||||
super("diamond");
|
||||
}
|
||||
|
||||
generateReactWidget(diagramEngine: SRD.DiagramEngine, node: SRD.NodeModel): JSX.Element {
|
||||
return <DiamonNodeWidget node={node} />;
|
||||
}
|
||||
|
||||
getNewInstance() {
|
||||
return new DiamondNodeModel();
|
||||
}
|
||||
}
|
||||
12
demos/demo-custom-node1/DiamondNodeModel.ts
Normal file
12
demos/demo-custom-node1/DiamondNodeModel.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NodeModel } from "storm-react-diagrams";
|
||||
import { DiamondPortModel } from "./DiamondPortModel";
|
||||
|
||||
export class DiamondNodeModel extends NodeModel {
|
||||
constructor() {
|
||||
super("diamond");
|
||||
this.addPort(new DiamondPortModel("top"));
|
||||
this.addPort(new DiamondPortModel("left"));
|
||||
this.addPort(new DiamondPortModel("bottom"));
|
||||
this.addPort(new DiamondPortModel("right"));
|
||||
}
|
||||
}
|
||||
105
demos/demo-custom-node1/DiamondNodeWidget.tsx
Normal file
105
demos/demo-custom-node1/DiamondNodeWidget.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import * as React from "react";
|
||||
import { DiamondNodeModel } from "./DiamondNodeModel";
|
||||
import { PortWidget } from "storm-react-diagrams";
|
||||
|
||||
export interface DiamonNodeWidgetProps {
|
||||
node: DiamondNodeModel;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface DiamonNodeWidgetState {}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, DiamonNodeWidgetState> {
|
||||
public static defaultProps: DiamonNodeWidgetProps = {
|
||||
size: 150,
|
||||
node: null
|
||||
};
|
||||
|
||||
constructor(props: DiamonNodeWidgetProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={"diamond-node"}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: this.props.size,
|
||||
height: this.props.size
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width={this.props.size}
|
||||
height={this.props.size}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
`
|
||||
<g id="Layer_1">
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<polygon fill="purple" stroke="#000000" stroke-width="3" stroke-miterlimit="10" points="10,` +
|
||||
this.props.size / 2 +
|
||||
` ` +
|
||||
this.props.size / 2 +
|
||||
`,10 ` +
|
||||
(this.props.size - 10) +
|
||||
`,` +
|
||||
this.props.size / 2 +
|
||||
` ` +
|
||||
this.props.size / 2 +
|
||||
`,` +
|
||||
(this.props.size - 10) +
|
||||
` "/>
|
||||
</g>
|
||||
`
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 10,
|
||||
top: this.props.size / 2 - 8,
|
||||
left: -8
|
||||
}}
|
||||
>
|
||||
<PortWidget name="left" node={this.props.node} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 10,
|
||||
left: this.props.size / 2 - 8,
|
||||
top: -8
|
||||
}}
|
||||
>
|
||||
<PortWidget name="top" node={this.props.node} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 10,
|
||||
left: this.props.size - 8,
|
||||
top: this.props.size / 2 - 8
|
||||
}}
|
||||
>
|
||||
<PortWidget name="right" node={this.props.node} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 10,
|
||||
left: this.props.size / 2 - 8,
|
||||
top: this.props.size - 8
|
||||
}}
|
||||
>
|
||||
<PortWidget name="bottom" node={this.props.node} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
26
demos/demo-custom-node1/DiamondPortModel.ts
Normal file
26
demos/demo-custom-node1/DiamondPortModel.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as _ from "lodash";
|
||||
import { LinkModel, DiagramEngine, PortModel, DefaultLinkModel } from "storm-react-diagrams";
|
||||
|
||||
export class DiamondPortModel extends PortModel {
|
||||
position: string | "top" | "bottom" | "left" | "right";
|
||||
|
||||
constructor(pos: string = "top") {
|
||||
super(pos, "diamond");
|
||||
this.position = pos;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return _.merge(super.serialize(), {
|
||||
position: this.position
|
||||
});
|
||||
}
|
||||
|
||||
deSerialize(data: any, engine: DiagramEngine) {
|
||||
super.deSerialize(data, engine);
|
||||
this.position = data.position;
|
||||
}
|
||||
|
||||
createLinkModel(): LinkModel {
|
||||
return new DefaultLinkModel();
|
||||
}
|
||||
}
|
||||
14
demos/demo-custom-node1/SimplePortFactory.ts
Normal file
14
demos/demo-custom-node1/SimplePortFactory.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PortModel, AbstractPortFactory } from "storm-react-diagrams";
|
||||
|
||||
export class SimplePortFactory extends AbstractPortFactory {
|
||||
cb: (initialConfig?: any) => PortModel;
|
||||
|
||||
constructor(type: string, cb: (initialConfig?: any) => PortModel) {
|
||||
super(type);
|
||||
this.cb = cb;
|
||||
}
|
||||
|
||||
getNewInstance(initialConfig?: any): PortModel {
|
||||
return this.cb(initialConfig);
|
||||
}
|
||||
}
|
||||
57
demos/demo-custom-node1/index.tsx
Normal file
57
demos/demo-custom-node1/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "storm-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";
|
||||
|
||||
/**
|
||||
* @Author Dylan Vorster
|
||||
*/
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
// register some other factories as well
|
||||
engine.registerPortFactory(new SimplePortFactory("diamond", config => new DiamondPortModel()));
|
||||
engine.registerNodeFactory(new DiamondNodeFactory());
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100, 150);
|
||||
|
||||
//3-B) create our new custom node
|
||||
var node2 = new DiamondNodeModel();
|
||||
node2.setPosition(250, 108);
|
||||
|
||||
var node3 = new DefaultNodeModel("Node 3", "red");
|
||||
var port3 = node3.addInPort("In");
|
||||
node3.setPosition(500, 150);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(node2.getPort("left"));
|
||||
var link2 = port3.link(node2.getPort("right"));
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, node3, link1, link2);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
|
||||
};
|
||||
56
demos/demo-dagre/dagre-utils.ts
Normal file
56
demos/demo-dagre/dagre-utils.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as dagre from "dagre";
|
||||
import * as _ from "lodash";
|
||||
|
||||
const size = {
|
||||
width: 60,
|
||||
height: 60
|
||||
};
|
||||
|
||||
export function distributeElements(model) {
|
||||
let clonedModel = _.cloneDeep(model);
|
||||
let nodes = distributeGraph(clonedModel);
|
||||
nodes.forEach(node => {
|
||||
let modelNode = clonedModel.nodes.find(item => item.id === node.id);
|
||||
modelNode.x = node.x;
|
||||
modelNode.y = node.y;
|
||||
});
|
||||
return clonedModel;
|
||||
}
|
||||
|
||||
function distributeGraph(model) {
|
||||
let nodes = mapElements(model);
|
||||
let edges = mapEdges(model);
|
||||
let graph = new dagre.graphlib.Graph();
|
||||
graph.setGraph({});
|
||||
graph.setDefaultEdgeLabel(() => ({}));
|
||||
//add elements to dagre graph
|
||||
nodes.forEach(node => {
|
||||
graph.setNode(node.id, node.metadata);
|
||||
});
|
||||
edges.forEach(edge => {
|
||||
if (edge.from && edge.to) {
|
||||
graph.setEdge(edge.from, edge.to);
|
||||
}
|
||||
});
|
||||
//auto-distribute
|
||||
dagre.layout(graph);
|
||||
return graph.nodes().map(node => graph.node(node));
|
||||
}
|
||||
|
||||
function mapElements(model) {
|
||||
// dagre compatible format
|
||||
return model.nodes.map(node => ({ id: node.id, metadata: { ...size, id: node.id } }));
|
||||
}
|
||||
|
||||
function mapEdges(model) {
|
||||
// returns links which connects nodes
|
||||
// we check are there both from and to nodes in the model. Sometimes links can be detached
|
||||
return model.links
|
||||
.map(link => ({
|
||||
from: link.source,
|
||||
to: link.target
|
||||
}))
|
||||
.filter(
|
||||
item => model.nodes.find(node => node.id === item.from) && model.nodes.find(node => node.id === item.to)
|
||||
);
|
||||
}
|
||||
120
demos/demo-dagre/index.tsx
Normal file
120
demos/demo-dagre/index.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DefaultNodeFactory,
|
||||
DefaultLinkFactory,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "storm-react-diagrams";
|
||||
import { distributeElements } from "./dagre-utils";
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
|
||||
function createNode(name) {
|
||||
return new DefaultNodeModel(name, "rgb(0,192,255)");
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
|
||||
function connectNodes(nodeFrom, nodeTo) {
|
||||
//just to get id-like structure
|
||||
count++;
|
||||
const portOut = nodeFrom.addPort(new DefaultPortModel(true, `${nodeFrom.name}-out-${count}`, "Out"));
|
||||
const portTo = nodeTo.addPort(new DefaultPortModel(false, `${nodeFrom.name}-to-${count}`, "IN"));
|
||||
return portOut.link(portTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests auto distribution
|
||||
*/
|
||||
class Demo8Widget extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.autoDistribute = this.autoDistribute.bind(this);
|
||||
}
|
||||
|
||||
autoDistribute() {
|
||||
const { engine } = this.props;
|
||||
const model = engine.getDiagramModel();
|
||||
let distributedModel = getDistributedModel(engine, model);
|
||||
engine.setModel(distributedModel);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { engine } = this.props;
|
||||
|
||||
return (
|
||||
<DemoWorkspaceWidget buttons={<button onClick={this.autoDistribute}>Re-distribute</button>}>
|
||||
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getDistributedModel(engine, model) {
|
||||
const serialized = model.serializeDiagram();
|
||||
const distributedSerializedDiagram = distributeElements(serialized);
|
||||
|
||||
//deserialize the model
|
||||
let deSerializedModel = new DiagramModel();
|
||||
deSerializedModel.deSerializeDiagram(distributedSerializedDiagram, engine);
|
||||
return deSerializedModel;
|
||||
}
|
||||
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
let engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
let model = new DiagramModel();
|
||||
|
||||
//3) create a default nodes
|
||||
let nodesFrom = [];
|
||||
let nodesTo = [];
|
||||
|
||||
nodesFrom.push(createNode("from-1"));
|
||||
nodesFrom.push(createNode("from-2"));
|
||||
nodesFrom.push(createNode("from-3"));
|
||||
|
||||
nodesTo.push(createNode("to-1"));
|
||||
nodesTo.push(createNode("to-2"));
|
||||
nodesTo.push(createNode("to-3"));
|
||||
|
||||
//4) link nodes together
|
||||
let links = nodesFrom.map((node, index) => {
|
||||
return connectNodes(node, nodesTo[index]);
|
||||
});
|
||||
|
||||
// more links for more complicated diagram
|
||||
links.push(connectNodes(nodesFrom[0], nodesTo[1]));
|
||||
links.push(connectNodes(nodesTo[0], nodesFrom[1]));
|
||||
links.push(connectNodes(nodesFrom[1], nodesTo[2]));
|
||||
|
||||
// initial random position
|
||||
nodesFrom.forEach((node, index) => {
|
||||
node.x = index * 70;
|
||||
model.addNode(node);
|
||||
});
|
||||
|
||||
nodesTo.forEach((node, index) => {
|
||||
node.x = index * 70;
|
||||
node.y = 100;
|
||||
model.addNode(node);
|
||||
});
|
||||
|
||||
links.forEach(link => {
|
||||
model.addLink(link);
|
||||
});
|
||||
|
||||
//5) load model into engine
|
||||
let model2 = getDistributedModel(engine, model);
|
||||
|
||||
engine.setModel(model2);
|
||||
|
||||
return <Demo8Widget engine={engine} />;
|
||||
};
|
||||
44
demos/demo-drag-and-drop/Application.ts
Normal file
44
demos/demo-drag-and-drop/Application.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import * as SRD from "storm-react-diagrams";
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class Application {
|
||||
protected activeModel: SRD.DiagramModel;
|
||||
protected diagramEngine: SRD.DiagramEngine;
|
||||
|
||||
constructor() {
|
||||
this.diagramEngine = new SRD.DiagramEngine();
|
||||
this.diagramEngine.installDefaults();
|
||||
|
||||
this.newModel();
|
||||
}
|
||||
|
||||
public newModel() {
|
||||
this.activeModel = new SRD.DiagramModel();
|
||||
this.diagramEngine.setModel(this.activeModel);
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new SRD.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 SRD.DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
let port2 = node2.addInPort("In");
|
||||
node2.setPosition(400, 100);
|
||||
|
||||
// link the ports
|
||||
let link1 = port.link(port2);
|
||||
|
||||
this.activeModel.addAll(node1, node2, link1);
|
||||
}
|
||||
|
||||
public getActiveDiagram(): SRD.DiagramModel {
|
||||
return this.activeModel;
|
||||
}
|
||||
|
||||
public getDiagramEngine(): SRD.DiagramEngine {
|
||||
return this.diagramEngine;
|
||||
}
|
||||
}
|
||||
72
demos/demo-drag-and-drop/components/BodyWidget.tsx
Normal file
72
demos/demo-drag-and-drop/components/BodyWidget.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as React from "react";
|
||||
import * as _ from "lodash";
|
||||
import { TrayWidget } from "./TrayWidget";
|
||||
import { Application } from "../Application";
|
||||
import { TrayItemWidget } from "./TrayItemWidget";
|
||||
import { DefaultNodeModel, DiagramWidget } from "storm-react-diagrams";
|
||||
|
||||
export interface BodyWidgetProps {
|
||||
app: Application;
|
||||
}
|
||||
|
||||
export interface BodyWidgetState {}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class BodyWidget extends React.Component<BodyWidgetProps, BodyWidgetState> {
|
||||
constructor(props: BodyWidgetProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="body">
|
||||
<div className="header">
|
||||
<div className="title">Storm React Diagrams - Demo 5</div>
|
||||
</div>
|
||||
<div className="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"
|
||||
onDrop={event => {
|
||||
var data = JSON.parse(event.dataTransfer.getData("storm-diagram-node"));
|
||||
var nodesCount = _.keys(
|
||||
this.props.app
|
||||
.getDiagramEngine()
|
||||
.getDiagramModel()
|
||||
.getNodes()
|
||||
).length;
|
||||
|
||||
var node = null;
|
||||
if (data.type === "in") {
|
||||
node = new DefaultNodeModel("Node " + (nodesCount + 1), "rgb(192,255,0)");
|
||||
node.addInPort("In");
|
||||
} else {
|
||||
node = new DefaultNodeModel("Node " + (nodesCount + 1), "rgb(0,192,255)");
|
||||
node.addOutPort("Out");
|
||||
}
|
||||
var points = this.props.app.getDiagramEngine().getRelativeMousePoint(event);
|
||||
node.x = points.x;
|
||||
node.y = points.y;
|
||||
this.props.app
|
||||
.getDiagramEngine()
|
||||
.getDiagramModel()
|
||||
.addNode(node);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
onDragOver={event => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DiagramWidget className="srd-demo-canvas" diagramEngine={this.props.app.getDiagramEngine()} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,20 @@ export interface TrayItemWidgetProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface TrayItemWidgetState {
|
||||
}
|
||||
export interface TrayItemWidgetState {}
|
||||
|
||||
export class TrayItemWidget extends React.Component<TrayItemWidgetProps, TrayItemWidgetState> {
|
||||
|
||||
constructor(props: TrayItemWidgetProps) {
|
||||
super(props);
|
||||
this.state = {}
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
style={{borderColor: this.props.color}}
|
||||
style={{ borderColor: this.props.color }}
|
||||
draggable={true}
|
||||
onDragStart={(event) => {
|
||||
onDragStart={event => {
|
||||
event.dataTransfer.setData("storm-diagram-node", JSON.stringify(this.props.model));
|
||||
}}
|
||||
className="tray-item"
|
||||
@@ -30,4 +28,4 @@ export class TrayItemWidget extends React.Component<TrayItemWidgetProps, TrayIte
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,21 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface TrayWidgetProps {
|
||||
}
|
||||
export interface TrayWidgetProps {}
|
||||
|
||||
export interface TrayWidgetState {
|
||||
}
|
||||
export interface TrayWidgetState {}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class TrayWidget extends React.Component<TrayWidgetProps, TrayWidgetState> {
|
||||
|
||||
public static defaultProps: TrayWidgetProps = {
|
||||
};
|
||||
public static defaultProps: TrayWidgetProps = {};
|
||||
|
||||
constructor(props: TrayWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
}
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="tray">{this.props.children}</div>
|
||||
)
|
||||
return <div className="tray">{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
demos/demo-drag-and-drop/index.tsx
Normal file
12
demos/demo-drag-and-drop/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
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} />;
|
||||
};
|
||||
47
demos/demo-drag-and-drop/sass/main.scss
Normal file
47
demos/demo-drag-and-drop/sass/main.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
demos/demo-grid/index.tsx
Normal file
37
demos/demo-grid/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
* Tests the grid size
|
||||
*/
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
model.setGridSize(50);
|
||||
|
||||
//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);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
|
||||
};
|
||||
73
demos/demo-labelled-links/index.tsx
Normal file
73
demos/demo-labelled-links/index.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget,
|
||||
DefaultLinkModel
|
||||
} from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
|
||||
export default () => {
|
||||
// setup the diagram engine
|
||||
const engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
// setup the diagram model
|
||||
const model = new DiagramModel();
|
||||
|
||||
// create four nodes
|
||||
const node1 = new DefaultNodeModel("Node A", "rgb(0,192,255)");
|
||||
const port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
const node2 = new DefaultNodeModel("Node B", "rgb(255,255,0)");
|
||||
const port2 = node2.addInPort("In");
|
||||
node2.setPosition(400, 50);
|
||||
|
||||
const node3 = new DefaultNodeModel("Node C (no label)", "rgb(192,255,255)");
|
||||
const port3 = node3.addInPort("In");
|
||||
node3.setPosition(450, 180);
|
||||
|
||||
const node4 = new DefaultNodeModel("Node D", "rgb(192,0,255)");
|
||||
const port4 = node4.addInPort("In");
|
||||
node4.setPosition(300, 250);
|
||||
|
||||
// link node A and B together and give it a label
|
||||
const link1 = port1.link(port2);
|
||||
(link1 as DefaultLinkModel).addLabel("Custom label 1");
|
||||
(link1 as DefaultLinkModel).addLabel("Custom label 2");
|
||||
|
||||
// no label for A and C, just a link
|
||||
const link2 = port1.link(port3);
|
||||
|
||||
// also a label for A and D
|
||||
const link3 = port1.link(port4);
|
||||
link3.setTargetPort(port4);
|
||||
(link3 as DefaultLinkModel).addLabel("Emoji label: 🎉");
|
||||
|
||||
// add all to the main model
|
||||
model.addAll(node1, node2, node3, node4, link1, link2, link3);
|
||||
|
||||
// load model into engine and render
|
||||
engine.setModel(model);
|
||||
|
||||
return (
|
||||
<DemoWorkspaceWidget
|
||||
buttons={
|
||||
<button
|
||||
onClick={() => {
|
||||
action("Serialized Graph")(JSON.stringify(model.serializeDiagram(), null, 2));
|
||||
}}
|
||||
>
|
||||
Serialize Graph
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
};
|
||||
49
demos/demo-limit-points/index.tsx
Normal file
49
demos/demo-limit-points/index.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DiagramWidget,
|
||||
DiagramProps
|
||||
} from "storm-react-diagrams";
|
||||
|
||||
/**
|
||||
* Shows that a limit of points can be set for links
|
||||
*/
|
||||
export default () => {
|
||||
// setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
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.setModel(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>
|
||||
);
|
||||
};
|
||||
62
demos/demo-listeners/index.tsx
Normal file
62
demos/demo-listeners/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as React from "react";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DiagramProps,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DiagramWidget
|
||||
} from "storm-react-diagrams";
|
||||
|
||||
/**
|
||||
* Shows some of the events triggered when elements are selected
|
||||
*/
|
||||
export default () => {
|
||||
// setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
var model = new DiagramModel();
|
||||
|
||||
// sample for link with simple line
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(255,99,66)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(400, 40);
|
||||
|
||||
var node3 = new DefaultNodeModel("Node 3", "rgb(128,99,255)");
|
||||
var port3 = node3.addInPort("In");
|
||||
node3.setPosition(300, 160);
|
||||
|
||||
//link the nodes
|
||||
let link1 = port1.link(port2);
|
||||
let link2 = port1.link(port3);
|
||||
|
||||
// add all the models
|
||||
let models = model.addAll(node1, node2, node3, link1, link2);
|
||||
|
||||
// add a selection listener to each
|
||||
models.forEach(item => {
|
||||
item.addListener({
|
||||
selectionChanged: action("selectionChanged")
|
||||
});
|
||||
});
|
||||
|
||||
engine.setModel(model);
|
||||
|
||||
var props = {
|
||||
diagramEngine: engine,
|
||||
maxNumberPointsPerLink: 0 // no extra points so link selection is fired straight away
|
||||
} as DiagramProps;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Click the diagram elements to inspect some of the possible events.</p>
|
||||
<DiagramWidget className="srd-demo-canvas" {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
69
demos/demo-locks/index.tsx
Normal file
69
demos/demo-locks/index.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
PointModel,
|
||||
DiagramWidget,
|
||||
DiagramProps
|
||||
} from "storm-react-diagrams";
|
||||
|
||||
/**
|
||||
*
|
||||
* Shows how you can lock down the system so that the entire scene cant be interacted with.
|
||||
*
|
||||
* @Author Dylan Vorster
|
||||
*/
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
var model = new DiagramModel();
|
||||
|
||||
// sample for link with simple line (no additional points)
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(400, 100);
|
||||
|
||||
let link1 = port1.link(port2);
|
||||
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
// sample for link with complex line (additional points)
|
||||
var node3 = new DefaultNodeModel("Node 3", "rgb(0,192,255)");
|
||||
var port3 = node3.addOutPort("Out");
|
||||
node3.setPosition(100, 250);
|
||||
|
||||
var node4 = new DefaultNodeModel("Node 4", "rgb(192,255,0)");
|
||||
var port4 = node4.addInPort("In");
|
||||
node4.setPosition(400, 250);
|
||||
|
||||
var link2 = port3.link(port4);
|
||||
|
||||
link2.point(350, 225);
|
||||
link2.point(200, 225);
|
||||
|
||||
model.addAll(node3, node4, link2);
|
||||
|
||||
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} />;
|
||||
};
|
||||
86
demos/demo-mutate-graph/index.tsx
Normal file
86
demos/demo-mutate-graph/index.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
|
||||
/**
|
||||
* Tests the grid size
|
||||
*/
|
||||
class NodeDelayedPosition extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.updatePosition = this.updatePosition.bind(this);
|
||||
this.updatePositionViaSerialize = this.updatePositionViaSerialize.bind(this);
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
const { engine } = this.props;
|
||||
let model = engine.getDiagramModel();
|
||||
const nodes = model.getNodes();
|
||||
let node = nodes[Object.keys(nodes)[0]];
|
||||
node.setPosition(node.x + 30, node.y + 30);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
updatePositionViaSerialize() {
|
||||
let { engine } = this.props;
|
||||
let model = engine.getDiagramModel();
|
||||
let str = JSON.stringify(model.serializeDiagram());
|
||||
let model2 = new DiagramModel();
|
||||
let obj = JSON.parse(str);
|
||||
let node = obj.nodes[0];
|
||||
node.x += 30;
|
||||
node.y += 30;
|
||||
model2.deSerializeDiagram(obj, engine);
|
||||
engine.setModel(model2);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { engine } = this.props;
|
||||
return (
|
||||
<DemoWorkspaceWidget
|
||||
buttons={[
|
||||
<button key={1} onClick={this.updatePosition}>
|
||||
Update position
|
||||
</button>,
|
||||
<button key={2} onClick={this.updatePositionViaSerialize}>
|
||||
Update position via serialize
|
||||
</button>
|
||||
]}
|
||||
>
|
||||
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(400, 100);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <NodeDelayedPosition engine={engine} model={model} />;
|
||||
};
|
||||
48
demos/demo-performance/index.tsx
Normal file
48
demos/demo-performance/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
*
|
||||
* Simple stress test of the system, shows that it can handle many nodes, and
|
||||
* retain good performance
|
||||
*
|
||||
* @Author Dylan Vorster
|
||||
*/
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
generateNodes(model, i * 200, j * 100);
|
||||
}
|
||||
}
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
|
||||
};
|
||||
|
||||
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(200 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
}
|
||||
60
demos/demo-serializing/index.tsx
Normal file
60
demos/demo-serializing/index.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel } from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import * as beautify from "json-beautify";
|
||||
import {DeserializeEvent} from "../../../react-canvas/src/models/BaseModel";
|
||||
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(400, 100);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//!------------- SERIALIZING ------------------
|
||||
|
||||
var str = JSON.stringify(model.serialize());
|
||||
|
||||
//!------------- DESERIALIZING ----------------
|
||||
|
||||
var model2 = new DiagramModel();
|
||||
model2.deSerialize(new DeserializeEvent(JSON.parse(str), engine));
|
||||
engine.setModel(model2);
|
||||
|
||||
return (
|
||||
<DemoWorkspaceWidget
|
||||
buttons={
|
||||
<button
|
||||
onClick={() => {
|
||||
action("Serialized Graph")(beautify(model2.serializeDiagram(), null, 2, 80));
|
||||
}}
|
||||
>
|
||||
Serialize Graph
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
};
|
||||
38
demos/demo-simple-flow/index.tsx
Normal file
38
demos/demo-simple-flow/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { DiagramEngine, DiagramModel, DefaultNodeModel, LinkModel, DiagramWidget } from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(400, 100);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//3-D) create an orphaned node
|
||||
var node3 = new DefaultNodeModel("Node 3", "rgb(0,192,255)");
|
||||
node3.addOutPort("Out");
|
||||
node3.setPosition(100, 200);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, node3, link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} allowLooseLinks={false} />;
|
||||
};
|
||||
9
demos/demo-simple/docs.md
Normal file
9
demos/demo-simple/docs.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Simple Usage
|
||||
|
||||
Welcome to STORM React Diagrams (SRD). SRD is a no-nonsense easy to use library for creating
|
||||
flow diagrams in the web that can ultimately represent any type of process/network/graph etc..
|
||||
|
||||
<!-- STORY -->
|
||||
|
||||
Try moving around one of the nodes or clicking and dragging the links to create new link anchors (points).
|
||||
You can also zoom the canvas using the mouse wheel / scroll gesture and drag to select multiple entities on the graph by shift + dragging the mouse.
|
||||
41
demos/demo-simple/index.tsx
Normal file
41
demos/demo-simple/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DiagramWidget,
|
||||
DefaultLinkModel
|
||||
} from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
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");
|
||||
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 = port1.link(port2);
|
||||
(link1 as DefaultLinkModel).addLabel("Hello World!");
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
|
||||
};
|
||||
68
demos/demo-smart-routing/index.tsx
Normal file
68
demos/demo-smart-routing/index.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
|
||||
export default () => {
|
||||
// setup the diagram engine
|
||||
const engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
// 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 DefaultPortModel(false, "out-1", "Out"));
|
||||
node1.setPosition(340, 350);
|
||||
|
||||
const node2 = new DefaultNodeModel("Node B", "rgb(255,255,0)");
|
||||
const port2 = node2.addPort(new DefaultPortModel(false, "out-1", "Out"));
|
||||
node2.setPosition(240, 80);
|
||||
const node3 = new DefaultNodeModel("Node C", "rgb(192,255,255)");
|
||||
const port3 = node3.addPort(new DefaultPortModel(true, "in-1", "In"));
|
||||
node3.setPosition(540, 180);
|
||||
const node4 = new DefaultNodeModel("Node D", "rgb(192,0,255)");
|
||||
const port4 = node4.addPort(new DefaultPortModel(true, "in-1", "In"));
|
||||
node4.setPosition(95, 185);
|
||||
const node5 = new DefaultNodeModel("Node E", "rgb(192,255,0)");
|
||||
node5.setPosition(250, 180);
|
||||
|
||||
// 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, node5, link1, link2);
|
||||
|
||||
// load model into engine and render
|
||||
engine.setModel(model);
|
||||
|
||||
return (
|
||||
<DemoWorkspaceWidget
|
||||
buttons={
|
||||
<button
|
||||
onClick={() => {
|
||||
action("Serialized Graph")(JSON.stringify(model.serializeDiagram(), null, 2));
|
||||
}}
|
||||
>
|
||||
Serialize Graph
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<DiagramWidget
|
||||
className="srd-demo-canvas"
|
||||
diagramEngine={engine}
|
||||
smartRouting={true}
|
||||
maxNumberPointsPerLink={0}
|
||||
/>
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
};
|
||||
61
demos/demo-zoom-to-fit/index.tsx
Normal file
61
demos/demo-zoom-to-fit/index.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "storm-react-diagrams";
|
||||
import * as React from "react";
|
||||
import { DemoWorkspaceWidget } from "../.helpers/DemoWorkspaceWidget";
|
||||
|
||||
/**
|
||||
*
|
||||
* Simple stress test of the system plus zoom to fit function
|
||||
*
|
||||
* @Author Dylan Vorster
|
||||
*/
|
||||
export default () => {
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.installDefaults();
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
generateNodes(model, i * 200, j * 100);
|
||||
}
|
||||
}
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return (
|
||||
<DemoWorkspaceWidget
|
||||
buttons={<button onClick={() => engine.getCanvasWidget().zoomToFit()}>Zoom to fit</button>}
|
||||
>
|
||||
<DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />
|
||||
</DemoWorkspaceWidget>
|
||||
);
|
||||
};
|
||||
|
||||
function generateNodes(model: DiagramModel, offsetX: number, offsetY: number) {
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
|
||||
var port1 = node1.addOutPort("Out");
|
||||
node1.setPosition(100 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
|
||||
var port2 = node2.addInPort("In");
|
||||
node2.setPosition(200 + offsetX, 100 + offsetY);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
}
|
||||
2
demos/demo1/index.d.ts
vendored
2
demos/demo1/index.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
/// <reference types="react" />
|
||||
export default function render(): JSX.Element;
|
||||
@@ -1,51 +0,0 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DefaultNodeFactory,
|
||||
DefaultLinkFactory,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "../../src/main";
|
||||
import * as React from "react";
|
||||
|
||||
export default () => {
|
||||
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.registerNodeFactory(new DefaultNodeFactory());
|
||||
engine.registerLinkFactory(new DefaultLinkFactory());
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1","rgb(0,192,255)");
|
||||
var port1 = node1.addPort(new DefaultPortModel(false,"out-1","Out"));
|
||||
node1.x = 100;
|
||||
node1.y = 100;
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2","rgb(192,255,0)");
|
||||
var port2 = node2.addPort(new DefaultPortModel(true,"in-1","IN"));
|
||||
node2.x = 400;
|
||||
node2.y = 100;
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = new LinkModel();
|
||||
link1.setSourcePort(port1);
|
||||
link1.setTargetPort(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addNode(node1);
|
||||
model.addNode(node2);
|
||||
model.addLink(link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget diagramEngine={engine} />;
|
||||
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DefaultNodeFactory,
|
||||
DefaultLinkFactory,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "../../src/main";
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
*
|
||||
* Simple stress test of the system, shows that it can handle many nodes, and
|
||||
* retain good performance
|
||||
*
|
||||
* @Author Dylan Vorster
|
||||
*/
|
||||
export default () => {
|
||||
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.registerNodeFactory(new DefaultNodeFactory());
|
||||
engine.registerLinkFactory(new DefaultLinkFactory());
|
||||
|
||||
function generateNodes(model: DiagramModel, offsetX: number,offsetY: number){
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1","rgb(0,192,255)");
|
||||
var port1 = node1.addPort(new DefaultPortModel(false,"out-1","Out"));
|
||||
node1.x = 100 + offsetX;
|
||||
node1.y = 100 + offsetY;
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2","rgb(192,255,0)");
|
||||
var port2 = node2.addPort(new DefaultPortModel(true,"in-1","IN"));
|
||||
node2.x = 200 + offsetX;
|
||||
node2.y = 100 + offsetY;
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = new LinkModel();
|
||||
link1.setSourcePort(port1);
|
||||
link1.setTargetPort(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addNode(node1);
|
||||
model.addNode(node2);
|
||||
model.addLink(link1);
|
||||
}
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
for(var i =0;i < 8;i++){
|
||||
for(var j = 0;j < 8;j++){
|
||||
generateNodes(model,i*200,j*100);
|
||||
}
|
||||
}
|
||||
|
||||
//5) load model into engine
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget diagramEngine={engine} />;
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import * as SRD from "../../src/main";
|
||||
import {DiamondNodeModel} from "./DiamondNodeModel";
|
||||
import {DiamondPortModel} from "./DiamondPortModel";
|
||||
|
||||
export class DiamondNodeFactory extends SRD.AbstractInstanceFactory<DiamondNodeModel>{
|
||||
|
||||
constructor(){
|
||||
super("DiamondNodeModel");
|
||||
}
|
||||
|
||||
getInstance(){
|
||||
return new DiamondNodeModel();
|
||||
}
|
||||
}
|
||||
|
||||
export class DiamondPortFactory extends SRD.AbstractInstanceFactory<DiamondPortModel>{
|
||||
|
||||
constructor(){
|
||||
super("DiamondPortModel");
|
||||
}
|
||||
|
||||
getInstance(){
|
||||
return new DiamondPortModel();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import * as SRD from "../../src/main";
|
||||
import {DiamondPortModel} from "./DiamondPortModel";
|
||||
|
||||
export class DiamondNodeModel extends SRD.NodeModel{
|
||||
|
||||
constructor(){
|
||||
super('diamond');
|
||||
this.addPort(new DiamondPortModel('top'));
|
||||
this.addPort(new DiamondPortModel('left'));
|
||||
this.addPort(new DiamondPortModel('bottom'));
|
||||
this.addPort(new DiamondPortModel('right'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import * as React from "react";
|
||||
import {DiamondNodeModel} from "./DiamondNodeModel";
|
||||
import * as SRD from "../../src/main";
|
||||
|
||||
export interface DiamonNodeWidgetProps {
|
||||
node: DiamondNodeModel,
|
||||
size?: number
|
||||
}
|
||||
|
||||
export interface DiamonNodeWidgetState {
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DiamonNodeWidget extends React.Component<DiamonNodeWidgetProps, DiamonNodeWidgetState> {
|
||||
|
||||
public static defaultProps: DiamonNodeWidgetProps = {
|
||||
size: 150,
|
||||
node: null
|
||||
};
|
||||
|
||||
constructor(props: DiamonNodeWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
React.DOM.div({className: "diamond-node", style: {position: 'relative', width: this.props.size, height: this.props.size}},
|
||||
React.DOM.svg({
|
||||
width:this.props.size,height: this.props.size,dangerouslySetInnerHTML: {__html:`
|
||||
<g id="Layer_1">
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<polygon fill="purple" stroke="#000000" stroke-width="3" stroke-miterlimit="10" points="10,`+(this.props.size/2)+` `+(this.props.size/2)+`,10 `+(this.props.size-10)+`,`+(this.props.size/2)+` `+(this.props.size/2)+`,`+(this.props.size-10)+` "/>
|
||||
</g>
|
||||
`}}),
|
||||
|
||||
//left node
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,top:this.props.size/2 - 8, left: -8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'left', node: this.props.node})
|
||||
),
|
||||
|
||||
//top node
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left:this.props.size/2-8,top:-8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'top', node: this.props.node})
|
||||
),
|
||||
|
||||
//right
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left:this.props.size-8,top:this.props.size/2 - 8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'right', node: this.props.node})
|
||||
),
|
||||
|
||||
//bottom
|
||||
React.DOM.div({style: {position: 'absolute', zIndex:10,left :this.props.size/2 - 8,top:this.props.size-8}},
|
||||
React.createElement(SRD.PortWidget,{name: 'bottom', node: this.props.node})
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export var DiamonNodeWidgetFactory = React.createFactory(DiamonNodeWidget);
|
||||
@@ -1,24 +0,0 @@
|
||||
import * as SRD from "../../src/main";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export class DiamondPortModel extends SRD.PortModel{
|
||||
|
||||
position: string|'top'|'bottom'|'left'|'right';
|
||||
|
||||
constructor(pos: string = 'top'){
|
||||
super(pos);
|
||||
this.position = pos ;
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
position: this.position,
|
||||
});
|
||||
}
|
||||
|
||||
deSerialize(data:any){
|
||||
super.deSerialize(data);
|
||||
this.position = data.position;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import * as SRD from "../../src/main";
|
||||
import {DiamonNodeWidgetFactory} from "./DiamondNodeWidget";
|
||||
|
||||
export class DiamondWidgetFactory extends SRD.NodeWidgetFactory{
|
||||
|
||||
constructor(){
|
||||
super('diamond');
|
||||
}
|
||||
|
||||
generateReactWidget(diagramEngine:SRD.DiagramEngine,node: SRD.NodeModel): JSX.Element{
|
||||
return DiamonNodeWidgetFactory({node: node});
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DefaultNodeFactory,
|
||||
DefaultLinkFactory,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "../../src/main";
|
||||
import * as React from "react";
|
||||
import {DiamondNodeModel} from "./DiamondNodeModel";
|
||||
import {DiamondWidgetFactory} from "./DiamondWidgetFactory";
|
||||
|
||||
|
||||
/**
|
||||
* @Author Dylan Vorster
|
||||
*/
|
||||
export default () => {
|
||||
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.registerNodeFactory(new DefaultNodeFactory());
|
||||
engine.registerLinkFactory(new DefaultLinkFactory());
|
||||
engine.registerNodeFactory(new DiamondWidgetFactory());
|
||||
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1","rgb(0,192,255)");
|
||||
var port1 = node1.addPort(new DefaultPortModel(false,"out-1","Out"));
|
||||
node1.x = 100;
|
||||
node1.y = 150;
|
||||
|
||||
//3-B) create our new custom node
|
||||
var node2 = new DiamondNodeModel();
|
||||
node2.x = 400;
|
||||
node2.y = 100;
|
||||
|
||||
var node3 = new DefaultNodeModel("Node 3","red");
|
||||
var port3 = node3.addPort(new DefaultPortModel(true,"in-1","In"));
|
||||
node3.x = 800;
|
||||
node3.y = 150;
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = new LinkModel();
|
||||
link1.setSourcePort(port1);
|
||||
link1.setTargetPort(node2.ports['left']);
|
||||
|
||||
var link2 = new LinkModel();
|
||||
link2.setSourcePort(node2.ports['right']);
|
||||
link2.setTargetPort(port3);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addNode(node1);
|
||||
model.addNode(node2);
|
||||
model.addNode(node3);
|
||||
model.addLink(link1);
|
||||
model.addLink(link2);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
|
||||
return <DiagramWidget diagramEngine={engine} />
|
||||
|
||||
|
||||
// //!------------- SERIALIZING / DESERIALIZING ------------
|
||||
//
|
||||
// //we need this to help the system know what models to create form the JSON
|
||||
// engine.registerInstanceFactory(new SRD.DefaultNodeInstanceFactory());
|
||||
// engine.registerInstanceFactory(new SRD.DefaultPortInstanceFactory());
|
||||
// engine.registerInstanceFactory(new SRD.LinkInstanceFactory());
|
||||
// engine.registerInstanceFactory(new DiamondNodeFactory());
|
||||
// engine.registerInstanceFactory(new DiamondPortFactory());
|
||||
//
|
||||
// //serialize the model
|
||||
// var str = JSON.stringify(model.serializeDiagram());
|
||||
// console.log(str);
|
||||
//
|
||||
// //deserialize the model
|
||||
// var model2 = new SRD.DiagramModel();
|
||||
// model2.deSerializeDiagram(JSON.parse(str),engine);
|
||||
// engine.setDiagramModel(model2);
|
||||
// console.log(model2);
|
||||
//
|
||||
// //re-render the model
|
||||
// ReactDOM.render(React.createElement(SRD.DiagramWidget,{diagramEngine: engine}), document.body);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import * as SRD from "../../src/main";
|
||||
import * as React from "react";
|
||||
import {
|
||||
DiagramEngine,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DiagramWidget
|
||||
} from "../../src/main";
|
||||
|
||||
/**
|
||||
*
|
||||
* Shows how you can lock down the system so that the entire scene cant be interacted with.
|
||||
*
|
||||
* @Author Dylan Vorster
|
||||
*/
|
||||
export default () => {
|
||||
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.registerNodeFactory(new SRD.DefaultNodeFactory());
|
||||
engine.registerLinkFactory(new SRD.DefaultLinkFactory());
|
||||
|
||||
var model = new DiagramModel();
|
||||
|
||||
var node1 = new DefaultNodeModel("Node 1","rgb(0,192,255)");
|
||||
var port1 = node1.addPort(new SRD.DefaultPortModel(false,"out-1","Out"));
|
||||
node1.x = 100;
|
||||
node1.y = 100;
|
||||
|
||||
var node2 = new DefaultNodeModel("Node 2","rgb(192,255,0)");
|
||||
var port2 = node2.addPort(new SRD.DefaultPortModel(true,"in-1","IN"));
|
||||
node2.x = 400;
|
||||
node2.y = 100;
|
||||
|
||||
var link1 = new LinkModel();
|
||||
link1.setSourcePort(port1);
|
||||
link1.setTargetPort(port2);
|
||||
|
||||
model.addNode(node1);
|
||||
model.addNode(node2);
|
||||
model.addLink(link1);
|
||||
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
//!========================================= <<<<<<<
|
||||
|
||||
model.setLocked(true);
|
||||
var props = {
|
||||
diagramEngine: engine,
|
||||
allowLooseLinks: false,
|
||||
allowCanvasTranslation: false,
|
||||
allowCanvasZoom: false
|
||||
} as SRD.DiagramProps;
|
||||
|
||||
//!========================================= <<<<<<<
|
||||
|
||||
return <DiagramWidget {...props} />;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import * as SRD from "../../src/main";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class Application{
|
||||
|
||||
protected activeModel: SRD.DiagramModel;
|
||||
protected diagramEngine: SRD.DiagramEngine;
|
||||
|
||||
constructor(){
|
||||
this.diagramEngine = new SRD.DiagramEngine();
|
||||
|
||||
this.diagramEngine.registerNodeFactory(new SRD.DefaultNodeFactory());
|
||||
this.diagramEngine.registerLinkFactory(new SRD.DefaultLinkFactory());
|
||||
|
||||
this.newModel();
|
||||
}
|
||||
|
||||
public newModel(){
|
||||
this.activeModel = new SRD.DiagramModel();
|
||||
this.diagramEngine.setDiagramModel(this.activeModel);
|
||||
|
||||
|
||||
var node1 = new SRD.DefaultNodeModel("Node 1","rgb(0,192,255)");
|
||||
var port1 = node1.addPort(new SRD.DefaultPortModel(false,"out-1","Out"));
|
||||
node1.x = 100;
|
||||
node1.y = 100;
|
||||
|
||||
var node2 = new SRD.DefaultNodeModel("Node 2","rgb(192,255,0)");
|
||||
var port2 = node2.addPort(new SRD.DefaultPortModel(true,"in-1","IN"));
|
||||
node2.x = 400;
|
||||
node2.y = 100;
|
||||
|
||||
var link1 = new SRD.LinkModel();
|
||||
link1.setSourcePort(port1);
|
||||
link1.setTargetPort(port2);
|
||||
|
||||
this.activeModel.addNode(node1);
|
||||
this.activeModel.addNode(node2);
|
||||
this.activeModel.addLink(link1);
|
||||
}
|
||||
|
||||
public getActiveDiagram(): SRD.DiagramModel{
|
||||
return this.activeModel;
|
||||
}
|
||||
|
||||
public getDiagramEngine(): SRD.DiagramEngine{
|
||||
return this.diagramEngine;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as _ from "lodash";
|
||||
import {TrayWidget} from "./TrayWidget";
|
||||
import {DiagramWidget} from "../../../src/main";
|
||||
import {Application} from "../Application";
|
||||
import {TrayItemWidget} from "./TrayItemWidget";
|
||||
import {DefaultNodeModel,DefaultPortModel} from "../../../src/main";
|
||||
|
||||
export interface BodyWidgetProps {
|
||||
app: Application;
|
||||
}
|
||||
|
||||
export interface BodyWidgetState {
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class BodyWidget extends React.Component<BodyWidgetProps, BodyWidgetState> {
|
||||
|
||||
constructor(props: BodyWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="body">
|
||||
<div className="header">
|
||||
<div className="title">Storm React Diagrams - Demo 5</div>
|
||||
</div>
|
||||
<div className="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"
|
||||
onDrop={(event) => {
|
||||
|
||||
var data = JSON.parse(event.dataTransfer.getData('storm-diagram-node'));
|
||||
var nodesCount = _.keys(this.props.app.getDiagramEngine().getDiagramModel().getNodes()).length;
|
||||
|
||||
var node = null;
|
||||
if(data.type === 'in'){
|
||||
node = new DefaultNodeModel("Node "+(nodesCount+1),"rgb(192,255,0)");
|
||||
node.addPort(new DefaultPortModel(true,"in-1","In"));
|
||||
}else{
|
||||
node = new DefaultNodeModel("Node "+(nodesCount+1),"rgb(0,192,255)");
|
||||
node.addPort(new DefaultPortModel(false,"out-1","Out"));
|
||||
}
|
||||
var points = this.props.app.getDiagramEngine().getRelativeMousePoint(event);
|
||||
node.x = points.x;
|
||||
node.y = points.y;
|
||||
this.props.app.getDiagramEngine().getDiagramModel().addNode(node);
|
||||
this.forceUpdate();
|
||||
|
||||
}}
|
||||
onDragOver={(event) => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DiagramWidget diagramEngine={this.props.app.getDiagramEngine()}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import {BodyWidget} from "./components/BodyWidget";
|
||||
import {Application} from "./Application";
|
||||
|
||||
require("./sass/main.scss");
|
||||
|
||||
export default () => {
|
||||
|
||||
var app = new Application();
|
||||
|
||||
return <BodyWidget app={app} />;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
.body{
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.storm-diagrams-canvas{
|
||||
$color: rgba(white, .05);
|
||||
background: rgba(black,0.2);
|
||||
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;
|
||||
|
||||
path{
|
||||
stroke: rgba(white,0.5);
|
||||
}
|
||||
|
||||
.pointui{
|
||||
fill: rgba(white,0.5);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DefaultNodeFactory,
|
||||
DefaultLinkFactory,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget,
|
||||
DefaultNodeInstanceFactory,
|
||||
DefaultPortInstanceFactory,
|
||||
LinkInstanceFactory
|
||||
} from "../../src/main";
|
||||
import * as React from "react";
|
||||
|
||||
export default () => {
|
||||
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.registerNodeFactory(new DefaultNodeFactory());
|
||||
engine.registerLinkFactory(new DefaultLinkFactory());
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1","rgb(0,192,255)");
|
||||
var port1 = node1.addPort(new DefaultPortModel(false,"out-1","Out"));
|
||||
node1.x = 100;
|
||||
node1.y = 100;
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2","rgb(192,255,0)");
|
||||
var port2 = node2.addPort(new DefaultPortModel(true,"in-1","IN"));
|
||||
node2.x = 400;
|
||||
node2.y = 100;
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = new LinkModel();
|
||||
link1.setSourcePort(port1);
|
||||
link1.setTargetPort(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addNode(node1);
|
||||
model.addNode(node2);
|
||||
model.addLink(link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
|
||||
//!------------- SERIALIZING ------------------
|
||||
|
||||
var str = JSON.stringify(model.serializeDiagram());
|
||||
|
||||
//!------------- DESERIALIZING ----------------
|
||||
|
||||
//we need this to help the system know what models to create form the JSON
|
||||
engine.registerInstanceFactory(new DefaultNodeInstanceFactory());
|
||||
engine.registerInstanceFactory(new DefaultPortInstanceFactory());
|
||||
engine.registerInstanceFactory(new LinkInstanceFactory());
|
||||
|
||||
//deserialize the model
|
||||
var model2 = new DiagramModel();
|
||||
model2.deSerializeDiagram(JSON.parse(str),engine);
|
||||
engine.setDiagramModel(model2);
|
||||
|
||||
return <DiagramWidget diagramEngine={engine} />;
|
||||
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
import {
|
||||
DiagramEngine,
|
||||
DefaultNodeFactory,
|
||||
DefaultLinkFactory,
|
||||
DiagramModel,
|
||||
DefaultNodeModel,
|
||||
LinkModel,
|
||||
DefaultPortModel,
|
||||
DiagramWidget
|
||||
} from "../../src/main";
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
* Tests the grid size
|
||||
*/
|
||||
export default () => {
|
||||
|
||||
//1) setup the diagram engine
|
||||
var engine = new DiagramEngine();
|
||||
engine.registerNodeFactory(new DefaultNodeFactory());
|
||||
engine.registerLinkFactory(new DefaultLinkFactory());
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
model.setGridSize(50);
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel("Node 1","rgb(0,192,255)");
|
||||
var port1 = node1.addPort(new DefaultPortModel(false,"out-1","Out"));
|
||||
node1.x = 100;
|
||||
node1.y = 100;
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel("Node 2","rgb(192,255,0)");
|
||||
var port2 = node2.addPort(new DefaultPortModel(true,"in-1","IN"));
|
||||
node2.x = 400;
|
||||
node2.y = 100;
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = new LinkModel();
|
||||
link1.setSourcePort(port1);
|
||||
link1.setTargetPort(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addNode(node1);
|
||||
model.addNode(node2);
|
||||
model.addLink(link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setDiagramModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <DiagramWidget diagramEngine={engine} />;
|
||||
|
||||
};
|
||||
157
demos/index.tsx
157
demos/index.tsx
@@ -1,36 +1,129 @@
|
||||
import * as React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import * as React from "react";
|
||||
import { storiesOf, addDecorator } from "@storybook/react";
|
||||
import { setOptions } from "@storybook/addon-options";
|
||||
import { host } from "storybook-host";
|
||||
import { Helper } from "./.helpers/Helper";
|
||||
import { Toolkit } from "../src/Toolkit";
|
||||
|
||||
import demo1 from "./demo1/index";
|
||||
import demo2 from "./demo2/index";
|
||||
import demo3 from "./demo3/index";
|
||||
import demo4 from "./demo4/index";
|
||||
import demo5 from "./demo5/index";
|
||||
import demo6 from "./demo6/index";
|
||||
import demo7 from "./demo7/index";
|
||||
//include the SCSS for the demo
|
||||
import "./.helpers/demo.scss";
|
||||
|
||||
require("./test.scss");
|
||||
Toolkit.TESTING = true;
|
||||
|
||||
storiesOf('React Diagrams', module)
|
||||
.add('Simple Example', () => {
|
||||
return demo1();
|
||||
addDecorator(
|
||||
host({
|
||||
cropMarks: false,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
padding: 20
|
||||
})
|
||||
.add('Performance Test', () => {
|
||||
return demo2();
|
||||
})
|
||||
.add('Custom Diamond Widget', () => {
|
||||
return demo3();
|
||||
})
|
||||
.add('Locked Widget', () => {
|
||||
return demo4();
|
||||
})
|
||||
.add('Embedded diagram', () => {
|
||||
return demo5();
|
||||
})
|
||||
.add('Serializing and Deserializing', () => {
|
||||
return demo6();
|
||||
})
|
||||
.add('Grid Size', () => {
|
||||
return demo7();
|
||||
})
|
||||
);
|
||||
|
||||
setOptions({
|
||||
name: "STORM React Diagrams",
|
||||
url: "https://github.com/projectstorm/react-diagrams",
|
||||
addonPanelInRight: true
|
||||
});
|
||||
|
||||
storiesOf("Simple Usage", module)
|
||||
.add(
|
||||
"Simple example",
|
||||
Helper.makeDemo(
|
||||
require("./demo-simple/index").default(),
|
||||
require("!!raw-loader!./demo-simple/index"),
|
||||
require("./demo-simple/docs.md")
|
||||
)
|
||||
)
|
||||
// .add(
|
||||
// "Simple flow example",
|
||||
// Helper.makeDemo(require("./demo-simple-flow/index").default(), require("!!raw-loader!./demo-simple-flow/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Performance demo",
|
||||
// Helper.makeDemo(require("./demo-performance/index").default(), require("!!raw-loader!./demo-performance/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Locked widget",
|
||||
// Helper.makeDemo(require("./demo-locks/index").default(), require("!!raw-loader!./demo-locks/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Canvas grid size",
|
||||
// Helper.makeDemo(require("./demo-grid/index").default(), require("!!raw-loader!./demo-grid/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Limiting link points",
|
||||
// Helper.makeDemo(
|
||||
// require("./demo-limit-points/index").default(),
|
||||
// require("!!raw-loader!./demo-limit-points/index")
|
||||
// )
|
||||
// )
|
||||
// .add(
|
||||
// "Events and listeners",
|
||||
// Helper.makeDemo(require("./demo-listeners/index").default(), require("!!raw-loader!./demo-listeners/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Zoom to fit",
|
||||
// Helper.makeDemo(require("./demo-zoom-to-fit/index").default(), require("!!raw-loader!./demo-zoom-to-fit/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Links with labels",
|
||||
// Helper.makeDemo(
|
||||
// require("./demo-labelled-links/index").default(),
|
||||
// require("!!raw-loader!./demo-labelled-links/index")
|
||||
// )
|
||||
// );
|
||||
//
|
||||
// storiesOf("Advanced Techniques", module)
|
||||
// .add(
|
||||
// "Clone Selected",
|
||||
// Helper.makeDemo(require("./demo-cloning/index").default(), require("!!raw-loader!./demo-cloning/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Serializing and de-serializing",
|
||||
// Helper.makeDemo(require("./demo-serializing/index").default(), require("!!raw-loader!./demo-serializing/index"))
|
||||
// )
|
||||
// .add(
|
||||
// "Programatically modifying graph",
|
||||
// Helper.makeDemo(
|
||||
// require("./demo-mutate-graph/index").default(),
|
||||
// require("!!raw-loader!./demo-mutate-graph/index")
|
||||
// )
|
||||
// )
|
||||
// .add(
|
||||
// "Large application",
|
||||
// Helper.makeDemo(
|
||||
// require("./demo-drag-and-drop/index").default(),
|
||||
// require("!!raw-loader!./demo-drag-and-drop/components/BodyWidget")
|
||||
// )
|
||||
// )
|
||||
// .add(
|
||||
// "Smart routing",
|
||||
// Helper.makeDemo(
|
||||
// require("./demo-smart-routing/index").default(),
|
||||
// require("!!raw-loader!./demo-smart-routing/index")
|
||||
// )
|
||||
// );
|
||||
//
|
||||
// storiesOf("Custom Models", module)
|
||||
// .add(
|
||||
// "Custom diamond node",
|
||||
// Helper.makeDemo(
|
||||
// require("./demo-custom-node1/index").default(),
|
||||
// require("!!raw-loader!./demo-custom-node1/index")
|
||||
// )
|
||||
// )
|
||||
// .add(
|
||||
// "Custom animated links",
|
||||
// Helper.makeDemo(
|
||||
// require("./demo-custom-link1/index").default(),
|
||||
// require("!!raw-loader!./demo-custom-link1/index")
|
||||
// )
|
||||
// );
|
||||
//
|
||||
// storiesOf("3rd party libraries", module).add(
|
||||
// "Auto Distribute (Dagre)",
|
||||
// Helper.makeDemo(require("./demo-dagre/index").default(), require("!!raw-loader!./demo-dagre/index"))
|
||||
// );
|
||||
|
||||
// enable this to log mouse location when writing new puppeteer tests
|
||||
//Helper.logMousePosition()
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
@import "../src/sass";
|
||||
|
||||
.storm-diagrams-canvas{
|
||||
height: 400px;
|
||||
background-color: rgb(60,60,60);
|
||||
$color: rgba(white, .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;
|
||||
|
||||
path{
|
||||
stroke: rgba(white,0.5);
|
||||
}
|
||||
|
||||
.pointui{
|
||||
fill: rgba(white,0.5);
|
||||
}
|
||||
}
|
||||
10
demos/tslint.json
Normal file
10
demos/tslint.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": [
|
||||
"../tslint.json"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": false,
|
||||
"max-classes-per-file": false,
|
||||
"no-var-requires": false
|
||||
}
|
||||
}
|
||||
76
docs/Architecture Questions.md
Normal file
76
docs/Architecture Questions.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 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/Model–view–controller), 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
|
||||
@@ -1,88 +0,0 @@
|
||||
# Events
|
||||
|
||||
Each model (DiagramModel, NodeModel etc..) are all built ontop of an event system. You can listen for most of these events by registering
|
||||
an event on the model itself.
|
||||
|
||||
## All models
|
||||
|
||||
All models will fire these events:
|
||||
|
||||
lockChanged?(entity: BaseEntity,locked: boolean)
|
||||
|
||||
Fires when the lock state of the entity changes. If an element is locked, it cannot be moved or deletes.
|
||||
|
||||
## All Base models excluding DiagramModel
|
||||
|
||||
|
||||
selectionChanged?(item: BaseModel, isSelected:boolean)
|
||||
|
||||
When the _selected_ property of a model changes
|
||||
|
||||
entityRemoved?(item:any)
|
||||
|
||||
When the entity is going to be deleted. The DiagramModel listeners for this event to when to remove the model from itself.
|
||||
|
||||
|
||||
## DiagramModel
|
||||
|
||||
nodesUpdated(node: any, isCreated:boolean)
|
||||
|
||||
When nodes are added or removed
|
||||
|
||||
linksUpdated(link: any, isCreated:boolean)
|
||||
|
||||
when links are added or removed
|
||||
|
||||
controlsUpdated() [DEPRECIATED]
|
||||
|
||||
_depreciated, use offsetUpdated and zoomUpdated instead_
|
||||
|
||||
offsetUpdated(model: DiagramModel,offsetX: number, offsetY: number)
|
||||
|
||||
to know when the canvas was translated in any direction
|
||||
|
||||
zoomUpdated(model: DiagramModel,zoom: number)
|
||||
|
||||
to know when the zoom level of the canvas was updated
|
||||
|
||||
## DiagramEngine
|
||||
|
||||
The diagram engine
|
||||
|
||||
nodeFactoriesUpdated
|
||||
|
||||
When node factories have been added or removed from the engine
|
||||
|
||||
linkFactoriesUpdated
|
||||
|
||||
When link factories have been added or removed from the engine
|
||||
|
||||
## LinkModel
|
||||
|
||||
sourcePortChanged?(item:LinkModel,target: null|PortModel)
|
||||
|
||||
targetPortChanged?(item:LinkModel,target: null|PortModel)
|
||||
|
||||
|
||||
# Example of usage
|
||||
|
||||
```javascript
|
||||
let model = new SRD.DiagramModel();
|
||||
let node1 = new SRD.DefaultNodeModel("default","rgb(0,192,255)");
|
||||
node1.addListener({
|
||||
entityRemoved: (node) => {
|
||||
console.log('Removed', node.id)
|
||||
},
|
||||
selectionChanged: (node, isSelected) => {
|
||||
console.log(isSelected?'Selected':'Unselected', node)
|
||||
}
|
||||
});
|
||||
model.addListener({
|
||||
linksUpdated:(entity, isAdded) => {
|
||||
console.log(isAdded?'added':'removed', entity)
|
||||
},
|
||||
nodesUpdated: (entity, isAdded) => {
|
||||
console.log(isAdded?'added':'removed', entity)
|
||||
}
|
||||
});
|
||||
```
|
||||
142
docs/Getting Started.md
Normal file
142
docs/Getting Started.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 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 install 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} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
22
docs/Interactive Usage.md
Normal file
22
docs/Interactive Usage.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# End user usage
|
||||
|
||||
__Delete__ removes any selected items
|
||||

|
||||
|
||||
__Shift + Mouse Drag__ triggers a multi-selection box
|
||||

|
||||
|
||||
__Shift + Mouse Click__ selects the item (items can be multi-selected)
|
||||

|
||||
|
||||
__Mouse Drag__ drags the entire diagram
|
||||

|
||||
|
||||
__Mouse Wheel__ zooms the diagram in / out
|
||||

|
||||
|
||||
__Click Link + Drag__ creates a new link point
|
||||

|
||||
|
||||
__Click Node Port + Drag__ creates a new link
|
||||

|
||||
@@ -1,24 +0,0 @@
|
||||
## Questions
|
||||
|
||||
#### Why didn’t I render the nodes as SVG's?
|
||||
|
||||
Because its vastly better to render nodes as standard HTML so that we can embed input controls and not have
|
||||
to deal with the complexities of trying to get SVG to work like we want it to. I also created this primarily to embed into
|
||||
enterprise applications where the nodes themselves are highly interactive with buttons and other controls that cave when I try to use SVG.
|
||||
|
||||
#### Why Typescript?
|
||||
|
||||
Because it can transpile into any level of ECMA Script, and the library got really complicated, so I ported it to Typescript
|
||||
to accommodate the heavy architectural changes I was starting to make. <3 Type Script
|
||||
|
||||
#### How do I make my own elements?
|
||||
|
||||
Take a look at the __defaults__ directory, with specific attention to the __DefaultNodeWidget__
|
||||
|
||||
That being said, the defaults 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 base node like the onw you see
|
||||
|
||||
#### 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
|
||||
33
docs/Testing.md
Normal file
33
docs/Testing.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 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 cna 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.
|
||||
BIN
images/demo3.png
BIN
images/demo3.png
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB |
BIN
images/example1.jpg
Normal file
BIN
images/example1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 438 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 MiB |
BIN
images/example2.jpg
Normal file
BIN
images/example2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 870 KiB |
BIN
images/example3.jpg
Normal file
BIN
images/example3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
27
jest.config.js
Normal file
27
jest.config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const path = require("path");
|
||||
// jest.config.js
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
moduleFileExtensions: [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
transform: {
|
||||
".*test_loader.*": path.join(__dirname, "tests", "helpers", "storybook-loader.js" ),
|
||||
"^.+\\.tsx?$": "ts-jest",
|
||||
},
|
||||
moduleNameMapper:{
|
||||
"\\.(scss|css|png)$": path.join(__dirname,"tests","helpers","css-mock.js"),
|
||||
"storm-react-diagrams": path.join(__dirname, "src", "main")
|
||||
},
|
||||
roots:[
|
||||
__dirname+'/tests'
|
||||
],
|
||||
testMatch: [
|
||||
"**/*\.test\.tsx"
|
||||
]
|
||||
};
|
||||
135
package.json
135
package.json
@@ -1,51 +1,88 @@
|
||||
{
|
||||
"name": "storm-react-diagrams",
|
||||
"version": "2.5.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/projectstorm/react-diagrams.git"
|
||||
},
|
||||
"keywords": [
|
||||
"web",
|
||||
"diagram",
|
||||
"diagrams",
|
||||
"react",
|
||||
"typescript",
|
||||
"flowchart",
|
||||
"simple",
|
||||
"links",
|
||||
"nodes"
|
||||
],
|
||||
"main": "./dist/main.js",
|
||||
"typings": "./dist/src/main",
|
||||
"author": "dylanvorster",
|
||||
"scripts": {
|
||||
"storybook": "start-storybook -p 9001 -c .storybook",
|
||||
"storybook:build": "build-storybook -c .storybook -o .out",
|
||||
"storybook:github": "storybook-to-ghpages",
|
||||
"prepare": "export NODE_ENV=production && webpack && ./node_modules/node-sass/bin/node-sass --output-style compressed ./src/sass.scss > ./dist/style.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/storybook-deployer": "^2.0.0",
|
||||
"closest": "^0.0.1",
|
||||
"lodash": "^4.17.4",
|
||||
"react": "^15.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-notes": "^3.2.0",
|
||||
"@storybook/react": "^3.2.5",
|
||||
"@types/lodash": "^4.14.73",
|
||||
"@types/node": "^8.0.24",
|
||||
"@types/react": "^16.0.2",
|
||||
"awesome-typescript-loader": "^3.2.3",
|
||||
"css-loader": "^0.28.5",
|
||||
"node-sass": "^4.5.3",
|
||||
"react-dom": "^15.6.1",
|
||||
"sass-loader": "^6.0.6",
|
||||
"source-map-loader": "^0.2.1",
|
||||
"style-loader": "^0.18.2",
|
||||
"tslint": "^5.6.0",
|
||||
"typescript": "^2.4.2",
|
||||
"webpack": "^3.5.5"
|
||||
}
|
||||
"name": "storm-react-diagrams",
|
||||
"version": "5.1.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/projectstorm/react-diagrams.git"
|
||||
},
|
||||
"keywords": [
|
||||
"web",
|
||||
"diagram",
|
||||
"diagrams",
|
||||
"react",
|
||||
"typescript",
|
||||
"flowchart",
|
||||
"simple",
|
||||
"links",
|
||||
"nodes"
|
||||
],
|
||||
"main": "./dist/main.js",
|
||||
"typings": "./dist/@types/src/main",
|
||||
"author": "dylanvorster",
|
||||
"scripts": {
|
||||
"build:ts": "webpack",
|
||||
"build:ts:prod": "cross-env NODE_ENV=production webpack",
|
||||
"build:sass:prod": "node-sass --output-style compressed ./src/sass/main.scss > ./dist/style.min.css",
|
||||
"storybook": "start-storybook -p 9001 -c .storybook",
|
||||
"storybook:build": "build-storybook -c .storybook -o .out",
|
||||
"storybook:github": "storybook-to-ghpages",
|
||||
"pretty": "prettier --use-tabs --write \"{src,demos,tests}/**/*.{ts,tsx}\" --print-width 120",
|
||||
"lint": "tslint -p .",
|
||||
"test:ci": "rm -rf ./dist && node ./tests/e2e/generate-e2e.js && jest --no-cache",
|
||||
"test": "jest --no-cache",
|
||||
"prepublishOnly": "yarn run build:ts:prod && yarn run build:sass:prod"
|
||||
},
|
||||
"dependencies": {
|
||||
"closest": "^0.0.1",
|
||||
"lodash": "^4.17.5",
|
||||
"pathfinding": "^0.4.18",
|
||||
"paths-js": "^0.4.7",
|
||||
"react": "^16.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^3.3.15",
|
||||
"@storybook/addon-options": "^3.3.15",
|
||||
"@storybook/addon-storyshots": "^3.3.15",
|
||||
"@storybook/addons": "^3.3.15",
|
||||
"@storybook/react": "^3.3.15",
|
||||
"@storybook/storybook-deployer": "^2.3.0",
|
||||
"@types/jest": "^22.2.0",
|
||||
"@types/lodash": "^4.14.104",
|
||||
"@types/node": "^9.4.7",
|
||||
"@types/promise": "^7.1.30",
|
||||
"@types/puppeteer": "^1.1.0",
|
||||
"@types/react": "^16.0.40",
|
||||
"awesome-typescript-loader": "^4.0.1",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"cross-env": "^5.1.4",
|
||||
"css-loader": "^0.28.10",
|
||||
"dagre": "^0.8.2",
|
||||
"enzyme": "^3.3.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"glob": "^7.1.2",
|
||||
"jest": "^22.4.2",
|
||||
"jest-cli": "^22.4.2",
|
||||
"json-beautify": "^1.0.1",
|
||||
"node-sass": "^4.7.2",
|
||||
"prettier": "^1.11.1",
|
||||
"puppeteer": "^1.1.1",
|
||||
"raf": "^3.4.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-syntax-highlighter": "^7.0.2",
|
||||
"react-test-renderer": "^16.2.0",
|
||||
"sass-loader": "^6.0.7",
|
||||
"source-map-loader": "^0.2.3",
|
||||
"storybook-host": "^4.1.5",
|
||||
"storybook-readme": "^3.2.1",
|
||||
"style-loader": "^0.20.3",
|
||||
"ts-jest": "^22.4.1",
|
||||
"tslint": "^5.9.1",
|
||||
"ts-loader": "^4.1.0",
|
||||
"typescript": "^2.7.2",
|
||||
"uglifyjs-webpack-plugin": "^1.2.3",
|
||||
"val-loader": "^1.1.0",
|
||||
"webpack": "^4.1.1",
|
||||
"webpack-cli": "^2.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import {BaseEntity, BaseListener} from "./BaseEntity";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export abstract class AbstractInstanceFactory<T extends BaseEntity<BaseListener>>{
|
||||
|
||||
className: string;
|
||||
|
||||
constructor(className: string){
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
getName(){
|
||||
return this.className;
|
||||
}
|
||||
|
||||
abstract getInstance(initialConfig?:any): T;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import {Toolkit} from "./Toolkit";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class BaseListener{
|
||||
lockChanged?(entity: BaseEntity<BaseListener>,locked: boolean): void;
|
||||
}
|
||||
|
||||
export class BaseEntity<T extends BaseListener>{
|
||||
|
||||
public listeners:{[s: string]: T};
|
||||
public id: string;
|
||||
public locked: boolean;
|
||||
|
||||
constructor(id?:string){
|
||||
this.listeners = {};
|
||||
this.id = id || Toolkit.UID();
|
||||
this.locked = false;
|
||||
}
|
||||
|
||||
getID(){
|
||||
return this.id;
|
||||
}
|
||||
|
||||
clearListeners(){
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
public deSerialize(data){
|
||||
this.id = data.id;
|
||||
}
|
||||
|
||||
public serialize(){
|
||||
return {
|
||||
id: this.id,
|
||||
}
|
||||
}
|
||||
|
||||
public iterateListeners(cb: (t: T) => any){
|
||||
|
||||
for (var i in this.listeners){
|
||||
cb(this.listeners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public removeListener(listener: string){
|
||||
if (this.listeners[listener]){
|
||||
delete this.listeners[listener];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public addListener(listener: T): string{
|
||||
var uid = Toolkit.UID();
|
||||
this.listeners[uid] = listener;
|
||||
return uid;
|
||||
}
|
||||
|
||||
public isLocked(): boolean{
|
||||
return this.locked;
|
||||
}
|
||||
|
||||
public setLocked(locked: boolean = true){
|
||||
this.locked = locked;
|
||||
this.iterateListeners((listener) => {
|
||||
if (listener.lockChanged){
|
||||
listener.lockChanged(this, locked);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import {DiagramModel} from "./DiagramModel";
|
||||
import {DiagramEngine} from "./DiagramEngine";
|
||||
import {NodeModel, PointModel} from "./Common";
|
||||
import {SelectionModel} from "./widgets/DiagramWidget";
|
||||
|
||||
export class BaseAction {
|
||||
mouseX: number;
|
||||
mouseY: number;
|
||||
ms: number;
|
||||
|
||||
constructor(mouseX: number, mouseY: number) {
|
||||
this.mouseX = mouseX;
|
||||
this.mouseY = mouseY;
|
||||
this.ms = (new Date()).getTime();
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectingAction extends BaseAction {
|
||||
mouseX2: number;
|
||||
mouseY2: number;
|
||||
|
||||
constructor(mouseX: number, mouseY: number) {
|
||||
super(mouseX, mouseY);
|
||||
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();
|
||||
|
||||
x = diagramModel.getGridPosition(x);
|
||||
y = diagramModel.getGridPosition(y);
|
||||
|
||||
return (
|
||||
(x + diagramModel.getOffsetX()) * z > dimensions.left &&
|
||||
(x + diagramModel.getOffsetX()) * z < dimensions.right &&
|
||||
(y + diagramModel.getOffsetY()) * z > dimensions.top &&
|
||||
(y + diagramModel.getOffsetY()) * z < dimensions.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveCanvasAction extends BaseAction {
|
||||
initialOffsetX: number;
|
||||
initialOffsetY: number;
|
||||
|
||||
constructor(mouseX: number, mouseY: number, diagramModel: DiagramModel) {
|
||||
super(mouseX, mouseY);
|
||||
this.initialOffsetX = diagramModel.getOffsetX();
|
||||
this.initialOffsetY = diagramModel.getOffsetY();
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveItemsAction extends BaseAction {
|
||||
selectionModels: SelectionModel[];
|
||||
moved: boolean;
|
||||
|
||||
constructor(mouseX: number, mouseY: number, diagramEngine: DiagramEngine) {
|
||||
super(mouseX, mouseY);
|
||||
this.moved = false;
|
||||
diagramEngine.enableRepaintEntities(diagramEngine.getDiagramModel().getSelectedItems());
|
||||
var selectedItems = diagramEngine.getDiagramModel().getSelectedItems();
|
||||
|
||||
//dont allow items which are locked to move
|
||||
selectedItems = selectedItems.filter((item) => {
|
||||
return !diagramEngine.isModelLocked(item);
|
||||
})
|
||||
|
||||
this.selectionModels = selectedItems.map((item: PointModel | NodeModel) => {
|
||||
return {
|
||||
model: item,
|
||||
initialX: item.x,
|
||||
initialY: item.y,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
379
src/Common.ts
379
src/Common.ts
@@ -1,379 +0,0 @@
|
||||
import {BaseEntity, BaseListener} from "./BaseEntity";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export interface BaseModelListener extends BaseListener{
|
||||
|
||||
selectionChanged?(item: BaseModel<BaseModelListener>, isSelected:boolean): void;
|
||||
|
||||
entityRemoved?(item:any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class BaseModel<T extends BaseModelListener> extends BaseEntity<BaseModelListener>{
|
||||
|
||||
selected: boolean;
|
||||
|
||||
constructor(id?:string){
|
||||
super(id);
|
||||
this.selected = false;
|
||||
}
|
||||
|
||||
deSerialize(ob){
|
||||
super.deSerialize(ob);
|
||||
this.selected = ob.selected;
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
_class: this.constructor.name,
|
||||
selected: this.selected
|
||||
});
|
||||
}
|
||||
|
||||
public getID(): string{
|
||||
return this.id
|
||||
}
|
||||
|
||||
public isSelected(): boolean{
|
||||
return this.selected;
|
||||
}
|
||||
|
||||
public setSelected(selected: boolean = true){
|
||||
this.selected = selected;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.selectionChanged){
|
||||
listener.selectionChanged(this, selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
remove(){
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.entityRemoved){
|
||||
listener.entityRemoved(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class PointModel extends BaseModel<BaseModelListener>{
|
||||
|
||||
x:number;
|
||||
y:number;
|
||||
link: LinkModel;
|
||||
|
||||
constructor(link: LinkModel,points: {x:number,y: number}){
|
||||
super();
|
||||
this.x = points.x;
|
||||
this.y = points.y;
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
deSerialize(ob){
|
||||
super.deSerialize(ob);
|
||||
this.x = ob.x;
|
||||
this.y = ob.y;
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
x: this.x,
|
||||
y: this.y
|
||||
});
|
||||
}
|
||||
|
||||
remove(){
|
||||
super.remove();
|
||||
|
||||
//clear references
|
||||
if (this.link){
|
||||
this.link.removePoint(this);
|
||||
}
|
||||
}
|
||||
|
||||
updateLocation(points: {x:number,y: number}){
|
||||
this.x = points.x;
|
||||
this.y = points.y;
|
||||
}
|
||||
|
||||
getX():number{
|
||||
return this.x;
|
||||
}
|
||||
|
||||
getY():number{
|
||||
return this.y;
|
||||
}
|
||||
|
||||
getLink(): LinkModel{
|
||||
return this.link;
|
||||
}
|
||||
}
|
||||
|
||||
export interface LinkModelListener extends BaseModelListener{
|
||||
|
||||
sourcePortChanged?(item:LinkModel,target: null|PortModel): void;
|
||||
|
||||
targetPortChanged?(item:LinkModel,target: null|PortModel): void;
|
||||
}
|
||||
|
||||
export class LinkModel extends BaseModel<LinkModelListener>{
|
||||
|
||||
linkType: string;
|
||||
sourcePort: PortModel|null;
|
||||
targetPort: PortModel|null;
|
||||
points: PointModel[];
|
||||
extras: {};
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
this.linkType = 'default';
|
||||
this.points = [
|
||||
new PointModel(this,{x: 0,y: 0}),
|
||||
new PointModel(this,{x: 0,y: 0}),
|
||||
];
|
||||
this.extras = {};
|
||||
this.sourcePort = null;
|
||||
this.targetPort = null;
|
||||
}
|
||||
|
||||
deSerialize(ob){
|
||||
super.deSerialize(ob);
|
||||
this.linkType = ob.type;
|
||||
this.extras = ob.extras;
|
||||
this.points = _.map(ob.points,(point: {x,y}) => {
|
||||
var p = new PointModel(this, {x: point.x,y:point.y});
|
||||
p.deSerialize(point);
|
||||
return p;
|
||||
})
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
type: this.linkType,
|
||||
source: this.sourcePort ? this.sourcePort.getParent().id:null,
|
||||
sourcePort: this.sourcePort ? this.sourcePort.id:null,
|
||||
target: this.targetPort ? this.targetPort.getParent().id:null,
|
||||
targetPort: this.targetPort ? this.targetPort.id:null,
|
||||
points: _.map(this.points,(point) => {
|
||||
return point.serialize();
|
||||
}),
|
||||
extras: this.extras
|
||||
});
|
||||
}
|
||||
|
||||
remove(){
|
||||
super.remove();
|
||||
if (this.sourcePort){
|
||||
this.sourcePort.removeLink(this);
|
||||
}
|
||||
if (this.targetPort){
|
||||
this.targetPort.removeLink(this);
|
||||
}
|
||||
}
|
||||
|
||||
isLastPoint(point: PointModel){
|
||||
var index = this.getPointIndex(point);
|
||||
return index === this.points.length-1;
|
||||
}
|
||||
|
||||
getPointIndex(point: PointModel){
|
||||
return this.points.indexOf(point);
|
||||
}
|
||||
|
||||
getPointModel(id: string): PointModel|null{
|
||||
for (var i = 0; i < this.points.length;i++){
|
||||
if (this.points[i].id === id){
|
||||
return this.points[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getFirstPoint(): PointModel{
|
||||
return this.points[0];
|
||||
}
|
||||
|
||||
getLastPoint(): PointModel{
|
||||
return this.points[this.points.length-1];
|
||||
}
|
||||
|
||||
setSourcePort(port: PortModel){
|
||||
port.addLink(this);
|
||||
this.sourcePort = port;
|
||||
this.iterateListeners((listener: LinkModelListener) => {
|
||||
listener.sourcePortChanged && listener.sourcePortChanged(this, port);
|
||||
});
|
||||
}
|
||||
|
||||
getSourcePort(): PortModel{
|
||||
return this.sourcePort;
|
||||
}
|
||||
|
||||
getTargetPort(): PortModel{
|
||||
return this.targetPort;
|
||||
}
|
||||
|
||||
setTargetPort(port: PortModel){
|
||||
port.addLink(this);
|
||||
this.targetPort = port;
|
||||
this.iterateListeners((listener: LinkModelListener) => {
|
||||
listener.targetPortChanged && listener.targetPortChanged(this, port);
|
||||
});
|
||||
}
|
||||
|
||||
getPoints(): PointModel[]{
|
||||
return this.points;
|
||||
}
|
||||
|
||||
setPoints(points: PointModel[]){
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
removePoint(pointModel: PointModel){
|
||||
this.points.splice(this.getPointIndex(pointModel),1);
|
||||
}
|
||||
|
||||
addPoint(pointModel:PointModel,index = 1){
|
||||
this.points.splice(index,0,pointModel);
|
||||
}
|
||||
|
||||
getType(): string{
|
||||
return this.linkType;
|
||||
}
|
||||
}
|
||||
|
||||
export class PortModel extends BaseModel<BaseModelListener>{
|
||||
name: string;
|
||||
parentNode: NodeModel;
|
||||
links: {[id: string]: LinkModel};
|
||||
|
||||
deSerialize(ob){
|
||||
super.deSerialize(ob);
|
||||
this.name = ob.name;
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
name: this.name,
|
||||
parentNode: this.parentNode.id,
|
||||
links: _.map(this.links,(link) => {
|
||||
return link.id;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
constructor(name: string, id?:string){
|
||||
super(id);
|
||||
this.name = name;
|
||||
this.links = {};
|
||||
this.parentNode = null;
|
||||
}
|
||||
|
||||
getName(): string{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getParent(): NodeModel{
|
||||
return this.parentNode;
|
||||
}
|
||||
|
||||
setParentNode(node: NodeModel){
|
||||
this.parentNode = node;
|
||||
}
|
||||
|
||||
removeLink(link: LinkModel){
|
||||
delete this.links[link.getID()];
|
||||
}
|
||||
|
||||
addLink(link: LinkModel){
|
||||
this.links[link.getID()] = link;
|
||||
}
|
||||
|
||||
getLinks(): {[id: string]: LinkModel}{
|
||||
return this.links;
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeModel extends BaseModel<BaseModelListener>{
|
||||
|
||||
nodeType: string;
|
||||
x: number;
|
||||
y: number;
|
||||
extras: {};
|
||||
ports: {[s: string]:PortModel};
|
||||
|
||||
constructor(nodeType: string = 'default', id?:string){
|
||||
super(id);
|
||||
this.nodeType = nodeType;
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.extras = {};
|
||||
this.ports = {};
|
||||
}
|
||||
|
||||
deSerialize(ob){
|
||||
super.deSerialize(ob);
|
||||
this.nodeType = ob.type;
|
||||
this.x = ob.x;
|
||||
this.y = ob.y;
|
||||
this.extras = ob.extras;
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
type: this.nodeType,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
extras: this.extras,
|
||||
ports: _.map(this.ports,(port) => {
|
||||
return port.serialize()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
remove(){
|
||||
super.remove();
|
||||
for (var i in this.ports){
|
||||
_.forEach(this.ports[i].getLinks(),(link) => {
|
||||
link.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getPortFromID(id): PortModel | null{
|
||||
for (var i in this.ports){
|
||||
if (this.ports[i].id === id){
|
||||
return this.ports[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getPort(name: string): PortModel | null{
|
||||
return this.ports[name];
|
||||
}
|
||||
|
||||
getPorts(): {[s: string]:PortModel}{
|
||||
return this.ports;
|
||||
}
|
||||
|
||||
removePort(port: PortModel){
|
||||
//clear the parent node reference
|
||||
if(this.ports[port.name]){
|
||||
this.ports[port.name].setParentNode(null);
|
||||
delete this.ports[port.name];
|
||||
}
|
||||
}
|
||||
|
||||
addPort(port: PortModel): PortModel{
|
||||
port.setParentNode(this);
|
||||
this.ports[port.name] = port;
|
||||
return port;
|
||||
}
|
||||
|
||||
getType(): string{
|
||||
return this.nodeType;
|
||||
}
|
||||
}
|
||||
@@ -1,210 +1,41 @@
|
||||
import {NodeWidgetFactory, LinkWidgetFactory} from "./WidgetFactories";
|
||||
import {LinkModel, NodeModel, BaseModel,BaseModelListener, PortModel, PointModel} from "./Common";
|
||||
import {BaseEntity, BaseListener} from "./BaseEntity";
|
||||
import {DiagramModel} from "./DiagramModel";
|
||||
import {AbstractInstanceFactory} from "./AbstractInstanceFactory";
|
||||
import * as _ from "lodash";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export interface DiagramEngineListener extends BaseListener{
|
||||
|
||||
nodeFactoriesUpdated?(): void;
|
||||
|
||||
linkFactoriesUpdated?(): void;
|
||||
|
||||
repaintCanvas?(): void;
|
||||
}
|
||||
import { DiagramModel } from "./models/DiagramModel";
|
||||
import { CanvasEngine } from "@projectstorm/react-canvas";
|
||||
import { DefaultLabelFactory, DefaultLinkFactory, DefaultNodeFactory, DefaultPortFactory } from "storm-react-diagrams";
|
||||
|
||||
/**
|
||||
* Passed as a parameter to the DiagramWidget
|
||||
*/
|
||||
export class DiagramEngine extends BaseEntity<DiagramEngineListener>{
|
||||
|
||||
nodeFactories: {[s: string]:NodeWidgetFactory};
|
||||
linkFactories: {[s: string]:LinkWidgetFactory};
|
||||
instanceFactories: {[s: string]: AbstractInstanceFactory<BaseEntity<BaseListener>>};
|
||||
|
||||
diagramModel: DiagramModel;
|
||||
canvas: Element;
|
||||
export class DiagramEngine extends CanvasEngine<DiagramModel> {
|
||||
paintableWidgets: {};
|
||||
|
||||
constructor(){
|
||||
linksThatHaveInitiallyRendered: {};
|
||||
maxNumberPointsPerLink: number;
|
||||
smartRouting: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.diagramModel = new DiagramModel();
|
||||
this.nodeFactories = {};
|
||||
this.linkFactories = {};
|
||||
this.instanceFactories = {};
|
||||
this.canvas = null;
|
||||
this.paintableWidgets = null;
|
||||
}
|
||||
|
||||
repaintCanvas(){
|
||||
this.iterateListeners((listener) => {
|
||||
listener.repaintCanvas && listener.repaintCanvas();
|
||||
})
|
||||
}
|
||||
|
||||
clearRepaintEntities(){
|
||||
this.paintableWidgets = null;
|
||||
}
|
||||
|
||||
enableRepaintEntities(entities: BaseModel<BaseModelListener>[]){
|
||||
this.paintableWidgets = {};
|
||||
entities.forEach((entity) => {
|
||||
|
||||
//if a node is requested to repaint, add all of its links
|
||||
if (entity instanceof NodeModel){
|
||||
_.forEach(entity.getPorts(),(port) => {
|
||||
_.forEach(port.getLinks(),(link) => {
|
||||
this.paintableWidgets[link.getID()] = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (entity instanceof PointModel){
|
||||
this.paintableWidgets[entity.getLink().getID()] = true;
|
||||
}
|
||||
|
||||
this.paintableWidgets[entity.getID()] = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a model is locked by running through
|
||||
* its parents to see if they are locked first
|
||||
*/
|
||||
isModelLocked(model: BaseEntity<BaseListener>){
|
||||
|
||||
//always check the diagram model
|
||||
if (this.diagramModel.isLocked()){
|
||||
return true;
|
||||
}
|
||||
|
||||
//a point is locked, if its model is locked
|
||||
if (model instanceof PointModel){
|
||||
if (model.getLink().isLocked()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return model.isLocked();
|
||||
}
|
||||
|
||||
canEntityRepaint(baseModel: BaseModel<BaseModelListener>){
|
||||
//no rules applied, allow repaint
|
||||
if(this.paintableWidgets === null){
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.paintableWidgets[baseModel.getID()] !== undefined;
|
||||
}
|
||||
|
||||
setCanvas(canvas: Element| null){
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
setDiagramModel(model: DiagramModel){
|
||||
this.diagramModel = model;
|
||||
}
|
||||
|
||||
getDiagramModel(): DiagramModel{
|
||||
return this.diagramModel;
|
||||
}
|
||||
|
||||
getNodeFactories(): {[s: string]:NodeWidgetFactory}{
|
||||
return this.nodeFactories;
|
||||
}
|
||||
|
||||
getLinkFactories(): {[s: string]:LinkWidgetFactory}{
|
||||
return this.linkFactories;
|
||||
}
|
||||
|
||||
getInstanceFactory(className: string): AbstractInstanceFactory<BaseEntity<BaseListener>>{
|
||||
return this.instanceFactories[className];
|
||||
}
|
||||
|
||||
registerInstanceFactory(factory: AbstractInstanceFactory<BaseEntity<BaseListener>>){
|
||||
this.instanceFactories[factory.getName()] = factory;
|
||||
}
|
||||
|
||||
registerNodeFactory(factory: NodeWidgetFactory){
|
||||
this.nodeFactories[factory.getType()] = factory;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.nodeFactoriesUpdated) listener.nodeFactoriesUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
registerLinkFactory(factory: LinkWidgetFactory){
|
||||
this.linkFactories[factory.getType()] = factory;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.linkFactoriesUpdated) listener.linkFactoriesUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
getFactoryForNode(node: NodeModel): NodeWidgetFactory | null{
|
||||
if (this.nodeFactories[node.getType()]){
|
||||
return this.nodeFactories[node.getType()];
|
||||
}
|
||||
console.log("cannot find widget factory for node of type: [" + node.getType()+"]");
|
||||
return null;
|
||||
}
|
||||
|
||||
getFactoryForLink(link: LinkModel): LinkWidgetFactory | null{
|
||||
if (this.linkFactories[link.getType()]){
|
||||
return this.linkFactories[link.getType()];
|
||||
}
|
||||
console.log("cannot find widget factory for link of type: [" + link.getType()+"]");
|
||||
return null;
|
||||
}
|
||||
|
||||
generateWidgetForLink(link: LinkModel): JSX.Element|null{
|
||||
var linkFactory = this.getFactoryForLink(link);
|
||||
if(!linkFactory){
|
||||
throw "Cannot find link factory for link: " + link.getType();
|
||||
}
|
||||
return linkFactory.generateReactWidget(this,link);
|
||||
}
|
||||
|
||||
generateWidgetForNode(node: NodeModel): JSX.Element|null{
|
||||
var nodeFactory = this.getFactoryForNode(node);
|
||||
if(!nodeFactory){
|
||||
throw "Cannot find widget factory for node: " + node.getType();
|
||||
}
|
||||
return nodeFactory.generateReactWidget(this,node);
|
||||
}
|
||||
|
||||
getRelativeMousePoint(event): {x:number,y:number}{
|
||||
var point = this.getRelativePoint(event.clientX,event.clientY);
|
||||
return {
|
||||
x: (point.x / (this.diagramModel.getZoomLevel() / 100.0)) - this.diagramModel.getOffsetX(),
|
||||
y: (point.y / (this.diagramModel.getZoomLevel() / 100.0)) - this.diagramModel.getOffsetY()
|
||||
};
|
||||
this.linksThatHaveInitiallyRendered = {};
|
||||
this.smartRouting = false;
|
||||
}
|
||||
|
||||
getRelativePoint(x,y){
|
||||
var canvasRect = this.canvas.getBoundingClientRect();
|
||||
return {x: x-canvasRect.left,y:y-canvasRect.top};
|
||||
installDefaults() {
|
||||
super.installDefaults();
|
||||
this.registerElementFactory(new DefaultLabelFactory());
|
||||
this.registerElementFactory(new DefaultLinkFactory());
|
||||
this.registerElementFactory(new DefaultNodeFactory());
|
||||
this.registerElementFactory(new DefaultPortFactory());
|
||||
}
|
||||
|
||||
getNodePortElement(port: PortModel): any{
|
||||
var selector = this.canvas.querySelector('.port[data-name="' + port.getName() + '"][data-nodeid="' + port.getParent().getID()+'"]');
|
||||
if(selector === null){
|
||||
throw "Cannot find Node Port element with nodeID: [" + port.getParent().getID()+"] and name: ["+port.getName()+"]";
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
getPortCenter(port: PortModel){
|
||||
var sourceElement = this.getNodePortElement(port);
|
||||
var sourceRect = sourceElement.getBoundingClientRect();
|
||||
|
||||
var rel = this.getRelativePoint(sourceRect.left,sourceRect.top);
|
||||
|
||||
return {
|
||||
x: ((sourceElement.offsetWidth / 2) + rel.x / (this.diagramModel.getZoomLevel() / 100.0)) - this.diagramModel.getOffsetX(),
|
||||
y: ((sourceElement.offsetHeight/2)+rel.y/(this.diagramModel.getZoomLevel()/100.0)) - this.diagramModel.getOffsetY()
|
||||
};
|
||||
getMaxNumberPointsPerLink(): number {
|
||||
return this.maxNumberPointsPerLink;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setMaxNumberPointsPerLink(max: number) {
|
||||
this.maxNumberPointsPerLink = max;
|
||||
}
|
||||
|
||||
isSmartRoutingEnabled() {
|
||||
return this.smartRouting;
|
||||
}
|
||||
|
||||
setSmartRoutingStatus(status: boolean) {
|
||||
this.smartRouting = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
import {LinkModel, NodeModel, BaseModel,BaseModelListener, PortModel} from "./Common";
|
||||
import {BaseListener, BaseEntity} from "./BaseEntity";
|
||||
import * as _ from "lodash";
|
||||
import {DiagramEngine} from "./DiagramEngine";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*
|
||||
*/
|
||||
export interface DiagramListener extends BaseListener{
|
||||
|
||||
nodesUpdated?(node: any, isCreated:boolean): void;
|
||||
|
||||
linksUpdated?(link: any, isCreated:boolean): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
controlsUpdated?(): void;
|
||||
|
||||
offsetUpdated?(model: DiagramModel,offsetX: number, offsetY: number): void;
|
||||
|
||||
zoomUpdated?(model: DiagramModel,zoom: number): void;
|
||||
|
||||
gridUpdated?(model: DiagramModel,size: number): void;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export class DiagramModel extends BaseEntity<DiagramListener>{
|
||||
|
||||
//models
|
||||
links: {[s:string] : LinkModel};
|
||||
nodes: {[s:string] : NodeModel};
|
||||
|
||||
//control variables
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
zoom: number;
|
||||
rendered: boolean;
|
||||
gridSize: number;
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
|
||||
this.links = {};
|
||||
this.nodes = {};
|
||||
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
this.zoom = 100;
|
||||
this.rendered = false;
|
||||
this.gridSize = 0;
|
||||
}
|
||||
|
||||
setGridSize(size: number = 0){
|
||||
this.gridSize = size;
|
||||
this.iterateListeners((listener) => {
|
||||
listener.gridUpdated && listener.gridUpdated(this, size);
|
||||
})
|
||||
}
|
||||
|
||||
getGridPosition(pos){
|
||||
if(this.gridSize === 0){
|
||||
return pos;
|
||||
}
|
||||
return this.gridSize * Math.floor(pos/this.gridSize);
|
||||
}
|
||||
|
||||
deSerializeDiagram(object: any, diagramEngine: DiagramEngine){
|
||||
this.deSerialize(object);
|
||||
|
||||
this.offsetX = object.offsetX;
|
||||
this.offsetY = object.offsetY;
|
||||
this.zoom = object.zoom;
|
||||
|
||||
|
||||
//deserialize nodes
|
||||
_.forEach(object.nodes,(node) => {
|
||||
let nodeOb = diagramEngine.getInstanceFactory(node._class).getInstance(node) as NodeModel;
|
||||
nodeOb.deSerialize(node);
|
||||
//deserialize ports
|
||||
_.forEach(node.ports,(port) => {
|
||||
let portOb = diagramEngine.getInstanceFactory(port._class).getInstance() as PortModel;
|
||||
portOb.deSerialize(port);
|
||||
nodeOb.addPort(portOb);
|
||||
});
|
||||
|
||||
this.addNode(nodeOb);
|
||||
});
|
||||
|
||||
_.forEach(object.links,(link) => {
|
||||
let linkOb = diagramEngine.getInstanceFactory(link._class).getInstance() as LinkModel;
|
||||
linkOb.deSerialize(link);
|
||||
|
||||
if(link.target){
|
||||
linkOb.setTargetPort(this.getNode(link.target).getPortFromID(link.targetPort));
|
||||
}
|
||||
|
||||
if(link.source){
|
||||
linkOb.setSourcePort(this.getNode(link.source).getPortFromID(link.sourcePort))
|
||||
}
|
||||
|
||||
this.addLink(linkOb);
|
||||
});
|
||||
}
|
||||
|
||||
serializeDiagram(){
|
||||
return _.merge(this.serialize(),{
|
||||
offsetX: this.offsetX,
|
||||
offsetY: this.offsetY,
|
||||
zoom: this.zoom,
|
||||
links: _.map(this.links,(link) => {
|
||||
return link.serialize();
|
||||
}),
|
||||
nodes: _.map(this.nodes,(link) => {
|
||||
return link.serialize();
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
clearSelection(ignore: BaseModel<BaseModelListener>|null = null){
|
||||
_.forEach(this.getSelectedItems(),(element) => {
|
||||
if (ignore && ignore.getID() === element.getID()){
|
||||
return;
|
||||
}
|
||||
element.setSelected(false); //TODO dont fire the listener
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedItems(): BaseModel<BaseModelListener>[]{
|
||||
var items = [];
|
||||
|
||||
//find all nodes
|
||||
items = items.concat(_.filter(this.nodes,(node) => {
|
||||
return node.isSelected();
|
||||
}));
|
||||
|
||||
//find all points
|
||||
items = items.concat(_.filter(_.flatMap(this.links,(node) => {
|
||||
return node.points;
|
||||
}),(port) => {
|
||||
return port.isSelected();
|
||||
}));
|
||||
|
||||
//find all links
|
||||
return items.concat(_.filter(this.links,(link) => {
|
||||
return link.isSelected();
|
||||
}));
|
||||
}
|
||||
|
||||
setZoomLevel(zoom:number){
|
||||
this.zoom = zoom;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.controlsUpdated) listener.controlsUpdated();
|
||||
});
|
||||
this.iterateListeners((listener) => {
|
||||
listener.zoomUpdated && listener.zoomUpdated(this,this.zoom);
|
||||
});
|
||||
}
|
||||
|
||||
setOffset(offsetX: number, offsetY: number){
|
||||
this.offsetX = offsetX;
|
||||
this.offsetY = offsetY;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.controlsUpdated) listener.controlsUpdated();
|
||||
});
|
||||
this.iterateListeners((listener) => {
|
||||
listener.offsetUpdated && listener.offsetUpdated(this,this.offsetX, this.offsetY)
|
||||
});
|
||||
}
|
||||
|
||||
setOffsetX(offsetX: number){
|
||||
this.offsetX = offsetX;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.controlsUpdated) listener.controlsUpdated();
|
||||
});
|
||||
this.iterateListeners((listener) => {
|
||||
listener.offsetUpdated && listener.offsetUpdated(this,this.offsetX, this.offsetY)
|
||||
});
|
||||
}
|
||||
setOffsetY(offsetY: number){
|
||||
this.offsetX = offsetY;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.controlsUpdated) listener.controlsUpdated();
|
||||
});
|
||||
this.iterateListeners((listener) => {
|
||||
listener.offsetUpdated && listener.offsetUpdated(this,this.offsetX, this.offsetY)
|
||||
});
|
||||
}
|
||||
|
||||
getOffsetY(){
|
||||
return this.offsetY;
|
||||
}
|
||||
|
||||
getOffsetX(){
|
||||
return this.offsetX;
|
||||
}
|
||||
|
||||
getZoomLevel(){
|
||||
return this.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];
|
||||
}
|
||||
|
||||
addLink(link: LinkModel): LinkModel{
|
||||
link.addListener({
|
||||
entityRemoved: () => {
|
||||
this.removeLink(link);
|
||||
}
|
||||
});
|
||||
this.links[link.getID()] = link;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.linksUpdated) listener.linksUpdated(link, true);
|
||||
});
|
||||
return link
|
||||
}
|
||||
|
||||
addNode(node: NodeModel): NodeModel{
|
||||
node.addListener({
|
||||
entityRemoved: () => {
|
||||
this.removeNode(node);
|
||||
}
|
||||
});
|
||||
this.nodes[node.getID()] = node;
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.nodesUpdated) listener.nodesUpdated(node, true);
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
removeLink(link: LinkModel | string){
|
||||
if (link instanceof LinkModel){
|
||||
delete this.links[link.getID()];
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.linksUpdated) listener.linksUpdated(link, false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
delete this.links[''+link];
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.linksUpdated) listener.linksUpdated(link, false);
|
||||
});
|
||||
}
|
||||
removeNode(node: NodeModel | string){
|
||||
if (node instanceof NodeModel){
|
||||
delete this.nodes[node.getID()];
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.nodesUpdated) listener.nodesUpdated(node, false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.nodes[''+node];
|
||||
this.iterateListeners((listener) => {
|
||||
if(listener.nodesUpdated) listener.nodesUpdated(node, false);
|
||||
});
|
||||
}
|
||||
|
||||
getLinks():{[s:string] : LinkModel} {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
getNodes(): {[s: string]: NodeModel}{
|
||||
return this.nodes;
|
||||
}
|
||||
}
|
||||
3
src/Function.d.ts
vendored
3
src/Function.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
interface Function{
|
||||
name: string
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import {LinkModel} from "./Common";
|
||||
import * as _ from "lodash";
|
||||
import {AbstractInstanceFactory} from "./AbstractInstanceFactory";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class LinkInstanceFactory extends AbstractInstanceFactory<LinkModel>{
|
||||
|
||||
constructor(){
|
||||
super("LinkModel");
|
||||
}
|
||||
|
||||
getInstance(){
|
||||
return new LinkModel();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,45 @@
|
||||
// tslint:disable no-bitwise
|
||||
import closest = require("closest");
|
||||
import { PointModel } from "./models/PointModel";
|
||||
import { ROUTING_SCALING_FACTOR } from "./routing/PathFinding";
|
||||
import * as Path from "paths-js/path";
|
||||
import { Toolkit as TK } from "@projectstorm/react-canvas";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class Toolkit {
|
||||
/**
|
||||
* Generats a unique ID (thanks Stack overflow :3)
|
||||
* @returns {String}
|
||||
*/
|
||||
public static UID():string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
export class Toolkit extends TK {
|
||||
/**
|
||||
* Finds the closest element as a polyfill
|
||||
*
|
||||
* @param {Element} element [description]
|
||||
* @param {string} selector [description]
|
||||
*/
|
||||
public static closest(element: Element,selector:string){
|
||||
if(document.body.closest){
|
||||
public static closest(element: Element, selector: string) {
|
||||
if (document.body.closest) {
|
||||
return element.closest(selector);
|
||||
}
|
||||
return closest(element,selector);
|
||||
return closest(element, selector);
|
||||
}
|
||||
|
||||
public static generateLinePath(firstPoint: PointModel, lastPoint: PointModel): string {
|
||||
return `M${firstPoint.x},${firstPoint.y} L ${lastPoint.x},${lastPoint.y}`;
|
||||
}
|
||||
|
||||
public static generateCurvePath(firstPoint: PointModel, lastPoint: PointModel, curvy: number = 0): string {
|
||||
var isHorizontal = Math.abs(firstPoint.x - lastPoint.x) > Math.abs(firstPoint.y - lastPoint.y);
|
||||
var curvyX = isHorizontal ? curvy : 0;
|
||||
var curvyY = isHorizontal ? 0 : curvy;
|
||||
|
||||
return `M${firstPoint.x},${firstPoint.y} C ${firstPoint.x + curvyX},${firstPoint.y + curvyY}
|
||||
${lastPoint.x - curvyX},${lastPoint.y - curvyY} ${lastPoint.x},${lastPoint.y}`;
|
||||
}
|
||||
|
||||
public static generateDynamicPath(pathCoords: number[][]) {
|
||||
let path = Path();
|
||||
path = path.moveto(pathCoords[0][0] * ROUTING_SCALING_FACTOR, pathCoords[0][1] * ROUTING_SCALING_FACTOR);
|
||||
pathCoords.slice(1).forEach(coords => {
|
||||
path = path.lineto(coords[0] * ROUTING_SCALING_FACTOR, coords[1] * ROUTING_SCALING_FACTOR);
|
||||
});
|
||||
return path.print();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import {NodeModel, LinkModel} from "./Common";
|
||||
import {DiagramEngine} from "./DiagramEngine";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export abstract class WidgetFactory{
|
||||
|
||||
type: string;
|
||||
|
||||
constructor(name: string){
|
||||
this.type = name;
|
||||
}
|
||||
|
||||
getType(): string{
|
||||
return this.type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class NodeWidgetFactory extends WidgetFactory{
|
||||
|
||||
|
||||
abstract generateReactWidget(diagramEngine:DiagramEngine,node: NodeModel): JSX.Element;
|
||||
|
||||
}
|
||||
|
||||
export abstract class LinkWidgetFactory extends WidgetFactory{
|
||||
|
||||
abstract generateReactWidget(diagramEngine:DiagramEngine,link: LinkModel): JSX.Element;
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import {LinkWidgetFactory} from "../WidgetFactories";
|
||||
import {LinkModel} from "../Common";
|
||||
import * as React from "react";
|
||||
import {DefaultLinkWidget} from "./DefaultLinkWidget";
|
||||
import {DiagramEngine} from "../DiagramEngine";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultLinkFactory extends LinkWidgetFactory{
|
||||
|
||||
constructor(){
|
||||
super("default");
|
||||
}
|
||||
|
||||
generateReactWidget(diagramEngine: DiagramEngine,link: LinkModel): JSX.Element{
|
||||
return React.createElement(DefaultLinkWidget,{
|
||||
link: link,
|
||||
diagramEngine: diagramEngine
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
import * as React from "react";
|
||||
import {LinkModel, PointModel} from "../Common";
|
||||
import * as _ from "lodash";
|
||||
import {DiagramEngine} from "../DiagramEngine";
|
||||
|
||||
export interface DefaultLinkProps {
|
||||
color?: string;
|
||||
width?: number;
|
||||
link: LinkModel;
|
||||
smooth?: boolean;
|
||||
diagramEngine: DiagramEngine;
|
||||
pointAdded?: (point: PointModel,event) => any
|
||||
}
|
||||
|
||||
export interface DefaultLinkState {
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultLinkWidget extends React.Component<DefaultLinkProps, DefaultLinkState> {
|
||||
|
||||
public static defaultProps: DefaultLinkProps = {
|
||||
color: 'black',
|
||||
width: 3,
|
||||
link:null,
|
||||
engine: null,
|
||||
smooth: false,
|
||||
diagramEngine: null
|
||||
};
|
||||
|
||||
constructor(props: DefaultLinkProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selected: false
|
||||
}
|
||||
}
|
||||
|
||||
generatePoint(pointIndex: number): JSX.Element{
|
||||
|
||||
let x = this.props.diagramEngine.getDiagramModel().getGridPosition(this.props.link.points[pointIndex].x);
|
||||
let y = this.props.diagramEngine.getDiagramModel().getGridPosition(this.props.link.points[pointIndex].y);
|
||||
|
||||
return React.DOM.g({key:'point-'+this.props.link.points[pointIndex].id},
|
||||
React.DOM.circle({
|
||||
className: 'point pointui' + (this.props.link.points[pointIndex].isSelected()?' selected':''),
|
||||
cx:x,
|
||||
cy:y,
|
||||
r:5,
|
||||
}),
|
||||
React.DOM.circle({
|
||||
className:'point',
|
||||
'data-linkid':this.props.link.id,
|
||||
'data-id':this.props.link.points[pointIndex].id,
|
||||
cx: x,
|
||||
cy: y,
|
||||
r:15,
|
||||
opacity: 0,
|
||||
onMouseLeave:() => {
|
||||
this.setState({selected: false});
|
||||
},
|
||||
onMouseEnter:() => {
|
||||
this.setState({selected: true});
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
generateLink(extraProps: {id: number}): JSX.Element{
|
||||
var Bottom = React.DOM.path(_.merge({
|
||||
className: (this.state.selected || this.props.link.isSelected())?'selected':'',
|
||||
strokeWidth:this.props.width,
|
||||
stroke: this.props.color
|
||||
},extraProps));
|
||||
|
||||
var Top = React.DOM.path(_.merge({
|
||||
strokeLinecap:'round' as 'round',
|
||||
onMouseLeave:() => {
|
||||
this.setState({selected: false});
|
||||
},
|
||||
onMouseEnter:() => {
|
||||
this.setState({selected: true});
|
||||
},
|
||||
'data-linkid': this.props.link.getID(),
|
||||
stroke: this.props.color,
|
||||
strokeOpacity:this.state.selected?0.1:0 ,
|
||||
strokeWidth: 20,
|
||||
onContextMenu: (event) => {
|
||||
event.preventDefault();
|
||||
this.props.link.remove();
|
||||
},
|
||||
},extraProps));
|
||||
|
||||
return React.DOM.g({key:'link-'+extraProps.id},
|
||||
Bottom,Top
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
//ensure id is present for all points on the path
|
||||
var points = this.props.link.points;
|
||||
var paths = [];
|
||||
let model = this.props.diagramEngine.getDiagramModel();
|
||||
|
||||
//draw the smoothing
|
||||
if(points.length === 2){
|
||||
|
||||
//if the points are too close, just draw a straight line
|
||||
var margin = 50;
|
||||
if(Math.abs(points[0].x-points[1].x) < 50){
|
||||
margin = 5;
|
||||
}
|
||||
|
||||
var pointLeft = points[0];
|
||||
var pointRight = points[1];
|
||||
|
||||
//some defensive programming to make sure the smoothing is
|
||||
//always in the right direction
|
||||
if(pointLeft.x > pointRight.x){
|
||||
pointLeft = points[1];
|
||||
pointRight = points[0];
|
||||
}
|
||||
|
||||
paths.push(this.generateLink({
|
||||
id: 0,
|
||||
onMouseDown: (event) =>{
|
||||
if (!event.shiftKey && !this.props.diagramEngine.isModelLocked(this.props.link)){
|
||||
var point = new PointModel(this.props.link,this.props.diagramEngine.getRelativeMousePoint(event));
|
||||
point.setSelected(true);
|
||||
this.forceUpdate();
|
||||
this.props.link.addPoint(point,1);
|
||||
this.props.pointAdded(point,event);
|
||||
}
|
||||
},
|
||||
d:
|
||||
" M"+pointLeft.x+" "+pointLeft.y
|
||||
+" C"+(pointLeft.x+margin)+" "+pointLeft.y
|
||||
+" " +(pointRight.x-margin)+" "+pointRight.y
|
||||
+" " +pointRight.x+" "+pointRight.y
|
||||
}));
|
||||
if (this.props.link.targetPort === null){
|
||||
paths.push(this.generatePoint(1));
|
||||
}
|
||||
}
|
||||
|
||||
//draw the multiple anchors and complex line instead
|
||||
else{
|
||||
var ds = [];
|
||||
if(this.props.smooth){
|
||||
ds.push(" M"+points[0].x+" "+points[0].y+" C "+(points[0].x+50)+" "+points[0].y+" "+points[1].x+" "+points[1].y+" "+points[1].x+" "+points[1].y);
|
||||
for(var i = 1;i < points.length-2;i++){
|
||||
ds.push(" M "+points[i].x+" "+points[i].y+" L "+points[i+1].x+" "+points[i+1].y);
|
||||
}
|
||||
ds.push(" M"+points[i].x+" "+points[i].y+" C "+points[i].x+" "+points[i].y+" "+(points[i+1].x-50)+" "+points[i+1].y+" "+points[i+1].x+" "+points[i+1].y);
|
||||
}else{
|
||||
var ds = [];
|
||||
for(var i = 0;i < points.length-1;i++){
|
||||
if(i === 0){
|
||||
ds.push(" M "+points[i].x+" "+points[i].y+" L "+model.getGridPosition(points[i+1].x)+" "+model.getGridPosition(points[i+1].y));
|
||||
}else if(i === points.length-1){
|
||||
ds.push(" M "+model.getGridPosition(points[i].x)+" "+model.getGridPosition(points[i].y)+" L "+model.getGridPosition(points[i+1].x)+" "+model.getGridPosition(points[i+1].y));
|
||||
}else{
|
||||
ds.push(" M "+model.getGridPosition(points[i].x)+" "+model.getGridPosition(points[i].y)+" L "+points[i+1].x+" "+points[i+1].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths = ds.map((data,index) => {
|
||||
return this.generateLink({
|
||||
id:index,
|
||||
'data-linkid':this.props.link.id,
|
||||
'data-point':index,
|
||||
onMouseDown: (event: MouseEvent) => {
|
||||
if (!event.shiftKey){
|
||||
var point = new PointModel(this.props.link,this.props.diagramEngine.getRelativeMousePoint(event));
|
||||
point.setSelected(true);
|
||||
this.forceUpdate();
|
||||
this.props.link.addPoint(point,index+1);
|
||||
this.props.pointAdded(point,event);
|
||||
}
|
||||
},
|
||||
d:data
|
||||
});
|
||||
});
|
||||
|
||||
//render the circles
|
||||
for(var i = 1;i < points.length-1;i++){
|
||||
paths.push(this.generatePoint(i));
|
||||
}
|
||||
|
||||
if (this.props.link.targetPort === null){
|
||||
paths.push(this.generatePoint(points.length-1));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
React.DOM.g(null,paths)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import {NodeWidgetFactory} from "../WidgetFactories";
|
||||
import {DefaultNodeModel} from "./DefaultNodeModel";
|
||||
import * as React from "react";
|
||||
import {DefaultNodeWidget} from "./DefaultNodeWidget";
|
||||
import {DiagramEngine} from "../DiagramEngine";
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultNodeFactory extends NodeWidgetFactory{
|
||||
|
||||
constructor(){
|
||||
super("default");
|
||||
}
|
||||
|
||||
generateReactWidget(diagramEngine:DiagramEngine,node: DefaultNodeModel): JSX.Element{
|
||||
return React.createElement(DefaultNodeWidget,{
|
||||
node: node,
|
||||
diagramEngine: diagramEngine
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import {NodeModel} from "../Common";
|
||||
import {DefaultPortModel} from "./DefaultPortModel";
|
||||
import * as _ from "lodash";
|
||||
|
||||
import {AbstractInstanceFactory} from "../AbstractInstanceFactory";
|
||||
|
||||
export class DefaultNodeInstanceFactory extends AbstractInstanceFactory<DefaultNodeModel>{
|
||||
|
||||
constructor(){
|
||||
super("DefaultNodeModel");
|
||||
}
|
||||
|
||||
getInstance(){
|
||||
return new DefaultNodeModel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultNodeModel extends NodeModel{
|
||||
|
||||
name: string;
|
||||
color: string;
|
||||
ports: {[s: string]:DefaultPortModel};
|
||||
|
||||
constructor(name: string = 'Untitled',color: string = 'rgb(0,192,255)'){
|
||||
super("default");
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
deSerialize(object){
|
||||
super.deSerialize(object);
|
||||
this.name = object.name;
|
||||
this.color = object.color;
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
name: this.name,
|
||||
color: this.color,
|
||||
});
|
||||
}
|
||||
|
||||
getInPorts(): DefaultPortModel[]{
|
||||
return _.filter(this.ports,(portModel) => {
|
||||
return portModel.in;
|
||||
});
|
||||
}
|
||||
|
||||
getOutPorts(): DefaultPortModel[]{
|
||||
return _.filter(this.ports,(portModel) => {
|
||||
return !portModel.in;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as _ from "lodash";
|
||||
var div = React.DOM.div;
|
||||
import {DefaultNodeModel} from "./DefaultNodeModel";
|
||||
import {DefaultPortLabel} from "./DefaultPortLabelWidget";
|
||||
import {DiagramEngine} from "../DiagramEngine";
|
||||
|
||||
export interface DefaultNodeProps {
|
||||
node: DefaultNodeModel;
|
||||
diagramEngine: DiagramEngine;
|
||||
}
|
||||
|
||||
export interface DefaultNodeState {
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultNodeWidget extends React.Component<DefaultNodeProps, DefaultNodeState> {
|
||||
|
||||
constructor(props: DefaultNodeProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
generatePort(port){
|
||||
return <DefaultPortLabel model={port} key={port.id} />
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="basic-node" style={{background: this.props.node.color}}>
|
||||
<div className="title">
|
||||
<div className="name">{this.props.node.name}</div>
|
||||
<div className="fa fa-close" onClick={() => {
|
||||
this.props.node.remove();
|
||||
this.props.diagramEngine.repaintCanvas();
|
||||
}} />
|
||||
</div>
|
||||
<div className="ports">
|
||||
<div className="in">
|
||||
{ _.map(this.props.node.getInPorts(),this.generatePort.bind(this)) }
|
||||
</div>
|
||||
<div className="out">
|
||||
{ _.map(this.props.node.getOutPorts(),this.generatePort.bind(this)) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import * as React from "react";
|
||||
import {DefaultPortModel} from "./DefaultPortModel";
|
||||
import {PortWidget} from "../widgets/PortWidget";
|
||||
|
||||
export interface DefaultPortLabelProps {
|
||||
model?: DefaultPortModel;
|
||||
in?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface DefaultPortLabelState {
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultPortLabel extends React.Component<DefaultPortLabelProps, DefaultPortLabelState> {
|
||||
|
||||
public static defaultProps: DefaultPortLabelProps = {
|
||||
in: true,
|
||||
label: "port"
|
||||
};
|
||||
|
||||
render() {
|
||||
var port = <PortWidget node={this.props.model.getParent()} name={this.props.model.name} />
|
||||
var label = <div className="name">{this.props.model.label}</div>
|
||||
|
||||
return (
|
||||
<div className={(this.props.model.in?'in':'out') + '-port'}>
|
||||
{ this.props.model.in?port:label }
|
||||
{ this.props.model.in?label:port }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import {PortModel} from "../Common";
|
||||
import * as _ from "lodash";
|
||||
import {AbstractInstanceFactory} from "../AbstractInstanceFactory";
|
||||
|
||||
export class DefaultPortInstanceFactory extends AbstractInstanceFactory<DefaultPortModel>{
|
||||
|
||||
constructor(){
|
||||
super("DefaultPortModel");
|
||||
}
|
||||
|
||||
getInstance(){
|
||||
return new DefaultPortModel(true,"unknown");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dylan Vorster
|
||||
*/
|
||||
export class DefaultPortModel extends PortModel{
|
||||
in: boolean;
|
||||
label: string;
|
||||
|
||||
constructor(isInput:boolean,name: string,label: string = null, id?: string){
|
||||
super(name, id);
|
||||
this.in = isInput;
|
||||
this.label = label || name;
|
||||
}
|
||||
|
||||
deSerialize(object){
|
||||
super.deSerialize(object);
|
||||
this.in = object.in;
|
||||
this.label = object.label;
|
||||
}
|
||||
|
||||
serialize(){
|
||||
return _.merge(super.serialize(),{
|
||||
in: this.in,
|
||||
label: this.label,
|
||||
});
|
||||
}
|
||||
}
|
||||
19
src/defaults/factories/DefaultLabelFactory.tsx
Normal file
19
src/defaults/factories/DefaultLabelFactory.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { DiagramEngine } from "../../DiagramEngine";
|
||||
import { DefaultLabelModel } from "../models/DefaultLabelModel";
|
||||
import { DefaultLabelWidget } from "../widgets/DefaultLabelWidget";
|
||||
import { AbstractElementFactory } from "@projectstorm/react-canvas";
|
||||
|
||||
export class DefaultLabelFactory extends AbstractElementFactory<DefaultLabelModel> {
|
||||
constructor() {
|
||||
super("default");
|
||||
}
|
||||
|
||||
generateWidget(engine: DiagramEngine, model: DefaultLabelModel): JSX.Element {
|
||||
return <DefaultLabelWidget model={model} />;
|
||||
}
|
||||
|
||||
generateModel(): DefaultLabelModel {
|
||||
return new DefaultLabelModel();
|
||||
}
|
||||
}
|
||||
33
src/defaults/factories/DefaultLinkFactory.tsx
Normal file
33
src/defaults/factories/DefaultLinkFactory.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as React from "react";
|
||||
import { DefaultLinkWidget } from "../widgets/DefaultLinkWidget";
|
||||
import { DiagramEngine } from "../../DiagramEngine";
|
||||
import { AbstractElementFactory } from "@projectstorm/react-canvas";
|
||||
import { DefaultLinkModel } from "../models/DefaultLinkModel";
|
||||
|
||||
export class DefaultLinkFactory extends AbstractElementFactory<DefaultLinkModel> {
|
||||
constructor() {
|
||||
super("default");
|
||||
}
|
||||
|
||||
generateWidget(engine: DiagramEngine, model: DefaultLinkModel): JSX.Element {
|
||||
return React.createElement(DefaultLinkWidget, {
|
||||
link: model,
|
||||
diagramEngine: engine
|
||||
});
|
||||
}
|
||||
|
||||
generateModel(): DefaultLinkModel {
|
||||
return new DefaultLinkModel();
|
||||
}
|
||||
|
||||
generateLinkSegment(model: DefaultLinkModel, widget: DefaultLinkWidget, selected: boolean, path: string) {
|
||||
return (
|
||||
<path
|
||||
className={selected ? widget.bem("--path-selected") : ""}
|
||||
strokeWidth={model.getWidth()}
|
||||
stroke={model.getColor()}
|
||||
d={path}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user