Angular 4 based Spring-Flo
21
.gitignore
vendored
@@ -1,9 +1,16 @@
|
||||
node/
|
||||
bower_components/
|
||||
dist/
|
||||
node_modules/
|
||||
lib/
|
||||
target/
|
||||
out-tsc/
|
||||
debug.log
|
||||
npm-debug.log
|
||||
src/**/*.js
|
||||
!src/demo/systemjs.config.js
|
||||
!src/demo/systemjs.config.lib.js
|
||||
!**/*systemjs-angular-loader.js
|
||||
*.js.map
|
||||
e2e/**/*.js
|
||||
e2e/**/*.js.map
|
||||
.DS_STORE
|
||||
**/.DS_STORE
|
||||
*.iml
|
||||
.idea/
|
||||
.project
|
||||
.DS_Store
|
||||
spring-flo.iml
|
||||
|
||||
30
.jshintrc
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"esnext": true,
|
||||
"bitwise": true,
|
||||
"camelcase": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"indent": 2,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"quotmark": "single",
|
||||
"regexp": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"strict": true,
|
||||
"trailing": true,
|
||||
"smarttabs": true,
|
||||
"globals": {
|
||||
"angular": false,
|
||||
"requirejs": true,
|
||||
"define": true,
|
||||
"XRegExp": true,
|
||||
"moment": true,
|
||||
"$": true,
|
||||
"_": true
|
||||
}
|
||||
}
|
||||
29
.travis.yml
@@ -1,14 +1,19 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6.10'
|
||||
before_script:
|
||||
- 'npm install -g bower grunt-cli'
|
||||
- 'bower install'
|
||||
script: grunt
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- aclement@pivotal.io
|
||||
- aboyko@pivotal.io
|
||||
- sanandan@pivotal.io
|
||||
on_failure: always
|
||||
- "6"
|
||||
os:
|
||||
- linux
|
||||
before_install:
|
||||
# Use a virtual display.
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
# Install latest chrome.
|
||||
- export CHROME_BIN=chromium-browser
|
||||
install:
|
||||
- npm install
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test:once
|
||||
- npm run integration
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
http://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
http://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
||||
@@ -1,32 +0,0 @@
|
||||
= Contributing to Spring Flo
|
||||
|
||||
Spring Flo is released under the Apache 2.0 license. If you would like to contribute
|
||||
something, or simply want to hack on the code this document should help you get started.
|
||||
|
||||
|
||||
|
||||
== Code of Conduct
|
||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of
|
||||
conduct]. By participating, you are expected to uphold this code. Please report
|
||||
unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
|
||||
|
||||
== Using GitHub issues
|
||||
We use GitHub issues to track bugs and enhancements - or you can also raise an issue simply
|
||||
to ask questions.
|
||||
|
||||
If you are reporting a bug, please help to speed up problem diagnosis by providing as much
|
||||
information as possible.
|
||||
|
||||
|
||||
|
||||
== Sign the Contributor License Agreement
|
||||
Before we accept a non-trivial patch or pull request we will need you to sign the
|
||||
https://support.springsource.com/spring_committer_signup[contributor's agreement].
|
||||
Signing the contributor's agreement does not grant anyone commit rights to the main
|
||||
repository, but it does mean that we can accept your contributions, and you will get an
|
||||
author credit if we do. Active contributors might be asked to join the core team, and
|
||||
given the ability to merge pull requests. Use '`Andy Clement`' in the
|
||||
project lead field when you complete the form.
|
||||
|
||||
@@ -14,12 +14,7 @@ Refer to the https://github.com/spring-projects/spring-flo/wiki[wiki] for more i
|
||||
|
||||
## Build
|
||||
|
||||
Spring Flo is built using Grunt. Grunt commands can be run directly or indirectly through maven. Simplest way to build is via `mvn clean package` to build the project and run the tests. Build results are produced under the __dist__ folder. Contents of the folder are
|
||||
|
||||
* __spring-flo.js__ that contains all JS code from various JS dev modules (produced by development build)
|
||||
* __spring-flo.min.js__ a minified version of __flo.js__ (produced by production build)
|
||||
* __spring-flo.css__ concatenated CSS file for Flo Editor
|
||||
* __spring-flo.min.css__ minified version of the CSS above
|
||||
Spring Flo is built using NPM commands. Simplest way to build is via `npm run build` to build the project and run the tests (Prerequisite for th build is `npm install` executed before the build command is). Build results are produced under the __dist__ folder.
|
||||
|
||||
## Getting Help
|
||||
|
||||
@@ -27,7 +22,7 @@ If you have any questions, issues, feedback or feature request please https://gi
|
||||
|
||||
## Samples
|
||||
|
||||
A small self contained sample usage of Spring Flo is now available in the https://github.com/spring-projects/spring-flo/tree/master/samples/spring-flo-sample[samples] sub folder. It includes a README that describes running it and where you might want to customize it. The https://github.com/spring-cloud/spring-cloud-dataflow-ui[Spring Cloud Data Flow UI] at github shows a larger scale usage of Spring Flo.
|
||||
A small self contained sample usage of Spring Flo is available in the https://github.com/spring-projects/spring-flo/tree/master/src/demo[demo] sub folder. It includes a README that describes running it and where you might want to customize it. The https://github.com/spring-cloud/spring-cloud-dataflow-ui[Spring Cloud Data Flow UI] at github shows a larger scale usage of Spring Flo.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
49
bower.json
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"name": "spring-flo",
|
||||
"description": "Angular based embeddable graphical component for pipeline/graph editing",
|
||||
"main": [
|
||||
"dist/spring-flo.css",
|
||||
"dist/spring-flo.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"requirejs": "2.1.22",
|
||||
"angular": "1.3.5",
|
||||
"bootstrap": "3.3.4",
|
||||
"backbone": "1.3.3",
|
||||
"joint": "1.0.3",
|
||||
"codemirror": "5.18.2",
|
||||
"jshint": "2.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.3.5",
|
||||
"angular-scenario": "1.3.5",
|
||||
"dagre": "0.7.4",
|
||||
"requirejs-text": "2.0.14"
|
||||
},
|
||||
"authors": [
|
||||
"Andy Clement <aclement@pivotal.io>",
|
||||
"Alex Boyko <aboyko@pivotal.io"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/spring-projects/spring-flo.git"
|
||||
},
|
||||
"homepage": "https://github.com/spring-projects/spring-flo",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"tests",
|
||||
"karma.conf.js",
|
||||
"CODE_OF_CONDUCT.adoc",
|
||||
"CONTRIBUTING.adoc",
|
||||
"package.json",
|
||||
"pom.xml",
|
||||
"docs",
|
||||
"bower_components",
|
||||
"gruntfile.js",
|
||||
"css",
|
||||
"src",
|
||||
"samples"
|
||||
]
|
||||
}
|
||||
373
css/flo.css
@@ -1,373 +0,0 @@
|
||||
.flo-view {
|
||||
width:100%;
|
||||
height:100%;
|
||||
margin: 0;
|
||||
background-color: #eeeeee;
|
||||
font-family: "Varela Round",sans-serif;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
border: 1px solid;
|
||||
border-color: #6db33f;
|
||||
border-radius: 2px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
/* Canvas contains the palette on the left and the paper on the right */
|
||||
|
||||
.paper {
|
||||
padding: 0px;
|
||||
background-color: #ffffff;
|
||||
/* height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
*//* margin-left: 400px; */
|
||||
}
|
||||
|
||||
#sidebar-resizer {
|
||||
background-color: #34302d;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6px;
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
#palette-container {
|
||||
background-color: #EEE;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#paper-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
/* Joint JS paper for drawing palette -> canvas DnD visual feedback START */
|
||||
|
||||
#palette-floater {
|
||||
/* TODO size relative to paper that goes on it? */
|
||||
width:170px;
|
||||
height:60px;
|
||||
opacity: 0.75;
|
||||
/*
|
||||
background-color: #6db33f;
|
||||
*/
|
||||
float:left;
|
||||
position: absolute;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Joint JS paper for drawing palette -> canvas DnD visual feedback END */
|
||||
|
||||
/* Palette START */
|
||||
|
||||
.palette-filter {
|
||||
border: 3px solid #6db33f;
|
||||
}
|
||||
|
||||
.palette-filter-textfield {
|
||||
width: 100%;
|
||||
font-size:24px;
|
||||
/* border: 3px solid #6db33f;
|
||||
*/ font-family: "Varela Round",sans-serif;
|
||||
/* padding: 2px; */
|
||||
}
|
||||
|
||||
.palette-paper {
|
||||
background-color: #eeeeee;
|
||||
/*
|
||||
border-right: 7px solid;
|
||||
*/
|
||||
border-color: #6db33f;
|
||||
/* width: 170px;
|
||||
height:100%;
|
||||
float: left;
|
||||
*/
|
||||
}
|
||||
|
||||
/* Palette END */
|
||||
|
||||
/* Tooltip START */
|
||||
|
||||
.node-tooltip .tooltip-description {
|
||||
margin-top: 5px;
|
||||
margin-left: 0px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.node-tooltip {
|
||||
display:none;
|
||||
position:absolute;
|
||||
border:1px solid #333;
|
||||
background-color:#34302d;/*#161616;*/
|
||||
border-radius:5px;
|
||||
padding:5px;
|
||||
color:#fff;
|
||||
/* font-size:12px Arial;*/
|
||||
font-family: "Varela Round",sans-serif;
|
||||
font-size: 19px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.tooltip-title-type {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tooltip-title-group {
|
||||
padding-left: 5px;
|
||||
font-size: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.node-tooltip-option-name {
|
||||
font-family: monospace;/*"Varela Round",sans-serif;*/
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
padding-right: 20px;
|
||||
|
||||
}
|
||||
|
||||
.node-tooltip-option-description {
|
||||
font-family: "Varela Round",sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Tooltip END */
|
||||
|
||||
/* Properties DIV Start */
|
||||
|
||||
.properties td {
|
||||
border-top: 1px solid #34302d;
|
||||
}
|
||||
|
||||
.properties {
|
||||
/* opacity: 0.5; */
|
||||
border: 8px #eeeeee;
|
||||
/* border-radius: 2px;*/
|
||||
/* border: 2px solid;
|
||||
*/ border-color: #6db33f;
|
||||
margin-top: 3px;
|
||||
background-color: #eeeeee;
|
||||
/* height: 115px;
|
||||
*/ font-family: monospace;
|
||||
z-index: 2;
|
||||
/* padding-top:1px; */
|
||||
position: absolute;
|
||||
/* left: 850px;
|
||||
top: 386px;
|
||||
width:360px;
|
||||
height:0px;
|
||||
overflow-y:auto;
|
||||
*/}
|
||||
.properties-node-name {
|
||||
|
||||
width: 100%;
|
||||
/* background-color: #eeeeee; */
|
||||
background: #34302d;
|
||||
color: #ffffff;
|
||||
padding-left:2px;
|
||||
border:0px;
|
||||
font-size: 18px;
|
||||
font-family: "Varela Round",sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
.properties-node-name-row {
|
||||
/*
|
||||
background: #34302d;
|
||||
color: #ffffff;
|
||||
*/background: #34302d;
|
||||
width: 100%;
|
||||
padding-left:2px;
|
||||
}
|
||||
|
||||
.properties-row-even {
|
||||
width: 100%;
|
||||
border-top: 1px #34302d;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
|
||||
.properties-row-odd {
|
||||
width: 100%;
|
||||
border-top: 1px #34302d;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.properties-row-text-even {
|
||||
background-color: #ffffff;
|
||||
border-left:0px;
|
||||
border-right:0px;
|
||||
border-bottom:0px;
|
||||
border-top:1px #34302d;
|
||||
}
|
||||
|
||||
.properties-row-text-odd {
|
||||
background-color: #eeeeee;
|
||||
border-left:0px;
|
||||
border-right:0px;
|
||||
border-bottom:0px;
|
||||
border-top:1px #34302d;
|
||||
}
|
||||
|
||||
.properties-input {
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
font-family: "Varela Round",sans-serif;
|
||||
}
|
||||
|
||||
.properties-key {
|
||||
width: 30%;
|
||||
padding-left:2px;
|
||||
padding-right:4px;
|
||||
}
|
||||
|
||||
.properties-value {
|
||||
width: 70%;
|
||||
padding-left:2px;
|
||||
padding-right:2px;
|
||||
}
|
||||
|
||||
.properties-table {
|
||||
border: 1px solid #d1d1d1;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.properties-new-property {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
/* Properties DIV END */
|
||||
|
||||
/* Validation Error Marker on Canvas START */
|
||||
|
||||
.error-tooltip p {
|
||||
margin-top: 5px;
|
||||
margin-left: 0px;
|
||||
margin-bottom: 5px;
|
||||
color:#fff;
|
||||
}
|
||||
.error-tooltip {
|
||||
display:none;
|
||||
position:absolute;
|
||||
border:1px solid #333;
|
||||
background-color:red;/*#161616;*/
|
||||
border-radius:5px;
|
||||
padding:5px;
|
||||
color:#fff;
|
||||
/* font-size:12px Arial;*/
|
||||
font-family: "Varela Round",sans-serif;
|
||||
font-size: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Validation Error Marker on Canvas END */
|
||||
|
||||
/* Controls on Canvas START */
|
||||
|
||||
.canvas-controls-container {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.canvas-control {
|
||||
background: transparent;
|
||||
font-family: "Varela Round",sans-serif;
|
||||
font-size: 11px;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.zoom-canvas-control {
|
||||
border: 0px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.zoom-canvas-input {
|
||||
text-align: right;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.zoom-canvas-label {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/* Controls on Canvas END */
|
||||
|
||||
/* Code Mirror related styles START */
|
||||
|
||||
.CodeMirror {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
}
|
||||
.CodeMirror-hint {
|
||||
max-width: 38em;
|
||||
}
|
||||
.CodeMirror-vertical-ruler-error {
|
||||
background-color: rgba(188, 0, 0, 0.5);
|
||||
}
|
||||
.CodeMirror-vertical-ruler-warning {
|
||||
background-color: rgba(255, 188, 0, 0.5);
|
||||
}
|
||||
|
||||
|
||||
/* Code Mirror related styles END */
|
||||
|
||||
|
||||
/* START - FLO CANVAS STYLES - override joint js styles */
|
||||
|
||||
.highlighted {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.joint-element.highlighted rect {
|
||||
stroke: #34302d;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.joint-type-handle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.available-magnet {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.link {
|
||||
fill: none;
|
||||
stroke: #ccc;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.link-tools .tool-options {
|
||||
display: none; /* by default, we don't display link options tool */
|
||||
}
|
||||
|
||||
/* END - FLO CANVAS STYLES */
|
||||
|
||||
1276
dist/spring-flo.css
vendored
30274
dist/spring-flo.js
vendored
7
dist/spring-flo.min.css
vendored
118
dist/spring-flo.min.js
vendored
BIN
docs/Flo.png
|
Before Width: | Height: | Size: 124 KiB |
151
gruntfile.js
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Load grunt tasks automatically
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// Time how long tasks take. Can help when optimizing build times
|
||||
require('time-grunt')(grunt);
|
||||
|
||||
var LIB_DIR = './lib';
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
// Test settings
|
||||
karma: {
|
||||
options: {
|
||||
browsers: ['PhantomJS'],
|
||||
singleRun: true
|
||||
},
|
||||
unit: {
|
||||
configFile: 'karma.conf.js'
|
||||
}
|
||||
},
|
||||
|
||||
requirejs: {
|
||||
// global config
|
||||
options: {
|
||||
baseUrl: './src',
|
||||
name: 'app',
|
||||
exclude: [ 'joint', 'angular', 'jquery', 'bootstrap', 'underscore' ],
|
||||
// insertRequire: [ './app' ],
|
||||
mainConfigFile: './src/main.js'
|
||||
},
|
||||
production: {
|
||||
// overwrites the default config above
|
||||
options: {
|
||||
out: './dist/spring-flo.min.js'
|
||||
}
|
||||
},
|
||||
development: {
|
||||
// overwrites the default config above
|
||||
options: {
|
||||
out: './dist/spring-flo.js',
|
||||
optimize: 'none' // no minification
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Empties folders to start fresh
|
||||
clean: {
|
||||
build: {
|
||||
files: [
|
||||
{
|
||||
dot: true,
|
||||
src: [ './dist']
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Make sure code styles are up to par and there are no obvious mistakes
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc',
|
||||
reporter: require('jshint-stylish')
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'./src/{,**/}*.js'
|
||||
],
|
||||
test: {
|
||||
options: {
|
||||
jshintrc: 'tests/.jshintrc'
|
||||
},
|
||||
src: ['./tests/spec/{,*/}*.js', './tests/resources/{,*/}*.js']
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
copy: {
|
||||
codemirror: {
|
||||
files: [
|
||||
{expand:true,
|
||||
src: ['codemirror/**'],
|
||||
cwd: 'bower_components',
|
||||
dest: LIB_DIR
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
concat: {
|
||||
css: {
|
||||
src: [
|
||||
'./bower_components/codemirror/lib/codemirror.css',
|
||||
'./bower_components/codemirror/lib/codemirror.css',
|
||||
'./bower_components/codemirror/addon/lint/lint.css',
|
||||
'./bower_components/codemirror/addon/hint/show-hint.css',
|
||||
'./bower_components/codemirror/addon/scroll/simplescrollbars.css',
|
||||
'./lib/joint/joint.css',
|
||||
'./css/flo.css'
|
||||
],
|
||||
dest: 'dist/spring-flo.css'
|
||||
}
|
||||
},
|
||||
|
||||
cssmin: {
|
||||
css:{
|
||||
src: 'dist/spring-flo.css',
|
||||
dest: 'dist/spring-flo.min.css'
|
||||
}
|
||||
},
|
||||
|
||||
// Set bower task's targetDir to use src/main/resources/static/bower_components
|
||||
bower: {
|
||||
options: {
|
||||
targetDir: LIB_DIR,
|
||||
cleanTargetDir: true
|
||||
},
|
||||
// Provide install target
|
||||
install: {}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
grunt.registerTask('css', ['concat:css', 'cssmin:css']);
|
||||
grunt.registerTask('dev', ['clean:build', 'bower:install', 'copy:codemirror', 'requirejs:development', 'concat:css' ]);
|
||||
grunt.registerTask('prod', ['clean:build', 'bower:install', 'copy:codemirror', 'requirejs:production', 'css' ]);
|
||||
grunt.registerTask('build', ['clean:build', 'jshint', 'bower:install', 'copy:codemirror', 'requirejs', 'css' ]);
|
||||
grunt.registerTask('test', ['karma']);
|
||||
grunt.registerTask('default', ['build', 'test']);
|
||||
|
||||
};
|
||||
133
karma.conf.js
@@ -1,64 +1,89 @@
|
||||
// Karma configuration
|
||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
'use strict';
|
||||
config.set({
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath: '',
|
||||
|
||||
// testing framework to use (jasmine/mocha/qunit/...)
|
||||
frameworks: ['jasmine', 'requirejs'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
{pattern: 'src/**/*.js', included: false},
|
||||
{pattern: 'lib/**/*.js', included: false},
|
||||
{pattern: 'tests/spec/**/*.js', included: false},
|
||||
{pattern: 'dist/**/*.js', included: false},
|
||||
{pattern: 'tests/resources/**/*', included: false},
|
||||
'tests/karma-test-main.js'
|
||||
],
|
||||
var libBase = 'src/lib/'; // transpiled app JS and map files
|
||||
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
plugins: [
|
||||
'karma-junit-reporter',
|
||||
'karma-chrome-launcher',
|
||||
'karma-firefox-launcher',
|
||||
'karma-jasmine',
|
||||
'karma-phantomjs-launcher',
|
||||
'karma-requirejs',
|
||||
'karma-jasmine-html-reporter',
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter')
|
||||
],
|
||||
|
||||
// reporters: [
|
||||
// 'html'
|
||||
// ],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
|
||||
client: {
|
||||
builtPaths: [libBase], // add more spec base paths as needed
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
|
||||
customLaunchers: {
|
||||
// From the CLI. Not used here but interesting
|
||||
// chrome setup for travis CI using chromium
|
||||
Chrome_travis_ci: {
|
||||
base: 'Chrome',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
|
||||
files: [
|
||||
// System.js for module loading
|
||||
'node_modules/systemjs/dist/system.src.js',
|
||||
|
||||
// Polyfills
|
||||
'node_modules/core-js/client/shim.js',
|
||||
|
||||
// zone.js
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||
'node_modules/zone.js/dist/proxy.js',
|
||||
'node_modules/zone.js/dist/sync-test.js',
|
||||
'node_modules/zone.js/dist/jasmine-patch.js',
|
||||
'node_modules/zone.js/dist/async-test.js',
|
||||
'node_modules/zone.js/dist/fake-async-test.js',
|
||||
|
||||
// RxJs
|
||||
{ pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false },
|
||||
{ pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false },
|
||||
|
||||
// Paths loaded via module imports:
|
||||
// Angular itself
|
||||
{ pattern: 'node_modules/@angular/**/*.js', included: false, watched: false },
|
||||
{ pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false },
|
||||
|
||||
{ pattern: 'src/demo/systemjs-angular-loader.js', included: false, watched: false },
|
||||
|
||||
'karma-test-shim.js', // optionally extend SystemJS mapping e.g., with barrels
|
||||
|
||||
// transpiled application & spec code paths loaded via module imports
|
||||
{ pattern: libBase + '**/*.js', included: false, watched: true },
|
||||
|
||||
// Asset (HTML & CSS) paths loaded via Angular's component compiler
|
||||
// (these paths need to be rewritten, see proxies section)
|
||||
{ pattern: libBase + '**/*.html', included: false, watched: true },
|
||||
{ pattern: libBase + '**/*.css', included: false, watched: true },
|
||||
|
||||
// Paths for debugging with source maps in dev tools
|
||||
{ pattern: libBase + '**/*.ts', included: false, watched: false },
|
||||
{ pattern: libBase + '**/*.js.map', included: false, watched: false }
|
||||
],
|
||||
|
||||
// Proxied base paths for loading assets
|
||||
proxies: {
|
||||
// required for modules fetched by SystemJS
|
||||
'/base/src/lib/node_modules/': '/base/node_modules/',
|
||||
'/base/src/lib/demo/': '/base/src/demo/'
|
||||
},
|
||||
|
||||
exclude: [],
|
||||
preprocessors: {},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
|
||||
// web server port
|
||||
port: 7070,
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['Firefox', 'Chrome'],
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
195
package.json
@@ -1,71 +1,148 @@
|
||||
{
|
||||
"name": "Spring-Flo",
|
||||
"name": "spring-flo",
|
||||
"version": "0.7.0",
|
||||
"description": "Library for quickly building text DSL visualization diagram editor",
|
||||
"main": "./dist/bundles/spring-flo.umd.js",
|
||||
"module": "./dist/spring-flo.es5.js",
|
||||
"es2015": "./dist/spring-flo.js",
|
||||
"typings": "./dist/spring-flo.d.ts",
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.6.0",
|
||||
"main": "dist/spring-flo",
|
||||
"files": [ "dist/*" ],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/spring-projects/spring-flo.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.9.0",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf out-tsc dist/*",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "node build.js",
|
||||
"build-demo": "tsc -p src/demo/",
|
||||
"build-demo:watch": "tsc -p src/demo/ -w",
|
||||
"serve": "lite-server -c=bs-config.json",
|
||||
"prestart": "npm run build-demo",
|
||||
"start": "concurrently \"npm run build-demo:watch\" \"npm run serve\"",
|
||||
"build-test": "tsc -p src/lib/tsconfig.spec.json",
|
||||
"build-test:watch": "tsc -p src/lib/tsconfig.spec.json -w",
|
||||
"pretest": "npm run build-test",
|
||||
"test": "concurrently \"npm run build-test:watch\" \"karma start karma.conf.js\"",
|
||||
"pretest:once": "npm run build-test",
|
||||
"test:once": "karma start karma.conf.js --single-run",
|
||||
"preintegration": "npm run build && cd integration && npm run clean && npm install",
|
||||
"integration": "npm run integration:aot && npm run integration:jit",
|
||||
"integration:jit": "cd integration && npm run e2e",
|
||||
"integration:aot": "cd integration && npm run e2e:aot",
|
||||
"lint": "tslint ./src/**/*.ts -t verbose",
|
||||
"release": "standard-version",
|
||||
|
||||
"postinstall": "postinstall-build --only-as-dependency dist \"npm run build\" && rm -rf ./node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"requirejs": "2.1.22",
|
||||
"angular": "1.3.5",
|
||||
"bootstrap": "3.3.4",
|
||||
"backbone": "1.3.3",
|
||||
"@angular/core": "^4.2.4",
|
||||
"@angular/forms": "^4.2.4",
|
||||
"@angular/platform-browser": "^4.2.4",
|
||||
"@types/codemirror": "0.0.45",
|
||||
"@types/jointjs": "^1.0.4",
|
||||
"@types/lodash": "^4.14.73",
|
||||
"codemirror": "^5.28.0",
|
||||
"jointjs": "1.0.3",
|
||||
"codemirror": "5.18.2",
|
||||
"jshint": "2.6.3"
|
||||
"lodash": "3.10.1",
|
||||
"ts-disposables": "^2.2.3",
|
||||
|
||||
"postinstall-build": "^5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": ">=4.0.0 <5.0.0 || >=4.0.0-beta <5.0.0",
|
||||
"@angular/animations": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bower": "1.8.0",
|
||||
"grunt": "1.0.1",
|
||||
"grunt-autoprefixer": "3.0.4",
|
||||
"grunt-bower-task": "0.4.0",
|
||||
"grunt-cli": "1.2.0",
|
||||
"grunt-concurrent": "2.3.1",
|
||||
"grunt-contrib-clean": "1.0.0",
|
||||
"grunt-contrib-concat": "1.0.1",
|
||||
"grunt-contrib-connect": "1.0.2",
|
||||
"grunt-contrib-copy": "1.0.0",
|
||||
"grunt-contrib-cssmin": "2.0.0",
|
||||
"grunt-contrib-htmlmin": "2.3.0",
|
||||
"grunt-contrib-imagemin": "1.0.1",
|
||||
"grunt-contrib-jshint": "1.1.0",
|
||||
"grunt-contrib-less": "1.4.1",
|
||||
"grunt-contrib-nodeunit": "1.0.0",
|
||||
"grunt-contrib-requirejs": "1.0.0",
|
||||
"grunt-contrib-uglify": "2.2.0",
|
||||
"grunt-contrib-watch": "1.0.0",
|
||||
"load-grunt-tasks": "3.5.2",
|
||||
"time-grunt": "1.4.0",
|
||||
"grunt-karma": "2.0.0",
|
||||
"grunt-newer": "1.2.0",
|
||||
"grunt-ng-annotate": "3.0.0",
|
||||
"grunt-protractor-runner": "5.0.0",
|
||||
"grunt-rev": "0.1.0",
|
||||
"grunt-usemin": "3.1.1",
|
||||
"jasmine-reporters": "2.2.1",
|
||||
"jshint-stylish": "2.2.1",
|
||||
"karma": "1.5.0",
|
||||
"karma-chrome-launcher": "2.0.0",
|
||||
"karma-coffee-preprocessor": "1.0.1",
|
||||
"karma-firefox-launcher": "1.0.1",
|
||||
"karma-html2js-preprocessor": "1.1.0",
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-jasmine-html-reporter": "0.2.2",
|
||||
"karma-junit-reporter": "1.2.0",
|
||||
"karma-ng-html2js-preprocessor": "1.0.0",
|
||||
"karma-phantomjs-launcher": "1.0.4",
|
||||
"karma-requirejs": "1.1.0",
|
||||
"karma-safari-launcher": "1.0.0",
|
||||
"karma-script-launcher": "1.0.0",
|
||||
"phantomjs-prebuilt": "2.1.14",
|
||||
"protractor": "5.1.1",
|
||||
"requirejs": "2.1.22",
|
||||
"time-grunt": "1.4.0"
|
||||
"@angular/common": "^4.2.4",
|
||||
"@angular/compiler": "^4.2.4",
|
||||
"@angular/compiler-cli": "4.1.3",
|
||||
"@angular/core": "^4.2.4",
|
||||
"@angular/platform-browser": "^4.2.4",
|
||||
"@angular/platform-browser-dynamic": "^4.2.4",
|
||||
"@angular/platform-server": "^4.2.4",
|
||||
"@types/jasmine": "2.5.36",
|
||||
"@types/node": "^6.0.46",
|
||||
"camelcase": "^4.0.0",
|
||||
"concurrently": "3.4.0",
|
||||
"core-js": "^2.4.1",
|
||||
"glob": "^7.1.1",
|
||||
"jasmine-core": "^2.5.2",
|
||||
"karma": "^1.5.0",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-cli": "^1.0.1",
|
||||
"karma-html-reporter": "^0.2.7",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"lite-server": "^2.2.2",
|
||||
"ngx-bootstrap": "^1.8.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "^0.48.2",
|
||||
"rollup-plugin-sourcemaps": "^0.4.2",
|
||||
"rollup-plugin-uglify": "^2.0.1",
|
||||
"rxjs": "^5.4.2",
|
||||
"standard-version": "^4.0.0",
|
||||
"systemjs": "^0.19.40",
|
||||
"tslint": "~5.3.2",
|
||||
"typescript": "~2.3.3",
|
||||
"zone.js": "^0.8.14",
|
||||
|
||||
"@angular/forms": "^4.2.4",
|
||||
"@angular/platform-browser": "^4.2.4",
|
||||
"@types/codemirror": "0.0.45",
|
||||
"@types/jointjs": "^1.0.4",
|
||||
"@types/lodash": "^4.14.73",
|
||||
"codemirror": "^5.28.0",
|
||||
"jointjs": "1.0.3",
|
||||
"lodash": "3.10.1",
|
||||
"ts-disposables": "^2.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
"buildDependencies": [
|
||||
"@angular/core",
|
||||
"@angular/forms",
|
||||
"@angular/platform-browser",
|
||||
"@types/codemirror",
|
||||
"@types/jointjs",
|
||||
"@types/lodash",
|
||||
"codemirror",
|
||||
"jointjs",
|
||||
"lodash",
|
||||
"ts-disposables",
|
||||
"@angular/common",
|
||||
"@angular/compiler",
|
||||
"@angular/compiler-cli",
|
||||
"@angular/platform-browser",
|
||||
"@angular/platform-browser-dynamic",
|
||||
"@angular/platform-server",
|
||||
"@types/jasmine",
|
||||
"@types/node",
|
||||
"camelcase",
|
||||
"concurrently",
|
||||
"core-js",
|
||||
"glob",
|
||||
"jasmine-core",
|
||||
"karma",
|
||||
"karma-chrome-launcher",
|
||||
"karma-cli",
|
||||
"karma-html-reporter",
|
||||
"karma-jasmine",
|
||||
"karma-jasmine-html-reporter",
|
||||
"lite-server",
|
||||
"ngx-bootstrap",
|
||||
"rimraf",
|
||||
"rollup",
|
||||
"rollup-plugin-sourcemaps",
|
||||
"rollup-plugin-uglify",
|
||||
"rxjs",
|
||||
"standard-version",
|
||||
"systemjs",
|
||||
"tslint",
|
||||
"typescript",
|
||||
"zone.js"
|
||||
]
|
||||
}
|
||||
|
||||
103
pom.xml
@@ -1,103 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework.flo</groupId>
|
||||
<artifactId>spring-flo</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Spring Flo</name>
|
||||
|
||||
<prerequisites>
|
||||
<maven>3.1.0</maven>
|
||||
</prerequisites>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>0.0.23</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<nodeVersion>v6.10.1</nodeVersion>
|
||||
<npmVersion>4.4.4</npmVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>grunt build</id>
|
||||
<goals>
|
||||
<goal>grunt</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>--no-color</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>2.6.1</version>
|
||||
<configuration>
|
||||
<filesets>
|
||||
<fileset>
|
||||
<directory>node_modules</directory>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>node</directory>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>node_modules</directory>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>bower_components</directory>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>.bower_cache</directory>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>flo/lib</directory>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,62 +0,0 @@
|
||||
# A sample app for spring-flo
|
||||
|
||||
This is a very basic example of spring-flo usage.
|
||||
|
||||
# Running the sample
|
||||
|
||||
A basic Spring Boot app is used to serve the sample. Launch it with:
|
||||
|
||||
mvn spring-boot:run
|
||||
|
||||
then open http://localhost:8080 then you can type text into the box
|
||||
(e.g. "filewatch > ftp") or drag elements from the palette and link them
|
||||
together to build a pipeline.
|
||||
|
||||
# Understanding the sample
|
||||
|
||||
Spring Flo allows simultaneous synchronization between some textual domain
|
||||
specific language (DSL) and the graphical representation. You can type in the
|
||||
text version or you can interactively build the graph version. Any custom
|
||||
usage of spring-flo therefore needs three things:
|
||||
|
||||
- a metamodel of the domain. What are the 'things' you are connecting in your
|
||||
pipeline and what are their properties?
|
||||
- a converter from the text form to the graph form.
|
||||
- a converter from the graph form to the text form.
|
||||
|
||||
The same here includes a very basic domain that you can easily customize:
|
||||
|
||||
- there are a handful of nodes with basic properties. These are defined in
|
||||
`metamodel-sample.json` - that is a JSON array of the modules supported.
|
||||
- the DSL form is `module --key=value --key=value > nextModule --key=value`.
|
||||
- the conversion from text to graph is done in `text-to-graph.js`
|
||||
- the conversion from graph to text is done in `graph-to-text.js`
|
||||
|
||||
Note the convertors (and the DSL) do not describe or support linking multiple nodes
|
||||
to a single node, but spring-flo supports that, you just need to enhance the
|
||||
convertors and have a representation in the DSL.
|
||||
|
||||
If you wanted to use this sample in your domain, perhaps you would enhance `index.html`
|
||||
to include some kind of 'execute' button that used the text representation to
|
||||
drive some other (perhaps backend) API.
|
||||
|
||||
The included `index.html` has basic buttons that exercise some of the control features
|
||||
of flo - for example turning off the palette and switching it to a read-only mode (for
|
||||
use when embedding flo into something that should just be showing a read only view of a
|
||||
pipeline).
|
||||
|
||||
# Navigating the sample
|
||||
|
||||
In order to keep the project to a single build file, spring-flo is referenced
|
||||
through the maven pom using webjars dependencies.
|
||||
|
||||
- `index.html` the single HTML file that embeds sping-flo
|
||||
- `main.js` the launch config file for requirejs, referenced from index.html
|
||||
- `sample-app.js` setup the angular app with custom services
|
||||
- `metamodel-service.js` the custom metamodel service for this usage of spring-flo
|
||||
- `editor-service.js` the custom editor service for this usage of spring-flo
|
||||
- `render-service.js` the custom render service for this usage of spring-flo
|
||||
- `metamodel-sample.json` a basic json description of the domain
|
||||
- `graph-to-text.js` convert from the graph form to our custom DSL text
|
||||
- `text-to-graph.js` convert from the custom DSL text to our graph form
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-flo-sample</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>spring-flo-sample</name>
|
||||
<description>Spring Flo Sample</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.3.2.RELEASE</version>
|
||||
<relativePath /> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<!-- TODO maybe adjust flos dependencies so this configuration isn't so complicated -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>codemirror</artifactId>
|
||||
<version>5.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>angular</artifactId>
|
||||
<version>1.3.8</version> <!-- flo wants 1.3.5 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jshint</artifactId>
|
||||
<version>2.8.0</version> <!-- flo wants 2.6.3 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs</artifactId>
|
||||
<version>2.1.18</version> <!-- flo wants 2.1.15 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jquery</artifactId>
|
||||
<version>2.2.0</version> <!-- jointjs wants 2.0.3 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>lodash</artifactId>
|
||||
<version>3.10.1</version> <!-- jointjs -> graphlib -> wants 3.10.1-amd -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>webjars-locator</artifactId>
|
||||
<version>0.31</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs-domready</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs-text</artifactId>
|
||||
<version>2.0.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jointjs</artifactId>
|
||||
<version>0.9.7</version> <!-- flo wants 0.9.6 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>json5</artifactId>
|
||||
<version>0.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>spring-flo</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>joint</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<start-class>org.springframework.flo.Application</start-class>
|
||||
<java.version>1.7</java.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.flo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
|
||||
.header {
|
||||
font-weight: 400;
|
||||
font-family: "Varela Round",sans-serif;
|
||||
font-size: 36px;
|
||||
color: #eeeeee;
|
||||
padding: 2px;
|
||||
background-color: #34302d;
|
||||
border: none;
|
||||
border-top: 4px solid #6db33f;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #eeeeee;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.header-small {
|
||||
font: 300 24px "Helvetica Neue";
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.border-selected {
|
||||
stroke: #34302d;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.controls {
|
||||
border-radius: 2px;
|
||||
border: solid;
|
||||
border-color: #6db33f;
|
||||
padding: 5px;
|
||||
margin-top: 3px;
|
||||
background-color: #eeeeee;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #34302d;
|
||||
background-image: none;
|
||||
border-radius: 2px;
|
||||
color: #f1f1f1;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
font-family: Montserrat,sans-serif;
|
||||
border: 2px solid #6db33f;
|
||||
padding: 5px 20px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.button span {
|
||||
background-color: #34302d;
|
||||
background-image: none;
|
||||
border-radius: 2px;
|
||||
color: #f1f1f1;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
font-family: Montserrat,sans-serif;
|
||||
border: 2px solid #6db33f;
|
||||
padding: 5px 20px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.button input {
|
||||
background-color: #34302d;
|
||||
background-image: none;
|
||||
color: #f1f1f1;
|
||||
font-size: 14px;
|
||||
font-family: Montserrat,sans-serif;
|
||||
text-shadow: none;
|
||||
border: 0px;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
button.on {
|
||||
background-color: #5fa134;
|
||||
}
|
||||
|
||||
.flow-definition-container {
|
||||
border: 1px solid;
|
||||
border-color: #6db33f;
|
||||
border-radius: 2px;
|
||||
margin-top: 3px;
|
||||
background-color: #ffffff;
|
||||
font-family: monospace;
|
||||
z-index: 2;
|
||||
width:100%;
|
||||
height:100px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea:input {
|
||||
outline: none;
|
||||
border: 1px solid #6db33f;
|
||||
}
|
||||
|
||||
textarea:input:focus {
|
||||
outline: none;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
.flow-definition {
|
||||
border: 5px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
font-size: 16px;
|
||||
resize: none;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* The text label on the nodes */
|
||||
.label {
|
||||
font-family: 'Lucida Console';
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* The class for the 'icon/unicode_char' on the nodes */
|
||||
.label2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="collapse-handle" viewBox="0 0 24 24">
|
||||
<line x1="4" y1="4" x2="20" y2="20" stroke="black" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="20" y1="4" x2="4" y2="20" stroke="black" stroke-width="8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 296 B |
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="27.963px" height="27.963px" viewBox="0 0 27.963 27.963" style="enable-background:new 0 0 27.963 27.963;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<circle cx="13.9815" cy="13.9815" r="12.9815" stroke="black" stroke-width="1" fill="red" />
|
||||
<polygon style="fill:white;" points="15.578,17.158 16.19,4.579 11.803,4.579 12.413,17.158 "/>
|
||||
<path style="fill:white;" d="M13.997,18.546c-1.471,0-2.5,1.029-2.5,2.526c0,1.443,0.999,2.528,2.444,2.528h0.056
|
||||
c1.499,0,2.469-1.085,2.469-2.528C16.44,19.575,15.467,18.546,13.997,18.546z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 791 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="1000px" height="1000px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M139.2,264.6c-47.5,71.7-72.7,155.2-72.7,241.5c0,59,11.6,116.3,34.4,170.3c22,52.1,53.6,98.9,93.7,139
|
||||
c40.1,40.1,86.9,71.7,139,93.7c54,22.8,111.3,34.4,170.3,34.4s116.3-11.6,170.3-34.4c52.1-22,98.9-53.6,139-93.7
|
||||
c40.1-40.1,71.7-86.9,93.7-139c22.8-54,34.4-111.3,34.4-170.3c0-50.4-8.5-99.8-25.3-146.9c-16.2-45.5-39.8-87.8-70.1-125.7
|
||||
c-30-37.5-65.6-69.7-106-95.6c-41.1-26.4-86-45.6-133.4-57l-39.8,165.3c57.3,13.8,109.3,46.9,146.3,93.4
|
||||
c38.1,47.8,58.3,105.4,58.3,166.7c0,71.4-27.8,138.6-78.3,189.1c-50.5,50.5-117.7,78.3-189.1,78.3s-138.6-27.8-189.1-78.3
|
||||
c-50.5-50.5-78.3-117.7-78.3-189.1c0-52.8,15.3-103.8,44.3-147.6c6.5-9.9,13.7-19.2,21.4-28.1L399.1,456L508,70.1l-406.4,0.1
|
||||
l95.6,124C175.7,215.5,156.2,239,139.2,264.6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,51 +0,0 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<title>Spring Flo Sample</title>
|
||||
<link href="http://fonts.googleapis.com/css?family=Varela+Round|Montserrat:400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="/webjars/spring-flo/dist/spring-flo.css" rel="stylesheet" />
|
||||
<link href="css/sample.css" rel="stylesheet" />
|
||||
|
||||
<script data-main="js/main" src="/webjars/requirejs/require.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header" class="header">
|
||||
Spring Flo Sample
|
||||
</div>
|
||||
<div id="flo-container">
|
||||
<flo-editor ng-cloak metamodel-service-name="SampleMetamodelService" render-service-name="SampleRenderService" editor-service-name="SampleEditorService" palette-size="170"
|
||||
min-zoom="10" max-zoom="300" zoom-step="10" ng-init="canvasControls={zoom:true};">
|
||||
<div id="controls" class="controls">
|
||||
<button class="button" id="clearGraph" ng-click="flo.clearGraph()">Create New Flow</button>
|
||||
<!-- let's use the 'x' control on the nodes themselves when they are selected
|
||||
<button class="button" id="deleteSelectedNode" ng-click="flo.deleteSelectedNode()">Delete Selected Node</button>
|
||||
-->
|
||||
<button class="button" id="performLayout" ng-click="flo.performLayout(); flo.fitToPage();">Reset Layout</button>
|
||||
<button class="button" id="sync" ng-click="flo.updateTextRepresentation()">Synchronize</button>
|
||||
<button class="button" id="readOnly" ng-click="flo.readOnlyCanvas(!flo.readOnlyCanvas())" ng-class="{on:flo.readOnlyCanvas()}">Read-Only</button>
|
||||
<button class="button" id="noPalette" ng-click="flo.noPalette = !flo.noPalette" ng-class="{on:!flo.noPalette}">Palette</button>
|
||||
<button class="button" id="editor" ng-click="editor = !editor" ng-lcass="{on:editor}">{{editor ? 'TextArea' : 'Editor'}}</button>
|
||||
<!-- Defer to using zoom control on the canvas
|
||||
<span class="button">
|
||||
<label>Zoom: </label>
|
||||
<input id="zoomInput" type="text" ng-model="flo.zoomPercent" ng-model-options="{ getterSetter: true, updateOn: 'blur change' }" size="3"></input>
|
||||
<label>%</label>
|
||||
<input type="range" ng-model="flo.zoomPercent" ng-model-options="{ getterSetter: true }" step="10" max="300" min="10" data-type="range" name="range" class="range"></input>
|
||||
</span>
|
||||
-->
|
||||
<span class="button">
|
||||
<label>Grid Size: </label>
|
||||
<input id="gridSizeInput" type="text" ng-model="flo.gridSize" ng-model-options="{ getterSetter: true, updateOn: 'blur change' }" size="2"></input>
|
||||
<label>pixels</label>
|
||||
<input type="range" ng-model="flo.gridSize" ng-model-options="{ getterSetter: true }" step="1" max="50" min="1" data-type="range" name="range" class="range"></input>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flow-definition-container">
|
||||
<textarea ng-if="editor" dsl-editor="true" id="flow-definition" class="flow-definition"></textarea>
|
||||
<textarea ng-if="!editor" id="flow-definition" class="flow-definition" placeholder="Enter stream definition..."
|
||||
ng-model="definition.text" ng-keyup="flo.scheduleUpdateGraphRepresentation(); $event.stopPropagation();" ng-blur="flo.enableSyncing(true)" ng-focus="flo.enableSyncing(false)"></textarea>
|
||||
</div>
|
||||
</flo-editor>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,450 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var joint = require('joint');
|
||||
|
||||
return ['$log', function($log) {
|
||||
|
||||
function createHandles(flo, createHandle, element) {
|
||||
var bbox = element.getBBox();
|
||||
var pt = bbox.origin().offset(bbox.width + 3, bbox.height + 3);
|
||||
createHandle(element, 'remove', flo.deleteSelectedNode, pt);
|
||||
}
|
||||
|
||||
function validatePort(/*flo, cellView, magnet*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateLink(flo, cellViewS, magnetS, cellViewT, magnetT/*, end, linkView*/) {
|
||||
// Prevent linking from input ports.
|
||||
if (magnetS && magnetS.getAttribute('type') === 'input') {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking from output ports to input ports within one element.
|
||||
if (cellViewS === cellViewT) {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking to input ports.
|
||||
if (magnetT && magnetT.getAttribute('type') === 'output') {
|
||||
return false;
|
||||
}
|
||||
return cellViewS.model && cellViewT.model && !(cellViewS.model instanceof joint.shapes.flo.ErrorDecoration) && !(cellViewT.model instanceof joint.shapes.flo.ErrorDecoration);
|
||||
}
|
||||
|
||||
function preDelete(flo, cell) {
|
||||
repairDamage(flo, cell);
|
||||
}
|
||||
|
||||
function handleNodeDropping(flo, dragDescriptor) {
|
||||
var relinking = dragDescriptor.context && dragDescriptor.context.palette;
|
||||
var graph = flo.getGraph();
|
||||
var source = dragDescriptor.source ? dragDescriptor.source.cell : undefined;
|
||||
var target = dragDescriptor.target ? dragDescriptor.target.cell : undefined;
|
||||
if (target instanceof joint.dia.Element && target.attr('metadata/name')) {
|
||||
// Custom handling allowing a node to be dropped on a port and inserting
|
||||
// it into the flow directly without the user needing to do more link
|
||||
// drawing
|
||||
var type = source.attr('metadata/name');
|
||||
if (dragDescriptor.target.selector === '.output-port') {
|
||||
moveNodeOnNode(flo, source, target, 'right', true);
|
||||
relinking = true;
|
||||
} else if (dragDescriptor.target.selector === '.input-port') {
|
||||
moveNodeOnNode(flo, source, target, 'left', true);
|
||||
relinking = true;
|
||||
}
|
||||
} else if (target instanceof joint.dia.Link) { // jshint ignore:line
|
||||
// Custom handling allowing a node to be dropped on a link and inserting
|
||||
// itself in that link without the user needing to do more link drawing
|
||||
moveNodeOnLink(flo, source, target);
|
||||
relinking = true;
|
||||
}
|
||||
// Turn off auto layout
|
||||
// if (relinking) {
|
||||
// flo.performLayout();
|
||||
// }
|
||||
}
|
||||
|
||||
function calculateDragDescriptor(flo, draggedView, targetUnderMouse, point, context) {
|
||||
var source = draggedView.model;
|
||||
|
||||
// Find closest port
|
||||
var range = 30;
|
||||
var graph = flo.getGraph();
|
||||
var paper = flo.getPaper();
|
||||
var closestData;
|
||||
var minDistance = Number.MAX_VALUE;
|
||||
var maxIcomingLinks = draggedView.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var maxOutgoingLinks = draggedView.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var hasIncomingPort = typeof maxIcomingLinks !== 'number' || maxIcomingLinks > 0;
|
||||
var hasOutgoingPort = typeof maxOutgoingLinks !== 'number' || maxOutgoingLinks > 0;
|
||||
if (!hasIncomingPort && !hasOutgoingPort) {
|
||||
return;
|
||||
}
|
||||
var elements = graph.findModelsInArea(joint.g.rect(point.x - range, point.y - range, 2 * range, 2 * range)); // jshint ignore:line
|
||||
if (Array.isArray(elements)) {
|
||||
elements.forEach(function(model) {
|
||||
var view = paper.findViewByModel(model);
|
||||
if (view && view !== draggedView && model instanceof joint.dia.Element) { // jshint ignore:line
|
||||
var targetMaxIcomingLinks = view.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var targetMaxOutgoingLinks = view.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var targetHasIncomingPort = typeof targetMaxIcomingLinks !== 'number' || targetMaxIcomingLinks > 0;
|
||||
var targetHasOutgoingPort = typeof targetMaxOutgoingLinks !== 'number' || targetMaxOutgoingLinks > 0;
|
||||
if (view.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (targetHasIncomingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (targetHasOutgoingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
if (draggedView.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (hasIncomingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (hasOutgoingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
view.$('[magnet]').each(function(index, magnet) {
|
||||
var type = magnet.getAttribute('type');
|
||||
if ((type === 'input' && targetHasIncomingPort && hasOutgoingPort) || (type === 'output' && targetHasOutgoingPort && hasIncomingPort)) {
|
||||
var bbox = joint.V(magnet).bbox(false, paper.viewport); // jshint ignore:line
|
||||
var distance = point.distance({
|
||||
x: bbox.x + bbox.width / 2,
|
||||
y: bbox.y + bbox.height / 2
|
||||
});
|
||||
if (distance < range && distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestData = {
|
||||
context: context,
|
||||
source: {
|
||||
cell: draggedView.model,
|
||||
selector: type === 'output' ? '.input-port' : '.output-port'
|
||||
},
|
||||
target: {
|
||||
cell: model,
|
||||
selector: '.' + type+'-port'
|
||||
},
|
||||
range: minDistance
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (closestData) {
|
||||
return closestData;
|
||||
}
|
||||
|
||||
// Check if drop on a link is allowed
|
||||
if (targetUnderMouse instanceof joint.dia.Link &&
|
||||
!(source.attr('metadata/constraints/xorSourceSink') ||
|
||||
source.attr('metadata/constraints/maxOutgoingLinksNumber') === 0 ||
|
||||
source.attr('metadata/constraints/maxIncomingLinksNumber') === 0) &&
|
||||
graph.getConnectedLinks(source).length === 0) { // jshint ignore:line
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
target: {
|
||||
cell: targetUnderMouse
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function validateNode(flo, element) {
|
||||
var errors = [];
|
||||
var graph = flo.getGraph();
|
||||
var constraints = element.attr('metadata/constraints');
|
||||
if (constraints) {
|
||||
var incoming = graph.getConnectedLinks(element, {inbound: true});
|
||||
var outgoing = graph.getConnectedLinks(element, {outbound: true});
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' || typeof constraints.minIncomingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' && constraints.maxIncomingLinksNumber < incoming.length) {
|
||||
if (constraints.maxIncomingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sources must appear at the start of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of incoming links is ' + constraints.maxIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minIncomingLinksNumber === 'number' && constraints.minIncomingLinksNumber > incoming.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of incoming links is ' + constraints.minIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' || typeof constraints.minOutgoingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' && constraints.maxOutgoingLinksNumber < outgoing.length) {
|
||||
if (constraints.maxOutgoingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sinks must appear at the end of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of outgoing links is ' + constraints.maxOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minOutgoingLinksNumber === 'number' && constraints.minOutgoingLinksNumber > outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of outgoing links is ' + constraints.minOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (constraints.xorSourceSink && incoming.length && outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Node can either have incoming or outgoing links, but not both',
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!element.attr('metadata') || element.attr('metadata/unresolved')) {
|
||||
var msg = 'Unknown element \'' + element.attr('metadata/name') + '\'';
|
||||
if (element.attr('metadata/group')) {
|
||||
msg += ' from group \'' + element.attr('metadata/group') + '\'.';
|
||||
}
|
||||
errors.push({
|
||||
message: msg,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
|
||||
// If possible, verify the properties specified match those allowed on this type of element
|
||||
// propertiesRanges are the ranges for each property included the entire '--name=value'.
|
||||
// The format of a range is {'start':{'ch':NNNN,'line':NNNN},'end':{'ch':NNNN,'line':NNNN}}
|
||||
var propertiesRanges = element.attr('propertiesranges');
|
||||
if (propertiesRanges) {
|
||||
var moduleSchema = element.attr('metadata');
|
||||
// Grab the list of supported properties for this module type
|
||||
moduleSchema.get('properties').then(function(moduleSchemaProperties) {
|
||||
if (!moduleSchemaProperties) {
|
||||
moduleSchemaProperties = {};
|
||||
}
|
||||
// Example moduleSchemaProperties:
|
||||
// {"host":{"name":"host","type":"String","description":"the hostname of the mail server","defaultValue":"localhost","hidden":false},
|
||||
// "password":{"name":"password","type":"String","description":"the password to use to connect to the mail server ","defaultValue":null,"hidden":false}
|
||||
var specifiedProperties = element.attr('props');
|
||||
Object.keys(specifiedProperties).forEach(function(propertyName) {
|
||||
if (!moduleSchemaProperties[propertyName]) {
|
||||
// The schema does not mention that property
|
||||
var propertyRange = propertiesRanges[propertyName];
|
||||
if (propertyRange) {
|
||||
errors.push({
|
||||
message: 'unrecognized option \''+propertyName+'\' for module \''+element.attr('metadata/name')+'\'',
|
||||
range: propertyRange
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function moveNodeOnNode(flo, node, pivotNode, side, shouldRepairDamage) {
|
||||
side = side || 'left';
|
||||
if (canSwap(flo, node, pivotNode, side)) {
|
||||
var link;
|
||||
var i;
|
||||
if (side === 'left') {
|
||||
var sources = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {inbound: true}).length > 0 || graph.getConnectedLinks(node, {outbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotTargetLinks = flo.getGraph().getConnectedLinks(pivotNode, {inbound: true});
|
||||
for (i = 0; i < pivotTargetLinks.length; i++) {
|
||||
link = pivotTargetLinks[i];
|
||||
sources.push(link.get('source').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({
|
||||
'id': sources[i],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': pivotNode.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
} else if (side === 'right') {
|
||||
var targets = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {outbound: true}).length > 0 || graph.getConnectedLinks(node, {inbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotSourceLinks = flo.getGraph().getConnectedLinks(pivotNode, {outbound: true});
|
||||
for (i = 0; i < pivotSourceLinks.length; i++) {
|
||||
link = pivotSourceLinks[i];
|
||||
targets.push(link.get('target').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': targets[i],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': pivotNode.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node moved onto a link. Remove the existing link and replace it with two links
|
||||
* that go from the original link source to the dropped node and from the dropped node
|
||||
* to the original link target.
|
||||
*/
|
||||
function moveNodeOnLink(flo, node, link, shouldRepairDamage) {
|
||||
var source = link.get('source').id;
|
||||
var target = link.get('target').id;
|
||||
|
||||
if (shouldRepairDamage) {
|
||||
repairDamage(flo, node);
|
||||
}
|
||||
link.remove();
|
||||
|
||||
if (source) {
|
||||
flo.createLink({'id': source,'selector': '.output-port'}, {'id': node.id,'selector': '.input-port'});
|
||||
}
|
||||
if (target) {
|
||||
flo.createLink({'id': node.id,'selector': '.output-port'}, {'id': target,'selector': '.input-port'});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a node is removed any dangling links should be removed. What this function will also try to do
|
||||
* is if removing a node from a chain it will attempt to replace dangling links with a link from the
|
||||
* deleted nodes original source to the deleted nodes original target.
|
||||
*/
|
||||
function repairDamage(flo, node) {
|
||||
var sources = [];
|
||||
var targets = [];
|
||||
var i = 0;
|
||||
var links = flo.getGraph().getConnectedLinks(node);
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var targetId = links[i].get('target').id;
|
||||
var sourceId = links[i].get('source').id;
|
||||
if (targetId === node.id) {
|
||||
links[i].remove();
|
||||
sources.push(sourceId);
|
||||
} else if (sourceId === node.id) {
|
||||
links[i].remove();
|
||||
targets.push(targetId);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If appropriate, create new links to replace the dangling ones deleted
|
||||
*/
|
||||
if (sources.length === 1) {
|
||||
var source = sources[0];
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({'id': source,'selector': '.output-port'}, {'id': targets[i],'selector': '.input-port'});
|
||||
}
|
||||
} else if (targets.length === 1) {
|
||||
var target = targets[0];
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({'id': sources[i], 'selector': '.output-port'}, {'id': target,'selector': '.input-port'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if node being dropped and drop target node next to each other such that they won't be swapped by the drop
|
||||
*/
|
||||
function canSwap(flo, dropee, target, side) {
|
||||
var i, targetId, sourceId, noSwap = (dropee.id === target.id);
|
||||
if (dropee === target) {
|
||||
$log.debug('What!??? Dragged == Dropped!!! id = ' + target);
|
||||
}
|
||||
var links = flo.getGraph().getConnectedLinks(dropee);
|
||||
for (i = 0; i < links.length && !noSwap; i++) {
|
||||
targetId = links[i].get('target').id;
|
||||
sourceId = links[i].get('source').id;
|
||||
noSwap = (side === 'left' && targetId === target.id && sourceId === dropee.id) || (side === 'right' && targetId === dropee.id && sourceId === target.id);
|
||||
}
|
||||
return !noSwap;
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandles': createHandles,
|
||||
'validatePort': validatePort,
|
||||
'validateLink': validateLink,
|
||||
'calculateDragDescriptor': calculateDragDescriptor,
|
||||
'handleNodeDropping': handleNodeDropping,
|
||||
'validateNode': validateNode,
|
||||
'preDelete': preDelete,
|
||||
'interactive': {
|
||||
'vertexAdd': false
|
||||
},
|
||||
'allowLinkVertexEdit': false
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a graph to a text representation.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
// Graph
|
||||
var g;
|
||||
|
||||
// Number of Links left to visit
|
||||
var numberOfLinksToVisit;
|
||||
|
||||
// Number of nodes left to visit
|
||||
var numberOfNodesToVisit;
|
||||
|
||||
// Map of links left to visit indexed by id
|
||||
var linksToVisit;
|
||||
|
||||
// Map of nodes left to visit indexed by id
|
||||
var nodesToVisit;
|
||||
|
||||
// Map of nodes incoming non-visited links degrees index by node id
|
||||
var nodesInDegrees;
|
||||
|
||||
// Priority:
|
||||
// 1. find links whose source has no other links pointing at it
|
||||
// 2. find links whose source has already been processed (not currently needed in sample DSL since
|
||||
// can't create graphs like that due to metamodel constraints)
|
||||
// 3. find remaining links
|
||||
function nextLink() {
|
||||
var indegree = Number.MAX_INT;
|
||||
var currentBest;
|
||||
for (var id in linksToVisit) {
|
||||
var link = g.getCell(id);
|
||||
var source = g.getCell(link.get('source').id);
|
||||
var currentInDegree = nodesInDegrees[source.get('id')];
|
||||
if (currentInDegree === 0) {
|
||||
return visit(link);
|
||||
} else if (indegree > currentInDegree) {
|
||||
indegree = currentInDegree;
|
||||
currentBest = link;
|
||||
}
|
||||
}
|
||||
if (currentBest) {
|
||||
return visit(currentBest);
|
||||
}
|
||||
}
|
||||
|
||||
function visit(e) {
|
||||
if (e.isLink()) {
|
||||
delete linksToVisit[e.get('id')];
|
||||
nodesInDegrees[e.get('target').id]--;
|
||||
numberOfLinksToVisit--;
|
||||
} else {
|
||||
delete nodesToVisit[e.get('id')];
|
||||
numberOfNodesToVisit--;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
function init(graph) {
|
||||
numberOfLinksToVisit = 0;
|
||||
numberOfNodesToVisit = 0;
|
||||
linksToVisit = {};
|
||||
nodesToVisit = {};
|
||||
nodesInDegrees = {};
|
||||
g = graph;
|
||||
g.getElements().forEach(function(element) {
|
||||
if (element.attr('metadata/name')) { // is it a node?
|
||||
nodesToVisit[element.get('id')] = element;
|
||||
var indegree = 0;
|
||||
g.getConnectedLinks(element, {inbound: true}).forEach(function(link) {
|
||||
if (link.get('source') && link.get('source').id && g.getCell(link.get('source').id) &&
|
||||
g.getCell(link.get('source').id).attr('metadata/name')) {
|
||||
linksToVisit[link.get('id')] = link;
|
||||
numberOfLinksToVisit++;
|
||||
indegree++;
|
||||
}
|
||||
});
|
||||
nodesInDegrees[element.get('id')] = indegree;
|
||||
numberOfNodesToVisit++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts at a link and proceeds down a chain. Converts each node to
|
||||
* text and then joins them with a ' > '.
|
||||
*/
|
||||
function chainToText(link) {
|
||||
var text = '';
|
||||
var source = g.getCell(link.get('source').id);
|
||||
text += nodeToText(source, true);
|
||||
while (link) {
|
||||
var target = g.getCell(link.get('target').id);
|
||||
text += ' > ';
|
||||
text += nodeToText(target, false);
|
||||
|
||||
// Find next not visited link to follow
|
||||
link = null;
|
||||
var outgoingLinks = g.getConnectedLinks(target, {outbound: true});
|
||||
for (var i = 0; i < outgoingLinks.length && !link; i++) {
|
||||
if (linksToVisit[outgoingLinks[i].get('id')]) {
|
||||
source = target;
|
||||
link = visit(outgoingLinks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Very basic format. From a node to the text:
|
||||
* "name --key=value --key=value"
|
||||
*/
|
||||
function nodeToText(element) {
|
||||
var text = '';
|
||||
var props = element.attr('props');
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
text += element.attr('metadata/name');
|
||||
if (props) {
|
||||
Object.keys(props).forEach(function(propertyName) {
|
||||
text += ' --' + propertyName + '=' + props[propertyName];
|
||||
});
|
||||
}
|
||||
visit(element);
|
||||
return text;
|
||||
}
|
||||
|
||||
function appendChainText(text, chainText) {
|
||||
if (chainText) {
|
||||
if (text) {
|
||||
text += '\n';
|
||||
}
|
||||
text += chainText;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Translate a graph into a basic string
|
||||
return function(g) {
|
||||
var text = '';
|
||||
var chainText;
|
||||
var id;
|
||||
init(g);
|
||||
while (numberOfLinksToVisit) {
|
||||
chainText = chainToText(nextLink());
|
||||
text = appendChainText(text, chainText);
|
||||
}
|
||||
// Visit all disconnected nodes
|
||||
for (id in nodesToVisit) {
|
||||
chainText = nodeToText(nodesToVisit[id], true);
|
||||
text = appendChainText(text, chainText);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
requirejs.config({
|
||||
baseUrl:'js',
|
||||
paths: {
|
||||
joint: '/webjars/jointjs/dist/joint',
|
||||
backbone: '/webjars/backbone/backbone',
|
||||
domReady: '/webjars/requirejs-domready/domReady',
|
||||
angular: '/webjars/angular/angular',
|
||||
jquery: '/webjars/jquery/dist/jquery',
|
||||
bootstrap:'/webjars/bootstrap/bootstrap',
|
||||
lodash: '/webjars/lodash/lodash', // lodash.compat
|
||||
dagre: '/webjars/dagre/dist/dagre.core',
|
||||
graphlib: '/webjars/graphlib/graphlib.core',
|
||||
text : '/webjars/requirejs-text/text',
|
||||
flo : '/webjars/spring-flo/dist/spring-flo',
|
||||
json5 : '/webjars/json5/json5'
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
// Backbone requires underscore. This forces requireJS to load lodash instead:
|
||||
'underscore': 'lodash'
|
||||
}
|
||||
},
|
||||
packages: [
|
||||
{
|
||||
name: 'codemirror',
|
||||
location: '../lib/codemirror',
|
||||
main: 'lib/codemirror'
|
||||
}
|
||||
],
|
||||
shim: {
|
||||
angular: {
|
||||
deps: ['bootstrap'],
|
||||
exports: 'angular'
|
||||
},
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
graphlib: {
|
||||
deps: ['underscore']
|
||||
},
|
||||
dagre: {
|
||||
deps: ['graphlib', 'underscore']
|
||||
},
|
||||
joint: {
|
||||
deps: ['jquery', 'underscore', 'backbone'],
|
||||
},
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
'flo': {
|
||||
deps: ['angular', 'jquery', 'joint', 'underscore']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
define(['require','angular'], function (require, angular) {
|
||||
'use strict';
|
||||
require(['domReady!', 'sample-app'],
|
||||
function (document) {
|
||||
angular.bootstrap(document, ['floSampleApp']);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
[{
|
||||
'name':'http', 'group':'source', 'description':'Receive HTTP input',
|
||||
'properties':[
|
||||
{ 'id':'port','name':'port', 'defaultValue':'80', 'description':'Port on which to listen' }
|
||||
],
|
||||
'constraints':{ 'maxIncomingLinksNumber':0, 'maxOutgoingLinksNumber':1 }
|
||||
},{
|
||||
'name':'rabbit', 'group':'source', 'description':'Receives messages from RabbitMQ',
|
||||
'properties':[
|
||||
{ 'id':'queue','name':'queue', 'description':'the queue(s) from which messages will be received' }
|
||||
],
|
||||
'constraints':{ 'maxIncomingLinksNumber':0, 'maxOutgoingLinksNumber':1 }
|
||||
},{
|
||||
'name':'filewatch', 'group':'source', 'description':'Produce messages from the content of files created in a directory',
|
||||
'properties':[
|
||||
{'id':'dir','name':'dir','description':'the absolute path to monitor for files'}
|
||||
],
|
||||
'constraints':{ 'maxIncomingLinksNumber':0, 'maxOutgoingLinksNumber':1 }
|
||||
},{
|
||||
'name':'transform', 'group':'processor', 'description':'Apply an expression to modify incoming messages',
|
||||
'properties':[
|
||||
{ 'id':'expression','name':'expression', 'defaultValue':'payload', 'description':'SpEL expression to apply' }
|
||||
],
|
||||
'constraints':{ 'maxIncomingLinksNumber':1, 'maxOutgoingLinksNumber':1 }
|
||||
},{
|
||||
'name':'filter', 'group':'processor', 'description':'Only allow messages through that pass the filter expression',
|
||||
'properties':[
|
||||
{ 'id':'expression','name':'expression', 'defaultValue':'true', 'description':'SpEL expression to use for filtering' }
|
||||
],
|
||||
'constraints':{ 'maxIncomingLinksNumber':1, 'maxOutgoingLinksNumber':1 }
|
||||
},{
|
||||
'name':'filesave', 'group':'sink', 'description':'Writes messages to a file',
|
||||
'properties':[
|
||||
{ 'id':'dir','name':'dir', 'description':'Absolute path to directory' },
|
||||
{ 'id':'name','name':'name', 'description':'The name of the file to create' }
|
||||
],
|
||||
'constraints':{ 'maxIncomingLinksNumber':1, 'maxOutgoingLinksNumber':0 }
|
||||
},{
|
||||
'name':'ftp', 'group':'sink', 'description':'Send messages over FTP',
|
||||
'properties':[
|
||||
{ 'id':'host','name':'host', 'description':'the host name for the FTP server' },
|
||||
{ 'id':'port','name':'port', 'description':'The port for the FTP server' },
|
||||
{ 'id':'remoteDir','name':'remoteDir', 'description':'The remote directory on the server' },
|
||||
],
|
||||
'constraints':{ 'maxIncomingLinksNumber':1, 'maxOutgoingLinksNumber':0 }
|
||||
}]
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
require('json5');
|
||||
|
||||
var convertGraphToText = require('graph-to-text');
|
||||
var convertTextToGraph = require('text-to-graph');
|
||||
|
||||
return ['$http', '$q', '$timeout', '$log', 'MetamodelUtils', function($http, $q, $timeout, $log, metamodelUtils) {
|
||||
|
||||
var metamodel;
|
||||
|
||||
// Internally stored metamodel load promise
|
||||
var request;
|
||||
|
||||
/**
|
||||
* Helper that goes from basic JSON to a lazy getter structure. Useful when the
|
||||
* metamodel is 'cheap' to build. If it is costly to discover the actual properties
|
||||
* the getter may be more complex (e.g. make a REST request).
|
||||
*/
|
||||
function createMetadata(entry) {
|
||||
var props = {};
|
||||
if (Array.isArray(entry.properties)) {
|
||||
entry.properties.forEach(function(property) {
|
||||
if (!property.id) {
|
||||
property.id = property.name;
|
||||
}
|
||||
props[property.id] = property;
|
||||
});
|
||||
}
|
||||
entry.properties = props;
|
||||
return {
|
||||
name: entry.name,
|
||||
group: entry.group,
|
||||
icon: entry.icon,
|
||||
constraints: entry.constraints,
|
||||
description: entry.description,
|
||||
metadata: entry.metadata,
|
||||
properties: entry.properties,
|
||||
get: function(property) {
|
||||
var deferred = $q.defer();
|
||||
if (entry.hasOwnProperty(property)) {
|
||||
deferred.resolve(entry[property]);
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function load() {
|
||||
// COULDDO: to cache the result here, check result before doing this processing
|
||||
// and simply return it if it is set. If doing that may want to override refresh
|
||||
// in this service
|
||||
var metamodelData = JSON5.parse(require('text!metamodel-sample.json'));
|
||||
var deferred = $q.defer();
|
||||
var newData = {};
|
||||
metamodelData.forEach(function(data) {
|
||||
var metadata = createMetadata(data);
|
||||
if (!newData[metadata.group]) {
|
||||
newData[metadata.group] = {};
|
||||
}
|
||||
newData[metadata.group][metadata.name] = metadata;
|
||||
});
|
||||
metamodel = newData;
|
||||
deferred.resolve(metamodel);
|
||||
request = deferred.promise;
|
||||
return request;
|
||||
}
|
||||
|
||||
function graphToText(flo, definition) {
|
||||
definition.text = convertGraphToText(flo.getGraph());
|
||||
}
|
||||
|
||||
function textToGraph(flo, definition) {
|
||||
// TODO perhaps push these flo operations into the 'caller' to make this simpler
|
||||
flo.getGraph().clear();
|
||||
load().then(function(metamodel) {
|
||||
convertTextToGraph(definition.text, flo, metamodel, metamodelUtils);
|
||||
flo.performLayout();
|
||||
flo.fitToPage();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
'load': load,
|
||||
'textToGraph': textToGraph,
|
||||
'graphToText': graphToText
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var joint = require('joint');
|
||||
|
||||
var dagre = require('dagre');
|
||||
|
||||
var HANDLE_ICON_MAP = {
|
||||
'remove': 'icons/delete.svg',
|
||||
};
|
||||
|
||||
var DECORATION_ICON_MAP = {
|
||||
'error': 'icons/error.svg'
|
||||
};
|
||||
|
||||
return ['$log', function($log) {
|
||||
|
||||
function createHandle(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 10, height: 10},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': HANDLE_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDecoration(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 16, height: 16},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': DECORATION_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createNode(/*metadata, props*/) {
|
||||
return new joint.shapes.flo.Node();
|
||||
}
|
||||
|
||||
function initializeNewNode(node, context) {
|
||||
var metadata = node.attr('metadata');
|
||||
if (metadata) {
|
||||
node.attr('.label/text', node.attr('metadata/name'));
|
||||
if (node.attr('metadata/constraints/maxIncomingLinksNumber') === 0) {
|
||||
node.attr('.input-port/display','none');
|
||||
}
|
||||
if (node.attr('metadata/constraints/maxOutgoingLinksNumber') === 0) {
|
||||
node.attr('.output-port/display','none');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createLink() {
|
||||
var link = new joint.shapes.flo.Link(joint.util.deepSupplement({
|
||||
smooth: true,
|
||||
attrs: {
|
||||
'.': {
|
||||
//filter: { name: 'dropShadow', args: { dx: 1, dy: 1, blur: 2 } }
|
||||
},
|
||||
'.connection': { 'stroke-width': 3, 'stroke': 'black', 'stroke-linecap': 'round' },
|
||||
'.marker-arrowheads': { display: 'none' },
|
||||
'.tool-options': { display: 'none' }
|
||||
},
|
||||
}, joint.shapes.flo.Link.prototype.defaults));
|
||||
return link;
|
||||
}
|
||||
|
||||
function isSemanticProperty(propertyPath) {
|
||||
return propertyPath === '.label/text';
|
||||
}
|
||||
|
||||
function refreshVisuals(element, changedPropertyPath/*, paper*/) {
|
||||
// var type = element.attr('metadata/name');
|
||||
}
|
||||
|
||||
function layout(paper) {
|
||||
var graph = paper.model;
|
||||
var i;
|
||||
var g = new dagre.graphlib.Graph();
|
||||
g.setGraph({});
|
||||
g.setDefaultEdgeLabel(function() {return{};});
|
||||
|
||||
var nodes = graph.getElements();
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (node.get('type') === joint.shapes.flo.NODE_TYPE) {
|
||||
g.setNode(node.id, node.get('size'));
|
||||
}
|
||||
}
|
||||
|
||||
var links = graph.getLinks();
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
if (link.get('type') === joint.shapes.flo.LINK_TYPE) {
|
||||
var options = {
|
||||
minlen: 1.5
|
||||
};
|
||||
// if (link.get('labels') && link.get('labels').length > 0) {
|
||||
// options.minlen = 1 + link.get('labels').length * 0.5;
|
||||
// }
|
||||
g.setEdge(link.get('source').id, link.get('target').id, options);
|
||||
link.set('vertices', []);
|
||||
}
|
||||
}
|
||||
|
||||
g.graph().rankdir = 'LR';
|
||||
dagre.layout(g);
|
||||
g.nodes().forEach(function(v) {
|
||||
var node = graph.getCell(v);
|
||||
if (node) {
|
||||
var bbox = node.getBBox();
|
||||
node.translate(g.node(v).x - bbox.x, g.node(v).y - bbox.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLinkAnchorPoint(linkView, view, magnet, reference) {
|
||||
if (magnet) {
|
||||
var type = magnet.getAttribute('type');
|
||||
var bbox = joint.V(magnet).bbox(false, linkView.paper.viewport);
|
||||
var rect = joint.g.rect(bbox);
|
||||
if (type === 'input') {
|
||||
return joint.g.point(rect.x, rect.y + rect.height / 2);
|
||||
} else {
|
||||
return joint.g.point(rect.x + rect.width, rect.y + rect.height / 2);
|
||||
}
|
||||
} else {
|
||||
$log.debug('No magnet!');
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandle': createHandle,
|
||||
'createDecoration': createDecoration,
|
||||
'createNode': createNode,
|
||||
'createLink': createLink,
|
||||
'initializeNewNode': initializeNewNode,
|
||||
'isSemanticProperty': isSemanticProperty,
|
||||
'refreshVisuals': refreshVisuals,
|
||||
'layout': layout,
|
||||
'getLinkAnchorPoint': getLinkAnchorPoint
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
var angular = require('angular');
|
||||
require('flo');
|
||||
var app = angular.module('floSampleApp', [ 'spring.flo' ]);
|
||||
app.factory('SampleMetamodelService', require('metamodel-service'));
|
||||
app.factory('SampleRenderService', require('render-service'));
|
||||
app.factory('SampleEditorService', require('editor-service'));
|
||||
return app;
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a text representation to a graph.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function(input, flo, metamodel, metamodelUtils) {
|
||||
// input is a string like this (3 nodes: foo, goo and hoo): foo --a=b --c=d > goo --d=e --f=g>hoo
|
||||
var trimmed = input.trim();
|
||||
if (trimmed.length===0) {
|
||||
return;
|
||||
}
|
||||
var lines = trimmed.split('\n');
|
||||
for (var l=0;l<lines.length;l++) {
|
||||
var line = lines[l];
|
||||
var elements = line.split('>');
|
||||
var lastNode = null;
|
||||
for (var e=0;e<elements.length;e++) {
|
||||
var element = elements[e].trim();
|
||||
// Has properties?
|
||||
var startOfProps = element.indexOf(' ');
|
||||
var name = element;
|
||||
var properties = {};
|
||||
if (startOfProps !== -1) {
|
||||
name = element.substring(0,startOfProps);
|
||||
var propValues = element.substring(startOfProps+1).trim().split(' ');
|
||||
for (var p=0;p<propValues.length;p++) {
|
||||
var propValue = propValues[p].trim();
|
||||
if (propValue.length===0) {
|
||||
// allows for multiple spaces between options
|
||||
continue;
|
||||
}
|
||||
var equalsIndex = propValue.indexOf('=');
|
||||
// The 2 skips the '--'
|
||||
var key = propValue.substring(2,equalsIndex);
|
||||
var value = propValue.substring(equalsIndex+1);
|
||||
properties[key] = value;
|
||||
}
|
||||
}
|
||||
var group = metamodelUtils.matchGroup(metamodel, name, 1, 1);
|
||||
var newNode = flo.createNode(metamodelUtils.getMetadata(metamodel,name,group),properties);
|
||||
newNode.attr('.label/text',name);
|
||||
if (lastNode) {
|
||||
flo.createLink({'id': lastNode.id,'selector': '.output-port'},
|
||||
{'id': newNode.id,'selector': '.input-port'});
|
||||
}
|
||||
lastNode = newNode;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,42 +0,0 @@
|
||||
# A sample app using spring-flo to visualize Spring Integration applications
|
||||
|
||||
This sample uses flo as a live viewer for Spring Integration applications.
|
||||
|
||||
This https://spring.io/blog/2016/04/26/spring-integration-4-3-m2-is-available[blog] discusses
|
||||
how to activate the new endpoint in a Spring Integration application. When
|
||||
the endpoint is active in your SI application, just enter that into the spring
|
||||
flo viewer. You should then see something like this:
|
||||
|
||||
image::imgs/basicGraph.png[width="800"]
|
||||
|
||||
# Running the sample
|
||||
|
||||
A basic Spring Boot app is used to serve the sample. Launch it with:
|
||||
|
||||
mvn spring-boot:run
|
||||
|
||||
then open `http://localhost:8082`. In the `Spring Integration Graph Endpoint`
|
||||
field enter the url for the spring integration data, for example: `http://localhost:8080/integration`
|
||||
and the graph should load.
|
||||
|
||||
|
||||
# Using the application
|
||||
|
||||
Once the graph is loaded you can drag nodes around to adjust the layout. (Press the `Read-Only`
|
||||
button to prevent moving nodes around). Hovering over a node will show a tool tip with more
|
||||
information for that element. If you hover over a channel you will see many stats about
|
||||
traffic flowing over that channel:
|
||||
|
||||
image::imgs/tooltip.png[width="500"]
|
||||
|
||||
It is possible to select one of those stats of interest and have it shown directly on the graph.
|
||||
Simply select what you are interested in and enter the name of that stat in the `Link label path`
|
||||
field at the top. The values for that stat will then be shown on the links between graph
|
||||
elements:
|
||||
|
||||
image::imgs/numbersGraph.png[width="800"]
|
||||
|
||||
If you enter a Refresh rate (minimal allowed is 250ms) then that stat will actually
|
||||
update on the graph at that rate with a small animation indicating where on the graph changes
|
||||
in value are occurring.
|
||||
|
||||
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 147 KiB |
@@ -1,115 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-flo-sample-si</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>spring-flo-sample</name>
|
||||
<description>Spring Flo Sample</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.3.2.RELEASE</version>
|
||||
<relativePath /> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<!-- TODO maybe adjust flos dependencies so this configuration isn't so complicated -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>codemirror</artifactId>
|
||||
<version>5.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>angular</artifactId>
|
||||
<version>1.3.8</version> <!-- flo wants 1.3.5 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jshint</artifactId>
|
||||
<version>2.8.0</version> <!-- flo wants 2.6.3 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs</artifactId>
|
||||
<version>2.1.18</version> <!-- flo wants 2.1.15 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jquery</artifactId>
|
||||
<version>2.2.0</version> <!-- jointjs wants 2.0.3 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>lodash</artifactId>
|
||||
<version>3.10.1</version> <!-- jointjs -> graphlib -> wants 3.10.1-amd -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>webjars-locator</artifactId>
|
||||
<version>0.31</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs-domready</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs-text</artifactId>
|
||||
<version>2.0.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jointjs</artifactId>
|
||||
<version>0.9.7</version> <!-- flo wants 0.9.6 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>json5</artifactId>
|
||||
<version>0.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>spring-flo</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>joint</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<start-class>org.springframework.flo.Application</start-class>
|
||||
<java.version>1.7</java.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.flo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public WebMvcConfigurer corsConfigurer() {
|
||||
// return new WebMvcConfigurerAdapter() {
|
||||
// @Override
|
||||
// public void addCorsMappings(CorsRegistry registry) {
|
||||
// registry.addMapping("/").allowedOrigins("http://localhost:9000");
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
server.port=8082
|
||||
@@ -1,189 +0,0 @@
|
||||
|
||||
.header {
|
||||
font-weight: 400;
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 36px;
|
||||
color: #ffffff;
|
||||
padding: 2px;
|
||||
background-color: #283E49;
|
||||
border: none;
|
||||
/* border-top: 4px solid #6db33f; */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.input-label-div {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
color: #ffffff;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#labelpath {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#refreshrate {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.inputfield {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#endpoint {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#endpoint-button {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
height:80px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #283E49;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.header-small {
|
||||
font: 300 24px "Helvetica Neue";
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.border-selected {
|
||||
stroke: #34302d;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.controls {
|
||||
border-radius: 2px;
|
||||
border: solid;
|
||||
border-color: #283E49;
|
||||
padding: 5px;
|
||||
margin-top: 3px;
|
||||
background-color: #283E49;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #ffffff;
|
||||
background-image: none;
|
||||
border-radius: 2px;
|
||||
background-color: #00B0A7;
|
||||
font-size: 18px;
|
||||
line-height: 14px;
|
||||
font-family: "Roboto",sans-serif;
|
||||
border: 2px solid #00B0A7;
|
||||
padding: 5px 20px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.button span {
|
||||
background-color: #34302d;
|
||||
background-image: none;
|
||||
border-radius: 2px;
|
||||
color: #f1f1f1;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
font-family: Montserrat,sans-serif;
|
||||
border: 2px solid #6db33f;
|
||||
padding: 5px 20px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.button input {
|
||||
background-color: #34302d;
|
||||
background-image: none;
|
||||
color: #f1f1f1;
|
||||
font-size: 14px;
|
||||
font-family: Montserrat,sans-serif;
|
||||
text-shadow: none;
|
||||
border: 0px;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
button.off {
|
||||
background-color: #00B0A7;
|
||||
color: #283E49;
|
||||
}
|
||||
|
||||
.flow-definition-container {
|
||||
display: none;
|
||||
border: 1px solid;
|
||||
border-color: #283E49;
|
||||
border-radius: 2px;
|
||||
margin-top: 3px;
|
||||
background-color: #ffffff;
|
||||
font-family: monospace;
|
||||
z-index: 2;
|
||||
width:100%;
|
||||
height:100px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea:input {
|
||||
outline: none;
|
||||
border: 1px solid #6db33f;
|
||||
}
|
||||
|
||||
textarea:input:focus {
|
||||
outline: none;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
.canvas {
|
||||
border-color: #283E49;
|
||||
}
|
||||
.flow-definition {
|
||||
border: 5px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
font-size: 16px;
|
||||
resize: none;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* The text label on the nodes */
|
||||
|
||||
.label {
|
||||
font-family: 'Ubuntu Mono';
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
/* The class for the 'icon/unicode_char' on the nodes */
|
||||
.label2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
stroke: #283e49;
|
||||
}
|
||||
|
||||
|
||||
.node-tooltip-option-name {
|
||||
font-family: 'Ubuntu Mono', monospace;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="collapse-handle" viewBox="0 0 24 24">
|
||||
<line x1="4" y1="4" x2="20" y2="20" stroke="black" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="20" y1="4" x2="4" y2="20" stroke="black" stroke-width="8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 296 B |
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="27.963px" height="27.963px" viewBox="0 0 27.963 27.963" style="enable-background:new 0 0 27.963 27.963;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<circle cx="13.9815" cy="13.9815" r="12.9815" stroke="black" stroke-width="1" fill="red" />
|
||||
<polygon style="fill:white;" points="15.578,17.158 16.19,4.579 11.803,4.579 12.413,17.158 "/>
|
||||
<path style="fill:white;" d="M13.997,18.546c-1.471,0-2.5,1.029-2.5,2.526c0,1.443,0.999,2.528,2.444,2.528h0.056
|
||||
c1.499,0,2.469-1.085,2.469-2.528C16.44,19.575,15.467,18.546,13.997,18.546z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 791 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="1000px" height="1000px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M139.2,264.6c-47.5,71.7-72.7,155.2-72.7,241.5c0,59,11.6,116.3,34.4,170.3c22,52.1,53.6,98.9,93.7,139
|
||||
c40.1,40.1,86.9,71.7,139,93.7c54,22.8,111.3,34.4,170.3,34.4s116.3-11.6,170.3-34.4c52.1-22,98.9-53.6,139-93.7
|
||||
c40.1-40.1,71.7-86.9,93.7-139c22.8-54,34.4-111.3,34.4-170.3c0-50.4-8.5-99.8-25.3-146.9c-16.2-45.5-39.8-87.8-70.1-125.7
|
||||
c-30-37.5-65.6-69.7-106-95.6c-41.1-26.4-86-45.6-133.4-57l-39.8,165.3c57.3,13.8,109.3,46.9,146.3,93.4
|
||||
c38.1,47.8,58.3,105.4,58.3,166.7c0,71.4-27.8,138.6-78.3,189.1c-50.5,50.5-117.7,78.3-189.1,78.3s-138.6-27.8-189.1-78.3
|
||||
c-50.5-50.5-78.3-117.7-78.3-189.1c0-52.8,15.3-103.8,44.3-147.6c6.5-9.9,13.7-19.2,21.4-28.1L399.1,456L508,70.1l-406.4,0.1
|
||||
l95.6,124C175.7,215.5,156.2,239,139.2,264.6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,41 +0,0 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<title>Spring Integration Viewer</title>
|
||||
<link href="http://fonts.googleapis.com/css?family=Open+Sans|Montserrat:400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="http://fonts.googleapis.com/css?family=Ubuntu+Mono|Montserrat:400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="http://fonts.googleapis.com/css?family=Roboto|Montserrat:400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="/webjars/spring-flo/dist/spring-flo.css" rel="stylesheet" />
|
||||
<link href="css/flosi.css" rel="stylesheet" />
|
||||
|
||||
<script data-main="js/main" src="/webjars/requirejs/require.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header" class="header">
|
||||
Spring Integration Viewer
|
||||
</div>
|
||||
<div class="input-label-div" ng-controller="SiController">
|
||||
Spring Integration Graph endpoint:
|
||||
<input type="text" class="inputfield" name="endpoint" id="endpoint" ng-model="endpoint" ng-enter="load(endpoint)">
|
||||
<input type="submit" class="button" value="Load" ng-click="load(endpoint)">
|
||||
Link label path:
|
||||
<input type="text" class="inputfield" name="labelpath" id="labelpath" ng-model="labelpath" ng-enter="updateLabelPath(labelpath)">
|
||||
Refresh rate (ms):
|
||||
<input type="text" class="inputfield" name="refreshrate" id="refreshrate" ng-model="refreshrate" ng-enter="updateRefreshRate(refreshrate)">
|
||||
</div>
|
||||
<div id="flo-container">
|
||||
<flo-editor ng-cloak metamodel-service-name="SampleMetamodelService" render-service-name="SampleRenderService" editor-service-name="SampleEditorService" palette-size="170"
|
||||
min-zoom="10" max-zoom="300" zoom-step="10" ng-init="canvasControls={zoom:true};flo.noPalette=true;">
|
||||
<div id="controls" class="controls">
|
||||
<button class="button" id="performLayout" ng-click="flo.performLayout(); flo.fitToPage();">Reset Layout</button>
|
||||
<button class="button" id="readOnly" ng-click="flo.readOnlyCanvas(!flo.readOnlyCanvas())" ng-class="{off:!flo.readOnlyCanvas()}">Read-Only</button>
|
||||
</div>
|
||||
<div class="flow-definition-container">
|
||||
<textarea ng-if="editor" dsl-editor="true" id="flow-definition" class="flow-definition"></textarea>
|
||||
<textarea ng-if="!editor" id="flow-definition" class="flow-definition" placeholder="Enter stream definition..."
|
||||
ng-model="definition.text" ng-keyup="flo.scheduleUpdateGraphRepresentation(); $event.stopPropagation();" ng-blur="flo.enableSyncing(true)" ng-focus="flo.enableSyncing(false)"></textarea>
|
||||
</div>
|
||||
</flo-editor>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,450 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var joint = require('joint');
|
||||
|
||||
return ['$log', function($log) {
|
||||
|
||||
function createHandles(flo, createHandle, element) {
|
||||
var bbox = element.getBBox();
|
||||
var pt = bbox.origin().offset(bbox.width + 3, bbox.height + 3);
|
||||
createHandle(element, 'remove', flo.deleteSelectedNode, pt);
|
||||
}
|
||||
|
||||
function validatePort(/*flo, cellView, magnet*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateLink(flo, cellViewS, magnetS, cellViewT, magnetT/*, end, linkView*/) {
|
||||
// Prevent linking from input ports.
|
||||
if (magnetS && magnetS.getAttribute('type') === 'input') {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking from output ports to input ports within one element.
|
||||
if (cellViewS === cellViewT) {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking to input ports.
|
||||
if (magnetT && magnetT.getAttribute('type') === 'output') {
|
||||
return false;
|
||||
}
|
||||
return cellViewS.model && cellViewT.model && !(cellViewS.model instanceof joint.shapes.flo.ErrorDecoration) && !(cellViewT.model instanceof joint.shapes.flo.ErrorDecoration);
|
||||
}
|
||||
|
||||
function preDelete(flo, cell) {
|
||||
repairDamage(flo, cell);
|
||||
}
|
||||
|
||||
function handleNodeDropping(flo, dragDescriptor) {
|
||||
// this is a viewer not an editor, so do not adjust the graph structure
|
||||
}
|
||||
|
||||
function calculateDragDescriptor(flo, draggedView, targetUnderMouse, point, context) {
|
||||
// check if it's a tap being dragged
|
||||
var source = draggedView.model;
|
||||
if ((targetUnderMouse instanceof joint.dia.Element) && source.attr('metadata/name') === 'tap') { // jshint ignore:line
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: draggedView.model,
|
||||
},
|
||||
target: {
|
||||
cell: targetUnderMouse,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Find closest port
|
||||
var range = 30;
|
||||
var graph = flo.getGraph();
|
||||
var paper = flo.getPaper();
|
||||
var closestData;
|
||||
var minDistance = Number.MAX_VALUE;
|
||||
var maxIcomingLinks = draggedView.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var maxOutgoingLinks = draggedView.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var hasIncomingPort = typeof maxIcomingLinks !== 'number' || maxIcomingLinks > 0;
|
||||
var hasOutgoingPort = typeof maxOutgoingLinks !== 'number' || maxOutgoingLinks > 0;
|
||||
if (!hasIncomingPort && !hasOutgoingPort) {
|
||||
return;
|
||||
}
|
||||
var elements = graph.findModelsInArea(joint.g.rect(point.x - range, point.y - range, 2 * range, 2 * range)); // jshint ignore:line
|
||||
if (Array.isArray(elements)) {
|
||||
elements.forEach(function(model) {
|
||||
var view = paper.findViewByModel(model);
|
||||
if (view && view !== draggedView && model instanceof joint.dia.Element) { // jshint ignore:line
|
||||
var targetMaxIcomingLinks = view.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var targetMaxOutgoingLinks = view.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var targetHasIncomingPort = typeof targetMaxIcomingLinks !== 'number' || targetMaxIcomingLinks > 0;
|
||||
var targetHasOutgoingPort = typeof targetMaxOutgoingLinks !== 'number' || targetMaxOutgoingLinks > 0;
|
||||
if (view.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (targetHasIncomingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (targetHasOutgoingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
if (draggedView.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (hasIncomingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (hasOutgoingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
view.$('[magnet]').each(function(index, magnet) {
|
||||
var type = magnet.getAttribute('type');
|
||||
if ((type === 'input' && targetHasIncomingPort && hasOutgoingPort) || (type === 'output' && targetHasOutgoingPort && hasIncomingPort)) {
|
||||
var bbox = joint.V(magnet).bbox(false, paper.viewport); // jshint ignore:line
|
||||
var distance = point.distance({
|
||||
x: bbox.x + bbox.width / 2,
|
||||
y: bbox.y + bbox.height / 2
|
||||
});
|
||||
if (distance < range && distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestData = {
|
||||
context: context,
|
||||
source: {
|
||||
cell: draggedView.model,
|
||||
selector: type === 'output' ? '.input-port' : '.output-port'
|
||||
},
|
||||
target: {
|
||||
cell: model,
|
||||
selector: '.' + type+'-port'
|
||||
},
|
||||
range: minDistance
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (closestData) {
|
||||
return closestData;
|
||||
}
|
||||
|
||||
// Check if drop on a link is allowed
|
||||
if (targetUnderMouse instanceof joint.dia.Link && !(source.attr('metadata/constraints/xorSourceSink') || source.attr('metadata/constraints/maxOutgoingLinksNumber') === 0 || source.attr('metadata/constraints/maxIncomingLinksNumber') === 0) && graph.getConnectedLinks(source).length === 0) { // jshint ignore:line
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
target: {
|
||||
cell: targetUnderMouse
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function validateNode(flo, element) {
|
||||
var errors = [];
|
||||
var graph = flo.getGraph();
|
||||
var constraints = element.attr('metadata/constraints');
|
||||
if (constraints) {
|
||||
var incoming = graph.getConnectedLinks(element, {inbound: true});
|
||||
var outgoing = graph.getConnectedLinks(element, {outbound: true});
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' || typeof constraints.minIncomingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' && constraints.maxIncomingLinksNumber < incoming.length) {
|
||||
if (constraints.maxIncomingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sources must appear at the start of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of incoming links is ' + constraints.maxIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minIncomingLinksNumber === 'number' && constraints.minIncomingLinksNumber > incoming.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of incoming links is ' + constraints.minIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' || typeof constraints.minOutgoingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' && constraints.maxOutgoingLinksNumber < outgoing.length) {
|
||||
if (constraints.maxOutgoingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sinks must appear at the end of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of outgoing links is ' + constraints.maxOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minOutgoingLinksNumber === 'number' && constraints.minOutgoingLinksNumber > outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of outgoing links is ' + constraints.minOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (constraints.xorSourceSink && incoming.length && outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Node can either have incoming or outgoing links, but not both',
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!element.attr('metadata') || element.attr('metadata/unresolved')) {
|
||||
var msg = 'Unknown element \'' + element.attr('metadata/name') + '\'';
|
||||
if (element.attr('metadata/group')) {
|
||||
msg += ' from group \'' + element.attr('metadata/group') + '\'.';
|
||||
}
|
||||
errors.push({
|
||||
message: msg,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
|
||||
// If possible, verify the properties specified match those allowed on this type of element
|
||||
// propertiesRanges are the ranges for each property included the entire '--name=value'.
|
||||
// The format of a range is {'start':{'ch':NNNN,'line':NNNN},'end':{'ch':NNNN,'line':NNNN}}
|
||||
var propertiesRanges = element.attr('propertiesranges');
|
||||
if (propertiesRanges) {
|
||||
var moduleSchema = element.attr('metadata');
|
||||
// Grab the list of supported properties for this module type
|
||||
moduleSchema.get('properties').then(function(moduleSchemaProperties) {
|
||||
if (!moduleSchemaProperties) {
|
||||
moduleSchemaProperties = {};
|
||||
}
|
||||
// Example moduleSchemaProperties:
|
||||
// {"host":{"name":"host","type":"String","description":"the hostname of the mail server","defaultValue":"localhost","hidden":false},
|
||||
// "password":{"name":"password","type":"String","description":"the password to use to connect to the mail server ","defaultValue":null,"hidden":false}
|
||||
var specifiedProperties = element.attr('props');
|
||||
Object.keys(specifiedProperties).forEach(function(propertyName) {
|
||||
if (!moduleSchemaProperties[propertyName]) {
|
||||
// The schema does not mention that property
|
||||
var propertyRange = propertiesRanges[propertyName];
|
||||
if (propertyRange) {
|
||||
errors.push({
|
||||
message: 'unrecognized option \''+propertyName+'\' for module \''+element.attr('metadata/name')+'\'',
|
||||
range: propertyRange
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function moveNodeOnNode(flo, node, pivotNode, side, shouldRepairDamage) {
|
||||
side = side || 'left';
|
||||
if (canSwap(flo, node, pivotNode, side)) {
|
||||
var link;
|
||||
var i;
|
||||
if (side === 'left') {
|
||||
var sources = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {inbound: true}).length > 0 || graph.getConnectedLinks(node, {outbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotTargetLinks = flo.getGraph().getConnectedLinks(pivotNode, {inbound: true});
|
||||
for (i = 0; i < pivotTargetLinks.length; i++) {
|
||||
link = pivotTargetLinks[i];
|
||||
sources.push(link.get('source').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({
|
||||
'id': sources[i],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': pivotNode.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
} else if (side === 'right') {
|
||||
var targets = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {outbound: true}).length > 0 || graph.getConnectedLinks(node, {inbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotSourceLinks = flo.getGraph().getConnectedLinks(pivotNode, {outbound: true});
|
||||
for (i = 0; i < pivotSourceLinks.length; i++) {
|
||||
link = pivotSourceLinks[i];
|
||||
targets.push(link.get('target').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': targets[i],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': pivotNode.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveNodeOnLink(flo, node, link, shouldRepairDamage) {
|
||||
var source = link.get('source').id;
|
||||
var target = link.get('target').id;
|
||||
|
||||
if (shouldRepairDamage) {
|
||||
repairDamage(flo, node);
|
||||
}
|
||||
link.remove();
|
||||
|
||||
if (source) {
|
||||
flo.createLink({
|
||||
'id': source,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
if (target) {
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': target,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function repairDamage(flo, node) {
|
||||
/*
|
||||
* remove incoming, outgoing links and cache their sources and targets not equal to current node
|
||||
*/
|
||||
var sources = [];
|
||||
var targets = [];
|
||||
var i = 0;
|
||||
var links = flo.getGraph().getConnectedLinks(node);
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var targetId = links[i].get('target').id;
|
||||
var sourceId = links[i].get('source').id;
|
||||
if (targetId === node.id) {
|
||||
links[i].remove();
|
||||
sources.push(sourceId);
|
||||
} else if (sourceId === node.id) {
|
||||
links[i].remove();
|
||||
targets.push(targetId);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* best attempt to connect source and targets bypassing the node
|
||||
*/
|
||||
if (sources.length === 1) {
|
||||
var source = sources[0];
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({
|
||||
'id': source,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': targets[i],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
} else if (targets.length === 1) {
|
||||
var target = targets[0];
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({
|
||||
'id': sources[i],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': target,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if node being dropped and drop target node next to each other such that they won't be swapped by the drop
|
||||
*/
|
||||
function canSwap(flo, dropee, target, side) {
|
||||
var i, targetId, sourceId, noSwap = (dropee.id === target.id);
|
||||
if (dropee === target) {
|
||||
$log.debug('What!??? Dragged == Dropped!!! id = ' + target);
|
||||
}
|
||||
var links = flo.getGraph().getConnectedLinks(dropee);
|
||||
for (i = 0; i < links.length && !noSwap; i++) {
|
||||
targetId = links[i].get('target').id;
|
||||
sourceId = links[i].get('source').id;
|
||||
noSwap = (side === 'left' && targetId === target.id && sourceId === dropee.id) || (side === 'right' && targetId === dropee.id && sourceId === target.id);
|
||||
}
|
||||
return !noSwap;
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandles': createHandles,
|
||||
'validatePort': validatePort,
|
||||
'validateLink': validateLink,
|
||||
'calculateDragDescriptor': calculateDragDescriptor,
|
||||
'handleNodeDropping': handleNodeDropping,
|
||||
'validateNode': validateNode,
|
||||
'preDelete': preDelete,
|
||||
'interactive': {
|
||||
'vertexAdd': false
|
||||
},
|
||||
'allowLinkVertexEdit': false
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
var angular = require('angular');
|
||||
require('flo');
|
||||
var app = angular.module('floSiApp', [ 'spring.flo' ]);
|
||||
app.factory('SampleMetamodelService', require('metamodel-service'));
|
||||
app.factory('SampleRenderService', require('render-service'));
|
||||
app.factory('SampleEditorService', require('editor-service'));
|
||||
|
||||
app.controller('SiController', ['$scope', '$http', 'SampleMetamodelService', function($scope, $http, metamodelService) {
|
||||
$scope.endpoint = 'http://localhost:8080/integration';
|
||||
$scope.labelpath = "stats.sendcount";
|
||||
$scope.refreshrate=0;
|
||||
|
||||
var refreshTimer;
|
||||
|
||||
$scope.load = function(endpoint) {
|
||||
console.log("Loading from endpoint: '"+endpoint+"'");
|
||||
$scope.endpoint = endpoint;
|
||||
// Load the graph from the endpoint
|
||||
$http.get(endpoint, { }).success(function(json) {
|
||||
// console.log("JSON is "+json);
|
||||
// console.log("it is "+$('#endpoint').val());
|
||||
// $('#flow-definition').val('foo');
|
||||
$scope.definition.text = JSON.stringify(json);
|
||||
$scope.flo.updateGraphRepresentation();
|
||||
}).error(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateRefreshRate = function(newRefreshRate) {
|
||||
console.log("Update refresh rate: '"+newRefreshRate+"'");
|
||||
$scope.refreshrate=newRefreshRate;
|
||||
if (refreshTimer) {
|
||||
clearTimeout(refreshTimer);
|
||||
}
|
||||
if (newRefreshRate >0) {
|
||||
if (newRefreshRate < 250) {
|
||||
$scope.refreshrate = 250;
|
||||
}
|
||||
var refresher = function() {
|
||||
refresh();
|
||||
refreshTimer = setTimeout(function() { refresher() }, $scope.refreshrate);
|
||||
}
|
||||
refreshTimer = setTimeout(refresher, $scope.refreshrate);
|
||||
} else {
|
||||
$scope.refreshrate=0;
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
$http.get($scope.endpoint, { }).success(function(json) {
|
||||
metamodelService.updateGraphLabels($scope.flo, JSON.stringify(json), $scope.labelpath);
|
||||
}).error(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.updateLabelPath = function(newLabelPath) {
|
||||
console.log("Update label path: '"+newLabelPath+"'");
|
||||
$scope.labelpath = newLabelPath;
|
||||
// Update the graph from the endpoint
|
||||
$http.get($scope.endpoint, { }).success(function(json) {
|
||||
metamodelService.updateGraphLabels($scope.flo, JSON.stringify(json), newLabelPath);
|
||||
}).error(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
}]).directive('ngEnter', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.bind("keydown keypress", function(event) {
|
||||
if(event.which === 13) {
|
||||
scope.$apply(function(){
|
||||
scope.$eval(attrs.ngEnter);
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
return app;
|
||||
});
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a graph to a text representation.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
// Graph
|
||||
var g;
|
||||
|
||||
// Number of Links left to visit
|
||||
var numberOfLinksToVisit;
|
||||
|
||||
// Number of nodes left to visit
|
||||
var numberOfNodesToVisit;
|
||||
|
||||
// Map of links left to visit indexed by id
|
||||
var linksToVisit;
|
||||
|
||||
// Map of nodes left to visit indexed by id
|
||||
var nodesToVisit;
|
||||
|
||||
// Map of nodes incoming non-visited links degrees index by node id
|
||||
var nodesInDegrees;
|
||||
|
||||
// Priority:
|
||||
// 1. find links whose source has no other links pointing at it
|
||||
// 2. find links whose source has already been processed (not currently needed in sample DSL since
|
||||
// can't create graphs like that due to metamodel constraints)
|
||||
// 3. find remaining links
|
||||
function nextLink() {
|
||||
var indegree = Number.MAX_INT;
|
||||
var currentBest;
|
||||
for (var id in linksToVisit) {
|
||||
var link = g.getCell(id);
|
||||
var source = g.getCell(link.get('source').id);
|
||||
var currentInDegree = nodesInDegrees[source.get('id')];
|
||||
if (currentInDegree === 0) {
|
||||
return visit(link);
|
||||
} else if (indegree > currentInDegree) {
|
||||
indegree = currentInDegree;
|
||||
currentBest = link;
|
||||
}
|
||||
}
|
||||
if (currentBest) {
|
||||
return visit(currentBest);
|
||||
}
|
||||
}
|
||||
|
||||
function visit(e) {
|
||||
if (e.isLink()) {
|
||||
delete linksToVisit[e.get('id')];
|
||||
nodesInDegrees[e.get('target').id]--;
|
||||
numberOfLinksToVisit--;
|
||||
} else {
|
||||
delete nodesToVisit[e.get('id')];
|
||||
numberOfNodesToVisit--;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
function init(graph) {
|
||||
numberOfLinksToVisit = 0;
|
||||
numberOfNodesToVisit = 0;
|
||||
linksToVisit = {};
|
||||
nodesToVisit = {};
|
||||
nodesInDegrees = {};
|
||||
g = graph;
|
||||
g.getElements().forEach(function(element) {
|
||||
if (element.attr('metadata/name')) { // is it a node?
|
||||
nodesToVisit[element.get('id')] = element;
|
||||
var indegree = 0;
|
||||
g.getConnectedLinks(element, {inbound: true}).forEach(function(link) {
|
||||
if (link.get('source') && link.get('source').id && g.getCell(link.get('source').id) &&
|
||||
g.getCell(link.get('source').id).attr('metadata/name')) {
|
||||
linksToVisit[link.get('id')] = link;
|
||||
numberOfLinksToVisit++;
|
||||
indegree++;
|
||||
}
|
||||
});
|
||||
nodesInDegrees[element.get('id')] = indegree;
|
||||
numberOfNodesToVisit++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts at a link and proceeds down a chain. Converts each node to
|
||||
* text and then joins them with a ' > '.
|
||||
*/
|
||||
function chainToText(link) {
|
||||
var text = '';
|
||||
var source = g.getCell(link.get('source').id);
|
||||
text += nodeToText(source, true);
|
||||
while (link) {
|
||||
var target = g.getCell(link.get('target').id);
|
||||
text += ' > ';
|
||||
text += nodeToText(target, false);
|
||||
|
||||
// Find next not visited link to follow
|
||||
link = null;
|
||||
var outgoingLinks = g.getConnectedLinks(target, {outbound: true});
|
||||
for (var i = 0; i < outgoingLinks.length && !link; i++) {
|
||||
if (linksToVisit[outgoingLinks[i].get('id')]) {
|
||||
source = target;
|
||||
link = visit(outgoingLinks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Very basic format. From a node to the text:
|
||||
* "name --key=value --key=value"
|
||||
*/
|
||||
function nodeToText(element) {
|
||||
var text = '';
|
||||
var props = element.attr('props');
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
text += element.attr('metadata/name');
|
||||
if (props) {
|
||||
Object.keys(props).forEach(function(propertyName) {
|
||||
text += ' --' + propertyName + '=' + props[propertyName];
|
||||
});
|
||||
}
|
||||
visit(element);
|
||||
return text;
|
||||
}
|
||||
|
||||
function appendChainText(text, chainText) {
|
||||
if (chainText) {
|
||||
if (text) {
|
||||
text += '\n';
|
||||
}
|
||||
text += chainText;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Translate a graph into a basic string
|
||||
return function(g) {
|
||||
var text = '';
|
||||
var chainText;
|
||||
var id;
|
||||
init(g);
|
||||
while (numberOfLinksToVisit) {
|
||||
chainText = chainToText(nextLink());
|
||||
text = appendChainText(text, chainText);
|
||||
}
|
||||
// Visit all disconnected nodes
|
||||
for (id in nodesToVisit) {
|
||||
chainText = nodeToText(nodesToVisit[id], true);
|
||||
text = appendChainText(text, chainText);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
requirejs.config({
|
||||
baseUrl:'js',
|
||||
paths: {
|
||||
joint: '/webjars/jointjs/dist/joint',
|
||||
backbone: '/webjars/backbone/backbone',
|
||||
domReady: '/webjars/requirejs-domready/domReady',
|
||||
angular: '/webjars/angular/angular',
|
||||
jquery: '/webjars/jquery/dist/jquery',
|
||||
bootstrap:'/webjars/bootstrap/bootstrap',
|
||||
lodash: '/webjars/lodash/lodash', // lodash.compat
|
||||
dagre: '/webjars/dagre/dist/dagre.core',
|
||||
graphlib: '/webjars/graphlib/graphlib.core',
|
||||
text : '/webjars/requirejs-text/text',
|
||||
flo : '/webjars/spring-flo/dist/spring-flo',
|
||||
json5 : '/webjars/json5/json5'
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
// Backbone requires underscore. This forces requireJS to load lodash instead:
|
||||
'underscore': 'lodash'
|
||||
}
|
||||
},
|
||||
packages: [
|
||||
{
|
||||
name: 'codemirror',
|
||||
location: '../lib/codemirror',
|
||||
main: 'lib/codemirror'
|
||||
}
|
||||
],
|
||||
shim: {
|
||||
angular: {
|
||||
deps: ['bootstrap'],
|
||||
exports: 'angular'
|
||||
},
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
graphlib: {
|
||||
deps: ['underscore']
|
||||
},
|
||||
dagre: {
|
||||
deps: ['graphlib', 'underscore']
|
||||
},
|
||||
joint: {
|
||||
deps: ['jquery', 'underscore', 'backbone'],
|
||||
},
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
'flo': {
|
||||
deps: ['angular', 'jquery', 'joint', 'underscore']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
define(['require','angular'], function (require, angular) {
|
||||
'use strict';
|
||||
require(['domReady!', 'flosi-app'],
|
||||
function (document) {
|
||||
angular.bootstrap(document, ['floSiApp']);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
[
|
||||
{
|
||||
'name':'general', 'group':'general', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'service-activator', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'message-handler', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'gateway', 'group':'messaging-endpoints', 'description':'Gateway',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'splitter', 'group':'routing', 'description':'Splitter',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'router', 'group':'routing', 'description':'Router',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'transformer', 'group':'transformation', 'description':'Transformer',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'bridge', 'group':'routing', 'description':'Bridge',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'channel', 'group':'connectors', 'description':'Channel',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'publish-subscribe-channel', 'group':'connectors', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'test-producer', 'group':'producers', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'BareHandler', 'group':'producers', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
},
|
||||
{
|
||||
'name':'filter', 'group':'routing', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'aggregator', 'group':'routing', 'description':'Produce an output message after correlating some set of incoming messages',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'logging-channel-adapter', 'group':'connectors', 'description':'?',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'chain', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'inbound-channel-adapter', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'outbound-channel-adapter', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
]
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
require('json5');
|
||||
|
||||
var convertGraphToText = require('graph-to-text');
|
||||
var convertTextToGraph = require('text-to-graph');
|
||||
var updateGraph = require('update-graph');
|
||||
|
||||
return ['$http', '$q', '$timeout', '$log', 'MetamodelUtils', function($http, $q, $timeout, $log, metamodelUtils) {
|
||||
|
||||
var metamodel;
|
||||
|
||||
// Internally stored metamodel load promise
|
||||
var request;
|
||||
|
||||
var statsProperties = [
|
||||
{'name':'name','default':'?', 'description':'name'},
|
||||
{'name':'id','default':'?', 'description':'node id'},
|
||||
{'name':'componentType','default':'','description':'Detailed component type'},
|
||||
{'name':'stats.loggingEnabled', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.statsEnabled', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.countsEnabled', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.count', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.min', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.max', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.mean', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.standardDeviation', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.countLong', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.count', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.min', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.max', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.mean', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.standardDeviation', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.countLong', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendCount', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendErrorCount', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.timeSinceLastSend', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanSendRate', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanErrorRate', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanErrorRatio', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.minSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.maxSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.standardDeviationSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.count', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.min', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.max', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.mean', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.standardDeviation', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.countLong', 'default': '?', 'description':'?' }];
|
||||
|
||||
/**
|
||||
* Helper that goes from basic JSON to a lazy getter structure. Useful when the
|
||||
* metamodel is 'cheap' to build. If it is costly to discover the actual properties
|
||||
* the getter may be more complex (e.g. make a REST request).
|
||||
*/
|
||||
function createMetadata(entry) {
|
||||
var props = {};
|
||||
if (!entry.properties) {
|
||||
// use the default stats properties
|
||||
entry.properties = JSON.parse(JSON.stringify(statsProperties));
|
||||
}
|
||||
if (Array.isArray(entry.properties)) {
|
||||
entry.properties.forEach(function(property) {
|
||||
if (!property.id) {
|
||||
property.id = property.name;
|
||||
}
|
||||
props[property.id] = property;
|
||||
});
|
||||
}
|
||||
entry.properties = props;
|
||||
return {
|
||||
name: entry.name,
|
||||
group: entry.group,
|
||||
icon: entry.icon,
|
||||
constraints: entry.constraints,
|
||||
description: entry.description,
|
||||
metadata: entry.metadata,
|
||||
properties: entry.properties,
|
||||
get: function(property) {
|
||||
var deferred = $q.defer();
|
||||
if (entry.hasOwnProperty(property)) {
|
||||
deferred.resolve(entry[property]);
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function load() {
|
||||
// COULDDO: to cache the result here, check result before doing this processing
|
||||
// and simply return it if it is set. If doing that may want to override refresh
|
||||
// in this service
|
||||
var metamodelData = JSON5.parse(require('text!metamodel-sample.json'));
|
||||
var deferred = $q.defer();
|
||||
var newData = {};
|
||||
metamodelData.forEach(function(data) {
|
||||
var metadata = createMetadata(data);
|
||||
if (!newData[metadata.group]) {
|
||||
newData[metadata.group] = {};
|
||||
}
|
||||
newData[metadata.group][metadata.name] = metadata;
|
||||
});
|
||||
metamodel = newData;
|
||||
deferred.resolve(metamodel);
|
||||
request = deferred.promise;
|
||||
return request;
|
||||
}
|
||||
|
||||
function graphToText(flo, definition) {
|
||||
definition.text = convertGraphToText(flo.getGraph());
|
||||
}
|
||||
|
||||
function updateGraphLabels(flo, text, labelpath) {
|
||||
updateGraph(text, flo.getGraph(), labelpath);
|
||||
}
|
||||
|
||||
function textToGraph(flo, definition) {
|
||||
// TODO perhaps push these flo operations into the 'caller' to make this simpler
|
||||
flo.getGraph().clear();
|
||||
load().then(function(metamodel) {
|
||||
convertTextToGraph(definition.text, flo, metamodel, metamodelUtils);
|
||||
updateGraph(definition.text,flo.getGraph(),'stats.sendcount');
|
||||
flo.performLayout();
|
||||
flo.fitToPage();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
'load': load,
|
||||
'textToGraph': textToGraph,
|
||||
'updateGraphLabels': updateGraphLabels,
|
||||
'graphToText': graphToText
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,746 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var joint = require('joint');
|
||||
|
||||
var dagre = require('dagre');
|
||||
|
||||
var HANDLE_ICON_MAP = {
|
||||
'remove': 'icons/delete.svg',
|
||||
};
|
||||
|
||||
var DECORATION_ICON_MAP = {
|
||||
'error': 'icons/error.svg'
|
||||
};
|
||||
|
||||
var IMAGE_W = 120,
|
||||
IMAGE_H = 40;
|
||||
|
||||
var HORIZONTAL_PADDING = 10;
|
||||
|
||||
joint.shapes.si = {};
|
||||
|
||||
joint.routers.floforintegration = (function() {
|
||||
|
||||
// expands a box by specific value
|
||||
function expand(bbox, val) {
|
||||
return joint.g.rect(bbox).moveAndExpand({ x: -val, y: -val, width: 2 * val, height: 2 * val });
|
||||
}
|
||||
|
||||
function routeAround(exp, ref, anchor, opt) {
|
||||
|
||||
var anchorSide = exp.sideNearestToPoint(anchor);
|
||||
var expAnchor = exp.pointNearestToPoint(anchor);
|
||||
var line = joint.g.line(ref, anchor);
|
||||
var center = exp.center();
|
||||
var intersection;
|
||||
var pts = [];
|
||||
|
||||
if (anchorSide !== 'top') {
|
||||
intersection = line.intersection(joint.g.line(exp.origin(), exp.topRight()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'bottom') {
|
||||
if (intersection.x - exp.x + expAnchor.x - exp.x <= exp.width) {
|
||||
pts = [exp.origin(), exp.bottomLeft()];
|
||||
} else {
|
||||
pts = [exp.topRight(), exp.corner()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'left') {
|
||||
pts = [exp.origin()];
|
||||
} else {
|
||||
pts = [exp.topRight()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorSide !== 'bottom') {
|
||||
intersection = line.intersection(joint.g.line(exp.corner(), exp.bottomLeft()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'top') {
|
||||
if (intersection.x - exp.x + expAnchor.x - exp.x <= exp.width) {
|
||||
pts = [exp.bottomLeft(), exp.origin()];
|
||||
} else {
|
||||
pts = [exp.corner(), exp.topRight()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'left') {
|
||||
pts = [exp.bottomLeft()];
|
||||
} else {
|
||||
pts = [exp.corner()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorSide !== 'left') {
|
||||
intersection = line.intersection(joint.g.line(exp.origin(), exp.bottomLeft()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'right') {
|
||||
if (intersection.y - exp.y + expAnchor.y - exp.y <= exp.height) {
|
||||
pts = [exp.origin(), exp.topRight()];
|
||||
} else {
|
||||
pts = [exp.bottomLeft(), exp.corner()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'top') {
|
||||
pts = [exp.origin()];
|
||||
} else {
|
||||
pts = [exp.bottomLeft()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorSide !== 'right') {
|
||||
intersection = line.intersection(joint.g.line(exp.topRight(), exp.corner()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'left') {
|
||||
if (intersection.y - exp.y + expAnchor.y - exp.y <= exp.height) {
|
||||
pts = [exp.topRight(), exp.origin()];
|
||||
} else {
|
||||
pts = [exp.corner(), exp.bottomLeft()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'top') {
|
||||
pts = [exp.topRight()];
|
||||
} else {
|
||||
pts = [exp.corner()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
function findRoute(vx, opt, linkView) {
|
||||
|
||||
var vertices = opt.metro ? joint.routers.metro(vx, opt, linkView) : vx;
|
||||
var sourceRoute = [], targetRoute = [];
|
||||
|
||||
var paper = linkView.paper;
|
||||
var reference = vertices.length ? vertices[0] : joint.g.rect(linkView.targetBBox).center();
|
||||
var sourceAnchorPt = paper.options.linkConnectionPoint(linkView, linkView.sourceView, linkView.sourceMagnet, reference);
|
||||
var targetAnchorPt = paper.options.linkConnectionPoint(linkView, linkView.targetView, linkView.targetMagnet, sourceAnchorPt);
|
||||
var padding = opt.elementPadding || 20;
|
||||
|
||||
if (linkView.sourceView) {
|
||||
var expSource = expand(linkView.sourceView.model.getBBox(), padding);
|
||||
while (vertices.length && expSource.containsPoint(vertices[0])) {
|
||||
vertices.splice(0, 1);
|
||||
}
|
||||
var sourceRef = vertices.length ? joint.g.point(vertices[0]) : targetAnchorPt;
|
||||
sourceRoute = routeAround(expSource, sourceRef, sourceAnchorPt, opt).reverse();
|
||||
}
|
||||
if (linkView.targetView) {
|
||||
var expTarget = expand(linkView.targetView.model.getBBox(), padding);
|
||||
while (vertices.length && expTarget.containsPoint(vertices[vertices.length - 1])) {
|
||||
vertices.splice(vertices.length - 1, 1);
|
||||
}
|
||||
var targetRef = vertices.length ? joint.g.point(vertices[vertices.length - 1]) : sourceAnchorPt;
|
||||
|
||||
targetRoute = routeAround(expTarget, targetRef, targetAnchorPt, opt);
|
||||
}
|
||||
|
||||
return sourceRoute.concat(vertices).concat(targetRoute);
|
||||
};
|
||||
|
||||
return findRoute;
|
||||
|
||||
})();
|
||||
|
||||
joint.shapes.si.Channel = joint.shapes.basic.Generic.extend({
|
||||
|
||||
markup:
|
||||
'<g class="shape">'+
|
||||
'<rect class="border"/>' +
|
||||
'<path class="the_shape" d="M 0 10 H 100 A 8 10 0 0 1 100 30 H 0"/>'+
|
||||
'<ellipse class="the_shape" cx="0" cy="20" rx="8" ry="10"/>'+
|
||||
'<text class="label"/>'+
|
||||
'<text class="label2"/>'+
|
||||
'</g>' +
|
||||
// '<text class="stream-label"/>'+
|
||||
'<rect class="input-port" />'+
|
||||
'<rect class="output-port"/>'+
|
||||
'<circle class="tap-port"/>',
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
type: 'channel',//joint.shapes.flo.NODE_TYPE,
|
||||
position: {x: 0, y: 0},
|
||||
size: { width: 100, height: 40 },
|
||||
attrs: {
|
||||
'.': {
|
||||
magnet: false,
|
||||
},
|
||||
'.the_shape': {
|
||||
'stroke':'#000000'
|
||||
},
|
||||
// rounded edges around image
|
||||
// '.border': {
|
||||
// width: IMAGE_W,
|
||||
// height: IMAGE_H,
|
||||
// rx: 2,
|
||||
// ry: 2,
|
||||
// 'fill-opacity':0, // see through
|
||||
// stroke: '#eeeeee',
|
||||
// 'stroke-width': 0,
|
||||
// },
|
||||
// '.box': {
|
||||
// width: IMAGE_W,
|
||||
// height: IMAGE_H,
|
||||
// rx: 2,
|
||||
// ry: 2,
|
||||
// //'fill-opacity':0, // see through
|
||||
// stroke: '#6db33f',
|
||||
// fill: '#eeeeee',
|
||||
// 'stroke-width': 2,
|
||||
// },
|
||||
'.input-port': {
|
||||
type: 'input',
|
||||
port: 'input',
|
||||
r:4,
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + -2 + ',' + ((20/2)-2+10) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
'.output-port': {
|
||||
type: 'output',
|
||||
port: 'output',
|
||||
r:4,
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + (100+8-2) + ',' + ((20/2)-2+10) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
// '.tap-port': {
|
||||
// type: 'output',
|
||||
// port: 'tap',
|
||||
// r: 4,
|
||||
// magnet: true,
|
||||
// fill: '#eeeeee',
|
||||
// 'ref-x': 0.5,
|
||||
// 'ref-y': 0.99999999,
|
||||
// ref: '.border',
|
||||
// stroke: '#34302D'
|
||||
// },
|
||||
'.label': {
|
||||
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.525,
|
||||
'y-alignment': 'middle',
|
||||
'x-alignment' : 'middle',
|
||||
ref: '.the_shape', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'stroke': 'black',
|
||||
'font-size': '12px',
|
||||
'font-family': 'Ubuntu Mono',
|
||||
'color': 'black'
|
||||
},
|
||||
'.label2': {
|
||||
'y-alignment': 'middle',
|
||||
'ref-x': HORIZONTAL_PADDING+2, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.55, // jointjs specific: relative position to ref'd element
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'font-size': 20
|
||||
},
|
||||
// '.stream-label': {
|
||||
// 'x-alignment': 'middle',
|
||||
// 'y-alignment': -0.999999,
|
||||
// 'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
// 'ref-y': 0, // jointjs specific: relative position to ref'd element
|
||||
// ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
// fill: '#AAAAAA',
|
||||
// 'font-size': 15
|
||||
// },
|
||||
// '.shape': {
|
||||
// }
|
||||
}
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
|
||||
joint.shapes.si.Node = joint.shapes.basic.Generic.extend({
|
||||
markup:
|
||||
'<g class="shape"><image class="image" /></g>'+
|
||||
'<rect class="border-white"/>' +
|
||||
'<rect class="border"/>' +
|
||||
'<rect class="box"/>'+
|
||||
'<text class="label"/>'+
|
||||
'<text class="label2"></text>'+
|
||||
'<rect class="input-port" />'+
|
||||
'<rect class="error-port" />'+
|
||||
'<rect class="output-port"/>'+
|
||||
'<rect class="output-port-cover"/>',
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
|
||||
type: 'node',//joint.shapes.flo.NODE_TYPE,
|
||||
position: {x: 0, y: 0},
|
||||
size: { width: IMAGE_W, height: IMAGE_H },
|
||||
attrs: {
|
||||
'.': { magnet: false },
|
||||
// rounded edges around image
|
||||
'.border': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
'fill-opacity':0, // see through
|
||||
stroke: '#eeeeee',
|
||||
'stroke-width': 0
|
||||
},
|
||||
|
||||
'.box': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
//'fill-opacity':0, // see through
|
||||
stroke: '#6db33f',
|
||||
fill: '#eeeeee',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.input-port': {
|
||||
type: 'input',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + -2 + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.output-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + (IMAGE_W-2) + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.error-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#ff0000',
|
||||
transform: 'translate(' + (IMAGE_W/2-2) + ',' + ((IMAGE_H)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.label': {
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
// 'ref-y': -12, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.3,
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'font-size': 14
|
||||
},
|
||||
'.label2': {
|
||||
'text': '\u21d2',
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.15, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.15, // jointjs specific: relative position to ref'd element
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
transform: 'translate(' + (IMAGE_W/2) + ',' + (IMAGE_H/2) + ')',
|
||||
fill: 'black',
|
||||
'font-size': 24
|
||||
},
|
||||
'.shape': {
|
||||
},
|
||||
'.image': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H
|
||||
}
|
||||
}
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
joint.shapes.si.ServiceActivator = joint.shapes.basic.Generic.extend({
|
||||
markup:
|
||||
'<g class="shape"><image class="image" /></g>'+
|
||||
'<rect class="border-white"/>' +
|
||||
'<rect class="border"/>' +
|
||||
'<rect class="box"/>'+
|
||||
'<text class="label"/>'+
|
||||
'<text class="label2"></text>'+
|
||||
'<rect class="input-port" />'+
|
||||
'<rect class="error-port" />'+
|
||||
'<rect class="output-port"/>'+
|
||||
'<rect class="output-port-cover"/>'+
|
||||
'<g transform="scale(0.4)">'+
|
||||
'<path class="blockSolid" d="M 43 20 l 6 6 l 6 -6 l -6 -6 l -6 6"/>'+
|
||||
'<path class="blockEmpty" d="M 86 20 l 6 6 l 6 -6 l -6 -6 l -6 6"/>'+
|
||||
'<path class="arrowGray" d="M 0 20 h 33 l 0 -5 l 7 5 l -7 5 l 0 -5"/>'+
|
||||
'<path class="arrowBlack" d="M 52 20 h 26 l 0 -5 l 7 5 l -7 5 l 0 -5"/>'+
|
||||
'</g>'
|
||||
,
|
||||
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
|
||||
type: 'node',//joint.shapes.flo.NODE_TYPE,
|
||||
position: {x: 0, y: 0},
|
||||
size: { width: IMAGE_W, height: IMAGE_H },
|
||||
attrs: {
|
||||
'.': { magnet: false },
|
||||
// rounded edges around image
|
||||
'.border': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
'fill-opacity':0, // see through
|
||||
stroke: '#eeeeee',
|
||||
'stroke-width': 0
|
||||
},
|
||||
'.arrowGray': {
|
||||
'stroke':'#aaaaaa',
|
||||
'stroke-width':'2',
|
||||
'fill': '#aaaaaa'
|
||||
},
|
||||
'.arrowBlack': {
|
||||
'stroke':'black',
|
||||
'stroke-width':'2',
|
||||
'fill': 'black'
|
||||
},
|
||||
'.blockSolid': {
|
||||
'stroke':'#000000',
|
||||
'stroke-width':'2',
|
||||
'fill': 'black'
|
||||
},
|
||||
'.blockEmpty': {
|
||||
'stroke':'#000000',
|
||||
'stroke-width':'2',
|
||||
'fill': 'white'
|
||||
},
|
||||
'.box': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
//'fill-opacity':0, // see through
|
||||
stroke: '#6db33f',
|
||||
fill: '#eeeeee',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.input-port': {
|
||||
type: 'input',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + -2 + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.output-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + (IMAGE_W-2) + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.error-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#ff0000',
|
||||
transform: 'translate(' + (IMAGE_W/2-2) + ',' + ((IMAGE_H)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.label': {
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
// 'ref-y': -12, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.3,
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'font-size': 14
|
||||
},
|
||||
'.label2': {
|
||||
'text': '\u21d2',
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.15, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.15, // jointjs specific: relative position to ref'd element
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
transform: 'translate(' + (IMAGE_W/2) + ',' + (IMAGE_H/2) + ')',
|
||||
fill: 'black',
|
||||
'font-size': 24
|
||||
},
|
||||
'.shape': {
|
||||
},
|
||||
'.image': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H
|
||||
}
|
||||
}
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
|
||||
return ['$log', function($log) {
|
||||
|
||||
function createHandle(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 10, height: 10},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': HANDLE_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDecoration(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 16, height: 16},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': DECORATION_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createNode(metadata, props) {
|
||||
if (metadata.name === 'channel' || metadata.name === 'publish-subscribe-channel') {
|
||||
return new joint.shapes.si.Channel();
|
||||
} else if (metadata.name === 'service-activator') {
|
||||
return new joint.shapes.si.ServiceActivator();
|
||||
} else {
|
||||
return new joint.shapes.si.Node();
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNewNode(node, context) {
|
||||
var metadata = node.attr('metadata');
|
||||
if (metadata) {
|
||||
node.attr('.label/text', node.attr('metadata/name'));
|
||||
if (node.attr('metadata/constraints/maxIncomingLinksNumber') === 0) {
|
||||
node.attr('.input-port/display','none');
|
||||
}
|
||||
if (node.attr('metadata/constraints/maxOutgoingLinksNumber') === 0) {
|
||||
node.attr('.output-port/display','none');
|
||||
}
|
||||
|
||||
var type = node.attr('metadata/name');
|
||||
if (type === 'tap') {
|
||||
if (!node.attr('props/channel')) {
|
||||
node.attr('props/channel', 'tap:stream:STREAM');
|
||||
}
|
||||
refreshVisuals(node, 'props/channel', context.paper);
|
||||
} else if (type === 'named-channel') {
|
||||
// Default channel for named channel is 'queue:default'
|
||||
if (!node.attr('props/channel')) {
|
||||
node.attr('props/channel', 'queue:default');
|
||||
}
|
||||
refreshVisuals(node, 'props/channel', context.paper);
|
||||
}
|
||||
}
|
||||
node.attr('.label2/text','');
|
||||
|
||||
}
|
||||
|
||||
function validateNode(flo, node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function fitLabel(paper, node, labelPath) {
|
||||
var label = node.attr(labelPath);
|
||||
if (label && label.length<9) {
|
||||
return;
|
||||
}
|
||||
var view = paper.findViewByModel(node);
|
||||
if (view && label) {
|
||||
var textView = view.findBySelector(labelPath.substr(0, labelPath.indexOf('/')))[0];
|
||||
var offset = 0;
|
||||
if (node.attr('.label2/text')) {
|
||||
var label2View = view.findBySelector('.label2')[0];
|
||||
if (label2View) {
|
||||
var box = joint.V(label2View).bbox(false, paper.viewport);
|
||||
offset = HORIZONTAL_PADDING + box.width;
|
||||
}
|
||||
}
|
||||
var width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
var threshold = IMAGE_W - HORIZONTAL_PADDING - HORIZONTAL_PADDING - offset;
|
||||
if (offset) {
|
||||
node.attr('.label1/ref-x', Math.max((offset + HORIZONTAL_PADDING + width / 2) / IMAGE_W, 0.5), { silent: true });
|
||||
}
|
||||
// Trim package prefix
|
||||
|
||||
if (!label.endsWith('?')) {
|
||||
// console.log("modifying label "+label);
|
||||
// Sample name: com.foo.method(a.b.c.Order)
|
||||
var openParen = label.indexOf('(');
|
||||
if (openParen !== -1) {
|
||||
label = label.substring(0,openParen);
|
||||
}
|
||||
width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
if (width > threshold) {
|
||||
var lastDot = label.lastIndexOf('.');
|
||||
if (lastDot !== -1) {
|
||||
label = label.substring(lastDot+1);
|
||||
}
|
||||
console.log('driving label change');
|
||||
node.attr(labelPath, label, { silent: true });
|
||||
view.update();
|
||||
width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
for (var i = 1; i < label.length && width > threshold; i++) {
|
||||
node.attr(labelPath, label.substr(0, label.length - i) + '\u2026', { silent: true });
|
||||
view.update();
|
||||
width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
if (offset) {
|
||||
node.attr('.label1/ref-x', Math.max((offset + HORIZONTAL_PADDING + width / 2) / IMAGE_W, 0.5), { silent: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// view.update();
|
||||
}
|
||||
}
|
||||
|
||||
function createLink() {
|
||||
var link = new joint.shapes.flo.Link(joint.util.deepSupplement({
|
||||
router: { name: 'floforintegration', args: {elementPadding: 20/*, metro: true*/} },
|
||||
connector: { name: 'smooth' },
|
||||
attrs: {
|
||||
'.': {
|
||||
//filter: { name: 'dropShadow', args: { dx: 1, dy: 1, blur: 2 } }
|
||||
},
|
||||
'.connection': { 'stroke-width': 3, 'stroke': 'black', 'stroke-linecap': 'round' },
|
||||
'.marker-arrowheads': { display: 'none' },
|
||||
'.tool-options': { display: 'none' },
|
||||
'stroke':'red' // TODO necessary?
|
||||
},
|
||||
}, joint.shapes.flo.Link.prototype.defaults));
|
||||
return link;
|
||||
}
|
||||
|
||||
function isSemanticProperty(propertyPath) {
|
||||
return propertyPath === '.label/text';
|
||||
}
|
||||
|
||||
function refreshVisuals(element, changedPropertyPath, paper) {
|
||||
fitLabel(paper, element, '.label/text');
|
||||
}
|
||||
|
||||
function layout(paper) {
|
||||
var graph = paper.model;
|
||||
var i;
|
||||
var g = new dagre.graphlib.Graph();
|
||||
g.setGraph({});
|
||||
g.setDefaultEdgeLabel(function() {return{};});
|
||||
|
||||
var nodes = graph.getElements();
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (node.get('type') === joint.shapes.flo.NODE_TYPE) {
|
||||
g.setNode(node.id, node.get('size'));
|
||||
}
|
||||
}
|
||||
|
||||
var links = graph.getLinks();
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
if (link.get('type') === joint.shapes.flo.LINK_TYPE) {
|
||||
var options = {
|
||||
minlen: 1.5
|
||||
};
|
||||
// if (link.get('labels') && link.get('labels').length > 0) {
|
||||
// options.minlen = 1 + link.get('labels').length * 0.5;
|
||||
// }
|
||||
g.setEdge(link.get('source').id, link.get('target').id, options);
|
||||
link.set('vertices', []);
|
||||
}
|
||||
}
|
||||
|
||||
g.graph().rankdir = 'LR';
|
||||
dagre.layout(g);
|
||||
g.nodes().forEach(function(v) {
|
||||
var node = graph.getCell(v);
|
||||
if (node) {
|
||||
var bbox = node.getBBox();
|
||||
node.translate(g.node(v).x - bbox.x, g.node(v).y - bbox.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLinkAnchorPoint(linkView, view, magnet, reference) {
|
||||
if (magnet) {
|
||||
var cssClass = magnet.getAttribute('class');
|
||||
var bbox = joint.V(magnet).bbox(false, linkView.paper.viewport);
|
||||
var rect = joint.g.rect(bbox);
|
||||
if (cssClass.indexOf('input-port') !== -1) {
|
||||
return joint.g.point(rect.x, rect.y + rect.height / 2);
|
||||
} else if (cssClass.indexOf('error-port') !== -1) {
|
||||
return joint.g.point(rect.x + rect.width / 2, rect.y + rect.height);
|
||||
} else {
|
||||
return joint.g.point(rect.x + rect.width, rect.y + rect.height / 2);
|
||||
}
|
||||
} else {
|
||||
$log.debug('No magnet!');
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandle': createHandle,
|
||||
'createDecoration': createDecoration,
|
||||
'createNode': createNode,
|
||||
'createLink': createLink,
|
||||
'initializeNewNode': initializeNewNode,
|
||||
'isSemanticProperty': isSemanticProperty,
|
||||
'refreshVisuals': refreshVisuals,
|
||||
'layout': layout,
|
||||
'getLinkAnchorPoint': getLinkAnchorPoint
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,273 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a text representation to a graph.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
var joint = require('joint');
|
||||
|
||||
function collapseOneLevel(prefix, obj, collector) {
|
||||
var type = typeof obj;
|
||||
if (obj == null) {
|
||||
collector[prefix] = null;
|
||||
return;
|
||||
}
|
||||
if (type === 'object') {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+key,obj[key],collector);
|
||||
});
|
||||
} else if (type === 'array') {
|
||||
for (var i=0;i<obj.length;i++) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+i,obj[i],collector);
|
||||
}
|
||||
} else {
|
||||
collector[prefix] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
function collapse(obj, prefix) {
|
||||
if (!prefix) {
|
||||
prefix = '';
|
||||
}
|
||||
var retval = {};
|
||||
collapseOneLevel(prefix,obj,retval);
|
||||
console.log("collapsed = "+JSON.stringify(retval));
|
||||
return retval;
|
||||
}
|
||||
|
||||
var MAGNITUDE_NUMBERS = [ 1000000000, 1000000, 1000];
|
||||
var MAGNITUDE_LITERALS = ['B', 'M', 'K'];
|
||||
|
||||
var rateLabel = function() {
|
||||
var postFix, division, index = -1, fixed = 3;
|
||||
do {
|
||||
division = this.rate / MAGNITUDE_NUMBERS[++index];
|
||||
} while (!Math.floor(division) && index < MAGNITUDE_NUMBERS.length);
|
||||
if (index === MAGNITUDE_NUMBERS.length) {
|
||||
postFix = '';
|
||||
division = this.rate;
|
||||
} else {
|
||||
postFix = MAGNITUDE_LITERALS[index];
|
||||
}
|
||||
for (var decimal = 1; decimal <= 100 && Math.floor(division / decimal); decimal*=10) {
|
||||
fixed--;
|
||||
}
|
||||
return division.toFixed(fixed) + postFix;
|
||||
};
|
||||
|
||||
function animate(link,p) {
|
||||
// console.log("moving label on "+link.id+" to "+p);
|
||||
if (!link.label(1)) {
|
||||
console.log("No label1 on this link??");
|
||||
} else {
|
||||
link.label(1,{position: p})
|
||||
p+=0.025
|
||||
if (p>0.975) p = 0;
|
||||
setTimeout(function() {animate(link,p)},25);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return function(input, flo, metamodel, metamodelUtils) {
|
||||
// input is a string like this (3 nodes: foo, goo and hoo): foo --a=b --c=d > goo --d=e --f=g>hoo
|
||||
var trimmed = input.trim();
|
||||
if (trimmed.length===0) {
|
||||
return;
|
||||
}
|
||||
var getMetadata = function(type) {
|
||||
var group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
var md = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (!md || md.unresolved) {
|
||||
var secondAttempt;
|
||||
// Examples: mail:outbound-channel-adapter or file:inbound-channel-adapter
|
||||
if (type.indexOf("inbound-channel-adapter")!=-1) {
|
||||
type = "inbound-channel-adapter";
|
||||
group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
secondAttempt = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (secondAttempt && !secondAttempt.unresolved) {
|
||||
md = secondAttempt;
|
||||
}
|
||||
} else if (type.indexOf("outbound-channel-adapter")!=-1) {
|
||||
type = "outbound-channel-adapter";
|
||||
group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
secondAttempt = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (secondAttempt && !secondAttempt.unresolved) {
|
||||
md = secondAttempt;
|
||||
}
|
||||
} else {
|
||||
// use the general one - this will ensure validation is OK and tooltips work but
|
||||
// we aren't really sure what type it is.
|
||||
type = 'general';
|
||||
group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
secondAttempt = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (secondAttempt && !secondAttempt.unresolved) {
|
||||
md = secondAttempt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return md;
|
||||
}
|
||||
var integrationGraph = JSON.parse(input);
|
||||
var nodes = integrationGraph.nodes;
|
||||
var nodesMap = {};
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
var node = nodes[i];
|
||||
var stats = node.stats;
|
||||
var props = collapse(node.stats,'stats');
|
||||
var props2 = collapse(node.properties,'properties');
|
||||
for (var attrname in props2) { props[attrname] = props2[attrname]; }
|
||||
props.name = node.name;
|
||||
props.id = node.nodeId;
|
||||
var newNode = flo.createNode(getMetadata(node.componentType), props);
|
||||
var nodeName = node.name;
|
||||
var metadataName = newNode.attr('metadata').name;
|
||||
if (metadataName === 'splitter' && nodeName.endsWith('.splitter')) {
|
||||
nodeName = nodeName.substring(0,nodeName.length-'.splitter'.length);
|
||||
} else if (metadataName === 'aggregator' && nodeName.endsWith('.aggregator')) {
|
||||
nodeName = nodeName.substring(0,nodeName.length-'.aggregator'.length);
|
||||
} else if (metadataName === 'service-activator' && nodeName.endsWith('serviceActivator')) {
|
||||
nodeName = nodeName.substring(0,nodeName.length-'.serviceActivator'.length);
|
||||
}
|
||||
if (node.name.indexOf('ConsumerEndpointFactoryBean')!==-1) {
|
||||
if (metadataName === 'router' && props['properties.expression']) {
|
||||
nodeName = props['properties.expression']+'?';
|
||||
} else if (metadataName != 'general') {
|
||||
nodeName = metadataName;
|
||||
}
|
||||
}
|
||||
newNode.attr('props/componentType',node.componentType);
|
||||
// if (nodeName != node.componentType) {
|
||||
// // Don't lose the componentType. For example the nodeName might end up as mailOut but
|
||||
// // componentType is mail:outbound-channel-adapter
|
||||
// }
|
||||
newNode.attr('.label/text',nodeName);
|
||||
nodesMap[node.nodeId] = newNode;
|
||||
}
|
||||
var links = integrationGraph.links;
|
||||
for (var i=0;i<links.length;i++) {
|
||||
var link = links[i];
|
||||
var isErrorLink = false;
|
||||
var fromPort = '.output-port';
|
||||
var toName = nodesMap[link.to].attr('.label/text');
|
||||
var fromName = nodesMap[link.from].attr('.label/text');
|
||||
if (link.type == 'error') {
|
||||
fromPort = '.error-port';
|
||||
isErrorLink=true;
|
||||
}
|
||||
var jointLink = flo.createLink({'id': nodesMap[link.from].id,'selector': fromPort},
|
||||
{'id': nodesMap[link.to].id, 'selector': '.input-port'});
|
||||
if (isErrorLink) {
|
||||
jointLink.attr('.connection/stroke','red');
|
||||
} else {
|
||||
if (nodes[link.from-1].stats && nodes[link.from-1].stats.hasOwnProperty('sendCount')) {
|
||||
// jointLink.label(0, {
|
||||
// position: 15,
|
||||
// type: 'outgoing-rate',
|
||||
// // rate: sourceRates.outgoingRate,
|
||||
// attrs: {
|
||||
// text: {
|
||||
// transform: 'translate(0, -8)',
|
||||
// //text: '{{rateLabel()}}',
|
||||
// text: nodes[link.from-1].stats.sendCount,
|
||||
// 'fill': 'black',
|
||||
// 'stroke': 'none',
|
||||
// 'font-size': '12'
|
||||
// },
|
||||
// rect: {
|
||||
// display: 'none'
|
||||
//// transform: 'translate(0, -5)',
|
||||
//// stroke: 'black',
|
||||
//// rx:1,ry:1,
|
||||
//// 'border-width': '2px',
|
||||
//// 'stroke-width': 1,
|
||||
//// fill: '#00B0A7'
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// jointLink.label(1, {
|
||||
// position: 0.5,
|
||||
// type: 'message',
|
||||
// // rate: sourceRates.outgoingRate,
|
||||
// attrs: {
|
||||
// text: {
|
||||
// transform: 'translate(0, 0)',
|
||||
// //text: '{{rateLabel()}}',
|
||||
// text: ' ',
|
||||
// 'fill': 'black',
|
||||
// 'stroke': 'black',
|
||||
// 'font-size': '2'
|
||||
// },
|
||||
// rect: {
|
||||
// transform: 'translate(0, 0)',
|
||||
// stroke: '#ffffff',
|
||||
// rx:1,ry:1,
|
||||
// 'border-width': '3px',
|
||||
// 'stroke-width': 2,
|
||||
// fill: '#ffffff'
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// console.log("Label 1 on link id "+jointLink.id+" = "+jointLink.label(1));
|
||||
// setTimeout(function() {animate(this,0.0)}.bind(jointLink),1000);
|
||||
// jointLink.transition('labels/1/position',1,{valueFunction: joint.util.interpolate.unit, timingFunction: joint.util.timing.bounce});
|
||||
}
|
||||
}
|
||||
}
|
||||
// var lines = trimmed.split('\n');
|
||||
// for (var l=0;l<lines.length;l++) {
|
||||
// var line = lines[l];
|
||||
// var elements = line.split('>');
|
||||
// var lastNode = null;
|
||||
// for (var e=0;e<elements.length;e++) {
|
||||
// var element = elements[e].trim();
|
||||
// // Has properties?
|
||||
// var startOfProps = element.indexOf(' ');
|
||||
// var name = element;
|
||||
// var properties = {};
|
||||
// if (startOfProps !== -1) {
|
||||
// name = element.substring(0,startOfProps);
|
||||
// var propValues = element.substring(startOfProps+1).trim().split(' ');
|
||||
// for (var p=0;p<propValues.length;p++) {
|
||||
// var propValue = propValues[p].trim();
|
||||
// if (propValue.length===0) {
|
||||
// // allows for multiple spaces between options
|
||||
// continue;
|
||||
// }
|
||||
// var equalsIndex = propValue.indexOf('=');
|
||||
// // The 2 skips the '--'
|
||||
// var key = propValue.substring(2,equalsIndex);
|
||||
// var value = propValue.substring(equalsIndex+1);
|
||||
// properties[key] = value;
|
||||
// }
|
||||
// }
|
||||
// var group = metamodelUtils.matchGroup(metamodel, name, 1, 1);
|
||||
// var newNode = flo.createNode(metamodelUtils.getMetadata(metamodel,name,group),properties);
|
||||
// newNode.attr('.label/text',name);
|
||||
// if (lastNode) {
|
||||
// flo.createLink({'id': lastNode.id,'selector': '.output-port'},
|
||||
// {'id': newNode.id,'selector': '.input-port'});
|
||||
// }
|
||||
// lastNode = newNode;
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update the labels on links on the graph based on a fresh copy of the graph data.
|
||||
* This assumes no nodes have changed, no links have changed - purely the counter
|
||||
* stats on those elements.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
var joint = require('joint');
|
||||
|
||||
function collapseOneLevel(prefix, obj, collector) {
|
||||
var type = typeof obj;
|
||||
if (obj == null) {
|
||||
collector[prefix] = null;
|
||||
return;
|
||||
}
|
||||
if (type === 'object') {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+key,obj[key],collector);
|
||||
});
|
||||
} else if (type === 'array') {
|
||||
for (var i=0;i<obj.length;i++) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+i,obj[i],collector);
|
||||
}
|
||||
} else {
|
||||
collector[prefix] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
function collapse(obj, prefix) {
|
||||
if (!prefix) {
|
||||
prefix = '';
|
||||
}
|
||||
var retval = {};
|
||||
collapseOneLevel(prefix,obj,retval);
|
||||
// console.log("collapsed = "+JSON.stringify(retval));
|
||||
return retval;
|
||||
}
|
||||
|
||||
var MAGNITUDE_NUMBERS = [ 1000000000, 1000000, 1000];
|
||||
var MAGNITUDE_LITERALS = ['B', 'M', 'K'];
|
||||
|
||||
var rateLabel = function() {
|
||||
var postFix, division, index = -1, fixed = 3;
|
||||
do {
|
||||
division = this.rate / MAGNITUDE_NUMBERS[++index];
|
||||
} while (!Math.floor(division) && index < MAGNITUDE_NUMBERS.length);
|
||||
if (index === MAGNITUDE_NUMBERS.length) {
|
||||
postFix = '';
|
||||
division = this.rate;
|
||||
} else {
|
||||
postFix = MAGNITUDE_LITERALS[index];
|
||||
}
|
||||
for (var decimal = 1; decimal <= 100 && Math.floor(division / decimal); decimal*=10) {
|
||||
fixed--;
|
||||
}
|
||||
return division.toFixed(fixed) + postFix;
|
||||
};
|
||||
|
||||
function animate(link,p) {
|
||||
// console.log("moving label on "+link.id+" to "+p);
|
||||
if (!link.label(1)) {
|
||||
console.log("No label1 on this link??");
|
||||
} else {
|
||||
var label = link.label(1);
|
||||
if (label.timer) {
|
||||
clearTimeout(label.timer);
|
||||
}
|
||||
link.label(1,{position: p})
|
||||
p+=0.06
|
||||
if (p<1) {
|
||||
label.timer = setTimeout(function() {animate(link,p)},15);
|
||||
} else {
|
||||
link.label(1, {
|
||||
position: 0.0,
|
||||
type: 'blip',
|
||||
// rate: sourceRates.outgoingRate,
|
||||
attrs: {
|
||||
text: {
|
||||
transform: 'translate(0, 0)',
|
||||
//text: '{{rateLabel()}}',
|
||||
text: '',
|
||||
'fill': 'black',
|
||||
'stroke': 'black',
|
||||
'font-size': '4'
|
||||
},
|
||||
rect: {
|
||||
transform: 'translate(0, 0)',
|
||||
stroke: '#00ffff',
|
||||
rx:1,ry:1,
|
||||
'border-width': '3px',
|
||||
'stroke-width': 4,
|
||||
fill: '#00ffff'
|
||||
}
|
||||
}
|
||||
});
|
||||
// link.label(1,{text:{text:''}});
|
||||
// label.attr('rect/display','none');
|
||||
// label.attr('text/display','none');
|
||||
// label.attrs.text.display = 'none';
|
||||
// label.attrs.rect.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return function(input, graph, labelpath) { //flo, metamodel, metamodelUtils) {
|
||||
// input is a string like this (3 nodes: foo, goo and hoo): foo --a=b --c=d > goo --d=e --f=g>hoo
|
||||
var trimmed = input.trim();
|
||||
if (trimmed.length===0) {
|
||||
return;
|
||||
}
|
||||
var integrationGraph = JSON.parse(input);
|
||||
var nodes = integrationGraph.nodes;
|
||||
var graphNodes = graph.getElements();
|
||||
var map = {};
|
||||
var linksToVisit = graph.getLinks();
|
||||
graphNodes.forEach(function(element) {
|
||||
if (element.attr('metadata/name')) { // is it a node?
|
||||
// if (!element.get('source')) {
|
||||
map[element.attr('props/id')] = element;
|
||||
} else {
|
||||
linksToVisit.push(element);
|
||||
}
|
||||
});
|
||||
function toLabel(text) {
|
||||
var string = text.toString();
|
||||
if (string.length>5) {
|
||||
string = string.substring(0,5);
|
||||
if (string.endsWith('.')) {
|
||||
string = string.substring(0,4);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
// Go through nodes, updating properties
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
var inputNode = nodes[i];
|
||||
var props = collapse(inputNode.stats,'stats');
|
||||
var props2 = collapse(inputNode.properties,'properties');
|
||||
for (var attrname in props2) { props[attrname] = props2[attrname]; }
|
||||
|
||||
map[inputNode.nodeId].attr('props',props);
|
||||
}
|
||||
for (var i=0;i<linksToVisit.length;i++) {
|
||||
var link = linksToVisit[i];
|
||||
var sourceId = link.get('source').id;
|
||||
var sourceElement = graph.getCell(sourceId);
|
||||
var rate;
|
||||
var props = sourceElement.attr('props');
|
||||
Object.keys(props).forEach(function(key) {
|
||||
if (key.toLowerCase() === labelpath.toLowerCase()) {
|
||||
rate = props[key];
|
||||
}
|
||||
});
|
||||
var existingLabel = link.label(0);
|
||||
var existingValue;
|
||||
var animateLink = false;
|
||||
if (existingLabel) {
|
||||
existingValue = existingLabel.attrs.text.text;
|
||||
if (existingValue !== toLabel(rate)) {
|
||||
animateLink = true;
|
||||
}
|
||||
}
|
||||
if (rate) {
|
||||
link.label(0, {
|
||||
position: 15,
|
||||
type: 'outgoing-rate',
|
||||
attrs: {
|
||||
text: { transform: 'translate(0, -8)', text: toLabel(rate), 'fill': 'black', 'stroke': 'none', 'font-size': '12' },
|
||||
rect: {
|
||||
display: 'none'
|
||||
// transform: 'translate(0, -5)',
|
||||
// stroke: 'black',
|
||||
// rx:1,ry:1,
|
||||
// 'border-width': '2px',
|
||||
// 'stroke-width': 1,
|
||||
// fill: '#00B0A7'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (animateLink) {
|
||||
link.label(1, {
|
||||
position: 0.0,
|
||||
type: 'blip',
|
||||
// rate: sourceRates.outgoingRate,
|
||||
attrs: {
|
||||
text: {
|
||||
transform: 'translate(0, 0)',
|
||||
//text: '{{rateLabel()}}',
|
||||
text: ' ',
|
||||
'fill': '#00ffff',
|
||||
'stroke': 'black',
|
||||
'font-size': '10'
|
||||
},
|
||||
rect: {
|
||||
transform: 'translate(0, 0)',
|
||||
// stroke: '#00B0A7',
|
||||
'stroke': 'black',
|
||||
rx:2,ry:2,
|
||||
'border-width': 2,
|
||||
'stroke-width': 3,
|
||||
// fill: '#00B0A7'
|
||||
'fill': 'black'
|
||||
}
|
||||
}
|
||||
});
|
||||
link.label(1).timer = setTimeout(function() {animate(this,0.0)}.bind(link),0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
35
src/app.js
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Flo Angular app module
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
define('flo', function(require) {
|
||||
'use strict';
|
||||
|
||||
require('angular');
|
||||
require('floDirectives');
|
||||
require('floServices');
|
||||
|
||||
return angular.module('spring.flo', [
|
||||
'flo.services',
|
||||
'flo.directives'
|
||||
]);
|
||||
});
|
||||
|
||||
require(['flo']);
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function() {
|
||||
|
||||
// TODO remove this home grown eventing
|
||||
var triggerFns = {};
|
||||
|
||||
function on(triggerEvent, fn) {
|
||||
var triggers = triggerFns[triggerEvent];
|
||||
if (!triggers) {
|
||||
triggers = [];
|
||||
triggerFns[triggerEvent] = triggers;
|
||||
}
|
||||
triggers.push(fn);
|
||||
}
|
||||
|
||||
function off(triggerEvent, fn) {
|
||||
var triggers = triggerFns[triggerEvent];
|
||||
if (triggers) {
|
||||
var index = triggers.indexOf(fn);
|
||||
if (index >= 0) {
|
||||
triggers.splice(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fireEvent(event, paramsObject) {
|
||||
var triggers = triggerFns[event];
|
||||
if (triggers && Array.isArray(triggers)) {
|
||||
triggers.forEach(function(trigger) {
|
||||
trigger(paramsObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
on: on,
|
||||
off: off,
|
||||
fireEvent: fireEvent
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,798 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This module manages the properties viewer for Flo, showing detailed information on supported
|
||||
* properties for modules and enabling them to be edited to new values.
|
||||
*
|
||||
* Events coming out of here:
|
||||
* 'change' - something changed!
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
|
||||
var CodeMirror = require('codemirror');
|
||||
|
||||
require('codemirror/addon/mode/loadmode');
|
||||
require('codemirror/addon/edit/matchbrackets');
|
||||
require('codemirror/addon/edit/closebrackets');
|
||||
|
||||
// languages
|
||||
require('codemirror/mode/groovy/groovy');
|
||||
require('codemirror/mode/javascript/javascript');
|
||||
require('codemirror/mode/python/python');
|
||||
require('codemirror/mode/clike/clike'); // for Java
|
||||
|
||||
CodeMirror.modeURL = 'codemirror/mode/%N/%N';
|
||||
|
||||
return function(domContext, metamodelService) {
|
||||
|
||||
function cellTitle(cell, newValue) {
|
||||
var titleAttr = cell.attr('metadata/metadata/titleProperty') ? cell.attr('metadata/metadata/titleProperty') : '.label/text';
|
||||
if (newValue === undefined) {
|
||||
return cell.attr(titleAttr);
|
||||
} else {
|
||||
cell.attr(titleAttr, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO more home grown eventing, remove!
|
||||
|
||||
var triggerFns = {};
|
||||
|
||||
function on(triggerEvent, fn) {
|
||||
var triggers = triggerFns[triggerEvent];
|
||||
if (!triggers) {
|
||||
triggers = [];
|
||||
triggerFns[triggerEvent] = triggers;
|
||||
}
|
||||
triggers.push(fn);
|
||||
}
|
||||
|
||||
function trigger(triggerEvent) {
|
||||
var triggers = triggerFns[triggerEvent];
|
||||
if (triggers) {
|
||||
for (var i=0;i<triggers.length;i++) {
|
||||
triggers[i]();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isTitleReadOnly(cell) {
|
||||
var titleAttr = cell.attr('metadata/metadata/titleProperty') ? cell.attr('metadata/metadata/titleProperty') : '.label/text';
|
||||
return titleAttr.indexOf('metadata') === 0;
|
||||
}
|
||||
|
||||
function isApplicableFor(cellview) {
|
||||
return cellview && cellview.model && cellview.model.attr('metadata') &&
|
||||
!cellview.model.attr('metadata/metadata/noEditableProps') && !cellview.model.attr('metadata/unresolved');
|
||||
}
|
||||
|
||||
function setVisible(visible) {
|
||||
if (visible) {
|
||||
$('#properties', domContext).height('calc(30% - 1px)');
|
||||
$('#paper', domContext).height('70%');
|
||||
} else {
|
||||
$('#properties', domContext).height('0%');
|
||||
$('#paper', domContext).height('100%');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the properties view is currently being displayed. Optionally an element Id can
|
||||
* be passed and if so this function will only return true if the properties view is
|
||||
* open *and* it is displaying the properties for the specified element.
|
||||
*/
|
||||
function isVisible(optionalElementId) {
|
||||
if (optionalElementId) {
|
||||
if ($('#properties', domContext).height()===0) {
|
||||
return false;
|
||||
}
|
||||
var currentlyDisplayedElementId = $('#properties').attr('elementId');
|
||||
return currentlyDisplayedElementId === optionalElementId;
|
||||
} else {
|
||||
return $('#properties', domContext).height();
|
||||
}
|
||||
}
|
||||
|
||||
function togglePropertiesView(cellview) {
|
||||
if (isApplicableFor(cellview)) {
|
||||
// change state of properties view
|
||||
// TODO crude because not detecting 'double click' just detecting a second press sometime later
|
||||
var displayState = isVisible();
|
||||
setVisible(!displayState);
|
||||
}
|
||||
}
|
||||
|
||||
function resize() {
|
||||
// $('#properties', domContext).offset({left:$('#paper', domContext).position().left+$('#paper', domContext).width()+25-400});
|
||||
// $('#properties', domContext).offset({top:$('#paper', domContext).position().top+$('#paper', domContext).height()-$('#properties', domContext).height()-5});
|
||||
}
|
||||
|
||||
function moveTooltip(x, y, offsetChoice) {
|
||||
// var ppcPos = $('#properties', domContext).size();
|
||||
var mx = x;
|
||||
var my = y;
|
||||
if (offsetChoice === 'topleft') {
|
||||
mx = mx + 10; /*- $('.node-tooltip', domContext).width();*/
|
||||
my = my - 25 - $('.node-tooltip').height();
|
||||
}
|
||||
$('.node-tooltip').css({ top: my, left: mx });
|
||||
}
|
||||
|
||||
function closeTooltip() {
|
||||
$('.node-tooltip').remove();
|
||||
}
|
||||
|
||||
function createTooltip(tooltipText, x, y, offsetChoice) {
|
||||
var nodeTooltip = document.createElement('div');
|
||||
$(nodeTooltip).addClass('node-tooltip');
|
||||
// TODO as general purpose, append it somewhere else
|
||||
$(nodeTooltip).appendTo('body').fadeIn('fast');
|
||||
$(nodeTooltip).addClass('tooltip-description');
|
||||
var nodeDescription = document.createElement('div');
|
||||
$(nodeDescription).text(tooltipText);
|
||||
$(nodeTooltip).append(nodeDescription);
|
||||
moveTooltip(x,y,offsetChoice);
|
||||
}
|
||||
|
||||
function attachTooltip(element, text) {
|
||||
if (text) {
|
||||
$(element).mouseenter(function(evt) {
|
||||
createTooltip(text,evt.pageX,evt.pageY,'topleft');
|
||||
});
|
||||
$(element).mousemove(function(evt) {
|
||||
moveTooltip(evt.pageX,evt.pageY,'topleft');
|
||||
});
|
||||
$(element).mouseleave(function() {
|
||||
closeTooltip();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addNewPropertyKeyValue(cellview, key, newvalue) {
|
||||
cellview.model.attr('props/' + key, newvalue);
|
||||
// $('#properties', domContext).trigger('property-change');
|
||||
// paper.trigger('resync-required');
|
||||
trigger('change');
|
||||
}
|
||||
|
||||
function addNewPropertyRow(table, cellview, rowNum) {
|
||||
// Final row is where new properties can be added
|
||||
var row = document.createElement('tr');
|
||||
rowNum++;
|
||||
if ((rowNum % 2)===1) {
|
||||
$(row).addClass('properties-row-even');
|
||||
} else {
|
||||
$(row).addClass('properties-row-odd');
|
||||
}
|
||||
$(table).append(row);
|
||||
var keytext = document.createElement('input');
|
||||
$(keytext).addClass('properties-input');
|
||||
$(keytext).val('...');
|
||||
$(keytext).addClass('properties-new-property');
|
||||
if ((rowNum%2)===1) {
|
||||
$(keytext).addClass('properties-row-text-even');
|
||||
} else {
|
||||
$(keytext).addClass('properties-row-text-odd');
|
||||
}
|
||||
|
||||
var key = document.createElement('td');
|
||||
$(key).addClass('properties-key');
|
||||
key.appendChild(keytext);
|
||||
$(row).append(key);
|
||||
// TODO do something special when typing in the 'new key' row
|
||||
|
||||
var value = document.createElement('td');
|
||||
var valuetext = document.createElement('input');
|
||||
$(valuetext).addClass('properties-input');
|
||||
$(valuetext).val('...');
|
||||
$(valuetext).addClass('properties-new-property');
|
||||
if ((rowNum % 2)===1) {
|
||||
$(valuetext).addClass('properties-row-text-even');
|
||||
} else {
|
||||
$(valuetext).addClass('properties-row-text-odd');
|
||||
}
|
||||
|
||||
$(value).addClass('properties-value');
|
||||
// $(value).on('keyup',function(rowNumber,valueNode,evt) {
|
||||
// // cellview gives us the ID of the node that changed
|
||||
// // row gives us which entry in the properties list
|
||||
// var newKeyValue = $(valueNode).val();
|
||||
// }.bind(this,r,valuetext));
|
||||
|
||||
value.appendChild(valuetext);
|
||||
$(row).append(value);
|
||||
|
||||
$(keytext).on('focus',function(keyTextInputField, evt) { // jshint ignore:line
|
||||
console.log('properties: focus event on key in \'new value\' row');
|
||||
// Set it to blank so the user can start typing
|
||||
$(keyTextInputField).val('');
|
||||
$(this).val('placeholder');
|
||||
// Make them black and not gray for now
|
||||
$(keyTextInputField).removeClass('properties-new-property');
|
||||
$(this).removeClass('properties-new-property');
|
||||
}.bind(valuetext, keytext));
|
||||
|
||||
$(key).on('keydown', function(rowNumber, keyTextInputField, cellview, evt) {
|
||||
if (evt.keyCode === 13) {
|
||||
var newKeyValue = $(keyTextInputField).val();
|
||||
if (newKeyValue === '') {
|
||||
// set it back to how it was...
|
||||
$(keyTextInputField).addClass('properties-new-property');
|
||||
$(this).addClass('properties-new-property');
|
||||
$(keyTextInputField).val('...');
|
||||
$(this).val('...');
|
||||
}
|
||||
else {
|
||||
addNewPropertyKeyValue(cellview, newKeyValue, $(this).val());
|
||||
}
|
||||
}
|
||||
}.bind(valuetext, rowNum, keytext, cellview));
|
||||
|
||||
|
||||
$(key).on('focusout',function(rowNumber, keyTextInputField, cellview, evt) { // jshint ignore:line
|
||||
var newKeyValue = $(keyTextInputField).val();
|
||||
if (newKeyValue === '') {
|
||||
// set it back to how it was...
|
||||
$(keyTextInputField).addClass('properties-new-property');
|
||||
$(this).addClass('properties-new-property');
|
||||
$(keyTextInputField).val('...');
|
||||
$(this).val('...');
|
||||
}
|
||||
else {
|
||||
addNewPropertyKeyValue(cellview, newKeyValue, $(this).val());
|
||||
}
|
||||
}.bind(valuetext, rowNum, keytext, cellview));
|
||||
|
||||
// $(valuetext).on('keydown', function(rowNumber, keyTextInputField, cellview, evt) {
|
||||
// if (evt.keyCode == 13) {
|
||||
// var newKeyValue = $(keyTextInputField).val();
|
||||
// if (newKeyValue == '' || newKeyValue == '...') {
|
||||
// // set it back to how it was...
|
||||
// $(keyTextInputField).addClass('properties-new-property');
|
||||
// $(this).addClass('properties-new-property');
|
||||
// $(keyTextInputField).val('...');
|
||||
// $(this).val('...');
|
||||
// }
|
||||
// else {
|
||||
// addNewPropertyKeyValue(cellview, newKeyValue, $(this).val());
|
||||
// }
|
||||
// }
|
||||
// }.bind(valuetext, r, keytext, cellview));
|
||||
//
|
||||
// $(valuetext).on('focusout',function(rowNumber, keyTextInputField, cellview, evt) {
|
||||
// var newKeyValue = $(keyTextInputField).val();
|
||||
// if (newKeyValue == '' || newKeyValue == '...') {
|
||||
// // set it back to how it was...
|
||||
// $(keyTextInputField).addClass('properties-new-property');
|
||||
// $(this).addClass('properties-new-property');
|
||||
// $(keyTextInputField).val('...');
|
||||
// $(this).val('...');
|
||||
// }
|
||||
// else {
|
||||
//// updateModelPropertyValue(cellview, key, null, newValue);
|
||||
// //OR?
|
||||
// addNewPropertyKeyValue(cellview, newKeyValue, $(this).val());
|
||||
// }
|
||||
// }.bind(valuetext, r, keytext, cellview));
|
||||
}
|
||||
|
||||
function getDisplayDefaultValue(property) {
|
||||
if (property && property.defaultValue !== undefined && property.defaultValue !== null) {
|
||||
return property.defaultValue;
|
||||
}
|
||||
return '...';
|
||||
}
|
||||
|
||||
|
||||
function updateModelPropertyKey(cellview, oldkeyvalue, newkeyvalue) {
|
||||
var props = cellview.model.attr('props');
|
||||
if (newkeyvalue) {
|
||||
if (!props[newkeyvalue]) {
|
||||
props[newkeyvalue] = props[oldkeyvalue];
|
||||
}
|
||||
}
|
||||
delete props[oldkeyvalue];
|
||||
// $('#properties', domContext).trigger('property-change');
|
||||
}
|
||||
|
||||
function updateModelPropertyValue(cellview, key, oldvalue, newvalue) {
|
||||
// var props = cellview.model.attr('props');
|
||||
var hasDefaultValue = false;
|
||||
var defaultValue;
|
||||
var propertyMetadata = cellview.model.attr('metadata/properties/'+key);
|
||||
if (propertyMetadata && propertyMetadata.defaultValue !== undefined && propertyMetadata.defaultValue !== null) {
|
||||
hasDefaultValue = true;
|
||||
defaultValue = propertyMetadata.defaultValue;
|
||||
}
|
||||
// Setting it back to original value (or blank, undefined or null - if no default value)
|
||||
if ((hasDefaultValue && newvalue === defaultValue) || newvalue === '' || newvalue === undefined || newvalue === null) {
|
||||
// trigger events listening in for a change
|
||||
cellview.model.removeAttr('props/'+key,{'propertyPath':'attrs/props/'+key});
|
||||
} else {
|
||||
cellview.model.attr('props/'+key,newvalue);
|
||||
}
|
||||
// $('#properties', domContext).trigger('property-change');
|
||||
trigger('change');//paper.trigger('resync-required');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a selection is made or changed so that the properties view will correctly
|
||||
* represent the properties set on the new selection.
|
||||
*/
|
||||
function updatePropertiesView(cellview) {
|
||||
/*jshint validthis:true */
|
||||
var propertiesNode = $('#properties', domContext);
|
||||
if (!isApplicableFor(cellview)) {
|
||||
propertiesNode.empty();
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
setVisible(true);
|
||||
var properties = cellview.model.attr('props');
|
||||
// propertiesNode.off('property-change');
|
||||
// propertiesNode.on('property-change',function(e) {
|
||||
// console.log("Event detected: property-change");
|
||||
// updatePropertiesView(cellview);
|
||||
// });
|
||||
var moduleName = cellTitle(cellview.model);
|
||||
|
||||
var moduleSchema = cellview.model.attr('metadata');
|
||||
|
||||
moduleSchema.get('properties').then(function(moduleSchemaProperties) {
|
||||
|
||||
$(propertiesNode).empty();
|
||||
$(propertiesNode).attr('elementId',cellview.model.id);
|
||||
|
||||
var nodename = document.createElement('input');
|
||||
if (isTitleReadOnly(cellview.model)) {
|
||||
$(nodename).prop('readonly', true);
|
||||
}
|
||||
$(nodename).addClass('properties-node-name');
|
||||
$(nodename).val(moduleName);
|
||||
|
||||
moduleSchema.get('description').then(function(description) {
|
||||
attachTooltip(nodename, description);
|
||||
}, function(error) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
function processNewModuleName() {
|
||||
var currentValidModuleName = cellTitle(cellview.model);
|
||||
var newModuleName = $(this).val();
|
||||
if (newModuleName !== currentValidModuleName) {
|
||||
if (newModuleName === '') {
|
||||
// ignore trying to set it to blank, just reset it
|
||||
$(this).val(currentValidModuleName);
|
||||
} else {
|
||||
cellTitle(cellview.model, newModuleName);
|
||||
// $('#properties', domContext).trigger('property-change');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Support editing of the module name - when the user presses ENTER
|
||||
$(nodename).on('keydown',function(cellview, originalModuleName, evt) {
|
||||
// TODO can't edit title of tap/namedchannel
|
||||
if (evt.keyCode === 13/*ENTER*/) {
|
||||
processNewModuleName.bind(this)();
|
||||
}
|
||||
}.bind(nodename, cellview, moduleName));
|
||||
// Support editing of the module name - when the user tabs out of the element
|
||||
$(nodename).on('focusout',function(cellview, originalModuleName, evt) { // jshint ignore:line
|
||||
processNewModuleName.bind(this)();
|
||||
}.bind(nodename, cellview, moduleName));
|
||||
|
||||
// Build the table of key/value options
|
||||
var table = document.createElement('table');
|
||||
$(propertiesNode).append(table);
|
||||
|
||||
$(table).width('100%');
|
||||
$(table).css('border', '0px');
|
||||
$(table).css('border-spacing', '0px');
|
||||
|
||||
var row = document.createElement('tr');
|
||||
$(row).addClass('properties-node-name-row');
|
||||
var headerCell = document.createElement('th');
|
||||
headerCell.colSpan = 2;
|
||||
headerCell.appendChild(nodename);
|
||||
$(row).append(headerCell);
|
||||
$(table).append(row);
|
||||
|
||||
var rowNum = 0;
|
||||
|
||||
// Special handling when a named channel has the channel property set, update the
|
||||
// label on the node in the graph (like taps, channel nodes include the channel
|
||||
// embedded in the icon)
|
||||
// function setNamedChannelLabel(namedChannel,value) {
|
||||
// // console.log("Named channel has channel updated to '"+value+"'");
|
||||
// var labelText = value;
|
||||
// if (value) {
|
||||
// var colonPos = value.indexOf(':');
|
||||
// if (colonPos !==-1) {
|
||||
// colonPos = value.indexOf(':',colonPos+1);
|
||||
// if (colonPos !== -1) {
|
||||
// labelText = value.substring(0,colonPos)+':\n'+value.substring(colonPos+1);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// namedChannel.model.attr('.label2/text',labelText);
|
||||
// }
|
||||
|
||||
|
||||
function processPropertyKeyChange(checkKey, rowNumber, originalKeyValue, keyTextInputField, cellview, evt) {
|
||||
if (!checkKey || evt.keyCode === 13) {
|
||||
var newKeyValue = $(keyTextInputField).val();
|
||||
if (newKeyValue !== originalKeyValue) {
|
||||
console.log('properties: key changed from '+originalKeyValue+' to '+newKeyValue);
|
||||
updateModelPropertyKey(cellview, originalKeyValue, newKeyValue);
|
||||
}
|
||||
else {
|
||||
console.log('Key unchanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
function processNewPropertyValue(checkKey, rowNumber, key, valueTextInputField, cellview, evt) {
|
||||
if (!checkKey || evt.keyCode === 13) {
|
||||
// TODO need to search props for the key, it isn't a map like this - underscore helper?
|
||||
var originalValue = cellview.model.attr('props/' + key);
|
||||
var newValue = $(valueTextInputField).val();
|
||||
if (newValue !== originalValue) {
|
||||
console.log('properties: for key \''+key+'\' value changed from '+originalValue+' to '+newValue);
|
||||
if (_.isFunction(metamodelService.isValidPropertyValue)) {
|
||||
if (metamodelService.isValidPropertyValue(cellview.model, key, newValue)) {
|
||||
updateModelPropertyValue(cellview, key, originalValue, newValue);
|
||||
} else {
|
||||
updateModelPropertyValue(cellview, key, originalValue, originalValue);
|
||||
}
|
||||
} else {
|
||||
updateModelPropertyValue(cellview, key, originalValue, newValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('Value unchanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
function processNewPropertyValueForCodeMirrorDoc(doc, key, cellview) {
|
||||
// TODO need to search props for the key, it isn't a map like this - underscore helper?
|
||||
var originalValue = cellview.model.attr('props/' + key);
|
||||
var newValue = doc.getValue();
|
||||
if (metamodelService && _.isFunction(metamodelService.encodeTextToDSL)) {
|
||||
newValue = metamodelService.encodeTextToDSL(newValue);
|
||||
}
|
||||
if (newValue !== originalValue) {
|
||||
console.log('properties: for key \''+key+'\' value changed from '+originalValue+' to '+newValue);
|
||||
if (_.isFunction(metamodelService.isValidPropertyValue)) {
|
||||
if (metamodelService.isValidPropertyValue(cellview.model, key, newValue)) {
|
||||
updateModelPropertyValue(cellview, key, originalValue, newValue);
|
||||
} else {
|
||||
updateModelPropertyValue(cellview, key, originalValue, originalValue);
|
||||
}
|
||||
} else {
|
||||
updateModelPropertyValue(cellview, key, originalValue, newValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('Value unchanged');
|
||||
}
|
||||
}
|
||||
|
||||
rowNum = 0;
|
||||
if (properties) {
|
||||
Object.keys(properties).sort().forEach(function(propertyName) {
|
||||
if (propertyName === cellview.model.attr('metadata/metadata/titleProperty')) {
|
||||
// Ignore the title property. It'll be displayed at the top anyway
|
||||
return;
|
||||
}
|
||||
if (!properties[propertyName] || properties[propertyName] === moduleSchemaProperties[propertyName].defaultValue) {
|
||||
// Ignore properties equal to default value
|
||||
return;
|
||||
}
|
||||
var row = document.createElement('tr');
|
||||
if ((rowNum%2) === 0) {
|
||||
$(row).addClass('properties-row-even');
|
||||
} else {
|
||||
$(row).addClass('properties-row-odd');
|
||||
}
|
||||
$(table).append(row);
|
||||
var keytext = document.createElement('input');
|
||||
$(keytext).addClass('properties-input');
|
||||
|
||||
if ((rowNum % 2) === 0) {
|
||||
$(keytext).addClass('properties-row-text-even');
|
||||
} else {
|
||||
$(keytext).addClass('properties-row-text-odd');
|
||||
}
|
||||
|
||||
var schemaProperty;
|
||||
if (moduleSchemaProperties && moduleSchemaProperties[propertyName]) {
|
||||
schemaProperty = moduleSchemaProperties[propertyName];
|
||||
// $(keytext).attr('title',schemaProperty.description);
|
||||
attachTooltip(row, schemaProperty.description);
|
||||
}
|
||||
|
||||
$(keytext).val(schemaProperty && schemaProperty.name ? schemaProperty.name : propertyName);
|
||||
|
||||
var key = document.createElement('td');
|
||||
$(key).addClass('properties-key');
|
||||
key.appendChild(keytext);
|
||||
$(row).append(key);
|
||||
// attachTooltip(row,rowdata.description);
|
||||
|
||||
|
||||
// Process editing the property key - ENTER key pressed
|
||||
$(key).on('keydown', processPropertyKeyChange.bind(this, true, rowNum, propertyName, keytext, cellview));
|
||||
// Process editing the property key - when cell loses focus (e.g. tab pressed)
|
||||
$(key).on('focusout',processPropertyKeyChange.bind(this, false, rowNum, propertyName, keytext, cellview));
|
||||
|
||||
var value = document.createElement('td');
|
||||
$(row).append(value);
|
||||
var valuetext;
|
||||
if (schemaProperty && schemaProperty.source) {
|
||||
valuetext = document.createElement('textarea');
|
||||
value.appendChild(valuetext);
|
||||
$(valuetext).addClass('properties-input');
|
||||
var doc = CodeMirror.fromTextArea(valuetext, {
|
||||
// gutters: ["CodeMirror-lint-markers"],
|
||||
// lint: {
|
||||
// async: true,
|
||||
// getAnnotations: function (text, updateFun) {
|
||||
// if (!updateLinting) {
|
||||
// updateLinting = updateFun;
|
||||
// $scope.$watch("definition.parseError", refreshMarkers);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
extraKeys: {'Ctrl-Space': 'autocomplete'},
|
||||
// hintOptions: {
|
||||
// async: 'true',
|
||||
// hint: contentAssist
|
||||
// },
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
mode: schemaProperty.source.mime
|
||||
});
|
||||
CodeMirror.autoLoadMode(doc, schemaProperty.source.type);
|
||||
var valueToSet = properties[propertyName] === undefined ? getDisplayDefaultValue(schemaProperty) : properties[propertyName];
|
||||
if (metamodelService && _.isFunction(metamodelService.decodeTextFromDSL)) {
|
||||
valueToSet = metamodelService.decodeTextFromDSL(valueToSet);
|
||||
}
|
||||
doc.setValue(valueToSet);
|
||||
doc.on('blur', function () {
|
||||
processNewPropertyValueForCodeMirrorDoc(doc, propertyName, cellview);
|
||||
});
|
||||
} else {
|
||||
valuetext = document.createElement('input');
|
||||
value.appendChild(valuetext);
|
||||
$(valuetext).addClass('properties-input');
|
||||
$(valuetext).val(properties[propertyName] === undefined ? getDisplayDefaultValue(schemaProperty) : properties[propertyName]);
|
||||
|
||||
// Handle new property value - pressing ENTER
|
||||
$(value).on('keydown', processNewPropertyValue.bind(this, true, rowNum, propertyName, valuetext, cellview));
|
||||
// Handle new property value - exiting property value (e.g. TAB pressed)
|
||||
$(value).on('focusout',processNewPropertyValue.bind(this, false, rowNum, propertyName, valuetext, cellview));
|
||||
}
|
||||
$(value).addClass('properties-value');
|
||||
|
||||
if ((rowNum % 2)===0) {
|
||||
$(valuetext).addClass('properties-row-text-even');
|
||||
} else {
|
||||
$(valuetext).addClass('properties-row-text-odd');
|
||||
}
|
||||
|
||||
rowNum++;
|
||||
});
|
||||
}
|
||||
|
||||
function processNewPropertyValueUpdate(checkKey, keyText, keyTextInputField, cellview, evt) {
|
||||
var property = moduleSchemaProperties[keyText];
|
||||
var newValue = $(this).val();
|
||||
if (!checkKey || evt.keyCode === 13) {
|
||||
// If nothing typed, reset to '...'
|
||||
if (newValue === '' || (property && property.defaultValue === newValue)) {
|
||||
$(this).val(getDisplayDefaultValue(property));
|
||||
$(keyTextInputField).addClass('properties-new-property');
|
||||
$(this).addClass('properties-new-property');
|
||||
} else {
|
||||
if (_.isFunction(metamodelService.isValidPropertyValue)) {
|
||||
if (metamodelService.isValidPropertyValue(cellview.model, keyText, newValue)) {
|
||||
addNewPropertyKeyValue(cellview, keyText, $(this).val());
|
||||
} else {
|
||||
$(this).val(getDisplayDefaultValue(property));
|
||||
}
|
||||
} else {
|
||||
addNewPropertyKeyValue(cellview, keyText, $(this).val());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processNewPropertyValueUpdateForCodeCodeMirrorDoc(doc, textarea, key, keyText, cellview) {
|
||||
var property = moduleSchemaProperties[key];
|
||||
var rawNewValue = doc.getValue();
|
||||
var newValue = doc.getValue();
|
||||
if (metamodelService && _.isFunction(metamodelService.encodeTextToDSL)) {
|
||||
newValue = metamodelService.encodeTextToDSL(rawNewValue);
|
||||
}
|
||||
// If nothing typed, reset to '...'
|
||||
if (rawNewValue === '' || (property && property.defaultValue === newValue)) {
|
||||
doc.setValue(getDisplayDefaultValue(property));
|
||||
$(keyText).addClass('properties-new-property');
|
||||
$(textarea).addClass('properties-new-property');
|
||||
} else {
|
||||
if (_.isFunction(metamodelService.isValidPropertyValue)) {
|
||||
if (metamodelService.isValidPropertyValue(cellview.model, keyText, newValue)) {
|
||||
addNewPropertyKeyValue(cellview, key, newValue);
|
||||
} else {
|
||||
var defValue = getDisplayDefaultValue(property);
|
||||
if (metamodelService && _.isFunction(metamodelService.decodeTextFromDSL)) {
|
||||
defValue = metamodelService.decodeTextFromDSL(defValue);
|
||||
}
|
||||
doc.setValue(defValue);
|
||||
}
|
||||
} else {
|
||||
addNewPropertyKeyValue(cellview, key, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add possible keys that don't have values yet, in gray
|
||||
if (moduleSchemaProperties) {
|
||||
Object.keys(moduleSchemaProperties).sort().forEach(function(propertyName) {
|
||||
if (propertyName === cellview.model.attr('metadata/metadata/titleProperty')) {
|
||||
// Ignore the title property. It'll be displayed at the top anyway
|
||||
return;
|
||||
}
|
||||
if (properties[propertyName]) {
|
||||
// Ensure not set properties are processed here
|
||||
return;
|
||||
}
|
||||
var property = moduleSchemaProperties[propertyName];
|
||||
var row = document.createElement('tr');
|
||||
if ((rowNum % 2)===0) {
|
||||
$(row).addClass('properties-row-even');
|
||||
} else {
|
||||
$(row).addClass('properties-row-odd');
|
||||
}
|
||||
$(table).append(row);
|
||||
var keytext = document.createElement('input');
|
||||
$(keytext).addClass('properties-input');
|
||||
$(keytext).val(property.name);
|
||||
$(keytext).addClass('properties-new-property');
|
||||
|
||||
if ((rowNum % 2)===0) {
|
||||
$(keytext).addClass('properties-row-text-even');
|
||||
} else {
|
||||
$(keytext).addClass('properties-row-text-odd');
|
||||
}
|
||||
$(keytext).attr('readonly', true);
|
||||
|
||||
var key = document.createElement('td');
|
||||
$(key).addClass('properties-key');
|
||||
key.appendChild(keytext);
|
||||
$(row).append(key);
|
||||
|
||||
attachTooltip(row, property.description);
|
||||
|
||||
var value = document.createElement('td');
|
||||
$(row).append(value);
|
||||
var valuetext;
|
||||
if (property && property.source) {
|
||||
valuetext = document.createElement('textarea');
|
||||
value.appendChild(valuetext);
|
||||
$(valuetext).addClass('properties-input');
|
||||
$(valuetext).addClass('properties-new-property');
|
||||
$(valuetext).addClass('properties-input');
|
||||
var doc = CodeMirror.fromTextArea(valuetext, {
|
||||
// gutters: ["CodeMirror-lint-markers"],
|
||||
// lint: {
|
||||
// async: true,
|
||||
// getAnnotations: function (text, updateFun) {
|
||||
// if (!updateLinting) {
|
||||
// updateLinting = updateFun;
|
||||
// $scope.$watch("definition.parseError", refreshMarkers);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
extraKeys: {'Ctrl-Space': 'autocomplete'},
|
||||
// hintOptions: {
|
||||
// async: 'true',
|
||||
// hint: contentAssist
|
||||
// },
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
mode: property.source.mime
|
||||
});
|
||||
CodeMirror.autoLoadMode(doc, property.source.type);
|
||||
var valueToSet = getDisplayDefaultValue(property);
|
||||
if (metamodelService && _.isFunction(metamodelService.decodeTextFromDSL)) {
|
||||
valueToSet = metamodelService.decodeTextFromDSL(valueToSet);
|
||||
}
|
||||
doc.setValue(valueToSet);
|
||||
doc.on('focus', function () {
|
||||
doc.setValue('');
|
||||
$(valuetext).removeClass('properties-new-property');
|
||||
});
|
||||
doc.on('blur', function () {
|
||||
processNewPropertyValueUpdateForCodeCodeMirrorDoc(doc, valuetext, propertyName, keytext, cellview);
|
||||
});
|
||||
} else {
|
||||
valuetext = document.createElement('input');
|
||||
value.appendChild(valuetext);
|
||||
$(valuetext).addClass('properties-input');
|
||||
$(valuetext).val(getDisplayDefaultValue(property));
|
||||
$(valuetext).addClass('properties-new-property');
|
||||
|
||||
// Focus event on property value - set it to blank so the user can start typing
|
||||
$(valuetext).on('focus',function(keyText, keyTextInputField, nodeNumber, evt) { // jshint ignore:line
|
||||
$(this).val('');
|
||||
// Make them black and not gray for now
|
||||
$(keyTextInputField).removeClass('properties-new-property');
|
||||
$(this).removeClass('properties-new-property');
|
||||
}.bind(valuetext, propertyName, keytext));
|
||||
|
||||
// Handle new property value - ENTER pressed
|
||||
$(valuetext).on('keydown', processNewPropertyValueUpdate.bind(valuetext, true, propertyName, keytext, cellview));
|
||||
// Handle new property value - focus leaving cell (e.g. TAB pressed)
|
||||
$(valuetext).on('focusout',processNewPropertyValueUpdate.bind(valuetext, false, propertyName, keytext, cellview));
|
||||
}
|
||||
|
||||
if ((rowNum % 2)===0) {
|
||||
$(valuetext).addClass('properties-row-text-even');
|
||||
} else {
|
||||
$(valuetext).addClass('properties-row-text-odd');
|
||||
}
|
||||
$(value).addClass('properties-value');
|
||||
|
||||
|
||||
rowNum++;
|
||||
});
|
||||
}
|
||||
|
||||
// If the module supports new (undefined) properties, add a row to support entering them
|
||||
if (cellview.model.attr('metadata/metadata/allow-additional-properties')) {
|
||||
addNewPropertyRow(table, cellview, rowNum);
|
||||
}
|
||||
|
||||
resize();
|
||||
}, function(error) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
resize: resize,
|
||||
on: on,
|
||||
updatePropertiesView: updatePropertiesView,
|
||||
togglePropertiesView: togglePropertiesView,
|
||||
setVisible: setVisible,
|
||||
isVisible: isVisible
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,610 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define the custom nodes and links that will be used in our graphs and
|
||||
* functions to create them.
|
||||
*/
|
||||
define(['joint', 'underscore'],function(joint, _) {
|
||||
'use strict';
|
||||
|
||||
var isChrome = !!window.chrome;
|
||||
var isFF = typeof InstallTrigger !== 'undefined';
|
||||
|
||||
var IMAGE_W = 120,
|
||||
IMAGE_H = 35;
|
||||
|
||||
var ERROR_MARKER_SIZE = {width: 16, height: 16};
|
||||
|
||||
var HANDLE_SIZE = {width: 10, height: 10};
|
||||
|
||||
joint.shapes.flo = {};
|
||||
|
||||
joint.shapes.flo.NODE_TYPE = 'sinspctr.IntNode';
|
||||
joint.shapes.flo.LINK_TYPE = 'sinspctr.Link';
|
||||
joint.shapes.flo.DECORATION_TYPE = 'decoration';
|
||||
joint.shapes.flo.HANDLE_TYPE = 'handle';
|
||||
|
||||
joint.shapes.flo.CANVAS_TYPE = 'canvas';
|
||||
joint.shapes.flo.PALETTE_TYPE = 'palette';
|
||||
joint.shapes.flo.FEEDBACK_TYPE = 'feedback';
|
||||
|
||||
var HANDLE_ICON_MAP = {
|
||||
'remove': 'icons/delete.svg'
|
||||
};
|
||||
|
||||
var DECORATION_ICON_MAP = {
|
||||
'error': 'icons/error.svg'
|
||||
};
|
||||
|
||||
joint.util.filter.redscale = function(args) {
|
||||
|
||||
var amount = _.isFinite(args.amount) ? args.amount : 1;
|
||||
|
||||
return _.template('<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 ${d} ${e} ${f} ${g} 0 0 ${h} ${i} ${k} 0 0 0 0 0 1 0"/></filter>', {
|
||||
a: 1 - 0.96 * amount,
|
||||
b: 0.95 * amount,
|
||||
c: 0.01 * amount,
|
||||
d: 0.3 * amount,
|
||||
e: 0.2 * amount,
|
||||
f: 1 - 0.9 * amount,
|
||||
g: 0.7 * amount,
|
||||
h: 0.05 * amount,
|
||||
i: 0.05 * amount,
|
||||
k: 1 - 0.1 * amount
|
||||
});
|
||||
};
|
||||
|
||||
joint.util.filter.orangescale = function(args) {
|
||||
|
||||
var amount = _.isFinite(args.amount) ? args.amount : 1;
|
||||
|
||||
return _.template('<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 ${d} ${e} ${f} ${g} 0 ${h} ${i} ${k} ${l} 0 0 0 0 0 1 0"/></filter>', {
|
||||
a: 1.0 + 0.5 * amount,
|
||||
b: 1.4 * amount,
|
||||
c: 0.2 * amount,
|
||||
d: 0.3 * amount,
|
||||
e: 0.3 * amount,
|
||||
f: 1 + 0.05 * amount,
|
||||
g: 0.2 * amount,
|
||||
h: 0.15 * amount,
|
||||
i: 0.3 * amount,
|
||||
k: 0.3 * amount,
|
||||
l: 1 - 0.6 * amount
|
||||
});
|
||||
};
|
||||
|
||||
joint.shapes.flo.Node = joint.shapes.basic.Generic.extend({
|
||||
markup:
|
||||
'<g class="shape"><image class="image" /></g>'+
|
||||
'<rect class="border-white"/>' +
|
||||
'<rect class="border"/>' +
|
||||
'<rect class="box"/>'+
|
||||
'<text class="label"/>'+
|
||||
'<text class="label2"></text>'+
|
||||
'<rect class="input-port" />'+
|
||||
'<rect class="output-port"/>'+
|
||||
'<rect class="output-port-cover"/>',
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
|
||||
type: joint.shapes.flo.NODE_TYPE,
|
||||
position: {x: 0, y: 0},
|
||||
size: { width: IMAGE_W, height: IMAGE_H },
|
||||
attrs: {
|
||||
'.': { magnet: false },
|
||||
// rounded edges around image
|
||||
'.border': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
'fill-opacity':0, // see through
|
||||
stroke: '#eeeeee',
|
||||
'stroke-width': 0
|
||||
},
|
||||
|
||||
'.box': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
//'fill-opacity':0, // see through
|
||||
stroke: '#6db33f',
|
||||
fill: '#eeeeee',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.input-port': {
|
||||
type: 'input',
|
||||
height: 8, width: 8,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + -4 + ',' + ((IMAGE_H/2)-4) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.output-port': {
|
||||
type: 'output',
|
||||
height: 8, width: 8,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + (IMAGE_W-4) + ',' + ((IMAGE_H/2)-4) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.label': {
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
// 'ref-y': -12, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.3,
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'font-size': 14
|
||||
},
|
||||
'.label2': {
|
||||
'text': '\u21d2',
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.15, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.15, // jointjs specific: relative position to ref'd element
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
transform: 'translate(' + (IMAGE_W/2) + ',' + (IMAGE_H/2) + ')',
|
||||
fill: 'black',
|
||||
'font-size': 24
|
||||
},
|
||||
'.shape': {
|
||||
},
|
||||
'.image': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H
|
||||
}
|
||||
}
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
|
||||
joint.shapes.flo.Link = joint.dia.Link.extend({
|
||||
defaults: joint.util.deepSupplement({
|
||||
type: joint.shapes.flo.LINK_TYPE,
|
||||
attrs: {
|
||||
'.connection': { stroke: '#34302d', 'stroke-width': 2 },
|
||||
// Lots of alternatives that have been played with:
|
||||
// '.smoooth': true
|
||||
// '.marker-source': { stroke: '#9B59B6', fill: '#9B59B6', d: 'M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z' },
|
||||
// '.marker-target': { stroke: '#F39C12', fill: '#F39C12', d: 'M14.615,4.928c0.487-0.986,1.284-0.986,1.771,0l2.249,4.554c0.486,0.986,1.775,1.923,2.864,2.081l5.024,0.73c1.089,0.158,1.335,0.916,0.547,1.684l-3.636,3.544c-0.788,0.769-1.28,2.283-1.095,3.368l0.859,5.004c0.186,1.085-0.459,1.553-1.433,1.041l-4.495-2.363c-0.974-0.512-2.567-0.512-3.541,0l-4.495,2.363c-0.974,0.512-1.618,0.044-1.432-1.041l0.858-5.004c0.186-1.085-0.307-2.6-1.094-3.368L3.93,13.977c-0.788-0.768-0.542-1.525,0.547-1.684l5.026-0.73c1.088-0.158,2.377-1.095,2.864-2.081L14.615,4.928z' },
|
||||
// '.connection': { 'stroke':'black'},
|
||||
// '.': { filter: { name: 'dropShadow', args: { dx: 1, dy: 1, blur: 2 } } },
|
||||
// '.connection': { 'stroke-width': 10, 'stroke-linecap': 'round' },
|
||||
// This means: moveto 10 0, lineto 0 5, lineto, 10 10 closepath(z)
|
||||
// '.marker-target': { d: 'M 5 0 L 0 7 L 5 14 z', stroke: '#34302d','stroke-width' : 1},
|
||||
// '.marker-target': { d: 'M 14 2 L 9,2 L9,0 L 0,7 L 9,14 L 9,12 L 14,12 z', 'stroke-width' : 1, fill: '#34302d', stroke: '#34302d'},
|
||||
// '.marker-source': {d: 'M 5 0 L 5,10 L 0,10 L 0,0 z', 'stroke-width' : 0, fill: '#34302d', stroke: '#34302d'},
|
||||
// '.marker-target': { stroke: '#E74C3C', fill: '#E74C3C', d: 'M 10 0 L 0 5 L 10 10 z' },
|
||||
'.marker-arrowheads': { display: 'none' },
|
||||
'.tool-options': { display: 'none' }
|
||||
},
|
||||
// connector: { name: 'normalDimFix' }
|
||||
}, joint.dia.Link.prototype.defaults)
|
||||
});
|
||||
|
||||
joint.shapes.flo.ErrorDecoration = joint.shapes.basic.Generic.extend({
|
||||
|
||||
markup: '<g class="rotatable"><g class="scalable"><image/></g></g>',
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
|
||||
type: joint.shapes.flo.DECORATION_TYPE,
|
||||
size: ERROR_MARKER_SIZE,
|
||||
attrs: {
|
||||
'image': ERROR_MARKER_SIZE
|
||||
}
|
||||
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
function createCustomElement(renderFn, paper, metadata, props, position) {
|
||||
var node;
|
||||
var graph = paper.model;
|
||||
if (!position) {
|
||||
position = {x: 0, y: 0};
|
||||
}
|
||||
node = new joint.shapes.flo.GenericElement({
|
||||
position: position,
|
||||
renderFunction: renderFn,
|
||||
attrs: {
|
||||
'props': props,
|
||||
'metadata': metadata
|
||||
}
|
||||
});
|
||||
if (graph) {
|
||||
graph.addCell(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JointJS node that embeds extra metadata (properties).
|
||||
*/
|
||||
function createNode(params) {
|
||||
params = params || {};
|
||||
var renderService = params.renderService;
|
||||
var paper = params.paper;
|
||||
var metadata = params.metadata;
|
||||
var position = params.position;
|
||||
var props = params.props;
|
||||
var graph = params.graph || (params.paper ? params.paper.model : undefined);
|
||||
|
||||
var node;
|
||||
if (!props) {
|
||||
props = {};
|
||||
}
|
||||
if (!position) {
|
||||
position = {x: 0, y: 0};
|
||||
}
|
||||
if (renderService && _.isFunction(renderService.createNode)) {
|
||||
node = renderService.createNode(metadata, props);
|
||||
} else {
|
||||
node = new joint.shapes.flo.Node();
|
||||
node.attr('.label/text', metadata.name);
|
||||
}
|
||||
node.set('type', joint.shapes.flo.NODE_TYPE);
|
||||
node.set('position', position);
|
||||
node.attr('props', props);
|
||||
node.attr('metadata', metadata);
|
||||
if (graph) {
|
||||
graph.addCell(node);
|
||||
}
|
||||
if (renderService && _.isFunction(renderService.initializeNewNode)) {
|
||||
renderService.initializeNewNode(node, {
|
||||
'paper': paper,
|
||||
'graph': graph
|
||||
});
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function createLink(params) {
|
||||
params = params || {};
|
||||
var renderService = params.renderService;
|
||||
var paper = params.paper;
|
||||
var metadata = params.metadata;
|
||||
var source = params.source;
|
||||
var target = params.target;
|
||||
var props = params.props;
|
||||
var graph = params.graph || (params.paper ? params.paper.model : undefined);
|
||||
|
||||
var link;
|
||||
if (!props) {
|
||||
props = {};
|
||||
}
|
||||
if (renderService && _.isFunction(renderService.createLink)) {
|
||||
link = renderService.createLink(source, target, metadata, props);
|
||||
} else {
|
||||
link = new joint.shapes.flo.Link();
|
||||
}
|
||||
link.set('source', source);
|
||||
link.set('target', target);
|
||||
link.set('type', joint.shapes.flo.LINK_TYPE);
|
||||
if (metadata) {
|
||||
link.attr('metadata', metadata);
|
||||
}
|
||||
link.attr('props', props);
|
||||
if (graph) {
|
||||
graph.addCell(link);
|
||||
}
|
||||
if (renderService && _.isFunction(renderService.initializeNewLink)) {
|
||||
renderService.initializeNewLink(link, {
|
||||
'paper': paper,
|
||||
'graph': graph
|
||||
});
|
||||
}
|
||||
// prevent creation of link breaks
|
||||
link.attr('.marker-vertices/display', 'none');
|
||||
return link;
|
||||
}
|
||||
|
||||
function createDecoration(params) {
|
||||
params = params || {};
|
||||
var renderService = params.renderService;
|
||||
var paper = params.paper;
|
||||
var parent = params.parent;
|
||||
var kind = params.kind;
|
||||
var messages = params.messages;
|
||||
var location = params.position;
|
||||
var graph = params.graph || (params.paper ? params.paper.model : undefined);
|
||||
|
||||
if (!location) {
|
||||
location = {x: 0, y: 0};
|
||||
}
|
||||
var decoration;
|
||||
if (renderService && _.isFunction(renderService.createDecoration)) {
|
||||
decoration = renderService.createDecoration(kind, parent);
|
||||
} else {
|
||||
decoration = new joint.shapes.flo.ErrorDecoration({
|
||||
attrs: {
|
||||
image: { 'xlink:href': DECORATION_ICON_MAP[kind] },
|
||||
}
|
||||
});
|
||||
}
|
||||
decoration.set('type', joint.shapes.flo.DECORATION_TYPE);
|
||||
decoration.set('position', location);
|
||||
if ((isChrome || isFF) && parent && typeof parent.get('z') === 'number') {
|
||||
decoration.set('z', parent.get('z') + 1);
|
||||
}
|
||||
decoration.attr('./kind', kind);
|
||||
decoration.attr('messages', messages);
|
||||
if (graph) {
|
||||
graph.addCell(decoration);
|
||||
}
|
||||
parent.embed(decoration);
|
||||
if (renderService && _.isFunction(renderService.initializeNewDecoration)) {
|
||||
renderService.initializeNewDecoration(decoration, {
|
||||
'paper': paper,
|
||||
'graph': graph
|
||||
});
|
||||
}
|
||||
return decoration;
|
||||
}
|
||||
|
||||
function createHandle(params) {
|
||||
params = params || {};
|
||||
var renderService = params.renderService;
|
||||
var paper = params.paper;
|
||||
var parent = params.parent;
|
||||
var kind = params.kind;
|
||||
var location = params.position;
|
||||
var graph = params.graph || (params.paper ? params.paper.model : undefined);
|
||||
|
||||
var handle;
|
||||
if (!location) {
|
||||
location = {x: 0, y: 0};
|
||||
}
|
||||
if (renderService && _.isFunction(renderService.createHandle)) {
|
||||
handle = renderService.createHandle(kind, parent);
|
||||
} else {
|
||||
handle = new joint.shapes.flo.ErrorDecoration({
|
||||
size: HANDLE_SIZE,
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': HANDLE_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
handle.set('type', joint.shapes.flo.HANDLE_TYPE);
|
||||
handle.set('position', location);
|
||||
if ((isChrome || isFF) && parent && typeof parent.get('z') === 'number') {
|
||||
handle.set('z', parent.get('z') + 1);
|
||||
}
|
||||
handle.attr('./kind', kind);
|
||||
if (graph) {
|
||||
graph.addCell(handle);
|
||||
}
|
||||
parent.embed(handle);
|
||||
if (renderService && _.isFunction(renderService.initializeNewHandle)) {
|
||||
renderService.initializeNewHandle(handle, {
|
||||
'paper': paper,
|
||||
'graph': graph
|
||||
});
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
joint.shapes.flo.GenericElement = joint.shapes.basic.Generic.extend({
|
||||
defaults: joint.util.deepSupplement({
|
||||
type: 'flo.GenericElement',
|
||||
size: { width: IMAGE_W, height: IMAGE_H },
|
||||
renderFunction: joint.dia.ElementView.prototype.renderMarkup,
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
joint.shapes.flo.GenericElementView = joint.dia.ElementView.extend({
|
||||
renderMarkup: function() {
|
||||
var renderFn = this.model.get('renderFunction');
|
||||
if (_.isFunction(renderFn)) {
|
||||
renderFn.call(this.model, this.el);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Gradient can only be applied to elements with non-0 size. Horizontal and vertical lines don't have any size.
|
||||
* Therefore, add a tiny value to target point x and y to ensure line is not completely vertical/horizontal
|
||||
*/
|
||||
joint.connectors.normalDimFix = function(sourcePoint, targetPoint, vertices) {
|
||||
var dimensionFix = 1e-3;
|
||||
var d = ['M', sourcePoint.x, sourcePoint.y];
|
||||
_.each(vertices, function(vertex) { d.push(vertex.x, vertex.y); });
|
||||
d.push(targetPoint.x + dimensionFix, targetPoint.y + dimensionFix);
|
||||
return d.join(' ');
|
||||
};
|
||||
|
||||
joint.shapes.flo.PatternLinkView = joint.dia.LinkView.extend({
|
||||
patternMarkup: '<pattern id="pattern-<%= id %>" patternUnits="userSpaceOnUse"></pattern>',
|
||||
initialize: function() {
|
||||
joint.dia.LinkView.prototype.initialize.apply(this, arguments);
|
||||
_.bindAll(this, 'fillWithPattern');
|
||||
},
|
||||
render: function() {
|
||||
joint.dia.LinkView.prototype.render.apply(this, arguments);
|
||||
|
||||
// make sure that pattern doesn't already exist
|
||||
if (!this.pattern) {
|
||||
this.pattern = joint.V(_.template(this.patternMarkup, { id: this.id }));
|
||||
joint.V(this.paper.svg).defs().append(this.pattern);
|
||||
}
|
||||
|
||||
// tell the '.connection' path to use the pattern
|
||||
var connection = joint.V(this.el).findOne('.connection').attr({
|
||||
stroke: 'url(#pattern-' + this.id + ')'
|
||||
});
|
||||
|
||||
// cache the stroke width
|
||||
this.strokeWidth = connection.attr('stroke-width') || 1;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
// make sure we stop an ongoing pattern update
|
||||
joint.util.cancelFrame(this.frameId);
|
||||
joint.dia.LinkView.prototype.remove.apply(this, arguments);
|
||||
this.pattern.remove();
|
||||
},
|
||||
|
||||
update: function() {
|
||||
joint.dia.LinkView.prototype.update.apply(this, arguments);
|
||||
joint.util.cancelFrame(this.frameId);
|
||||
this.frameId = joint.util.nextFrame(this.fillWithPattern);
|
||||
return this;
|
||||
},
|
||||
|
||||
fillWithPattern: function() {
|
||||
var strokeWidth = this.strokeWidth;
|
||||
// we get the bounding box of the linkView without the transformations
|
||||
// and expand it to all 4 sides by the stroke width
|
||||
// (making sure there is always enough room for drawing,
|
||||
// even if the bounding box was tiny.
|
||||
// Note that the bounding box doesn't include the stroke.)
|
||||
var bbox = joint.g.rect(joint.V(this.el).bbox(true)).moveAndExpand({
|
||||
x: - strokeWidth,
|
||||
y: - strokeWidth,
|
||||
width: 2 * strokeWidth,
|
||||
height: 2 * strokeWidth
|
||||
});
|
||||
|
||||
// create an array of all points the link goes through
|
||||
// (route doesn't contain the connection points)
|
||||
var points = [].concat(this.sourcePoint, this.route, this.targetPoint);
|
||||
|
||||
// transform all points to the links coordinate system
|
||||
points = _.map(points, function(point) {
|
||||
return joint.g.point(point.x - bbox.x, point.y - bbox.y);
|
||||
});
|
||||
|
||||
// iterate over the points and execute the drawing function
|
||||
// for each segment
|
||||
var elements = [];
|
||||
for (var i=0, pointsCount = points.length - 1; i < pointsCount; i++) {
|
||||
this.drawPattern.call(this, points[i], points[i+1], strokeWidth, i, elements);
|
||||
}
|
||||
|
||||
// Remove previous pattern if there is one
|
||||
while (this.pattern.node.firstChild) {
|
||||
this.pattern.node.removeChild(this.pattern.node.firstChild);
|
||||
}
|
||||
|
||||
// update the pattern dimensions and SVG contents
|
||||
this.pattern.attr(bbox);
|
||||
this.pattern.append(elements);
|
||||
},
|
||||
|
||||
// finds a gradient with perpendicular direction to a link segment
|
||||
gradientPoints: function(from, to, width) {
|
||||
|
||||
var angle = joint.g.toRad(from.theta(to) - 90);
|
||||
var center = joint.g.line(from, to).midpoint();
|
||||
var start = joint.g.point.fromPolar(width / 2, angle, center);
|
||||
var end = joint.g.point.fromPolar(width / 2, Math.PI + angle, center);
|
||||
return [start.x, start.y, end.x, end.y];
|
||||
},
|
||||
|
||||
// A drawing function executed for all links segments.
|
||||
drawPattern: function(from, to, width, i, elements) {
|
||||
var outlineWidth = 2;
|
||||
var innerWidth = width - outlineWidth;
|
||||
var outerWidth = width;
|
||||
var buttFrom = joint.g.point(from).move(to, -0.001);
|
||||
var buttTo = joint.g.point(to).move(from, -0.001);
|
||||
var nsSVG = 'http://www.w3.org/2000/svg';
|
||||
var gradientPoints = this.gradientPoints(from, to, width);
|
||||
|
||||
// Black background to paint the black outer border
|
||||
var background = document.createElementNS(nsSVG, 'path');
|
||||
background.setAttribute('stroke-width', outerWidth);
|
||||
background.setAttribute('stroke', 'rgba(52, 48, 45, 1.0)');
|
||||
background.setAttribute('d', ['M', from.x, from.y, to.x, to.y].join(' '));
|
||||
elements.push(joint.V(background));
|
||||
|
||||
// Construct gradient element for the pipe's inner contents
|
||||
var gradient = document.createElementNS(nsSVG, 'linearGradient');
|
||||
var gradientId = 'pattern-' + this.id + '-' + i;
|
||||
gradient.setAttribute('id', gradientId);
|
||||
gradient.setAttribute('gradientUnits', 'userSpaceOnUse');
|
||||
gradient.setAttribute('x1', gradientPoints[0]);
|
||||
gradient.setAttribute('y1', gradientPoints[1]);
|
||||
gradient.setAttribute('x2', gradientPoints[2]);
|
||||
gradient.setAttribute('y2', gradientPoints[3]);
|
||||
var stop1 = document.createElementNS(nsSVG, 'stop');
|
||||
stop1.setAttribute('offset', '0%');
|
||||
stop1.setAttribute('style', 'stop-color:rgba(52, 48, 45, 1.0)');
|
||||
gradient.appendChild(stop1);
|
||||
var stop2 = document.createElementNS(nsSVG, 'stop');
|
||||
stop2.setAttribute('offset', '50%');
|
||||
stop2.setAttribute('style', 'stop-color:white');
|
||||
gradient.appendChild(stop2);
|
||||
var stop3 = document.createElementNS(nsSVG, 'stop');
|
||||
stop3.setAttribute('offset', '100%');
|
||||
stop3.setAttribute('style', 'stop-color:rgba(52, 48, 45, 1.0)');
|
||||
gradient.appendChild(stop3);
|
||||
elements.push(joint.V(gradient));
|
||||
|
||||
// Inner contents of the pipe: path filled with gradient
|
||||
var interior = document.createElementNS(nsSVG, 'path');
|
||||
interior.setAttribute('stroke-width', innerWidth);
|
||||
interior.setAttribute('stroke', 'url(#' + gradientId +')');
|
||||
interior.setAttribute('d', joint.connectors.normalDimFix(from, to));
|
||||
elements.push(joint.V(interior));
|
||||
|
||||
// Rectangle at the start of the pipe
|
||||
var sourceEnd = document.createElementNS(nsSVG, 'path');
|
||||
sourceEnd.setAttribute('stroke-linecap', 'round');
|
||||
sourceEnd.setAttribute('stroke-width', outerWidth);
|
||||
sourceEnd.setAttribute('stroke', 'rgba(52, 48, 45, 1.0)');
|
||||
sourceEnd.setAttribute('d', ['M', from.x, from.y, buttFrom.x, buttFrom.y].join(' '));
|
||||
elements.push(joint.V(sourceEnd));
|
||||
|
||||
// Rectangle at the end of the pipe
|
||||
var targetEnd = document.createElementNS(nsSVG, 'path');
|
||||
targetEnd.setAttribute('stroke-linecap', 'round');
|
||||
targetEnd.setAttribute('stroke-width', outerWidth);
|
||||
targetEnd.setAttribute('stroke', 'rgba(52, 48, 45, 1.0)');
|
||||
targetEnd.setAttribute('d', ['M', to.x, to.y, buttTo.x, buttTo.y].join(' '));
|
||||
elements.push(joint.V(targetEnd));
|
||||
|
||||
// Arrow in the middle of the pipe
|
||||
var mid = joint.g.line(from,to).midpoint();
|
||||
var arrowStart = joint.g.point(mid).move(from, -15);
|
||||
var arrowEnd = joint.g.point(mid).move(to, -15);
|
||||
var arrow = document.createElementNS(nsSVG, 'path');
|
||||
var edgePoints = this.gradientPoints(from, to, innerWidth - 2);
|
||||
arrow.setAttribute('stroke-width', 1);
|
||||
arrow.setAttribute('stroke-linejoin', 'miter');
|
||||
arrow.setAttribute('stroke', 'black');
|
||||
arrow.setAttribute('fill', 'black');
|
||||
arrow.setAttribute('d', ['M', arrowStart.x, arrowStart.y, mid.x, mid.y, edgePoints[0], edgePoints[1], arrowEnd.x, arrowEnd.y, edgePoints[2], edgePoints[3], mid.x, mid.y].join(' '));
|
||||
elements.push(joint.V(arrow));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
'createNode': createNode,
|
||||
'createLink': createLink,
|
||||
'createDecoration': createDecoration,
|
||||
'createHandle': createHandle,
|
||||
'createCustomElement' : createCustomElement
|
||||
};
|
||||
});
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Code-editor directive. Connects Code-Mirror editor to angular.
|
||||
* Supports passing language and code text via 2-way scope binding variables or via DOM node attributes.
|
||||
* There is support for text transformation functions, i.e. encode/decode text before modifying the value
|
||||
*
|
||||
* Note: instead of language and code text it's likely the code will be changed to have getters and setters
|
||||
* for the code text which will avoid having encode and decode functions.
|
||||
*
|
||||
* This editor is typically used for properties where the property value is a script/code like value and
|
||||
* benefit from the use of a real editor that can provide features like syntax highlighting and mark
|
||||
* errors/warnings.
|
||||
*/
|
||||
define(function (require) {
|
||||
'use strict';
|
||||
|
||||
return ['$scope', function ($scope) {
|
||||
|
||||
var angular = require('angular');
|
||||
var CodeMirror = require('codemirror');
|
||||
|
||||
require('codemirror/mode/meta');
|
||||
require('codemirror/addon/lint/lint');
|
||||
require('codemirror/addon/hint/show-hint');
|
||||
require('codemirror/addon/mode/loadmode');
|
||||
require('codemirror/addon/edit/matchbrackets');
|
||||
require('codemirror/addon/edit/closebrackets');
|
||||
require('codemirror/addon/display/placeholder');
|
||||
require('codemirror/addon/scroll/annotatescrollbar');
|
||||
require('codemirror/addon/scroll/simplescrollbars');
|
||||
|
||||
|
||||
// languages
|
||||
require('codemirror/mode/groovy/groovy');
|
||||
require('codemirror/mode/javascript/javascript');
|
||||
require('codemirror/mode/python/python');
|
||||
require('codemirror/mode/ruby/ruby');
|
||||
require('codemirror/mode/clike/clike');
|
||||
|
||||
// Lint support
|
||||
require('jshint');
|
||||
require('codemirror/addon/lint/javascript-lint');
|
||||
|
||||
CodeMirror.modeURL = 'codemirror/mode/%N/%N';
|
||||
|
||||
|
||||
var defaultText = '';
|
||||
var defaultLanguage = 'Plain Text';
|
||||
|
||||
var doc;
|
||||
var errorRuler;
|
||||
var warningRuler;
|
||||
|
||||
var LINT_MAP = {
|
||||
'javascript': {
|
||||
onUpdateLinting: function (annotations) {
|
||||
var warnings = [];
|
||||
var errors = [];
|
||||
if ($scope.overviewRuler) {
|
||||
if (angular.isArray(annotations)) {
|
||||
annotations.forEach(function (a) {
|
||||
if (a.to && a.from && a.from.line >= 0 && a.from.ch >= 0 && a.to.line >= a.from.line && a.from.ch >= 0) {
|
||||
if (a.severity === 'error') {
|
||||
errors.push(a);
|
||||
} else if (a.severity === 'warning') {
|
||||
warnings.push(a);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
warningRuler.update(warnings);
|
||||
errorRuler.update(errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init = function (textarea, attrs) {
|
||||
|
||||
doc = CodeMirror.fromTextArea(textarea, {
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true
|
||||
});
|
||||
|
||||
if ($scope.scrollbarStyle) {
|
||||
doc.setOption('scrollbarStyle', $scope.scrollbarStyle);
|
||||
}
|
||||
|
||||
// CodeMirror would set 'placeholder` value at construction time based on the string value of placeholder attribute in the DOM
|
||||
// Thus, set the correct placeholder value in case value is angular expression.
|
||||
if (angular.isString($scope.placeholder)) {
|
||||
doc.setOption('placeholder', $scope.placeholder);
|
||||
}
|
||||
|
||||
warningRuler = doc.annotateScrollbar('CodeMirror-vertical-ruler-warning');
|
||||
errorRuler = doc.annotateScrollbar('CodeMirror-vertical-ruler-error');
|
||||
|
||||
function getLintOption(modeName) {
|
||||
var lint = LINT_MAP[modeName.toLowerCase()];
|
||||
return lint ? lint : false;
|
||||
}
|
||||
|
||||
function updateScopeText() {
|
||||
var text = doc.getValue();
|
||||
var result;
|
||||
var encodeFunc = $scope.encodeFunction();
|
||||
if (angular.isFunction(encodeFunc)) {
|
||||
result = encodeFunc.call(null, text);
|
||||
}
|
||||
if (typeof result === 'string') {
|
||||
text = result;
|
||||
}
|
||||
if (text === defaultText) {
|
||||
$scope.text = undefined;
|
||||
} else {
|
||||
$scope.text = text;
|
||||
}
|
||||
$scope.$apply();
|
||||
}
|
||||
|
||||
function updateMode() {
|
||||
var language = $scope.language && typeof $scope.language === 'string' ? $scope.language : defaultLanguage;
|
||||
if (language) {
|
||||
var info = CodeMirror.findModeByName(language);
|
||||
|
||||
// Set proper editor mode
|
||||
doc.setOption('mode', info.mime);
|
||||
CodeMirror.autoLoadMode(doc, info.mode);
|
||||
|
||||
// Set proper Lint mode
|
||||
doc.setOption('lint', getLintOption(info.name));
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.defaultText) {
|
||||
defaultText = attrs.defaultText;
|
||||
}
|
||||
|
||||
if (attrs.defaultLanguage) {
|
||||
defaultLanguage = attrs.defaultLanguage.toLowerCase();
|
||||
}
|
||||
|
||||
updateMode();
|
||||
|
||||
var text = $scope.text ? $scope.text : defaultText;
|
||||
var result;
|
||||
var decodeFunc = $scope.decodeFunction();
|
||||
if (angular.isFunction(decodeFunc)) {
|
||||
result = decodeFunc.call(this, text);
|
||||
}
|
||||
if (typeof result === 'string') {
|
||||
text = result;
|
||||
}
|
||||
doc.setValue(text);
|
||||
|
||||
doc.on('changes', function () {
|
||||
updateScopeText();
|
||||
});
|
||||
doc.on('blur', function () {
|
||||
updateScopeText();
|
||||
});
|
||||
$scope.$watch('language', function () {
|
||||
updateMode();
|
||||
});
|
||||
};
|
||||
|
||||
}];
|
||||
});
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
define(function (require) {
|
||||
'use strict';
|
||||
|
||||
var angular = require('angular');
|
||||
|
||||
return ['$scope', '$http', '$injector', '$log', function ($scope, $http, $injector, $log) {
|
||||
|
||||
var CodeMirror = require('codemirror');
|
||||
var enableTextToGraphSyncing = false;
|
||||
|
||||
var doc;
|
||||
|
||||
var errorMarkerRuler;
|
||||
|
||||
require('codemirror/addon/lint/lint');
|
||||
require('codemirror/addon/hint/show-hint');
|
||||
require('codemirror/addon/display/placeholder');
|
||||
require('codemirror/addon/scroll/annotatescrollbar');
|
||||
require('codemirror/addon/scroll/simplescrollbars');
|
||||
|
||||
/**
|
||||
* Control graph-to-text syncing. When it is active the graph will be automatically
|
||||
* updated as the text is modified.
|
||||
*/
|
||||
function enableGraphToTextSyncing(enable) {
|
||||
$scope.flo.enableSyncing(enable);
|
||||
enableTextToGraphSyncing = !enable;
|
||||
}
|
||||
|
||||
//TODO: using a controller to setup codemirror is probably not the 'nice'
|
||||
// way to do that. (angular docs say that dom manipulations are not the job of a controller
|
||||
// so probably this should be a directive rather than a controller.
|
||||
|
||||
// A bit dirty, we store the callback for codemirror linter here.
|
||||
// that way we can update markers each time error objects are
|
||||
// changed.
|
||||
var updateLinting;
|
||||
|
||||
/**
|
||||
* If new parse errors are discovered, this will create markers against
|
||||
* the editor text for them and call code mirror to update those markers.
|
||||
*/
|
||||
function refreshMarkers() {
|
||||
var markers = [];
|
||||
var parseErrors = $scope.definition.parseError;
|
||||
if (parseErrors && parseErrors.length) {
|
||||
for (var i = 0; i < parseErrors.length; i++) {
|
||||
var parseError = parseErrors[i];
|
||||
if (parseError.message && parseError.range) {
|
||||
var range = parseError.range;
|
||||
markers.push({
|
||||
from: range.start,
|
||||
to: range.end,
|
||||
message: parseError.message.split(/\r?\n/)[0],
|
||||
severity: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
updateLinting(doc, markers);
|
||||
errorMarkerRuler.update($scope.overviewRuler ? markers : []);
|
||||
}
|
||||
|
||||
function isDelimiter(c) {
|
||||
return c && (/\s|\|/).test(c);
|
||||
}
|
||||
|
||||
function findLast(string, predicate, start) {
|
||||
var pos = start || string.length - 1;
|
||||
while (pos >= 0 && !predicate(string[pos])) {
|
||||
pos--;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* The suggestions provided by rest api are very long and include the whole command typed
|
||||
* from the start of the line. This function determines the start of the 'interesting' part
|
||||
* at the end of the prefix, so that we can use it to chop-off the suggestion there.
|
||||
*/
|
||||
function interestingPrefixStart(prefix, completions) {
|
||||
var cursor = prefix.length;
|
||||
if (completions.every(function (completion) {
|
||||
return isDelimiter(completion[cursor]);
|
||||
})) {
|
||||
return cursor;
|
||||
}
|
||||
return findLast(prefix, isDelimiter);
|
||||
}
|
||||
|
||||
function contentAssist(doc, callback) {
|
||||
var cursor = doc.getCursor();
|
||||
var startOfLine = {line: cursor.line, ch: 0};
|
||||
var prefix = doc.getRange(startOfLine, cursor);
|
||||
|
||||
if ($scope.contentAssistServiceName) {
|
||||
var caService = $injector.get($scope.contentAssistServiceName);
|
||||
if (caService && angular.isFunction(caService.getProposals)) {
|
||||
return caService.getProposals(prefix).then(function (completions) {
|
||||
var chopAt = interestingPrefixStart(prefix, completions);
|
||||
return callback({
|
||||
list: completions.map(function (longCompletion) {
|
||||
var text = typeof longCompletion === 'string' ? longCompletion : longCompletion.text;
|
||||
return text.substring(chopAt);
|
||||
}),
|
||||
from: {line: startOfLine.line, ch: chopAt},
|
||||
to: cursor
|
||||
});
|
||||
}, function (err) {
|
||||
$log.error('Cannot get content assist: ' + err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.init = function (textarea) {
|
||||
contentAssist.async = true;
|
||||
|
||||
var options = {
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
lint: {
|
||||
async: true,
|
||||
getAnnotations: function (text, updateFun) {
|
||||
if (!updateLinting) {
|
||||
updateLinting = updateFun;
|
||||
$scope.$watch('definition.parseError', function() {
|
||||
// HACK!!!
|
||||
// CodeMirror syncs linting requests by id.
|
||||
// Hence can't simply call refreshMarkers without adjusting the waiting id
|
||||
// The first timeit's called the waitingFor id is equal to 1, hence reset it every time
|
||||
doc.state.lint.waitingFor = 1;
|
||||
refreshMarkers();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
extraKeys: {'Ctrl-Space': 'autocomplete'},
|
||||
hintOptions: {
|
||||
async: 'true',
|
||||
hint: contentAssist
|
||||
},
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
electricChars: false,
|
||||
smartIndent: false
|
||||
};
|
||||
|
||||
if ($scope.scrollbarStyle) {
|
||||
options.scrollbarStyle = $scope.scrollbarStyle;
|
||||
}
|
||||
|
||||
doc = CodeMirror.fromTextArea(textarea, options);
|
||||
|
||||
// CodeMirror would set 'placeholder` value at construction time based on the string value of placeholder attribute in the DOM
|
||||
// Thus, set the correct placeholder value in case value is angular expression.
|
||||
if (angular.isString($scope.placeholder)) {
|
||||
doc.setOption('placeholder', $scope.placeholder);
|
||||
}
|
||||
|
||||
doc.on('change', function () {
|
||||
if (enableTextToGraphSyncing) {
|
||||
$scope.definition.text = doc.getValue();
|
||||
$scope.flo.scheduleUpdateGraphRepresentation();
|
||||
}
|
||||
});
|
||||
doc.on('focus', function () {
|
||||
enableGraphToTextSyncing(false);
|
||||
});
|
||||
doc.on('blur', function () {
|
||||
enableGraphToTextSyncing(true);
|
||||
});
|
||||
errorMarkerRuler = doc.annotateScrollbar('CodeMirror-vertical-ruler-error');
|
||||
$scope.$watch('definition.text', function (newValue) {
|
||||
if (newValue !== doc.getValue()) {
|
||||
var cursorPosition = doc.getCursor();
|
||||
doc.setValue(newValue);
|
||||
doc.setCursor(cursorPosition);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,536 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var $ = require('jquery');
|
||||
var joint = require('joint');
|
||||
var shapesFactory = require('common/shapes-factory');
|
||||
var angular = require('angular');
|
||||
|
||||
// TODO move this node into custom nodes and links to centralize
|
||||
if (!joint.shapes.flo) {
|
||||
joint.shapes.flo = {};
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/23960312/can-i-add-new-attributes-in-jointjs-element
|
||||
joint.shapes.flo.PaletteGroupHeader = joint.shapes.basic.Generic.extend({
|
||||
// The path is the open/close arrow, defaults to vertical (open)
|
||||
markup: '<g class="scalable"><rect/></g><text/><g class="rotatable"><path d="m 10 10 l 5 8.7 l 5 -8.7 z"/></g>',
|
||||
defaults: joint.util.deepSupplement({
|
||||
type: 'palette.groupheader',
|
||||
size:{width:170,height:30},
|
||||
position:{x:0,y:0},
|
||||
attrs: {
|
||||
'rect': { fill: '#34302d', 'stroke-width': 1, stroke: '#6db33f', 'follow-scale':true, width:80, height:40 },
|
||||
'text': {
|
||||
text:'',
|
||||
fill: '#eeeeee',
|
||||
'ref-x': 0.5,
|
||||
'ref-y': 7,
|
||||
'x-alignment':'middle',
|
||||
'font-size': 18/*, 'font-weight': 'bold', 'font-variant': 'small-caps', 'text-transform': 'capitalize'*/
|
||||
},
|
||||
'path': { fill: 'white', 'stroke-width': 2, stroke: 'white'/*,transform:'rotate(90,15,15)'*/}
|
||||
},
|
||||
// custom properties
|
||||
isOpen:true
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
return ['$scope', '$timeout', '$log', '$injector', function($scope, $timeout, $log, $injector) {
|
||||
|
||||
var domContext;
|
||||
|
||||
var metamodelService;
|
||||
|
||||
var renderService;
|
||||
|
||||
var paletteGraph = new joint.dia.Graph();
|
||||
paletteGraph.attributes.type = joint.shapes.flo.PALETTE_TYPE;
|
||||
|
||||
var palette;
|
||||
|
||||
/**
|
||||
* The names of any groups in the palette that have been deliberately closed (the arrow clicked on)
|
||||
* @type {String[]}
|
||||
*/
|
||||
var closedGroups = [];
|
||||
|
||||
/**
|
||||
* Model of the clicked element
|
||||
*/
|
||||
var clickedElement;
|
||||
|
||||
var viewBeingDragged = null;
|
||||
|
||||
function trigger(triggerEvent,paramsObject) {
|
||||
if ($scope.paletteObservers) {
|
||||
$scope.paletteObservers.fireEvent(triggerEvent, paramsObject);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDrag(event) {
|
||||
// TODO offsetX/Y not on firefox
|
||||
//$log.debug("tracking move: x="+event.pageX+",y="+event.pageY);
|
||||
if (clickedElement && clickedElement.attr('metadata')) {
|
||||
if (!viewBeingDragged) {
|
||||
var dataOfClickedElement = clickedElement.attr('metadata');
|
||||
// custom div if not already built.
|
||||
$('<div>', {
|
||||
id: 'palette-floater'
|
||||
}).appendTo($('body'));
|
||||
var floatergraph = new joint.dia.Graph();
|
||||
floatergraph.attributes.type = joint.shapes.flo.FEEDBACK_TYPE;
|
||||
var floaterpaper = new joint.dia.Paper({
|
||||
el: $('#palette-floater'),
|
||||
elementView: renderService && angular.isFunction(renderService.getNodeView) ? renderService.getNodeView() : joint.dia.ElementView,
|
||||
gridSize:10,
|
||||
model: floatergraph,
|
||||
height: 400,
|
||||
width: 200,
|
||||
validateMagnet: function() {
|
||||
return false;
|
||||
},
|
||||
validateConnection: function() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// TODO float thing needs to be bigger otherwise icon label is missing
|
||||
// Initiative drag and drop - create draggable element
|
||||
var floaternode = shapesFactory.createNode({
|
||||
'renderService': renderService,
|
||||
'paper': floaterpaper,
|
||||
'graph': floatergraph,
|
||||
'metadata': dataOfClickedElement
|
||||
});
|
||||
var box = floaterpaper.findViewByModel(floaternode).getBBox();
|
||||
var size = floaternode.get('size');
|
||||
// Account for node real size including ports
|
||||
floaternode.translate(box.width - size.width, box.height - size.height);
|
||||
viewBeingDragged = floaterpaper.findViewByModel(floaternode);
|
||||
$('#palette-floater').offset({left:event.pageX+5,top:event.pageY+5});
|
||||
// trigger('dragStarted',{'dragged':viewBeingDragged,'x':x,'y':y});
|
||||
} else {
|
||||
$('#palette-floater').offset({left:event.pageX+5,top:event.pageY+5});
|
||||
trigger('drag',{'dragged':viewBeingDragged,'evt':event});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO lock to grid on drag? (if grid is on)
|
||||
|
||||
function getPaletteView(view) {
|
||||
return view.extend({
|
||||
pointerdown: function(/*evt, x, y*/) {
|
||||
// Remove the tooltip
|
||||
$('.node-tooltip').remove();
|
||||
// TODO move metadata to the right place (not inside attrs I think)
|
||||
clickedElement = this.model;
|
||||
if (clickedElement.attr('metadata')) {
|
||||
$(document).on('mousemove', handleDrag);
|
||||
}
|
||||
},
|
||||
pointermove: function(/*evt, x, y*/) {
|
||||
// Nothing to prevent move within the palette canvas
|
||||
},
|
||||
events: {
|
||||
// Tooltips on the palette elements
|
||||
'mouseenter': function(evt) {
|
||||
|
||||
// Ignore 'mouseenter' if any other buttons are pressed
|
||||
if (evt.buttons) {
|
||||
return;
|
||||
}
|
||||
|
||||
var model = this.model;
|
||||
var metadata = model.attr('metadata');
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showTooltip(evt.pageX, evt.pageY);
|
||||
},
|
||||
// TODO bug here - if the call to get the info takes a while, the tooltip may appear after the pointer has left the cell
|
||||
'mouseleave': function(/*evt, x,y*/) {
|
||||
this.hideTooltip();
|
||||
},
|
||||
'mousemove': function(evt) {
|
||||
this.moveTooltip(evt.pageX, evt.pageY);
|
||||
}
|
||||
},
|
||||
|
||||
showTooltip: function(x, y) {
|
||||
var model = this.model;
|
||||
var metadata = model.attr('metadata');
|
||||
// TODO refactor to use tooltip module
|
||||
var nodeTooltip = document.createElement('div');
|
||||
$(nodeTooltip).addClass('node-tooltip');
|
||||
$(nodeTooltip).appendTo($('body')).fadeIn('fast');
|
||||
var nodeDescription = document.createElement('div');
|
||||
$(nodeTooltip).addClass('tooltip-description');
|
||||
$(nodeTooltip).append(nodeDescription);
|
||||
|
||||
metadata.get('description').then(function(description) {
|
||||
$(nodeDescription).text(description ? description : model.attr('metadata/name'));
|
||||
}, function() {
|
||||
$(nodeDescription).text(model.attr('metadata/name'));
|
||||
});
|
||||
|
||||
if (!metadata.metadata || !metadata.metadata['hide-tooltip-options']) {
|
||||
metadata.get('properties').then(function(metaProps) {
|
||||
if (metaProps) {
|
||||
Object.keys(metaProps).sort().forEach(function(propertyName) {
|
||||
var optionRow = document.createElement('div');
|
||||
var optionName = document.createElement('span');
|
||||
var optionDescription = document.createElement('span');
|
||||
$(optionName).addClass('node-tooltip-option-name');
|
||||
$(optionDescription).addClass('node-tooltip-option-description');
|
||||
$(optionName).text(metaProps[propertyName].name);
|
||||
$(optionDescription).text(metaProps[propertyName].description);
|
||||
$(optionRow).append(optionName);
|
||||
$(optionRow).append(optionDescription);
|
||||
$(nodeTooltip).append(optionRow);
|
||||
});
|
||||
}
|
||||
}, function(error) {
|
||||
if (error) {
|
||||
$log.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var mousex = x + 10;
|
||||
var mousey = y + 10;
|
||||
$('.node-tooltip').css({ top: mousey, left: mousex });
|
||||
},
|
||||
|
||||
hideTooltip: function() {
|
||||
$('.node-tooltip').remove();
|
||||
},
|
||||
|
||||
moveTooltip: function(x, y) {
|
||||
var mousex = x + 10; // Get X coordinates
|
||||
var mousey = y + 10; // Get Y coordinates
|
||||
$('.node-tooltip').css({ top: mousey, left: mousex });
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function createPaletteGroup(title, isOpen) {
|
||||
var newGroupHeader = new joint.shapes.flo.PaletteGroupHeader({attrs:{text:{text:title}}});
|
||||
newGroupHeader.set('header',title);
|
||||
if (!isOpen) {
|
||||
newGroupHeader.attr({'path':{'transform':'rotate(-90,15,13)'}});
|
||||
newGroupHeader.set('isOpen',false);
|
||||
}
|
||||
paletteGraph.addCell(newGroupHeader);
|
||||
return newGroupHeader;
|
||||
}
|
||||
|
||||
function createPaletteEntry(title, metadata) {
|
||||
return shapesFactory.createNode({
|
||||
'renderService': renderService,
|
||||
'paper': palette,
|
||||
'metadata': metadata
|
||||
});
|
||||
}
|
||||
|
||||
function buildPalette(metamodel) {
|
||||
var startTime = new Date().getTime();
|
||||
paletteGraph.clear();
|
||||
|
||||
var filterText = $scope.filterText;
|
||||
if (filterText) {
|
||||
filterText = filterText.toLowerCase();
|
||||
}
|
||||
|
||||
var paletteNodes = [];
|
||||
var groupAdded = {};
|
||||
|
||||
var parentWidth = $(domContext.parentNode).width();
|
||||
|
||||
// The field closedGroups tells us which should not be shown
|
||||
// Work out the list of active groups/nodes based on the filter text
|
||||
var groups = metamodelService.groups && angular.isFunction(metamodelService.groups) ? metamodelService.groups() : Object.keys(metamodel);
|
||||
groups.forEach(function(group) {
|
||||
if (metamodel[group]) {
|
||||
Object.keys(metamodel[group]).sort().forEach(function(name) {
|
||||
var node = metamodel[group][name];
|
||||
var nodeActive = !node.noPaletteEntry;
|
||||
if (nodeActive && filterText) {
|
||||
nodeActive = false;
|
||||
if (name.toLowerCase().indexOf(filterText) !== -1) {
|
||||
nodeActive = true;
|
||||
}
|
||||
else if (group.toLowerCase().indexOf(filterText) !== -1) {
|
||||
nodeActive = true;
|
||||
}
|
||||
else if (node.description && node.description.toLowerCase().indexOf(filterText) !== -1) {
|
||||
nodeActive = true;
|
||||
}
|
||||
else if (node.properties) {
|
||||
Object.keys(node.properties).sort().forEach(function(propertyName) {
|
||||
if (propertyName.toLowerCase().indexOf(filterText) !== -1 ||
|
||||
(node.properties[propertyName].description &&
|
||||
node.properties[propertyName].description.toLowerCase().indexOf(filterText) !== -1)) {
|
||||
nodeActive=true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (nodeActive) {
|
||||
if (!groupAdded[group]) {
|
||||
var header = createPaletteGroup(group, !_.contains(closedGroups,group));
|
||||
header.set('size', {width: parentWidth, height: 30});
|
||||
paletteNodes.push(header);
|
||||
groupAdded[group] = true;
|
||||
}
|
||||
if (!_.contains(closedGroups,group)) {
|
||||
paletteNodes.push(createPaletteEntry(name, node));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var cellWidth = 0, cellHeight = 0;
|
||||
// Determine the size of the palette entry cell (width and height)
|
||||
paletteNodes.forEach(function(pnode) {
|
||||
if (pnode.attr('metadata/name')) {
|
||||
var dimension = {
|
||||
width: pnode.get('size').width,
|
||||
height: pnode.get('size').height
|
||||
};
|
||||
if (cellWidth < dimension.width) {
|
||||
cellWidth = dimension.width;
|
||||
}
|
||||
if (cellHeight < dimension.height) {
|
||||
cellHeight = dimension.height;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Adjust the palette entry cell size with paddings.
|
||||
cellWidth += 2 * $scope.flo.paletteEntryPadding.x;
|
||||
cellHeight += 2 * $scope.flo.paletteEntryPadding.y;
|
||||
|
||||
// Align palette entries row to be at the center
|
||||
var startX = parentWidth >= cellWidth ? (parentWidth - Math.floor(parentWidth / cellWidth) * cellWidth) / 2 : 0;
|
||||
var xpos = startX;
|
||||
var ypos= 0;
|
||||
var prevNode;
|
||||
|
||||
// Layout palette entry nodes
|
||||
paletteNodes.forEach(function(pnode) {
|
||||
var dimension = {
|
||||
width: pnode.get('size').width,
|
||||
height: pnode.get('size').height
|
||||
};
|
||||
if (pnode.get('header')) { //attributes.attrs.header) {
|
||||
// Palette entry header
|
||||
xpos = startX;
|
||||
pnode.set('position',{x:0, y:ypos});
|
||||
ypos += dimension.height + 5;
|
||||
} else {
|
||||
// Palette entry element
|
||||
if (xpos + cellWidth > parentWidth) {
|
||||
// Not enough real estate to place entry in a row - reset x position and leave the y pos which is next line
|
||||
xpos = startX;
|
||||
pnode.set('position', { x: xpos + (cellWidth - dimension.width) / 2, y: ypos + (cellHeight - dimension.height) / 2});
|
||||
} else {
|
||||
// Enough real estate to place entry in a row - adjust y position
|
||||
if (prevNode.attr('metadata/name')) {
|
||||
ypos -= cellHeight;
|
||||
}
|
||||
pnode.set('position', { x: xpos + (cellWidth - dimension.width) / 2, y: ypos + (cellHeight - dimension.height) / 2});
|
||||
}
|
||||
// increment x position and y position (can be reorganized)
|
||||
xpos += cellWidth;
|
||||
ypos += cellHeight;
|
||||
}
|
||||
prevNode = pnode;
|
||||
});
|
||||
palette.setDimensions(parentWidth, ypos);
|
||||
$log.info('buildPalette took '+(new Date().getTime()-startTime)+'ms');
|
||||
}
|
||||
|
||||
var _metamodelListener = {
|
||||
metadataError: function(data/*, status, headers, config*/) {
|
||||
$log.error(JSON.stringify(data));
|
||||
},
|
||||
metadataRefresh: function() {
|
||||
|
||||
},
|
||||
metadataChanged: function(data) {
|
||||
buildPalette(data.newData);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO handle case of clicking header whilst already rotating
|
||||
|
||||
/*
|
||||
* Modify the rotation of the arrow in the header from horizontal(closed) to vertical(open)
|
||||
*/
|
||||
function rotateOpen(element) {
|
||||
var angle = 90;
|
||||
var rotateIt = function() {
|
||||
angle = angle - 10;
|
||||
element.attr({'path':{'transform':'rotate(-'+angle+',15,13)'}});
|
||||
if (angle <= 0) {
|
||||
element.set('isOpen',true);
|
||||
closedGroups = _.without(closedGroups,element.get('header'));
|
||||
metamodelService.load().then(buildPalette);
|
||||
return;
|
||||
} else {
|
||||
$timeout(rotateIt,10);
|
||||
}
|
||||
};
|
||||
$timeout(rotateIt);
|
||||
}
|
||||
|
||||
// TODO better name for this function as this does the animation *and* updates the palette
|
||||
|
||||
/*
|
||||
* Modify the rotation of the arrow in the header from vertical(open) to horizontal(closed)
|
||||
*/
|
||||
function rotateClosed(element) {
|
||||
var angle = 0;
|
||||
var rotateIt = function() {
|
||||
angle = angle + 10;
|
||||
element.attr({'path':{'transform':'rotate(-'+angle+',15,13)'}});
|
||||
if (angle >= 90) {
|
||||
element.set('isOpen',false);
|
||||
closedGroups.push(element.get('header'));
|
||||
metamodelService.load().then(buildPalette);
|
||||
return;
|
||||
} else {
|
||||
$timeout(rotateIt,10);
|
||||
}
|
||||
};
|
||||
$timeout(rotateIt);
|
||||
}
|
||||
|
||||
function handleMouseUp(/*event*/) {
|
||||
$(document).off('mousemove', handleDrag);
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
if (metamodelService && angular.isFunction(metamodelService.unsubscribe)) {
|
||||
metamodelService.unsubscribe(_metamodelListener);
|
||||
}
|
||||
$(document).off('mouseup', handleMouseUp);
|
||||
}
|
||||
|
||||
function filterTextUpdated() {
|
||||
metamodelService.load().then(buildPalette);
|
||||
}
|
||||
|
||||
function init(element/*, attrs*/) {
|
||||
|
||||
domContext = element;
|
||||
|
||||
metamodelService = $injector.get($scope.metamodelServiceName);
|
||||
|
||||
if ($scope.renderServiceName) {
|
||||
renderService = $injector.get($scope.renderServiceName);
|
||||
}
|
||||
|
||||
// Create the paper for the palette using the specified element view
|
||||
palette = new joint.dia.Paper({
|
||||
el: $('#palette-paper', domContext),
|
||||
gridSize:1,
|
||||
model:paletteGraph,
|
||||
height: $(domContext.parentNode).height(),
|
||||
width: $(domContext.parentNode).width(),
|
||||
elementView: getPaletteView(renderService && angular.isFunction(renderService.getNodeView) ? renderService.getNodeView() : joint.dia.ElementView)
|
||||
});
|
||||
|
||||
palette.on('cell:pointerup',
|
||||
function(cellview, evt) {
|
||||
$log.debug('pointerup');
|
||||
if (viewBeingDragged) {
|
||||
trigger('drop',{'dragged':viewBeingDragged,'evt':evt});
|
||||
viewBeingDragged = null;
|
||||
}
|
||||
clickedElement = null;
|
||||
$('#palette-floater').remove();
|
||||
});
|
||||
|
||||
// Toggle the header open/closed on a click
|
||||
palette.on('cell:pointerclick',
|
||||
function(cellview/*,evt*/) {
|
||||
// TODO [design][palette] should the user need to click on the arrow rather than anywhere on the header?
|
||||
// Click position within the element would be: evt.offsetX, evt.offsetY
|
||||
var element = cellview.model;
|
||||
if (cellview.model.attributes.header) {
|
||||
// Toggle the header open/closed
|
||||
if (element.get('isOpen')) {
|
||||
rotateClosed(element);
|
||||
} else {
|
||||
rotateOpen(element);
|
||||
}
|
||||
}
|
||||
// TODO [palette] ensure other mouse handling events do nothing for headers
|
||||
// TODO [palette] move 'metadata' field to the right place (not inside attrs I think)
|
||||
});
|
||||
|
||||
$(document).on('mouseup', handleMouseUp);
|
||||
|
||||
if (metamodelService) {
|
||||
metamodelService.load().then(function(data) {
|
||||
buildPalette(data);
|
||||
if (metamodelService && angular.isFunction(metamodelService.subscribe)) {
|
||||
metamodelService.subscribe(_metamodelListener);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$log.error('No Metamodel service specified for palette!');
|
||||
}
|
||||
|
||||
if ($scope.flo) {
|
||||
$scope.flo.paletteSize = $scope.flo.paletteSize || $(domContext.parentNode).width();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', dispose);
|
||||
|
||||
$scope.init = init;
|
||||
|
||||
$scope.filterTextUpdated = filterTextUpdated;
|
||||
|
||||
if ($scope.flo) {
|
||||
$scope.flo._paletteGraph = function() {
|
||||
return paletteGraph;
|
||||
};
|
||||
$scope.flo.paletteEntryPadding = $scope.flo.paletteEntryPadding || {x:12, y:12};
|
||||
}
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $scope.flo.paletteSize;
|
||||
}, function(newValue, oldValue) {
|
||||
if (oldValue !== newValue) {
|
||||
metamodelService.load().then(buildPalette);
|
||||
}
|
||||
});
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of directives
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var angular = require('angular');
|
||||
|
||||
return angular.module('flo.directives', [])
|
||||
.directive('resizer', require('directives/resizer'))
|
||||
.directive('dslEditor', require('directives/dsl-editor'))
|
||||
.directive('codeEditor', require('directives/code-editor'))
|
||||
.directive('floPalette', require('directives/palette'))
|
||||
.directive('floEditor', require('directives/graph-editor'))
|
||||
.directive('genericDslEditor', require('directives/generic-dsl-editor'));
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
define(function () {
|
||||
'use strict';
|
||||
|
||||
return [function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: require('controllers/code-editor'),
|
||||
link: function(scope, element, attrs) {
|
||||
scope.init(element[0], attrs);
|
||||
},
|
||||
scope: {
|
||||
language: '=codeLanguage',
|
||||
text: '=codeText',
|
||||
decodeFunction: '&',
|
||||
encodeFunction: '&',
|
||||
placeholder: '@',
|
||||
scrollbarStyle: '@',
|
||||
overviewRuler: '@'
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
define(function () {
|
||||
'use strict';
|
||||
|
||||
return ['$interpolate', function ($interpolate) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: true,
|
||||
controller: require('controllers/dsl-editor'),
|
||||
link: function (scope, element, attrs) {
|
||||
if (attrs.contentAssistServiceName) {
|
||||
scope.contentAssistServiceName = attrs.contentAssistServiceName;
|
||||
}
|
||||
if (attrs.placeholder) {
|
||||
scope.placeholder = $interpolate(attrs.placeholder)(scope);
|
||||
}
|
||||
if (attrs.scrollbarStyle) {
|
||||
scope.scrollbarStyle = $interpolate(attrs.scrollbarStyle)(scope);
|
||||
}
|
||||
if (attrs.overviewRuler) {
|
||||
scope.overviewRuler = $interpolate(attrs.overviewRuler)(scope);
|
||||
}
|
||||
scope.init(element[0]);
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
define(function () {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var angular = require('angular');
|
||||
|
||||
return [ function() {
|
||||
|
||||
var CodeMirror = require('codemirror');
|
||||
|
||||
var doc;
|
||||
|
||||
var debounce;
|
||||
|
||||
require('codemirror/addon/lint/lint');
|
||||
require('codemirror/addon/hint/show-hint');
|
||||
require('codemirror/addon/display/placeholder');
|
||||
require('codemirror/addon/scroll/annotatescrollbar');
|
||||
require('codemirror/addon/scroll/simplescrollbars');
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
dsl: '=',
|
||||
hint: '=',
|
||||
lint: '=',
|
||||
placeholder: '@',
|
||||
scrollbarStyle: '@'
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
if (attrs.debounce) {
|
||||
debounce = parseInt(attrs.debounce);
|
||||
}
|
||||
|
||||
var options = {
|
||||
value: scope.dsl,
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
extraKeys: {'Ctrl-Space': 'autocomplete'},
|
||||
lineNumbers: attrs.lineNumbers && attrs.lineNumbers.toLowerCase() === 'true',
|
||||
lineWrapping: attrs.lineWrapping && attrs.lineWrapping.toLowerCase() === 'true',
|
||||
electricChars: false,
|
||||
smartIndent: false
|
||||
};
|
||||
|
||||
if (scope.scrollbarStyle) {
|
||||
options.scrollbarStyle = scope.scrollbarStyle;
|
||||
}
|
||||
|
||||
if (scope.lint) {
|
||||
options.lint = scope.lint;
|
||||
}
|
||||
|
||||
if (scope.hint) {
|
||||
options.hintOptions = scope.hint;
|
||||
}
|
||||
|
||||
doc = CodeMirror.fromTextArea(element[0], options);
|
||||
|
||||
// CodeMirror would set 'placeholder` value at construction time based on the string value of placeholder attribute in the DOM
|
||||
// Thus, set the correct placeholder value in case value is angular expression.
|
||||
if (angular.isString(scope.placeholder)) {
|
||||
doc.setOption('placeholder', scope.placeholder);
|
||||
}
|
||||
|
||||
var dslChangedHandler = function () {
|
||||
scope.$apply(function() {
|
||||
scope.dsl = doc.getValue();
|
||||
});
|
||||
};
|
||||
|
||||
doc.on('change', debounce ? _.debounce(dslChangedHandler, debounce) : dslChangedHandler);
|
||||
|
||||
scope.$watch('dsl', function (newValue) {
|
||||
if (newValue!==doc.getValue()) {
|
||||
var cursorPosition = doc.getCursor();
|
||||
doc.setValue(newValue ? newValue : '');
|
||||
doc.setCursor(cursorPosition);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch('hint', function(newValue) {
|
||||
doc.setOption('hintOptions', newValue);
|
||||
});
|
||||
|
||||
scope.$watch('lint', function(newValue) {
|
||||
doc.setOption('lint', newValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
define(function () {
|
||||
'use strict';
|
||||
|
||||
return [function () {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element, attrs) {
|
||||
if (attrs.metamodelServiceName) {
|
||||
scope.metamodelServiceName = attrs.metamodelServiceName;
|
||||
}
|
||||
|
||||
if (attrs.renderServiceName) {
|
||||
scope.renderServiceName = attrs.renderServiceName;
|
||||
}
|
||||
|
||||
if (attrs.editorServiceName) {
|
||||
scope.editorServiceName = attrs.editorServiceName;
|
||||
}
|
||||
|
||||
if (attrs.paletteSize) {
|
||||
scope.flo.paletteSize = Number(attrs.paletteSize);
|
||||
}
|
||||
|
||||
if (attrs.paperPadding) {
|
||||
scope.paperPadding = Number(attrs.paperPadding);
|
||||
}
|
||||
|
||||
scope.init(element[0], attrs);
|
||||
element.find('#paper').bind('keydown', function(e) {
|
||||
if (e.which === 8 || e.which === 46) {
|
||||
if (!scope.flo.readOnly() /*&& scope.flo.getSelection()*/) {
|
||||
scope.flo.deleteSelectedNode();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
controller: require('controllers/graph-editor'),
|
||||
controllerAs: 'floModel',
|
||||
transclude: true,
|
||||
template:
|
||||
'<ng-transclude></ng-transclude>' +
|
||||
'<div id="flow-view" class="flow-view" style="position:relative;">' +
|
||||
'<div id="canvas" class="canvas" style="position:relative; display: block; width: 100%; height: 100%;">' +
|
||||
'<div id="palette-container" class="palette-container" ng-if="!flo.noPalette" style="overflow:hidden;">' +
|
||||
'<flo-palette></flo-palette>' +
|
||||
'</div>' +
|
||||
'<div id="sidebar-resizer" ng-if="!flo.noPalette"' +
|
||||
'resizer="vertical"' +
|
||||
'resizer-width="6"' +
|
||||
'resizer-left="#palette-container"' +
|
||||
'resizer-right="#paper-container">' +
|
||||
'</div>' +
|
||||
|
||||
'<div id="paper-container">' +
|
||||
'<div id="paper" class="paper" tabindex="0" style="overflow: hidden; position: absolute; display: block; height:100%; width:100%; overflow:auto;"></div>' +
|
||||
'<span class="canvas-controls-container" ng-if="canvasControls">' +
|
||||
'<table ng-if="canvasControls.zoom" class="canvas-control zoom-canvas-control"><tbody><tr>' +
|
||||
'<td><input class="zoom-canvas-input canvas-control zoom-canvas-control" type="text" data-inline="true" ng-model="flo.zoomPercent" ng-model-options="{ getterSetter: true, updateOn: \'blur change\' }" size="3"></input>' +
|
||||
'<label class="canvas-control zoom-canvas-label">%</label></td>' +
|
||||
'<td><input type="range" data-inline="true" ng-model="flo.zoomPercent" ng-model-options="{ getterSetter: true }" step="{{flo.getZoomStep()}}" max="{{flo.getMaxZoom()}}" min="{{flo.getMinZoom()}}" data-type="range" name="range" class="canvas-control zoom-canvas-control"></input></td>' +
|
||||
'</tr></tbody></table>' +
|
||||
'</span>' +
|
||||
'<div id="properties" class="properties" style="position:absolute; bottom:0; width:100%; overflow:auto;"></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div id="hook"></div>' +
|
||||
'<div id="dialogs"></div>' +
|
||||
'</div>'
|
||||
};
|
||||
|
||||
}];
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
define(function () {
|
||||
'use strict';
|
||||
|
||||
return [function () {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: true,
|
||||
link: function(scope, element, attrs) {
|
||||
if (attrs.metamodelServiceName) {
|
||||
scope.metamodelServiceName = attrs.metamodelServiceName;
|
||||
}
|
||||
scope.init(element[0], attrs);
|
||||
},
|
||||
controller: require('controllers/palette'),
|
||||
controllerAs: 'floPalette',
|
||||
template:
|
||||
'<div id="palette-filter" class="palette-filter">' +
|
||||
'<input type="text" id="palette-filter-textfield" ng-model="filterText" ng-change="filterTextUpdated()" class="palette-filter-textfield" ng-model-options="{ debounce: 500 }"/>' +
|
||||
'</div>' +
|
||||
'<div id="palette-paper-container" style="height:calc(100% - 46px); width:100%; overflow:auto;">' +
|
||||
'<div id="palette-paper" class="palette-paper" style="overflow:hidden;"></div>' +
|
||||
'</div>'
|
||||
};
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
define(function () {
|
||||
'use strict';
|
||||
|
||||
return ['$document', function($document) {
|
||||
|
||||
return function($scope, $element, $attrs) {
|
||||
|
||||
function resize(size) {
|
||||
if ($attrs.resizer === 'vertical') {
|
||||
// Handle vertical resizer
|
||||
var x = size;
|
||||
|
||||
if ($attrs.resizerMax && x > $attrs.resizerMax) {
|
||||
x = parseInt($attrs.resizerMax);
|
||||
}
|
||||
|
||||
$element.css({
|
||||
left: x + 'px'
|
||||
});
|
||||
|
||||
$($attrs.resizerLeft).css({
|
||||
width: x + 'px'
|
||||
});
|
||||
$($attrs.resizerRight).css({
|
||||
left: (x + parseInt($attrs.resizerWidth)) + 'px'
|
||||
});
|
||||
} else {
|
||||
// Handle horizontal resizer
|
||||
var y = size;
|
||||
|
||||
$element.css({
|
||||
bottom: y + 'px'
|
||||
});
|
||||
|
||||
$($attrs.resizerTop).css({
|
||||
bottom: (y + parseInt($attrs.resizerHeight)) + 'px'
|
||||
});
|
||||
$($attrs.resizerBottom).css({
|
||||
height: y + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mousemove(event) {
|
||||
var size;
|
||||
if ($attrs.resizer === 'vertical') {
|
||||
// Handle vertical resizer. Calculate new size relative to palette container DOM node
|
||||
size = event.pageX - $($attrs.resizerLeft).offset().left;
|
||||
} else {
|
||||
// Handle horizontal resizer Calculate new size relative to palette container DOM node
|
||||
size = window.innerHeight - event.pageY - $($attrs.resizerTop).offset().top;
|
||||
}
|
||||
$scope.flo.paletteSize = size;
|
||||
// Apply changes to the scope such that various watchers can react to palette size change
|
||||
$scope.$apply();
|
||||
}
|
||||
|
||||
function mouseup() {
|
||||
$document.unbind('mousemove', mousemove);
|
||||
$document.unbind('mouseup', mouseup);
|
||||
}
|
||||
|
||||
$element.on('mousedown', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$document.on('mousemove', mousemove);
|
||||
$document.on('mouseup', mouseup);
|
||||
});
|
||||
|
||||
// Adjust DOM node sizes based on palette size model value
|
||||
$scope.$watch(function() {
|
||||
return $scope.flo.paletteSize;
|
||||
}, function(newValue) {
|
||||
resize(newValue);
|
||||
});
|
||||
|
||||
if ($scope.flo.paletteSize) {
|
||||
resize($scope.flo.paletteSize);
|
||||
}
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
60
src/main.js
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
joint: '../lib/joint/joint',
|
||||
backbone: '../lib/backbone/backbone',
|
||||
angular: '../lib/angular/angular',
|
||||
jquery: '../lib/jquery/jquery',
|
||||
bootstrap: '../lib/bootstrap/bootstrap',
|
||||
lodash: '../lib/lodash/lodash',
|
||||
jshint: '../lib/jshint/dist/jshint',
|
||||
floDirectives: './directives',
|
||||
floServices: './services'
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
// Backbone requires underscore. This forces requireJS to load lodash instead:
|
||||
'underscore': 'lodash'
|
||||
}
|
||||
},
|
||||
packages: [
|
||||
{
|
||||
name: 'codemirror',
|
||||
location: '../lib/codemirror',
|
||||
main: 'lib/codemirror'
|
||||
}
|
||||
],
|
||||
shim: {
|
||||
angular: {
|
||||
deps: ['bootstrap'],
|
||||
exports: 'angular'
|
||||
},
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
joint: {
|
||||
deps: ['jquery', 'underscore', 'backbone'],
|
||||
},
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
jshint: {
|
||||
deps: ['lodash']
|
||||
}
|
||||
}
|
||||
});
|
||||
101
src/services.js
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Services
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
define(['angular'], function (angular) {
|
||||
'use strict';
|
||||
|
||||
return angular.module('flo.services', [])
|
||||
.factory('MetamodelUtils', ['$q', function ($q) {
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Return the metadata for a particular palette entry in a particular group.
|
||||
* @param {String} name - name of the palette entry
|
||||
* @param {string} group - group in which the palette entry should exist (e.g. sinks)
|
||||
* @return {{name:string,group:string,unresolved:Boolean}}
|
||||
*/
|
||||
getMetadata: function (metamodel, name, group) {
|
||||
if (name && group && metamodel[group][name]) {
|
||||
return metamodel[group][name];
|
||||
} else {
|
||||
return {
|
||||
name: name,
|
||||
group: group,
|
||||
unresolved: true,
|
||||
get: function () {
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve();
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
matchGroup: function (metamodel, type, incoming, outgoing) {
|
||||
incoming = typeof incoming === 'number' ? incoming : 0;
|
||||
outgoing = typeof outgoing === 'number' ? outgoing : 0;
|
||||
var matches = [];
|
||||
var i;
|
||||
if (type) {
|
||||
for (i in metamodel) {
|
||||
if (metamodel[i][type]) {
|
||||
matches.push(metamodel[i][type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var group;
|
||||
var score = Number.MIN_VALUE;
|
||||
for (i = 0; i < matches.length; i++) {
|
||||
var constraints = matches[i].constraints;
|
||||
if (constraints) {
|
||||
var failedConstraintsNumber = 0;
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' && constraints.maxOutgoingLinksNumber < outgoing) {
|
||||
failedConstraintsNumber++;
|
||||
}
|
||||
if (typeof constraints.minOutgoingLinksNumber === 'number' && constraints.minOutgoingLinksNumber > outgoing) {
|
||||
failedConstraintsNumber++;
|
||||
}
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' && constraints.maxIncomingLinksNumber < incoming) {
|
||||
failedConstraintsNumber++;
|
||||
}
|
||||
if (typeof constraints.minIncomingLinksNumber === 'number' && constraints.minIncomingLinksNumber > incoming) {
|
||||
failedConstraintsNumber++;
|
||||
}
|
||||
|
||||
if (failedConstraintsNumber === 0) {
|
||||
return matches[i].group;
|
||||
} else if (failedConstraintsNumber > score) {
|
||||
score = failedConstraintsNumber;
|
||||
group = matches[i].group;
|
||||
}
|
||||
} else {
|
||||
return matches[i].group;
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"esnext": true,
|
||||
"bitwise": true,
|
||||
"camelcase": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"indent": 2,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"quotmark": "single",
|
||||
"regexp": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"strict": true,
|
||||
"trailing": true,
|
||||
"smarttabs": true,
|
||||
"globals": {
|
||||
"after": false,
|
||||
"afterEach": false,
|
||||
"angular": false,
|
||||
"before": false,
|
||||
"beforeEach": false,
|
||||
"browser": false,
|
||||
"describe": false,
|
||||
"expect": false,
|
||||
"inject": false,
|
||||
"it": false,
|
||||
"jasmine": false,
|
||||
"spyOn": false,
|
||||
"define": true
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
var tests = Object.keys(window.__karma__.files).filter(function (file) {
|
||||
return /spec\.js$/.test(file);
|
||||
});
|
||||
|
||||
requirejs.config({
|
||||
// Karma serves files from '/base'
|
||||
|
||||
paths: {
|
||||
joint: '/base/lib/joint/joint',
|
||||
backbone: '/base/lib/backbone/backbone',
|
||||
|
||||
text : '/base/lib/requirejs-text/text',
|
||||
angular: '/base/lib/angular/angular',
|
||||
jquery: '/base/lib/jquery/jquery',
|
||||
bootstrap: '/base/lib/bootstrap/bootstrap',
|
||||
lodash: '/base/lib/lodash/lodash',
|
||||
dagre: '/base/lib/dagre/dagre.core.min',
|
||||
graphlib: '/base/lib/graphlib/graphlib.core',
|
||||
angularMocks: '/base/lib/angular-mocks/angular-mocks',
|
||||
jshint: '/base/lib/jshint/dist/jshint',
|
||||
|
||||
flo: '/base/dist/spring-flo',
|
||||
metamodelService: '/base/tests/resources/metamodel-service',
|
||||
'editor-service': '/base/tests/resources/editor-service',
|
||||
'graph-to-text': '/base/tests/resources/graph-to-text',
|
||||
'render-service': '/base/tests/resources/render-service'
|
||||
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
// Backbone requires underscore. This forces requireJS to load lodash instead:
|
||||
'underscore': 'lodash'
|
||||
}
|
||||
},
|
||||
shim: {
|
||||
angular: {
|
||||
deps: ['bootstrap'],
|
||||
exports: 'angular'
|
||||
},
|
||||
'angularMocks': {
|
||||
deps:['angular'],
|
||||
'exports':'angular.mock'
|
||||
},
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
graphlib: {
|
||||
deps: ['underscore']
|
||||
},
|
||||
dagre: {
|
||||
deps: ['graphlib', 'underscore']
|
||||
},
|
||||
|
||||
backbone: {
|
||||
//These script dependencies should be loaded before loading backbone.js.
|
||||
deps: ['underscore', 'jquery'],
|
||||
},
|
||||
joint: {
|
||||
deps: ['jquery', 'underscore', 'backbone'],
|
||||
},
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
jshint: {
|
||||
deps: ['lodash']
|
||||
},
|
||||
flo: {
|
||||
deps: ['angular', 'jquery', 'joint', 'underscore']
|
||||
},
|
||||
metamodelService: {
|
||||
deps: [ 'flo', 'graph-to-text' ]
|
||||
},
|
||||
'editor-service': {
|
||||
deps: [ 'flo' ]
|
||||
},
|
||||
'render-service': {
|
||||
deps: [ 'flo' ]
|
||||
},
|
||||
'graph2text': {
|
||||
deps: [ 'joint' ]
|
||||
}
|
||||
},
|
||||
|
||||
// ask Require.js to load these files (all our tests)
|
||||
deps: tests,
|
||||
|
||||
// start test run, once Require.js is done
|
||||
callback: window.__karma__.start
|
||||
});
|
||||
@@ -1,539 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var joint = require('joint');
|
||||
var app = require('flo');
|
||||
|
||||
app.factory('sample-editor-service', [function() {
|
||||
|
||||
function createHandles(flo, createHandle, element) {
|
||||
var bbox = element.getBBox();
|
||||
var pt = bbox.origin().offset(bbox.width + 3, bbox.height + 3);
|
||||
createHandle(element, 'remove', flo.deleteSelectedNode, pt);
|
||||
}
|
||||
|
||||
function validatePort(/*flo, cellView, magnet*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateLink(flo, cellViewS, magnetS, cellViewT, magnetT, end, linkView) { // jshint ignore:line
|
||||
// Prevent linking from input ports.
|
||||
if (magnetS && magnetS.getAttribute('type') === 'input') {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking from output ports to input ports within one element.
|
||||
if (cellViewS === cellViewT) {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking to input ports.
|
||||
if (magnetT && magnetT.getAttribute('type') === 'output') {
|
||||
return false;
|
||||
}
|
||||
// var message = validateLinkEdit(flo.getGraph(), (cellViewS ? cellViewS.model : null), (cellViewT ? cellViewT.model : null), linkView.model, end);
|
||||
// return !message;
|
||||
return cellViewS.model && cellViewT.model && !(cellViewS.model instanceof joint.shapes.flo.ErrorDecoration) && !(cellViewT.model instanceof joint.shapes.flo.ErrorDecoration);
|
||||
}
|
||||
|
||||
// function validateLinkEdit(graph, source, target, link, end) {
|
||||
// var newOutgoing = 0, newIncoming = 0;
|
||||
// if (end === 'source') {
|
||||
// newOutgoing = 1;
|
||||
// } else if (end === 'target') {
|
||||
// newIncoming = 1;
|
||||
// } else if (end === 'addition') {
|
||||
// newOutgoing = 1;
|
||||
// newIncoming = 1;
|
||||
// } else if (end === 'deletion') {
|
||||
// newOutgoing = -1;
|
||||
// newIncoming = -1;
|
||||
// }
|
||||
// var outboundLinks = graph.getConnectedLinks(source, { outbound: true });
|
||||
// var inboundLinks = graph.getConnectedLinks(source, { inbound: true });
|
||||
// var sourceMaxOutgoingLinksNumber = source.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
// var sourceMinOutgoingLinksNumber = source.attr('metadata/constraints/minOutgoingLinksNumber');
|
||||
// var targetMaxIncomingLinksNumber = target.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
// var targetMinIncomingLinksNumber = target.attr('metadata/constraints/minIncomingLinksNumber');
|
||||
// if (typeof sourceMaxOutgoingLinksNumber === 'number' && sourceMaxOutgoingLinksNumber < outboundLinks.length + newOutgoing) {
|
||||
// return 'Source cannot have more than ' + sourceMaxOutgoingLinksNumber + ' outgoing links';
|
||||
// }
|
||||
// if (typeof sourceMinOutgoingLinksNumber === 'number' && sourceMinOutgoingLinksNumber < outboundLinks.length + newOutgoing) {
|
||||
// return 'Source cannot have less than ' + sourceMinOutgoingLinksNumber + ' outgoing links';
|
||||
// }
|
||||
// if (typeof targetMaxIncomingLinksNumber === 'number' && targetMaxIncomingLinksNumber < inboundLinks.length + newIncoming) {
|
||||
// return 'Target cannot have more than ' + targetMaxIncomingLinksNumber + ' incoming links';
|
||||
// }
|
||||
// if (typeof targetMinIncomingLinksNumber === 'number' && targetMinIncomingLinksNumber < outboundLinks.length + newIncoming) {
|
||||
// return 'Source cannot have less than ' + targetMinIncomingLinksNumber + ' incoming links';
|
||||
// }
|
||||
// }
|
||||
|
||||
function repairDamage(flo, node) {
|
||||
/*
|
||||
* remove incoming, outgoing links and cache their sources and targets not equal to current node
|
||||
*/
|
||||
var sources = [];
|
||||
var targets = [];
|
||||
var i = 0;
|
||||
var links = flo.getGraph().getConnectedLinks(node);
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var targetId = links[i].get('target').id;
|
||||
var sourceId = links[i].get('source').id;
|
||||
if (targetId === node.id) {
|
||||
links[i].remove();
|
||||
sources.push(sourceId);
|
||||
} else if (sourceId === node.id) {
|
||||
links[i].remove();
|
||||
targets.push(targetId);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* best attempt to connect source and targets bypassing the node
|
||||
*/
|
||||
if (sources.length === 1) {
|
||||
var source = sources[0];
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({
|
||||
'id': source,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': targets[i],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
} else if (targets.length === 1) {
|
||||
var target = targets[0];
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({
|
||||
'id': sources[i],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': target,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function preDelete(flo, cell) {
|
||||
repairDamage(flo, cell);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if node being dropped and drop target node next to each other such that they won't be swapped by the drop
|
||||
*/
|
||||
function canSwap(flo, dropee, target, side) {
|
||||
var i, targetId, sourceId, noSwap = (dropee.id === target.id);
|
||||
if (dropee === target) {
|
||||
console.log('What!??? Dragged == Dropped!!! id = ' + target);
|
||||
}
|
||||
var links = flo.getGraph().getConnectedLinks(dropee);
|
||||
for (i = 0; i < links.length && !noSwap; i++) {
|
||||
targetId = links[i].get('target').id;
|
||||
sourceId = links[i].get('source').id;
|
||||
noSwap = (side === 'left' && targetId === target.id && sourceId === dropee.id) || (side === 'right' && targetId === dropee.id && sourceId === target.id);
|
||||
}
|
||||
return !noSwap;
|
||||
}
|
||||
|
||||
function moveNodeOnNode(flo, node, pivotNode, side, shouldRepairDamage) {
|
||||
side = side || 'left';
|
||||
if (canSwap(flo, node, pivotNode, side)) {
|
||||
var link;
|
||||
var i;
|
||||
if (side === 'left') {
|
||||
var sources = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {inbound: true}).length > 0 || graph.getConnectedLinks(node, {outbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotTargetLinks = flo.getGraph().getConnectedLinks(pivotNode, {inbound: true});
|
||||
for (i = 0; i < pivotTargetLinks.length; i++) {
|
||||
link = pivotTargetLinks[i];
|
||||
sources.push(link.get('source').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({
|
||||
'id': sources[i],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': pivotNode.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
} else if (side === 'right') {
|
||||
var targets = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {outbound: true}).length > 0 || graph.getConnectedLinks(node, {inbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotSourceLinks = flo.getGraph().getConnectedLinks(pivotNode, {outbound: true});
|
||||
for (i = 0; i < pivotSourceLinks.length; i++) {
|
||||
link = pivotSourceLinks[i];
|
||||
targets.push(link.get('target').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': targets[i],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': pivotNode.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveNodeOnLink(flo, node, link, shouldRepairDamage) {
|
||||
var source = link.get('source').id;
|
||||
var target = link.get('target').id;
|
||||
|
||||
if (shouldRepairDamage) {
|
||||
repairDamage(flo, node);
|
||||
}
|
||||
link.remove();
|
||||
|
||||
if (source) {
|
||||
flo.createLink({
|
||||
'id': source,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
if (target) {
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': target,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleNodeDropping(flo, dragDescriptor) {
|
||||
var relinking = dragDescriptor.context && dragDescriptor.context.palette;
|
||||
var graph = flo.getGraph();
|
||||
var source = dragDescriptor.source ? dragDescriptor.source.cell : undefined;
|
||||
var target = dragDescriptor.target ? dragDescriptor.target.cell : undefined;
|
||||
if (target instanceof joint.dia.Element && target.attr('metadata/name')) { // jshint ignore:line
|
||||
var type = source.attr('metadata/name');
|
||||
if (type === 'tap') {
|
||||
// Fill in the channel on the new node
|
||||
// work out tap name, something like: tap:stream:mystream.filter (filter optional if on head of stream)
|
||||
// 1. work your way back from node that was dropped on to the first node (the one with no further links)
|
||||
var oncell = target;
|
||||
var headerCell = oncell;
|
||||
var incomingLinks = graph.getConnectedLinks(oncell, { inbound: true });
|
||||
while (incomingLinks.length !== 0) {
|
||||
headerCell = graph.getCell(incomingLinks[0].get('source').id);
|
||||
incomingLinks = graph.getConnectedLinks(headerCell, { inbound: true });
|
||||
}
|
||||
var streamname = 'STREAM';
|
||||
if (headerCell.attr('stream-name')) {
|
||||
streamname = headerCell.attr('stream-name');
|
||||
}
|
||||
else {
|
||||
var streamId = headerCell.attr('stream-id');
|
||||
if (streamId) {
|
||||
streamname = 'STREAM'+streamId;
|
||||
} else {
|
||||
streamname = 'STREAM';
|
||||
}
|
||||
headerCell.attr('stream-name',streamname);
|
||||
}
|
||||
var channel = 'tap:stream:'+streamname;
|
||||
var label2 = 'tap:stream:\n' + streamname;
|
||||
if (oncell.id !== headerCell.id) {
|
||||
channel = channel + '.' + oncell.attr('.label/text');
|
||||
label2 = label2 + '.' + oncell.attr('.label/text');
|
||||
}
|
||||
source.attr('.label2/text', label2);
|
||||
source.attr('props/channel', channel);
|
||||
relinking = true;
|
||||
} else {
|
||||
if (dragDescriptor.target.selector === '.output-port') {
|
||||
moveNodeOnNode(flo, source, target, 'right', true);
|
||||
relinking = true;
|
||||
} else if (dragDescriptor.target.selector === '.input-port') {
|
||||
moveNodeOnNode(flo, source, target, 'left', true);
|
||||
relinking = true;
|
||||
}
|
||||
}
|
||||
} else if (target instanceof joint.dia.Link) { // jshint ignore:line
|
||||
moveNodeOnLink(flo, source, target);
|
||||
relinking = true;
|
||||
}
|
||||
// Turn off auto layout
|
||||
// if (relinking) {
|
||||
// flo.performLayout();
|
||||
// }
|
||||
}
|
||||
|
||||
function calculateDragDescriptor(flo, draggedView, targetUnderMouse, point, context) {
|
||||
// check if it's a tap being dragged
|
||||
var source = draggedView.model;
|
||||
if ((targetUnderMouse instanceof joint.dia.Element) && source.attr('metadata/name') === 'tap') { // jshint ignore:line
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: draggedView.model,
|
||||
},
|
||||
target: {
|
||||
cell: targetUnderMouse,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Find closest port
|
||||
var range = 30;
|
||||
var graph = flo.getGraph();
|
||||
var paper = flo.getPaper();
|
||||
var closestData;
|
||||
var minDistance = Number.MAX_VALUE;
|
||||
var maxIcomingLinks = draggedView.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var maxOutgoingLinks = draggedView.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var hasIncomingPort = typeof maxIcomingLinks !== 'number' || maxIcomingLinks > 0;
|
||||
var hasOutgoingPort = typeof maxOutgoingLinks !== 'number' || maxOutgoingLinks > 0;
|
||||
if (!hasIncomingPort && !hasOutgoingPort) {
|
||||
return;
|
||||
}
|
||||
var elements = graph.findModelsInArea(joint.g.rect(point.x - range, point.y - range, 2 * range, 2 * range)); // jshint ignore:line
|
||||
if (Array.isArray(elements)) {
|
||||
elements.forEach(function(model) {
|
||||
var view = paper.findViewByModel(model);
|
||||
if (view && view !== draggedView && model instanceof joint.dia.Element) { // jshint ignore:line
|
||||
var targetMaxIcomingLinks = view.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var targetMaxOutgoingLinks = view.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var targetHasIncomingPort = typeof targetMaxIcomingLinks !== 'number' || targetMaxIcomingLinks > 0;
|
||||
var targetHasOutgoingPort = typeof targetMaxOutgoingLinks !== 'number' || targetMaxOutgoingLinks > 0;
|
||||
if (view.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (targetHasIncomingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (targetHasOutgoingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
if (draggedView.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (hasIncomingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (hasOutgoingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
view.$('[magnet]').each(function(index, magnet) {
|
||||
var type = magnet.getAttribute('type');
|
||||
if ((type === 'input' && targetHasIncomingPort && hasOutgoingPort) || (type === 'output' && targetHasOutgoingPort && hasIncomingPort)) {
|
||||
var bbox = joint.V(magnet).bbox(false, paper.viewport); // jshint ignore:line
|
||||
var distance = point.distance({
|
||||
x: bbox.x + bbox.width / 2,
|
||||
y: bbox.y + bbox.height / 2
|
||||
});
|
||||
if (distance < range && distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestData = {
|
||||
context: context,
|
||||
source: {
|
||||
cell: draggedView.model,
|
||||
selector: type === 'output' ? '.input-port' : '.output-port'
|
||||
},
|
||||
target: {
|
||||
cell: model,
|
||||
selector: '.' + type+'-port'
|
||||
},
|
||||
range: minDistance
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (closestData) {
|
||||
return closestData;
|
||||
}
|
||||
|
||||
// Check if drop on a link is allowed
|
||||
if (targetUnderMouse instanceof joint.dia.Link && !(source.attr('metadata/constraints/xorSourceSink') || source.attr('metadata/constraints/maxOutgoingLinksNumber') === 0 || source.attr('metadata/constraints/maxIncomingLinksNumber') === 0) && graph.getConnectedLinks(source).length === 0) { // jshint ignore:line
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
target: {
|
||||
cell: targetUnderMouse
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function validateNode(flo, element) {
|
||||
var errors = [];
|
||||
var graph = flo.getGraph();
|
||||
var constraints = element.attr('metadata/constraints');
|
||||
if (constraints) {
|
||||
var incoming = graph.getConnectedLinks(element, {inbound: true});
|
||||
var outgoing = graph.getConnectedLinks(element, {outbound: true});
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' || typeof constraints.minIncomingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' && constraints.maxIncomingLinksNumber < incoming.length) {
|
||||
if (constraints.maxIncomingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sources must appear at the start of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of incoming links is ' + constraints.maxIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minIncomingLinksNumber === 'number' && constraints.minIncomingLinksNumber > incoming.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of incoming links is ' + constraints.minIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' || typeof constraints.minOutgoingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' && constraints.maxOutgoingLinksNumber < outgoing.length) {
|
||||
if (constraints.maxOutgoingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sinks must appear at the end of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of outgoing links is ' + constraints.maxOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minOutgoingLinksNumber === 'number' && constraints.minOutgoingLinksNumber > outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of outgoing links is ' + constraints.minOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (constraints.xorSourceSink && incoming.length && outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Node can either have incoming or outgoing links, but not both',
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!element.attr('metadata') || element.attr('metadata/unresolved')) {
|
||||
var msg = 'Unknown element \'' + element.attr('metadata/name') + '\'';
|
||||
if (element.attr('metadata/group')) {
|
||||
msg += ' from group \'' + element.attr('metadata/group') + '\'.';
|
||||
}
|
||||
errors.push({
|
||||
message: msg,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
|
||||
// If possible, verify the properties specified match those allowed on this type of element
|
||||
// propertiesRanges are the ranges for each property included the entire '--name=value'.
|
||||
// The format of a range is {'start':{'ch':NNNN,'line':NNNN},'end':{'ch':NNNN,'line':NNNN}}
|
||||
var propertiesRanges = element.attr('propertiesranges');
|
||||
if (propertiesRanges) {
|
||||
var moduleSchema = element.attr('metadata');
|
||||
// Grab the list of supported properties for this module type
|
||||
moduleSchema.get('properties').then(function(moduleSchemaProperties) {
|
||||
if (!moduleSchemaProperties) {
|
||||
moduleSchemaProperties = {};
|
||||
}
|
||||
// Example moduleSchemaProperties:
|
||||
// {"host":{"name":"host","type":"String","description":"the hostname of the mail server","defaultValue":"localhost","hidden":false},
|
||||
// "password":{"name":"password","type":"String","description":"the password to use to connect to the mail server ","defaultValue":null,"hidden":false}
|
||||
var specifiedProperties = element.attr('props');
|
||||
Object.keys(specifiedProperties).forEach(function(propertyName) {
|
||||
if (!moduleSchemaProperties[propertyName]) {
|
||||
// The schema does not mention that property
|
||||
var propertyRange = propertiesRanges[propertyName];
|
||||
if (propertyRange) {
|
||||
errors.push({
|
||||
message: 'unrecognized option \''+propertyName+'\' for module \''+element.attr('metadata/name')+'\'',
|
||||
range: propertyRange
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandles': createHandles,
|
||||
'validatePort': validatePort,
|
||||
'validateLink': validateLink,
|
||||
'calculateDragDescriptor': calculateDragDescriptor,
|
||||
'handleNodeDropping': handleNodeDropping,
|
||||
'validateNode': validateNode,
|
||||
'preDelete': preDelete,
|
||||
'interactive': {
|
||||
'vertexAdd': false
|
||||
},
|
||||
'allowLinkVertexEdit': false
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
return app;
|
||||
|
||||
});
|
||||
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Graph representation of stream(s)/module(s).
|
||||
* @type {joint.dia.Graph}
|
||||
*/
|
||||
var g;
|
||||
|
||||
/**
|
||||
* Number of Links left to visit
|
||||
* @type {number}
|
||||
*/
|
||||
var numberOfLinksToVisit;
|
||||
|
||||
/**
|
||||
* Number of nodes left to visit
|
||||
* @type {number}
|
||||
*/
|
||||
var numberOfNodesToVisit;
|
||||
|
||||
/**
|
||||
* Links left to visit indexed by id
|
||||
* @type {Object.<string,{joint.dia.Link}>}
|
||||
*/
|
||||
var linksToVisit;
|
||||
|
||||
/**
|
||||
* Nodes left to visit indexed by id
|
||||
* @type {Object.<string,{joint.dia.Element}>}
|
||||
*/
|
||||
var nodesToVisit;
|
||||
|
||||
/**
|
||||
* Nodes incoming non-visited links degrees index by node id
|
||||
* @type {Object.<string,number>}
|
||||
*/
|
||||
var nodesInDegrees;
|
||||
|
||||
function visit(e) {
|
||||
if (e.isLink()) {
|
||||
delete linksToVisit[e.get('id')];
|
||||
nodesInDegrees[e.get('target').id]--;
|
||||
numberOfLinksToVisit--;
|
||||
} else {
|
||||
delete nodesToVisit[e.get('id')];
|
||||
numberOfNodesToVisit--;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
// Priority:
|
||||
// 1. find links whose source has no other links pointing at it
|
||||
// 2. find links whose source has already been processed
|
||||
// 3. find remaining links
|
||||
function nextLink() {
|
||||
var indegree = Number.MAX_INT;
|
||||
var currentBest;
|
||||
for (var id in linksToVisit) {
|
||||
var link = g.getCell(id);
|
||||
var source = g.getCell(link.get('source').id);
|
||||
var currentInDegree = nodesInDegrees[source.get('id')];
|
||||
if (currentInDegree === 0) {
|
||||
return visit(link);
|
||||
} else if (indegree > currentInDegree) {
|
||||
indegree = currentInDegree;
|
||||
currentBest = link;
|
||||
}
|
||||
}
|
||||
if (currentBest) {
|
||||
return visit(currentBest);
|
||||
}
|
||||
}
|
||||
|
||||
function init(graph) {
|
||||
numberOfLinksToVisit = 0;
|
||||
numberOfNodesToVisit = 0;
|
||||
linksToVisit = {};
|
||||
nodesToVisit = {};
|
||||
nodesInDegrees = {};
|
||||
g = graph;
|
||||
g.getElements().forEach(function(element) {
|
||||
if (element.attr('metadata/name')) {
|
||||
nodesToVisit[element.get('id')] = element;
|
||||
var indegree = 0;
|
||||
g.getConnectedLinks(element, {inbound: true}).forEach(function(link) {
|
||||
if (link.get('source') && link.get('source').id && g.getCell(link.get('source').id) && g.getCell(link.get('source').id).attr('metadata/name')) {
|
||||
linksToVisit[link.get('id')] = link;
|
||||
numberOfLinksToVisit++;
|
||||
indegree++;
|
||||
}
|
||||
});
|
||||
nodesInDegrees[element.get('id')] = indegree;
|
||||
numberOfNodesToVisit++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isChannel(e) {
|
||||
return e && (e.attr('metadata/name') === 'tap' || e.attr('metadata/name') === 'named-channel');
|
||||
}
|
||||
|
||||
function isJobDefinition(e) {
|
||||
return e && e.attr('metadata/group') === 'job definition';
|
||||
}
|
||||
|
||||
function node2text(element, first) {
|
||||
var text = '';
|
||||
var props = element.attr('props');
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
if (element.attr('stream-name')) {
|
||||
text += element.attr('stream-name') + '=';
|
||||
}
|
||||
}
|
||||
if ('job definition' === element.attr('metadata/group')) {
|
||||
// expressed as a queue
|
||||
if (first) {
|
||||
text += 'tap:job:' + element.attr('metadata/name');
|
||||
if (props) {
|
||||
Object.keys(props).forEach(function(propertyName) {
|
||||
var prop = props[propertyName];
|
||||
if (prop && prop.length !== 0) {
|
||||
text += '.' + props[propertyName];
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
text += 'queue:job:' + element.attr('metadata/name');
|
||||
}
|
||||
} else if ('tap' === element.attr('metadata/name') || 'named-channel' === element.attr('metadata/name')) {
|
||||
// if ('tap' === element.attr('metadata/name')) {
|
||||
// text += 'tap ';
|
||||
// }
|
||||
if (props.channel) {
|
||||
text += props.channel;
|
||||
}
|
||||
} else {
|
||||
if (element.attr('.label/text') && element.attr('.label/text') !== element.attr('metadata/name')) {
|
||||
text += element.attr('.label/text') + ': ';
|
||||
}
|
||||
text += element.attr('metadata/name');
|
||||
if (props) {
|
||||
Object.keys(props).forEach(function(propertyName) {
|
||||
text += ' --' + propertyName + '=' + props[propertyName];
|
||||
});
|
||||
}
|
||||
}
|
||||
visit(element);
|
||||
return text;
|
||||
}
|
||||
|
||||
function stream2text(link) {
|
||||
var text = '';
|
||||
var source = g.getCell(link.get('source').id);
|
||||
text += node2text(source, true);
|
||||
while (link) {
|
||||
var target = g.getCell(link.get('target').id);
|
||||
text += (isChannel(source) || isChannel(target) ||
|
||||
isJobDefinition(source) || isJobDefinition(target)) ? ' > '
|
||||
: ' | ';
|
||||
text += node2text(target, false);
|
||||
|
||||
/*
|
||||
* Find next not visited link to follow
|
||||
*/
|
||||
link = null;
|
||||
var outgoingLinks = g.getConnectedLinks(target, {outbound: true});
|
||||
for (var i = 0; i < outgoingLinks.length && !link; i++) {
|
||||
if (linksToVisit[outgoingLinks[i].get('id')]) {
|
||||
source = target;
|
||||
link = visit(outgoingLinks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function appendStreamText(text, stream) {
|
||||
if (stream) {
|
||||
if (text) {
|
||||
text += '\n';
|
||||
}
|
||||
text += stream;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the stream(s)/module(s) definition(s) from graph form into text form
|
||||
* @param {joint.dia.Graph} g Graph form of stream(s) and or module(s)
|
||||
* @return {string} Textual form
|
||||
*/
|
||||
return function(g) {
|
||||
var text = '';
|
||||
var streamText;
|
||||
var node;
|
||||
var id;
|
||||
|
||||
init(g);
|
||||
/*
|
||||
* Visit all connected nodes via links
|
||||
*/
|
||||
while (numberOfLinksToVisit) {
|
||||
streamText = stream2text(nextLink());
|
||||
text = appendStreamText(text, streamText);
|
||||
}
|
||||
/*
|
||||
* Visit all disconnected nodes
|
||||
*/
|
||||
for (id in nodesToVisit) {
|
||||
node = nodesToVisit[id];
|
||||
streamText = node2text(nodesToVisit[id], true);
|
||||
if (streamText && isChannel(node)) {
|
||||
streamText += ' > ';
|
||||
}
|
||||
text = appendStreamText(text, streamText);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,293 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var angular = require('angular');
|
||||
var app = require('flo');
|
||||
|
||||
var convertGraphToText = require('graph-to-text');
|
||||
|
||||
app.factory('StandaloneMetamodel', ['$http', '$q', '$timeout', '$log', 'MetamodelUtils', function($http, $q, $timeout, $log, metamodelUtils) {
|
||||
|
||||
/**
|
||||
* List of listeners to metamodel changes
|
||||
* Each listener may have the following functions defined: metadataChanged(oldData, newData), metadataRefresh(), metadataError()
|
||||
* @type {Object}
|
||||
*/
|
||||
var listeners = [];
|
||||
|
||||
var metamodel;
|
||||
|
||||
/**
|
||||
* Internally stored metamodel load promise
|
||||
*/
|
||||
var request;
|
||||
|
||||
function subscribe(listener) {
|
||||
listeners.push(listener);
|
||||
}
|
||||
|
||||
function unsubscribe(listener) {
|
||||
var index = listeners.indexOf(listener);
|
||||
if (index >= 0) {
|
||||
listeners.splice(index);
|
||||
}
|
||||
}
|
||||
|
||||
function createMetadata(entry) {
|
||||
var props = {};
|
||||
if (Array.isArray(entry.properties)) {
|
||||
entry.properties.forEach(function(property) {
|
||||
if (!property.id) {
|
||||
property.id = property.name;
|
||||
}
|
||||
props[property.id] = property;
|
||||
});
|
||||
}
|
||||
entry.properties = props;
|
||||
return {
|
||||
|
||||
name: entry.name,
|
||||
|
||||
group: entry.group,
|
||||
|
||||
icon: entry.icon,
|
||||
|
||||
constraints: entry.constraints,
|
||||
|
||||
description: entry.description,
|
||||
|
||||
metadata: entry.metadata,
|
||||
|
||||
properties: entry.properties,
|
||||
|
||||
get: function(property) {
|
||||
var deferred = $q.defer();
|
||||
if (entry.hasOwnProperty(property)) {
|
||||
deferred.resolve(entry[property]);
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
listeners.forEach(function(listener) {
|
||||
if (angular.isFunction(listener.metadataRefresh)) {
|
||||
listener.metadataRefresh();
|
||||
}
|
||||
});
|
||||
var deferred = $q.defer();
|
||||
$http.get('/metamodel').success(function(json) {
|
||||
var newData = {};
|
||||
if (Array.isArray(json)) {
|
||||
json.forEach(function(data) {
|
||||
var metadata = createMetadata(data);
|
||||
if (!newData[metadata.group]) {
|
||||
newData[metadata.group] = {};
|
||||
}
|
||||
newData[metadata.group][metadata.name] = metadata;
|
||||
});
|
||||
}
|
||||
var change = {
|
||||
newData: newData,
|
||||
oldData: metamodel
|
||||
};
|
||||
metamodel = newData;
|
||||
listeners.forEach(function(listener) {
|
||||
if (angular.isFunction(listener.metadataChanged)) {
|
||||
listener.metadataChanged(change);
|
||||
}
|
||||
});
|
||||
deferred.resolve(metamodel);
|
||||
}).error(function(data, status, headers, config) {
|
||||
listeners.forEach(function(listener) {
|
||||
if (angular.isFunction(listener.metadataError)) {
|
||||
listener.metadataError(data, status, headers, config);
|
||||
}
|
||||
});
|
||||
deferred.reject(data);
|
||||
});
|
||||
request = deferred.promise;
|
||||
return request;
|
||||
}
|
||||
|
||||
function load() {
|
||||
if (!request) {
|
||||
refresh();
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
function parseAndRefreshGraph(definitionText,updateGraphFn,setErrorFn) {
|
||||
return $http.get('/parse', { params: {'text': definitionText}}).success(function(data) {
|
||||
if (angular.isFunction(setErrorFn)) {
|
||||
setErrorFn(null);
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
data = angular.fromJson(data);
|
||||
}
|
||||
// TODO handle error case, clear the graph
|
||||
$log.info('parse responded with data:\''+JSON.stringify(data)+'\'');
|
||||
if (angular.isFunction(updateGraphFn)) {
|
||||
updateGraphFn(data);
|
||||
}
|
||||
}).error(function(data, status, headers, config, statusText) { // jshint ignore:line
|
||||
if (typeof data === 'string') {
|
||||
data = angular.fromJson(data);
|
||||
}
|
||||
if (angular.isFunction(setErrorFn)) {
|
||||
setErrorFn(data);
|
||||
}
|
||||
|
||||
$log.info(JSON.stringify(data));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the JSON description of the flow as provided by the server and map it into a series
|
||||
* of nodes that can be processed by dagre/joint.
|
||||
*/
|
||||
function buildGraphFromJson(flo, jsonFormatData, metamodel) {
|
||||
var inputnodes = jsonFormatData.nodes;
|
||||
var inputlinks = jsonFormatData.links;
|
||||
|
||||
var incoming = {};
|
||||
var outgoing = {};
|
||||
var link;
|
||||
for (var i = 0; i < inputlinks.length; i++) {
|
||||
link = inputlinks[i];
|
||||
if (typeof link.from === 'number') {
|
||||
if (typeof outgoing[link.from] !== 'number') {
|
||||
outgoing[link.from] = 0;
|
||||
}
|
||||
outgoing[link.from]++;
|
||||
}
|
||||
if (typeof link.to === 'number') {
|
||||
if (typeof incoming[link.to] !== 'number') {
|
||||
incoming[link.to] = 0;
|
||||
}
|
||||
incoming[link.to]++;
|
||||
}
|
||||
}
|
||||
|
||||
var inputnodesCount = inputnodes ? inputnodes.length : 0;
|
||||
var nodesIndex = [];
|
||||
for (var n = 0; n < inputnodesCount; n++) {
|
||||
var name = inputnodes[n].name;
|
||||
var label = inputnodes[n].label || inputnodes[n].name;
|
||||
var group = inputnodes[n].group;
|
||||
if (!group) {
|
||||
group = metamodelUtils.matchGroup(metamodel, name, incoming[n], outgoing[n]);
|
||||
}
|
||||
var newNode = flo.createNode(metamodelUtils.getMetadata(metamodel, name, group), inputnodes[n].properties);
|
||||
newNode.attr('.label/text', label);
|
||||
var streamname = inputnodes[n]['stream-name'];
|
||||
if (streamname) {
|
||||
newNode.attr('stream-name',streamname);
|
||||
}
|
||||
if (inputnodes[n].range) {
|
||||
newNode.attr('range',inputnodes[n].range);
|
||||
}
|
||||
if (inputnodes[n].propertiesranges) {
|
||||
newNode.attr('propertiesranges',inputnodes[n].propertiesranges);
|
||||
}
|
||||
if (inputnodes[n]['stream-id']) {
|
||||
newNode.attr('stream-id',inputnodes[n]['stream-id']);
|
||||
}
|
||||
nodesIndex.push(newNode.id);
|
||||
}
|
||||
// var nextId = inputnodesCount; // For dropped nodes, they will start getting this ID
|
||||
|
||||
var inputlinksCount = inputlinks?inputlinks.length:0;
|
||||
// $log.info('Links ' + inputlinksCount);
|
||||
for (var l = 0; l < inputlinksCount; l++) {
|
||||
link = inputlinks[l];
|
||||
flo.createLink({
|
||||
'id': nodesIndex[link.from],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': nodesIndex[link.to],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
|
||||
flo.performLayout();
|
||||
|
||||
flo.fitToPage();
|
||||
}
|
||||
|
||||
function textToGraph(flo, definition) {
|
||||
return parseAndRefreshGraph(definition.text, function(json) {
|
||||
flo.getGraph().clear();
|
||||
load().then(function(metamodel) {
|
||||
buildGraphFromJson(flo, json, metamodel);
|
||||
});
|
||||
},function(errors) {
|
||||
definition.parseError = errors;
|
||||
});
|
||||
}
|
||||
|
||||
function graphToText(flo, definition) {
|
||||
definition.text = convertGraphToText(flo.getGraph());
|
||||
return $timeout(function() {
|
||||
return parseAndRefreshGraph(definition.text, null, function(errors) {
|
||||
definition.parseError = errors;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isValidPropertyValue(model, property, value) {
|
||||
var type = model.attr('metadata/name');
|
||||
if (type === 'tap' && property === 'channel') {
|
||||
return value.indexOf('tap:stream:') === 0 || value.indexOf('tap:job:') === 0;
|
||||
}
|
||||
if (type === 'named-channel' && property === 'channel') {
|
||||
return value.indexOf('queue:') === 0 || value.indexOf('topic:') === 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function encodeTextToDSL(value) {
|
||||
return value.replace(/(?:\r\n|\r|\n)/g, '\\n');
|
||||
}
|
||||
|
||||
function decodeTextFromDSL(value) {
|
||||
return value.replace(/\\n/g, '\n');
|
||||
}
|
||||
|
||||
return {
|
||||
'subscribe': subscribe,
|
||||
'unsubscribe': unsubscribe,
|
||||
'refresh': refresh,
|
||||
'load': load,
|
||||
'textToGraph': textToGraph,
|
||||
'graphToText': graphToText,
|
||||
'isValidPropertyValue': isValidPropertyValue,
|
||||
'encodeTextToDSL': encodeTextToDSL,
|
||||
'decodeTextFromDSL': decodeTextFromDSL
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
return app;
|
||||
|
||||
});
|
||||
@@ -1,942 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name":"http",
|
||||
"group":"source",
|
||||
"description":"HTTP input source",
|
||||
"properties":[
|
||||
{
|
||||
"id":"server-port",
|
||||
"name":"port",
|
||||
"defaultValue":"9000",
|
||||
"description":"The http port where data will be posted"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"mail",
|
||||
"group":"source",
|
||||
"description":"Input source for receiving emails",
|
||||
"properties":[
|
||||
{
|
||||
"name":"protocol",
|
||||
"defaultValue":"imaps",
|
||||
"description":"the protocol to use amongst pop3, pop3s, imap, imaps (only imap variants for the imap module)"
|
||||
},
|
||||
{
|
||||
"name":"username",
|
||||
"description":"the username to use to connect to the mail server"
|
||||
},
|
||||
{
|
||||
"name":"password",
|
||||
"description":"the password to use to connect to the mail server"
|
||||
},
|
||||
{
|
||||
"name":"host",
|
||||
"defaultValue":"localhost",
|
||||
"description":"the hostname of the mail server (default: localhost)"
|
||||
},
|
||||
{
|
||||
"name":"port",
|
||||
"description":"the port of the mail server"
|
||||
},
|
||||
{
|
||||
"name":"folder",
|
||||
"defaultValue":"INBOX",
|
||||
"description":"the folder to take emails from"
|
||||
},
|
||||
{
|
||||
"name":"markAsRead",
|
||||
"defaultValue":false,
|
||||
"description":"whether to mark emails as read once they?ve been fetched by the module"
|
||||
},
|
||||
{
|
||||
"name":"delete",
|
||||
"defaultValue":true,
|
||||
"description":"whether to delete the emails once they?ve been fetched by the module"
|
||||
},
|
||||
{
|
||||
"name":"fixedDelay",
|
||||
"defaultValue":60,
|
||||
"description":"Does not apply to the imap source, the polling interval used for looking up messages, expressed in seconds."
|
||||
},
|
||||
{
|
||||
"name":"charset",
|
||||
"defaultValue":"UTF-8",
|
||||
"description":"the charset used to transform the body of the incoming emails to Strings."
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"file",
|
||||
"group":"source",
|
||||
"description":"Stream the data from a file on the host OS",
|
||||
"properties":[
|
||||
{
|
||||
"name":"name",
|
||||
"description":"file name"
|
||||
},
|
||||
{
|
||||
"name":"dir",
|
||||
"description":"directory name"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"twittersearch",
|
||||
"group":"source",
|
||||
"description":"The twittersearch source runs a continuous query against Twitter",
|
||||
"properties":[
|
||||
{
|
||||
"name":"connectTimeout",
|
||||
"description":"the connection timeout for making a connection to Twitter (ms)",
|
||||
"defaultValue":5000
|
||||
},
|
||||
{
|
||||
"name":"query",
|
||||
"description":"the query that will run against twitter, e.g. #springeone2gx",
|
||||
"defaultValue":""
|
||||
},
|
||||
{
|
||||
"name":"consumerKey",
|
||||
"description":"an application consumer key issued by twitter"
|
||||
},
|
||||
{
|
||||
"name":"consumerSecret",
|
||||
"description":"the secret corresponding to the consumerKey"
|
||||
},
|
||||
{
|
||||
"name":"geocode",
|
||||
"description":"geo-location given as latitude,longitude,radius. e.g., \"37.781157,-122.398720,1mi\"",
|
||||
"defaultValue":""
|
||||
},
|
||||
{
|
||||
"name":"icludeEntities",
|
||||
"description":"whether to include entities such as urls, media and hashtags",
|
||||
"defaultValue":true
|
||||
},
|
||||
{
|
||||
"name":"language",
|
||||
"description":"language code e.g. \"en\"",
|
||||
"defaultValue":""
|
||||
},
|
||||
{
|
||||
"name":"readTimeout",
|
||||
"description":"the read timeout for the underlying URLConnection to the twitter stream (ms)",
|
||||
"defaultValue":9000
|
||||
},
|
||||
{
|
||||
"name":"resultType",
|
||||
"description":"result type: recent, popular, or mixed",
|
||||
"defaultValue":"mixed"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"time",
|
||||
"group":"source",
|
||||
"description":"Emits time ticks",
|
||||
"properties":[
|
||||
{
|
||||
"name":"fixedDelay",
|
||||
"description":"how often to emit a message, expressed in seconds",
|
||||
"defaultValue":1
|
||||
},
|
||||
{
|
||||
"name":"format",
|
||||
"description":"how to render the current time, using SimpleDateFormat",
|
||||
"defaultValue":"yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"tcp",
|
||||
"group":"source",
|
||||
"description":"The tcp source acts as a server and allows a remote party to connect to XD and submit data over a raw tcp socket.",
|
||||
"properties":[
|
||||
{
|
||||
"name":"bufferSize",
|
||||
"description":"the size of the buffer (bytes) to use when encoding/decoding",
|
||||
"defaultValue":2048
|
||||
},
|
||||
{
|
||||
"name":"charset",
|
||||
"description":"the charset used when converting from bytes to String",
|
||||
"defaultValue":"UTF-8"
|
||||
},
|
||||
{
|
||||
"name":"decoder",
|
||||
"description":"the decoder to use when receiving messages",
|
||||
"defaultValue":"CRLF"
|
||||
},
|
||||
{
|
||||
"name":"nio",
|
||||
"description":"whether or not to use NIO",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"port",
|
||||
"description":"the port on which to listen",
|
||||
"defaultValue":1234
|
||||
},
|
||||
{
|
||||
"name":"reverseLookup",
|
||||
"description":"perform a reverse DNS lookup on the remote IP Address",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"socketTimeout",
|
||||
"description":"the timeout (ms) before closing the socket when no data is received",
|
||||
"defaultValue":120000
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"tail",
|
||||
"group":"source",
|
||||
"description":"Tail file input source",
|
||||
"properties":[
|
||||
{
|
||||
"name":"delay",
|
||||
"description":"how often (ms) to poll for new lines (forces use of the Apache Tailer, requires nativeOptions=\"\")"
|
||||
},
|
||||
{
|
||||
"name":"fileDelay",
|
||||
"description":"on platforms that do not wait for a missing file to appear, how often (ms) to look for the file",
|
||||
"defaultValue":5000
|
||||
},
|
||||
{
|
||||
"name":"fromEnd",
|
||||
"description":"whether to tail from the end (true) or from the start (false) of the file (forces use of the Apache Tailer, requires nativeOptions=\"\")"
|
||||
},
|
||||
{
|
||||
"name":"lines",
|
||||
"description":"the number of lines prior to the end of an existing file to tail; does not apply if \"nativeOptions\" is provided",
|
||||
"defaultValue":0
|
||||
},
|
||||
{
|
||||
"name":"name",
|
||||
"description":"the absolute path of the file to tail",
|
||||
"defaultValue":"/tmp/xd/input/<stream name>"
|
||||
},
|
||||
{
|
||||
"name":"nativeOptions",
|
||||
"description":"options for a native tail command; do not set and use \"end\", \"delay\", and/or \"reOpen\" to use the Apache Tailer"
|
||||
},
|
||||
{
|
||||
"name":"reOpen",
|
||||
"description":"whether to reopen the file each time it is polled (forces use of the Apache Tailer, requires nativeOptions=\"\""
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"mqtt",
|
||||
"group":"source",
|
||||
"description":"The mqtt source connects to an mqtt server and receives telemetry messages",
|
||||
"properties":[
|
||||
{
|
||||
"name":"clientId",
|
||||
"description":"identifies the client",
|
||||
"defaultValue":"xd.mqtt.client.id.src"
|
||||
},
|
||||
{
|
||||
"name":"password",
|
||||
"description":"the password to use when connecting to the broker",
|
||||
"defaultValue":"guest"
|
||||
},
|
||||
{
|
||||
"name":"topics",
|
||||
"description":"the topic(s) to which the source will subscribe",
|
||||
"defaultValue":"xd.mqtt.test"
|
||||
},
|
||||
{
|
||||
"name":"url",
|
||||
"description":"location of the mqtt broker",
|
||||
"defaultValue":"tcp://localhost:1883"
|
||||
},
|
||||
{
|
||||
"name":"username",
|
||||
"description":"the username to use when connecting to the broker",
|
||||
"defaultValue":"guest"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"tap",
|
||||
"group":"other",
|
||||
"description":"Input channel for tapping into an existing stream",
|
||||
"metadata":{
|
||||
"fixed-name": true,
|
||||
"hide-tooltip-options": true
|
||||
},
|
||||
"properties":[
|
||||
{
|
||||
"name":"channel"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":0,
|
||||
"maxOutgoingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"named-channel",
|
||||
"metadata":{
|
||||
"fixed-name": true
|
||||
},
|
||||
"description":"A named channel that can be used as a source or a sync",
|
||||
"group":"other",
|
||||
"properties":[
|
||||
{
|
||||
"name":"channel",
|
||||
"description":"the input/output channel name: e.g. queue:topic:foo"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1,
|
||||
"xorSourceSink": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"filter",
|
||||
"group":"processor",
|
||||
"description":"Filters messages from the stream where the supplied expression evaluates to false",
|
||||
"properties":[
|
||||
{
|
||||
"name":"expression",
|
||||
"description":"SpEL expression returning true or false"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"transform",
|
||||
"group":"processor",
|
||||
"description":"Modify a messages content or structure in the stream",
|
||||
"properties":[
|
||||
{
|
||||
"name":"expression",
|
||||
"description":"A SpEL expression to transform the message, default: payload.toString()"
|
||||
},
|
||||
{
|
||||
"name":"script",
|
||||
"description":"Reference to a script used to transform the message"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"groovy-transform",
|
||||
"group":"processor",
|
||||
"description":"Modify a messages content or structure in the stream",
|
||||
"properties":[
|
||||
{
|
||||
"name":"groovy",
|
||||
"source": {
|
||||
"mime":"text/x-groovy",
|
||||
"type":"groovy"
|
||||
},
|
||||
"description":"Groovy script to transform the message",
|
||||
"defaultValue":"/* Groovy script goes here */"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"aggregator",
|
||||
"group":"processor",
|
||||
"description":"Aggregates messages from the stream where the supplied expression evaluates to false",
|
||||
"properties":[
|
||||
{
|
||||
"name":"expression",
|
||||
"description":"SpEL expression returning true or false"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"splitter",
|
||||
"group":"processor",
|
||||
"description":"Splits messages from the stream where the supplied expression evaluates to false",
|
||||
"properties":[
|
||||
{
|
||||
"name":"expression",
|
||||
"description":"SpEL expression returning true or false"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"json-to-tuple",
|
||||
"group":"processor",
|
||||
"description":"Transforms JSON message into an object of tuples",
|
||||
"properties":[
|
||||
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"object-to-json",
|
||||
"group":"processor",
|
||||
"description":"Takes in object message and sends out the JSON representation of the object further",
|
||||
"properties":[
|
||||
|
||||
],
|
||||
"constraints":{
|
||||
"maxOutgoingLinksNumber":1,
|
||||
"maxIncomingLinksNumber":1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"log",
|
||||
"metadata":{
|
||||
"allow-additional-properties": true
|
||||
},
|
||||
"description":"A simple sink that logs the data using the application logger",
|
||||
"group":"sink",
|
||||
"properties":[
|
||||
{
|
||||
"name":"expression",
|
||||
"defaultValue":"payload",
|
||||
"description":"the expression to be evaluated for the log content; use #root to log the full message, default: payload"
|
||||
},
|
||||
{
|
||||
"name":"name",
|
||||
"defaultValue":"<streamName>",
|
||||
"description":"sink name (prefixed with logger during output)"
|
||||
},
|
||||
{
|
||||
"name":"level",
|
||||
"defaultValue":"INFO",
|
||||
"description":"the log level"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"file",
|
||||
"group":"sink",
|
||||
"description":"Stream the data to a file on the host OS",
|
||||
"properties":[
|
||||
{
|
||||
"name":"name",
|
||||
"description":"file name"
|
||||
},
|
||||
{
|
||||
"name":"dir",
|
||||
"description":"directory name"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"mail",
|
||||
"group":"sink",
|
||||
"description":"Output source for sending out emails",
|
||||
"properties":[
|
||||
{
|
||||
"name":"protocol",
|
||||
"defaultValue":"imaps",
|
||||
"description":"the protocol to use amongst pop3, pop3s, imap, imaps (only imap variants for the imap module)"
|
||||
},
|
||||
{
|
||||
"name":"username",
|
||||
"description":"the username to use to connect to the mail server"
|
||||
},
|
||||
{
|
||||
"name":"password",
|
||||
"description":"the password to use to connect to the mail server"
|
||||
},
|
||||
{
|
||||
"name":"host",
|
||||
"defaultValue":"localhost",
|
||||
"description":"the hostname of the mail server (default: localhost)"
|
||||
},
|
||||
{
|
||||
"name":"port",
|
||||
"description":"the port of the mail server"
|
||||
},
|
||||
{
|
||||
"name":"folder",
|
||||
"defaultValue":"INBOX",
|
||||
"description":"the folder to take emails from"
|
||||
},
|
||||
{
|
||||
"name":"markAsRead",
|
||||
"defaultValue":false,
|
||||
"description":"whether to mark emails as read once they?ve been fetched by the module"
|
||||
},
|
||||
{
|
||||
"name":"delete",
|
||||
"defaultValue":true,
|
||||
"description":"whether to delete the emails once they?ve been fetched by the module"
|
||||
},
|
||||
{
|
||||
"name":"fixedDelay",
|
||||
"defaultValue":60,
|
||||
"description":"Does not apply to the imap source, the polling interval used for looking up messages, expressed in seconds."
|
||||
},
|
||||
{
|
||||
"name":"charset",
|
||||
"defaultValue":"UTF-8",
|
||||
"description":"the charset used to transform the body of the incoming emails to Strings."
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"mqtt",
|
||||
"group":"sink",
|
||||
"description":"MQTT messages output",
|
||||
"properties":[
|
||||
{
|
||||
"name":"clientId",
|
||||
"description":"identifies the client",
|
||||
"defaultValue":"xd.mqtt.client.id.src"
|
||||
},
|
||||
{
|
||||
"name":"password",
|
||||
"description":"the password to use when connecting to the broker",
|
||||
"defaultValue":"guest"
|
||||
},
|
||||
{
|
||||
"name":"topics",
|
||||
"description":"the topic(s) to which the source will subscribe",
|
||||
"defaultValue":"xd.mqtt.test"
|
||||
},
|
||||
{
|
||||
"name":"url",
|
||||
"description":"location of the mqtt broker",
|
||||
"defaultValue":"tcp://localhost:1883"
|
||||
},
|
||||
{
|
||||
"name":"username",
|
||||
"description":"the username to use when connecting to the broker",
|
||||
"defaultValue":"guest"
|
||||
},
|
||||
{
|
||||
"name":"qos",
|
||||
"description":"the quality of service to use",
|
||||
"defaultValue":1
|
||||
},
|
||||
{
|
||||
"name":"retained",
|
||||
"description":"whether to set the \"retained\" flag",
|
||||
"defaultValue":false
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"hdfs",
|
||||
"group":"sink",
|
||||
"description":"HDFS output",
|
||||
"properties":[
|
||||
{
|
||||
"name":"codec",
|
||||
"description":"compression codec alias name (gzip, snappy, bzip2, lzo, or slzo)",
|
||||
"defaultValue":""
|
||||
},
|
||||
{
|
||||
"name":"fileExtension",
|
||||
"description":"the base filename extension to use for the created files",
|
||||
"defaultValue":"txt"
|
||||
},
|
||||
{
|
||||
"name":"fileOpenAttempts",
|
||||
"description":"maximum number of file open attempts to find a path",
|
||||
"defaultValue":10
|
||||
},
|
||||
{
|
||||
"name":"fileName",
|
||||
"description":"the base filename to use for the created files",
|
||||
"defaultValue":"<stream name>"
|
||||
},
|
||||
{
|
||||
"name":"directory",
|
||||
"description":"where to output the files in the Hadoop FileSystem",
|
||||
"defaultValue":"/xd/<stream name>"
|
||||
},
|
||||
{
|
||||
"name":"fileUuid",
|
||||
"description":"whether file name should contain uuid",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"fsUri",
|
||||
"description":"the URI to use to access the Hadoop FileSystem",
|
||||
"defaultValue":"${spring.hadoop.fsUri}"
|
||||
},
|
||||
{
|
||||
"name":"idleTimeout",
|
||||
"description":"inactivity timeout after file will be automatically closed",
|
||||
"defaultValue":0
|
||||
},
|
||||
{
|
||||
"name":"inUsePrefix",
|
||||
"description":"prefix for files currently being written",
|
||||
"defaultValue":""
|
||||
},
|
||||
{
|
||||
"name":"inUseSuffix",
|
||||
"description":"suffix for files currently being written",
|
||||
"defaultValue":".tmp"
|
||||
},
|
||||
{
|
||||
"name":"overwrite",
|
||||
"description":"whether writer is allowed to overwrite files in Hadoop FileSystem",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"partitionPath",
|
||||
"description":"a SpEL expression defining the partition path",
|
||||
"defaultValue":""
|
||||
},
|
||||
{
|
||||
"name":"rollover",
|
||||
"description":"threshold in bytes when file will be automatically rolled over",
|
||||
"defaultValue":"1G"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"jdbc",
|
||||
"group":"sink",
|
||||
"description":"Inserts message payload data into a relational database table",
|
||||
"properties":[
|
||||
{
|
||||
"name":"abandonWhenPercentageFull",
|
||||
"description":"connections that have timed out wont get closed and reported up unless the number of connections in use are above the percentage ",
|
||||
"defaultValue":0
|
||||
},
|
||||
{
|
||||
"name":"alternateUsernameAllowed",
|
||||
"description":"uses an alternate user name if connection fails",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"columns",
|
||||
"description":"the database columns to map the data to",
|
||||
"defaultValue":"payload"
|
||||
},
|
||||
{
|
||||
"name":"driverClassName",
|
||||
"description":"the JDBC driver to use"
|
||||
},
|
||||
{
|
||||
"name":"fairQueue",
|
||||
"description":"set to true if you wish that calls to getConnection should be treated fairly in a true FIFO fashion",
|
||||
"defaultValue":"false"
|
||||
},
|
||||
{
|
||||
"name":"initSQL",
|
||||
"description":"custom query to be run when a connection is first created"
|
||||
},
|
||||
{
|
||||
"name":"initialSize",
|
||||
"description":"initial number of connections that are created when the pool is started",
|
||||
"defaultValue":0
|
||||
},
|
||||
{
|
||||
"name":"initializeDatabase",
|
||||
"description":"whether the database initialization script should be run",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"jdbcInterceptors",
|
||||
"description":"semicolon separated list of classnames extending org.apache.tomcat.jdbc.pool.JdbcInterceptor"
|
||||
},
|
||||
{
|
||||
"name":"jmxEnabled",
|
||||
"description":"register the pool with JMX or not",
|
||||
"defaultValue":true
|
||||
},
|
||||
{
|
||||
"name":"logAbandoned",
|
||||
"description":"flag to log stack traces for application code which abandoned a Connection",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"maxActive",
|
||||
"description":"maximum number of active connections that can be allocated from this pool at the same time",
|
||||
"defaultValue":100
|
||||
},
|
||||
{
|
||||
"name":"maxAge",
|
||||
"description":"time in milliseconds to keep this connection",
|
||||
"defaultValue":0
|
||||
},
|
||||
{
|
||||
"name":"maxIdle",
|
||||
"description":"maximum number of connections that should be kept in the pool at all times",
|
||||
"defaultValue":100
|
||||
},
|
||||
{
|
||||
"name":"maxWait",
|
||||
"description":"maximum number of milliseconds that the pool will wait for a connection",
|
||||
"defaultValue":30000
|
||||
},
|
||||
{
|
||||
"name":"minEvictableIdleTimeMillis",
|
||||
"description":"minimum amount of time an object may sit idle in the pool before it is eligible for eviction",
|
||||
"defaultValue":60000
|
||||
},
|
||||
{
|
||||
"name":"minIdle",
|
||||
"description":"minimum number of established connections that should be kept in the pool at all times",
|
||||
"defaultValue":10
|
||||
},
|
||||
{
|
||||
"name":"password",
|
||||
"description":"the JDBC password"
|
||||
},
|
||||
{
|
||||
"name":"removeAbandoned",
|
||||
"description":"flag to remove abandoned connections if they exceed the removeAbandonedTimout",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"removeAbandonedTimeout",
|
||||
"description":"timeout in seconds before an abandoned connection can be removed",
|
||||
"defaultValue":60
|
||||
},
|
||||
{
|
||||
"name":"suspectTimeout",
|
||||
"description":"this simply logs the warning after timeout, connection remains",
|
||||
"defaultValue":0
|
||||
},
|
||||
{
|
||||
"name":"tableName",
|
||||
"description":"the database table to which the data will be written",
|
||||
"defaultValue":"<stream name>"
|
||||
},
|
||||
{
|
||||
"name":"testOnBorrow",
|
||||
"description":"indication of whether objects will be validated before being borrowed from the pool",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"testOnReturn",
|
||||
"description":"indication of whether objects will be validated before being returned to the pool",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"testWhileIdle",
|
||||
"description":"indication of whether objects will be validated by the idle object evictor",
|
||||
"defaultValue":false
|
||||
},
|
||||
{
|
||||
"name":"timeBetweenEvictionRunsMillis",
|
||||
"description":"number of milliseconds to sleep between runs of the idle connection validation/cleaner thread",
|
||||
"defaultValue":5000
|
||||
},
|
||||
{
|
||||
"name":"url",
|
||||
"description":"the JDBC URL for the database"
|
||||
},
|
||||
{
|
||||
"name":"useEquals",
|
||||
"description":"true if you wish the ProxyConnection class to use String.equals",
|
||||
"defaultValue":true
|
||||
},
|
||||
{
|
||||
"name":"username",
|
||||
"description":"the JDBC username"
|
||||
},
|
||||
{
|
||||
"name":"validationInterval",
|
||||
"description":"avoid excess validation, only run validation at most at this frequency - time in milliseconds",
|
||||
"defaultValue":30000
|
||||
},
|
||||
{
|
||||
"name":"validationQuery",
|
||||
"description":"sql query that will be used to validate connections from this pool"
|
||||
},
|
||||
{
|
||||
"name":"validatorClassName",
|
||||
"description":"name of a class which implements the org.apache.tomcat.jdbc.pool.Validator"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"null",
|
||||
"group":"sink",
|
||||
"description":"Used when the main stream is not focused on stream destination but the tap streams are used for analytics etc., It is also useful to iteratively add in steps to a stream without worrying about having to land data anywhere.",
|
||||
"properties":[
|
||||
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"counter",
|
||||
"group":"sink",
|
||||
"description":"A counter is a Metric that associates a unique name with a long value. It is primarily used for counting events triggered by incoming messages on a target stream",
|
||||
"properties":[
|
||||
{
|
||||
"name":"name",
|
||||
"description":"the name of the metric to contribute to (will be created if necessary)",
|
||||
"defaultValue":"<stream name>"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"field-value-counter",
|
||||
"group":"sink",
|
||||
"description":"A field value counter is a Metric used for counting occurrences of unique values for a named field in a message payload",
|
||||
"properties":[
|
||||
{
|
||||
"name":"name",
|
||||
"description":"the name of the metric to contribute to (will be created if necessary)",
|
||||
"defaultValue":"<stream name>"
|
||||
},
|
||||
{
|
||||
"name":"fieldName",
|
||||
"description":"the name of the field for which values are counted"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"aggregate-counter",
|
||||
"group":"sink",
|
||||
"description":"The aggregate counter differs from a simple counter in that it not only keeps a total value for the count, but also retains the total count values for each minute, hour day and month of the period for which it is run. The data can then be queried by supplying a start and end date and the resolution at which the data should be returned.",
|
||||
"properties":[
|
||||
{
|
||||
"name":"name",
|
||||
"description":"the name of the metric to contribute to (will be created if necessary)",
|
||||
"defaultValue":"<stream name>"
|
||||
},
|
||||
{
|
||||
"name":"dateFormat",
|
||||
"description":"a pattern (as in SimpleDateFormat) for parsing/formatting dates and timestamps",
|
||||
"defaultValue":"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
|
||||
},
|
||||
{
|
||||
"name":"timeField",
|
||||
"description":"name of a field in the message that contains the timestamp to contribute to",
|
||||
"defaultValue":null
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"gauge",
|
||||
"group":"sink",
|
||||
"description":"A gauge is a Metric, similar to a counter in that it holds a single long value associated with a unique name. In this case the value can represent any numeric value defined by the application",
|
||||
"properties":[
|
||||
{
|
||||
"name":"name",
|
||||
"description":"the name of the metric to contribute to (will be created if necessary)",
|
||||
"defaultValue":"<stream name>"
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"rich-gauge",
|
||||
"group":"sink",
|
||||
"description":"A rich gauge is a Metric that holds a double value associated with a unique name. In addition to the value, the rich gauge keeps a running average, along with the minimum and maximum values and the sample count",
|
||||
"properties":[
|
||||
{
|
||||
"name":"name",
|
||||
"description":"the name of the metric to contribute to (will be created if necessary)",
|
||||
"defaultValue":"<stream name>"
|
||||
},
|
||||
{
|
||||
"name":"alpha",
|
||||
"description":"smoothing constant, or -1 to use arithmetic mean",
|
||||
"defaultValue":-1.0
|
||||
}
|
||||
],
|
||||
"constraints":{
|
||||
"maxIncomingLinksNumber":1,
|
||||
"maxOutgoingLinksNumber":0
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var app = require('flo');
|
||||
|
||||
var joint = require('joint');
|
||||
|
||||
var dagre = require('dagre');
|
||||
|
||||
var HANDLE_ICON_MAP = {
|
||||
'remove': 'icons/delete.svg',
|
||||
};
|
||||
|
||||
var DECORATION_ICON_MAP = {
|
||||
'error': 'icons/error.svg'
|
||||
};
|
||||
|
||||
app.factory('SampleRenderService', function() {
|
||||
|
||||
function createHandle(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 10, height: 10},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': HANDLE_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDecoration(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 16, height: 16},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': DECORATION_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createNode(metadata, props) { // jshint ignore:line
|
||||
return new joint.shapes.flo.Node();
|
||||
}
|
||||
|
||||
function refreshVisuals(element, changedPropertyPath, paper) { // jshint ignore:line
|
||||
var type = element.attr('metadata/name');
|
||||
|
||||
if (type === 'tap' && changedPropertyPath === 'props/channel') {
|
||||
var name = element.attr('props/channel');
|
||||
if (name) {
|
||||
var colon = name.indexOf(':');
|
||||
if (colon !== -1) {
|
||||
colon = name.indexOf(':',colon+1);
|
||||
if (colon !== -1) {
|
||||
var displayValue = name.substring(0,colon) + '\n' + name.substring(colon);
|
||||
element.attr('.label2/text', displayValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
element.removeAttr('.label2/text');
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'named-channel' && changedPropertyPath === 'props/channel') {
|
||||
element.attr('.label2/text', element.attr('props/channel'));
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNewNode(node, context) {
|
||||
var metadata = node.attr('metadata');
|
||||
if (metadata) {
|
||||
node.attr('.label/text', node.attr('metadata/name'));
|
||||
//node.attr('.image/xlink:href', metadata && metadata.icon ? metadata.icon : 'icons/xd/unknown.png');
|
||||
if (node.attr('metadata/constraints/maxIncomingLinksNumber') === 0) {
|
||||
node.attr('.input-port/display','none');
|
||||
}
|
||||
if (node.attr('metadata/constraints/maxOutgoingLinksNumber') === 0) {
|
||||
node.attr('.output-port/display','none');
|
||||
}
|
||||
|
||||
var type = node.attr('metadata/name');
|
||||
if (type === 'tap') {
|
||||
if (!node.attr('props/channel')) {
|
||||
node.attr('props/channel', 'tap:stream:STREAM');
|
||||
}
|
||||
refreshVisuals(node, 'props/channel', context.paper);
|
||||
} else if (type === 'named-channel') {
|
||||
// Default channel for named channel is 'queue:default'
|
||||
if (!node.attr('props/channel')) {
|
||||
node.attr('props/channel', 'queue:default');
|
||||
}
|
||||
refreshVisuals(node, 'props/channel', context.paper);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createLink() {
|
||||
var link = new joint.shapes.flo.Link();
|
||||
link.set('smooth', true);
|
||||
return link;
|
||||
}
|
||||
|
||||
function getLinkView() {
|
||||
return joint.dia.LinkView;
|
||||
}
|
||||
|
||||
function isSemanticProperty(propertyPath) {
|
||||
return propertyPath === '.label/text';
|
||||
}
|
||||
|
||||
function layout(paper) {
|
||||
var graph = paper.model;
|
||||
var i;
|
||||
var g = new dagre.graphlib.Graph();
|
||||
g.setGraph({});
|
||||
g.setDefaultEdgeLabel(function() {return{};});
|
||||
|
||||
var nodes = graph.getElements();
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (node.get('type') === joint.shapes.flo.NODE_TYPE) {
|
||||
g.setNode(node.id, node.get('size'));
|
||||
}
|
||||
}
|
||||
|
||||
var links = graph.getLinks();
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
if (link.get('type') === joint.shapes.flo.LINK_TYPE) {
|
||||
var options = {
|
||||
minlen: 1.5
|
||||
};
|
||||
// if (link.get('labels') && link.get('labels').length > 0) {
|
||||
// options.minlen = 1 + link.get('labels').length * 0.5;
|
||||
// }
|
||||
g.setEdge(link.get('source').id, link.get('target').id, options);
|
||||
link.set('vertices', []);
|
||||
}
|
||||
}
|
||||
|
||||
g.graph().rankdir = 'LR';
|
||||
dagre.layout(g);
|
||||
g.nodes().forEach(function(v) {
|
||||
var node = graph.getCell(v);
|
||||
if (node) {
|
||||
var bbox = node.getBBox();
|
||||
node.translate(g.node(v).x - bbox.x, g.node(v).y - bbox.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLinkAnchorPoint(linkView, view, magnet, reference) {
|
||||
if (magnet) {
|
||||
var type = magnet.getAttribute('type');
|
||||
var bbox = joint.V(magnet).bbox(false, linkView.paper.viewport);
|
||||
var rect = joint.g.rect(bbox);
|
||||
if (type === 'input') {
|
||||
return joint.g.point(rect.x, rect.y + rect.height / 2);
|
||||
} else {
|
||||
return joint.g.point(rect.x + rect.width, rect.y + rect.height / 2);
|
||||
}
|
||||
} else {
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandle': createHandle,
|
||||
'createDecoration': createDecoration,
|
||||
'createNode': createNode,
|
||||
'getLinkView': getLinkView,
|
||||
'createLink': createLink,
|
||||
'initializeNewNode': initializeNewNode,
|
||||
'isSemanticProperty': isSemanticProperty,
|
||||
'refreshVisuals': refreshVisuals,
|
||||
'layout': layout,
|
||||
'getLinkAnchorPoint': getLinkAnchorPoint
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
return app;
|
||||
|
||||
});
|
||||
@@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var angular = require('angular');
|
||||
require('angularMocks');
|
||||
require('metamodelService');
|
||||
require('editor-service');
|
||||
require('render-service');
|
||||
require('flo');
|
||||
require('text');
|
||||
|
||||
var data = JSON.parse(require('text!../resources/metamodel.json'));
|
||||
var domElement;
|
||||
|
||||
var groupsData = {};
|
||||
data.forEach(function(e) {
|
||||
if (!groupsData[e.group]) {
|
||||
groupsData[e.group] = {};
|
||||
}
|
||||
groupsData[e.group][e.name] = e;
|
||||
});
|
||||
|
||||
describe('Unit testing Flo editor', function() {
|
||||
var $compile, $httpBackend, $scope, $http, $timeout, StandaloneMetamodel, MetamodelUtils;
|
||||
|
||||
// Load the Flo module, which contains the directive
|
||||
beforeEach(angular.mock.module('spring.flo'));
|
||||
|
||||
// Store references to $rootScope and $compile
|
||||
// so they are available to all tests in this describe block
|
||||
beforeEach(function(done) {
|
||||
inject(function($rootScope, $controller, _$http_, _$compile_, _$httpBackend_, _$timeout_, _StandaloneMetamodel_, _MetamodelUtils_) {
|
||||
$compile = _$compile_;
|
||||
$scope = $rootScope.$new();
|
||||
$http = _$http_;
|
||||
$httpBackend = _$httpBackend_;
|
||||
$timeout = _$timeout_;
|
||||
StandaloneMetamodel = _StandaloneMetamodel_;
|
||||
MetamodelUtils = _MetamodelUtils_;
|
||||
|
||||
// Set up the mock http service responses
|
||||
$httpBackend.when('GET', '/parse?text=').respond(200, {format: 'xd', nodes: [], links: []});
|
||||
$httpBackend.when('GET', '/metamodel').respond(200, data);
|
||||
|
||||
// Setup initial scope.
|
||||
$scope.flo = {};
|
||||
$scope.definition = {};
|
||||
$scope.editorServiceName = 'sample-editor-service';
|
||||
$scope.renderServiceName = 'SampleRenderService';
|
||||
$scope.metamodelServiceName = 'StandaloneMetamodel';
|
||||
|
||||
// Compile a piece of HTML containing the directive
|
||||
domElement = $compile('<div><flo-editor></flo-editor></div>')($scope);
|
||||
angular.element(document.body).append(domElement);
|
||||
$scope.$digest();
|
||||
$scope.$apply();
|
||||
$httpBackend.flush();
|
||||
setTimeout(function() {
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$scope.flo.clearGraph();
|
||||
domElement.remove();
|
||||
});
|
||||
|
||||
it('Replaces the element with the appropriate content', function() {
|
||||
// Check that the compiled element contains the templated content
|
||||
expect(domElement.find('#paper').length).toBe(1);
|
||||
expect(domElement.find('#palette-paper').length).toBe(1);
|
||||
expect(domElement.find('#properties').length).toBe(1);
|
||||
});
|
||||
|
||||
it('Verify palette contents', function() {
|
||||
var paletteGraph = $scope.flo._paletteGraph();
|
||||
|
||||
var groups = {};
|
||||
var elementCounter = {};
|
||||
paletteGraph.getElements().forEach(function(e) {
|
||||
if (e.attr('metadata')) {
|
||||
var group = e.attr('metadata/group');
|
||||
var name = e.attr('metadata/name');
|
||||
if (group) {
|
||||
if (!elementCounter[group]) {
|
||||
elementCounter[group] = {};
|
||||
}
|
||||
elementCounter[group][name] = e;
|
||||
}
|
||||
} else if (e.attr('text/text')) {
|
||||
groups[e.attr('text/text')] = e;
|
||||
}
|
||||
});
|
||||
|
||||
expect(Object.keys(groups).sort()).toEqual(Object.keys(groupsData).sort());
|
||||
expect(Object.keys(elementCounter).sort()).toEqual(Object.keys(groupsData).sort());
|
||||
Object.keys(elementCounter).forEach(function(group) {
|
||||
var current = Object.keys(elementCounter[group]).sort();
|
||||
var expected = Object.keys(groupsData[group]).sort();
|
||||
expect(current).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('Create module', function(done) {
|
||||
StandaloneMetamodel.load().then(function(metamodel) {
|
||||
var node = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'http', 'source'));
|
||||
var view = $scope.flo.getPaper().findViewByModel(node);
|
||||
expect(view).toBeDefined();
|
||||
expect(node.attr('.label/text')).toBe('http');
|
||||
expect($scope.flo.zoomPercent()).toBe(100);
|
||||
var bbox = view.getBBox();
|
||||
expect(node.get('position')).toEqual({x: 0, y: 0});
|
||||
expect(node.get('size')).toEqual({width: 120, height: 35});
|
||||
expect(bbox.x).toBe(0);
|
||||
expect(bbox.y).toBeDefined();
|
||||
expect(bbox.width).toBe(124);
|
||||
// Account for the label above.
|
||||
expect(bbox.height).toBe(35);
|
||||
done();
|
||||
});
|
||||
$timeout.flush();
|
||||
});
|
||||
|
||||
it('Test link creation', function(done) {
|
||||
StandaloneMetamodel.load().then(function(metamodel) {
|
||||
var http = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'http', 'source'));
|
||||
var log = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'log', 'sink'));
|
||||
$scope.flo.getPaper().findViewByModel(http); // httpView
|
||||
$scope.flo.getPaper().findViewByModel(log); // logView
|
||||
$scope.flo.createLink({
|
||||
'id': http.get('id'),
|
||||
'selector': '.output'
|
||||
}, {
|
||||
'id': log.get('id'),
|
||||
'selector': '.input'
|
||||
});
|
||||
expect($scope.flo.getGraph().getLinks().length).toBe(1);
|
||||
var link = $scope.flo.getGraph().getLinks()[0];
|
||||
expect($scope.flo.getPaper().findViewByModel(link)).toBeDefined();
|
||||
expect(link.get('source').id).toEqual(http.get('id'));
|
||||
expect(link.get('target').id).toEqual(log.get('id'));
|
||||
|
||||
// Test layout
|
||||
$scope.flo.performLayout();
|
||||
expect(http.get('position').y).toEqual(log.get('position').y);
|
||||
expect(http.get('position').x + http.get('size').width + 20).toBeLessThan(log.get('position').x);
|
||||
done();
|
||||
});
|
||||
$timeout.flush();
|
||||
});
|
||||
|
||||
it('Test basic layout of disoconnected elements', function(done) {
|
||||
StandaloneMetamodel.load().then(function(metamodel) {
|
||||
var http = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'http', 'source'));
|
||||
var log = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'log', 'sink'));
|
||||
|
||||
// Test layout
|
||||
$scope.flo.performLayout();
|
||||
expect(http.get('position').x).toEqual(log.get('position').x);
|
||||
expect(http.get('position').y + http.get('size').height + 20).toBeLessThan(log.get('position').y);
|
||||
done();
|
||||
});
|
||||
$timeout.flush();
|
||||
});
|
||||
|
||||
it('Test sync text stream', function(done) {
|
||||
StandaloneMetamodel.load().then(function(metamodel) {
|
||||
var mail = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'mail', 'source'));
|
||||
var log = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'log', 'sink'));
|
||||
$scope.flo.createLink({
|
||||
'id': mail.get('id'),
|
||||
'selector': '.output'
|
||||
}, {
|
||||
'id': log.get('id'),
|
||||
'selector': '.input'
|
||||
});
|
||||
/*return*/ $scope.flo.updateTextRepresentation();
|
||||
// }).then(function() {
|
||||
expect($scope.definition.text).toEqual('mail | log');
|
||||
done();
|
||||
});
|
||||
$timeout.flush();
|
||||
});
|
||||
|
||||
it('Test deferred automated text stream sync', function(done) {
|
||||
StandaloneMetamodel.load().then(function(metamodel) {
|
||||
var mail = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'mail', 'source'));
|
||||
var log = $scope.flo.createNode(MetamodelUtils.getMetadata(metamodel, 'log', 'sink'));
|
||||
$scope.flo.createLink({
|
||||
'id': mail.get('id'),
|
||||
'selector': '.output'
|
||||
}, {
|
||||
'id': log.get('id'),
|
||||
'selector': '.input'
|
||||
});
|
||||
expect($scope.definition.text).toEqual('');
|
||||
setTimeout(function() {
|
||||
var flush = true;
|
||||
while (flush) {
|
||||
try {
|
||||
$timeout.flush();
|
||||
} catch (error) {
|
||||
flush = false;
|
||||
}
|
||||
}
|
||||
expect($scope.definition.text).toEqual('mail | log');
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
$timeout.flush();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
define(['joint', 'angular'], function (joint) {
|
||||
'use strict';
|
||||
|
||||
console.log('RUNNING SPEC TEST!');
|
||||
|
||||
describe('Joint JS Graph', function () {
|
||||
var graph;
|
||||
|
||||
beforeEach(function() {
|
||||
graph = new joint.dia.Graph();
|
||||
var rect = new joint.shapes.basic.Rect({
|
||||
position: { x: 100, y: 30 },
|
||||
size: { width: 100, height: 30 },
|
||||
attrs: { rect: { fill: 'blue' }, text: { text: 'my box', fill: 'white' } }
|
||||
});
|
||||
var rect2 = rect.clone();
|
||||
rect2.translate(300);
|
||||
var link = new joint.dia.Link({
|
||||
source: { id: rect.id },
|
||||
target: { id: rect2.id }
|
||||
});
|
||||
graph.addCells([rect, rect2, link]);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (graph) {
|
||||
graph.clear();
|
||||
}
|
||||
});
|
||||
|
||||
it('create element on a graph', function () {
|
||||
expect(graph.getElements().length).toEqual(2);
|
||||
});
|
||||
|
||||
it('create link on a graph', function () {
|
||||
expect(graph.getLinks().length).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||