Merge pull request #3 from spring-projects/generic-dsl-editor
Change project layout. Generic DSL editor directive.
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
"backbone": "1.2.1",
|
||||
"joint": "0.9.6",
|
||||
"lodash": "2.4.2",
|
||||
"codemirror": "5.0.0",
|
||||
"codemirror": "5.18.2",
|
||||
"jshint": "2.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -329,6 +329,13 @@
|
||||
.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 */
|
||||
|
||||
|
||||
185
dist/spring-flo.css
vendored
185
dist/spring-flo.css
vendored
@@ -33,8 +33,7 @@
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
@@ -42,19 +41,21 @@
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -64,35 +65,51 @@
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% { background: #7e7; }
|
||||
50% { background: none; }
|
||||
100% { background: #7e7; }
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% { background: #7e7; }
|
||||
50% { background: none; }
|
||||
100% { background: #7e7; }
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% { background: #7e7; }
|
||||
50% { background: none; }
|
||||
100% { background: #7e7; }
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
div.CodeMirror-overwrite div.CodeMirror-cursor {}
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
@@ -112,21 +129,14 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
@@ -154,18 +164,14 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actuall scrolling happens, thus preventing shaking and
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
@@ -191,14 +197,14 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -30px;
|
||||
/* Hack to make IE7 behave */
|
||||
*zoom:1;
|
||||
@@ -207,7 +213,13 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
height: 100%;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
@@ -240,6 +252,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: none;
|
||||
font-variant-ligatures: none;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
@@ -265,6 +279,16 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@@ -272,19 +296,22 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
@@ -292,8 +319,8 @@ div.CodeMirror-cursors {
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror ::selection { background: #d7d4f0; }
|
||||
.CodeMirror ::-moz-selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background: #ffa;
|
||||
@@ -325,10 +352,10 @@ span.CodeMirror-selectedtext { background: none; }
|
||||
}
|
||||
|
||||
.CodeMirror-lint-tooltip {
|
||||
background-color: infobackground;
|
||||
background-color: #ffd;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px 4px 4px 4px;
|
||||
color: infotext;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
overflow: hidden;
|
||||
@@ -420,8 +447,6 @@ span.CodeMirror-selectedtext { background: none; }
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
max-width: 19em;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
@@ -432,6 +457,73 @@ li.CodeMirror-hint-active {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div {
|
||||
position: absolute;
|
||||
background: #ccc;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.CodeMirror-simplescroll-horizontal {
|
||||
bottom: 0; left: 0;
|
||||
height: 8px;
|
||||
}
|
||||
.CodeMirror-simplescroll-horizontal div {
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-simplescroll-vertical {
|
||||
right: 0; top: 0;
|
||||
width: 8px;
|
||||
}
|
||||
.CodeMirror-simplescroll-vertical div {
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
|
||||
position: absolute;
|
||||
background: #bcd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal {
|
||||
bottom: 0; left: 0;
|
||||
height: 6px;
|
||||
}
|
||||
.CodeMirror-overlayscroll-horizontal div {
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-vertical {
|
||||
right: 0; top: 0;
|
||||
width: 6px;
|
||||
}
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*! JointJS v0.9.6 - JavaScript diagramming library 2015-12-19
|
||||
|
||||
|
||||
@@ -1004,6 +1096,13 @@ Do not allow adding new vertices: .connection-wrap { pointer-events: none; }
|
||||
.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 */
|
||||
|
||||
|
||||
4634
dist/spring-flo.js
vendored
4634
dist/spring-flo.js
vendored
File diff suppressed because it is too large
Load Diff
4
dist/spring-flo.min.css
vendored
4
dist/spring-flo.min.css
vendored
File diff suppressed because one or more lines are too long
8
dist/spring-flo.min.js
vendored
8
dist/spring-flo.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -44,9 +44,9 @@ module.exports = function(grunt) {
|
||||
// global config
|
||||
options: {
|
||||
baseUrl: './src',
|
||||
name: 'flo',
|
||||
name: 'app',
|
||||
exclude: [ 'joint', 'angular', 'jquery', 'bootstrap', 'underscore' ],
|
||||
insertRequire: [ 'flo' ],
|
||||
// insertRequire: [ './app' ],
|
||||
mainConfigFile: './src/main.js'
|
||||
},
|
||||
production: {
|
||||
@@ -114,6 +114,7 @@ module.exports = function(grunt) {
|
||||
'./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'
|
||||
],
|
||||
|
||||
35
src/app.js
Normal file
35
src/app.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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,143 +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');
|
||||
|
||||
// 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 LINT_MAP = {
|
||||
'javascript': true
|
||||
};
|
||||
|
||||
var defaultText = '';
|
||||
var defaultLanguage = 'Plain Text';
|
||||
|
||||
$scope.init = function(textarea, attrs) {
|
||||
|
||||
var doc = CodeMirror.fromTextArea(textarea, {
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
};
|
||||
|
||||
}];});
|
||||
186
src/controllers/code-editor.js
Normal file
186
src/controllers/code-editor.js
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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();
|
||||
});
|
||||
};
|
||||
|
||||
}];
|
||||
});
|
||||
199
src/controllers/dsl-editor.js
Normal file
199
src/controllers/dsl-editor.js
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -29,9 +29,9 @@ define(function(require) {
|
||||
|
||||
var angular = require('angular');
|
||||
var joint = require('joint');
|
||||
var shapesFactory = require('shapes-factory');
|
||||
var createProperties = require('properties-manager');
|
||||
var createEventManager = require('event-manager');
|
||||
var shapesFactory = require('common/shapes-factory');
|
||||
var createProperties = require('common/properties-manager');
|
||||
var createEventManager = require('common/event-manager');
|
||||
|
||||
return ['$q', '$scope', '$timeout', '$http', '$log', '$injector', function($q, $scope, $timeout, $http, $log, $injector) {
|
||||
|
||||
@@ -19,60 +19,60 @@ define(function(require) {
|
||||
|
||||
var $ = require('jquery');
|
||||
var joint = require('joint');
|
||||
var shapesFactory = require('shapes-factory');
|
||||
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',
|
||||
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
|
||||
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
|
||||
* Model of the clicked element
|
||||
*/
|
||||
var clickedElement;
|
||||
|
||||
@@ -136,98 +136,98 @@ define(function(require) {
|
||||
|
||||
function getPaletteView(view) {
|
||||
return view.extend({
|
||||
pointerdown: function(/*evt, x, y*/) {
|
||||
// Remove the tooltip
|
||||
$('.node-tooltip').remove();
|
||||
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 });
|
||||
}
|
||||
},
|
||||
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 });
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
@@ -419,7 +419,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
*/
|
||||
@@ -438,31 +438,31 @@ define(function(require) {
|
||||
}
|
||||
};
|
||||
$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);
|
||||
renderService = $injector.get($scope.renderServiceName);
|
||||
}
|
||||
|
||||
// Create the paper for the palette using the specified element view
|
||||
@@ -474,17 +474,17 @@ define(function(require) {
|
||||
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();
|
||||
});
|
||||
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',
|
||||
@@ -503,7 +503,7 @@ define(function(require) {
|
||||
// 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) {
|
||||
@@ -520,22 +520,22 @@ define(function(require) {
|
||||
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) {
|
||||
@@ -543,7 +543,7 @@ define(function(require) {
|
||||
metamodelService.load().then(buildPalette);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}];
|
||||
|
||||
|
||||
});
|
||||
34
src/directives.js
Normal file
34
src/directives.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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'));
|
||||
});
|
||||
37
src/directives/code-editor.js
Normal file
37
src/directives/code-editor.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: require('controllers/code-editor'),
|
||||
link: function(scope, element, attrs) {
|
||||
scope.init(element.context, attrs);
|
||||
},
|
||||
scope: {
|
||||
language: '=codeLanguage',
|
||||
text: '=codeText',
|
||||
decodeFunction: '&',
|
||||
encodeFunction: '&',
|
||||
placeholder: '@',
|
||||
scrollbarStyle: '@',
|
||||
overviewRuler: '@'
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
41
src/directives/dsl-editor.js
Normal file
41
src/directives/dsl-editor.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 ['$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.context);
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
105
src/directives/generic-dsl-editor.js
Normal file
105
src/directives/generic-dsl-editor.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
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',
|
||||
};
|
||||
|
||||
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.context, 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);
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
89
src/directives/graph-editor.js
Normal file
89
src/directives/graph-editor.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 () {
|
||||
|
||||
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.context, 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>'
|
||||
};
|
||||
|
||||
}];
|
||||
});
|
||||
42
src/directives/palette.js
Normal file
42
src/directives/palette.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 () {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: true,
|
||||
link: function(scope, element, attrs) {
|
||||
if (attrs.metamodelServiceName) {
|
||||
scope.metamodelServiceName = attrs.metamodelServiceName;
|
||||
}
|
||||
scope.init(element.context, 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>'
|
||||
};
|
||||
}];
|
||||
|
||||
});
|
||||
99
src/directives/resizer.js
Normal file
99
src/directives/resizer.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,169 +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;
|
||||
|
||||
require('codemirror/addon/lint/lint');
|
||||
require('codemirror/addon/hint/show-hint');
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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;
|
||||
doc = CodeMirror.fromTextArea(textarea, {
|
||||
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
|
||||
});
|
||||
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);
|
||||
});
|
||||
$scope.$watch('definition.text', function (newValue) {
|
||||
if (newValue!==doc.getValue()) {
|
||||
var cursorPosition = doc.getCursor();
|
||||
doc.setValue(newValue);
|
||||
doc.setCursor(cursorPosition);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
301
src/flo.js
301
src/flo.js
@@ -1,301 +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';
|
||||
|
||||
require('angular');
|
||||
|
||||
var app = angular.module('spring.flo', []);
|
||||
|
||||
app.directive('dslEditor', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: true,
|
||||
controller: require('dsl-editor'),
|
||||
link: function(scope, element, attrs) {
|
||||
if (attrs.contentAssistServiceName) {
|
||||
scope.contentAssistServiceName = attrs.contentAssistServiceName;
|
||||
}
|
||||
scope.init(element.context);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('codeEditor', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: require('code-editor'),
|
||||
link: function(scope, element, attrs) {
|
||||
scope.init(element.context, attrs);
|
||||
},
|
||||
scope: {
|
||||
language: '=codeLanguage',
|
||||
text: '=codeText',
|
||||
decodeFunction: '&',
|
||||
encodeFunction: '&'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('floPalette', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: true,
|
||||
link: function(scope, element, attrs) {
|
||||
if (attrs.metamodelServiceName) {
|
||||
scope.metamodelServiceName = attrs.metamodelServiceName;
|
||||
}
|
||||
scope.init(element.context, attrs);
|
||||
},
|
||||
controller: require('palette-manager'),
|
||||
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>'
|
||||
};
|
||||
});
|
||||
|
||||
// Div for resizing the palette
|
||||
app.directive('resizer', 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);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('floEditor', 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.context, 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('editor-manager'),
|
||||
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>'
|
||||
};
|
||||
});
|
||||
|
||||
app.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;
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
return app;
|
||||
|
||||
});
|
||||
@@ -22,7 +22,9 @@ requirejs.config({
|
||||
jquery: '../lib/jquery/jquery',
|
||||
bootstrap: '../lib/bootstrap/bootstrap',
|
||||
lodash: '../lib/lodash/lodash.compat',
|
||||
jshint: '../lib/jshint/dist/jshint'
|
||||
jshint: '../lib/jshint/dist/jshint',
|
||||
floDirectives: './directives',
|
||||
floServices: './services'
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
|
||||
101
src/services.js
Normal file
101
src/services.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user