Angular 6 compiler: complete inline/compile/bundle/minify rework

This commit is contained in:
BoykoAlex
2018-07-04 22:20:38 -04:00
parent c130c5cc59
commit cdc4b07100
35 changed files with 1138 additions and 1164 deletions

309
build.js
View File

@@ -1,174 +1,149 @@
'use strict';
"use strict";
const fs = require('fs');
const shell = require('shelljs');
const chalk = require('chalk');
const path = require('path');
const glob = require('glob');
const camelCase = require('camelcase');
const ngc = require('@angular/compiler-cli/src/main').main;
const rollup = require('rollup');
const uglify = require('rollup-plugin-uglify');
const sourcemaps = require('rollup-plugin-sourcemaps');
const fs = require('fs');
const inlineResources = require('./inline-resources');
const PACKAGE = `spring-flo`;
const NPM_DIR = `dist`;
const TS_COMPILED_DIR = `${NPM_DIR}/out-tsc`;
const ESM2015_DIR = `${NPM_DIR}/esm2015`;
const ESM5_DIR = `${NPM_DIR}/esm5`;
const FESM2015_DIR = `${NPM_DIR}/fesm2015`;
const FESM5_DIR = `${NPM_DIR}/fesm5`;
const BUNDLES_DIR = `${NPM_DIR}/bundles`;
const OUT_DIR = `${NPM_DIR}/package`;
const OUT_DIR_ESM5 = `${NPM_DIR}/package/esm5`;
const gulp = require('gulp');
const inlineTemplates = require('gulp-inline-ng2-template');
/**
* Inline templates configuration.
* @see https://github.com/ludohenin/gulp-inline-ng2-template
*/
const INLINE_TEMPLATES = {
SRC: './src/lib/**/*.ts',
DIST: './dist/package/lib',
CONFIG: {
base: './src/lib',
target: 'es6',
useRelativePaths: true
}
};
shell.echo(`Start building...`);
shell.rm(`-Rf`, `${NPM_DIR}/*`);
shell.mkdir(`-p`, `./${ESM2015_DIR}`);
shell.mkdir(`-p`, `./${ESM5_DIR}`);
shell.mkdir(`-p`, `./${FESM2015_DIR}`);
shell.mkdir(`-p`, `./${FESM5_DIR}`);
shell.mkdir(`-p`, `./${BUNDLES_DIR}`);
shell.mkdir(`-p`, `./${OUT_DIR}`);
/* TSLint with Codelyzer */
// https://github.com/palantir/tslint/blob/master/src/configs/recommended.ts
// https://github.com/mgechev/codelyzer
shell.echo(`Start TSLint`);
shell.exec(`tslint -p tsconfig.json -t stylish src/lib/**/*.ts`);
shell.echo(chalk.green(`TSLint completed`));
const DIST_FOLDER_NAME = 'dist';
const libName = require('./package.json').name;
const rootFolder = path.join(__dirname);
const compilationFolder = path.join(rootFolder, 'out-tsc');
const srcFolder = path.join(rootFolder, 'src/lib');
const distFolder = path.join(rootFolder, DIST_FOLDER_NAME);
const tempLibFolder = path.join(compilationFolder, 'lib');
const es5OutputFolder = path.join(compilationFolder, 'lib-es5');
const es2015OutputFolder = path.join(compilationFolder, 'lib-es2015');
return Promise.resolve()
// Copy library to temporary folder and inline html/css.
.then(() => _relativeCopy(`**/*`, srcFolder, tempLibFolder)
.then(() => inlineResources(tempLibFolder))
.then(() => console.log('Inlining succeeded.'))
)
// Compile to ES2015.
.then(() => ngc({ project: `${tempLibFolder}/tsconfig.lib.json` })
.then(exitCode => exitCode === 0 ? Promise.resolve() : Promise.reject())
.then(() => console.log('ES2015 compilation succeeded.'))
)
// Compile to ES5.
.then(() => ngc({ project: `${tempLibFolder}/tsconfig.es5.json` })
.then(exitCode => exitCode === 0 ? Promise.resolve() : Promise.reject())
.then(() => console.log('ES5 compilation succeeded.'))
)
// Copy typings and metadata to `dist/` folder.
.then(() => Promise.resolve()
.then(() => _relativeCopy('**/*.d.ts', es2015OutputFolder, distFolder))
.then(() => _relativeCopy('**/*.metadata.json', es2015OutputFolder, distFolder))
.then(() => console.log('Typings and metadata copy succeeded.'))
)
// Bundle lib.
.then(() => {
// Base configuration.
const es5Entry = path.join(es5OutputFolder, `${libName}.js`);
const es2015Entry = path.join(es2015OutputFolder, `${libName}.js`);
const rollupBaseConfig = {
name: camelCase(libName),
sourcemap: true,
// ATTENTION:
// Add any dependency or peer dependency your library to `globals` and `external`.
// This is required for UMD bundle users.
globals: {
// The key here is library name, and the value is the the name of the global variable name
// the window object.
// See https://github.com/rollup/rollup/wiki/JavaScript-API#globals for more.
'@angular/core': 'ng.core',
'jquery': '$',
'lodash': '_'
},
external: [
// List of dependencies
// See https://github.com/rollup/rollup/wiki/JavaScript-API#external for more.
'@angular/core',
'@angular/forms',
'@angular/platform-browser',
'codemirror',
'jointjs',
'lodash',
'ts-disposables'
],
plugins: [
sourcemaps()
]
};
// UMD bundle.
const umdConfig = Object.assign({}, rollupBaseConfig, {
input: es5Entry,
file: path.join(distFolder, `bundles`, `${libName}.umd.js`),
format: 'umd',
});
// Minified UMD bundle.
const minifiedUmdConfig = Object.assign({}, rollupBaseConfig, {
input: es5Entry,
file: path.join(distFolder, `bundles`, `${libName}.umd.min.js`),
format: 'umd',
plugins: rollupBaseConfig.plugins.concat([uglify({})])
});
// ESM+ES5 flat module bundle.
const fesm5config = Object.assign({}, rollupBaseConfig, {
input: es5Entry,
file: path.join(distFolder, `${libName}.es5.js`),
format: 'es'
});
// ESM+ES2015 flat module bundle.
const fesm2015config = Object.assign({}, rollupBaseConfig, {
input: es2015Entry,
file: path.join(distFolder, `${libName}.js`),
format: 'es'
});
const allBundles = [
umdConfig,
minifiedUmdConfig,
fesm5config,
fesm2015config
].map(cfg => rollup.rollup(cfg).then(bundle => bundle.write(cfg)));
return Promise.all(allBundles)
.then(() => console.log('All bundles generated successfully.'))
})
// Copy package files
.then(() => Promise.resolve()
.then(() => _relativeCopy('LICENSE', rootFolder, distFolder))
.then(() => _relativeCopy('README.md', rootFolder, distFolder))
.then(() => _generatePackageJson(path.join(rootFolder, 'package.json'), distFolder))
.then(() => console.log('Package files copy succeeded.'))
)
.catch(e => {
console.error('\Build failed. See below for errors.\n');
console.error(e);
process.exit(1);
});
/**
* Inline external HTML and SCSS templates into Angular component files.
* @see: https://github.com/ludohenin/gulp-inline-ng2-template
*/
gulp.src(INLINE_TEMPLATES.SRC)
.pipe(inlineTemplates(INLINE_TEMPLATES.CONFIG))
.pipe(gulp.dest(INLINE_TEMPLATES.DIST)).on('end', compile);
// Copy files maintaining relative paths.
function _relativeCopy(fileGlob, from, to) {
return new Promise((resolve, reject) => {
glob(fileGlob, { cwd: from, nodir: true }, (err, files) => {
if (err) reject(err);
files.forEach(file => {
const origin = path.join(from, file);
const dest = path.join(to, file);
const data = fs.readFileSync(origin, 'utf-8');
_recursiveMkDir(path.dirname(dest));
fs.writeFileSync(dest, data);
resolve();
})
})
});
}
function compile() {
/* Try to process scss files */
// shell.echo(`Try to process scss files`);
// if (shell.exec(`node-sass -r ${OUT_DIR} -o ${OUT_DIR}`).code === 0) {
// shell.rm(`-Rf`, `${OUT_DIR}/**/*.scss`);
// shell.ls(`${OUT_DIR}/**/*.css`).forEach(function (file) {
// shell.mv(file, file.replace('.css', '.scss'));
// });
// }
shell.cp('-Rf', ['*.json'], OUT_DIR);
/* AoT compilation */
shell.echo(`Start AoT compilation`);
if (shell.exec(`ngc -p ${OUT_DIR}/tsconfig-build.json`).code !== 0) {
shell.echo(chalk.red(`Error: AoT compilation failed`));
shell.exit(1);
}
shell.echo(chalk.green(`AoT compilation completed`));
shell.echo(`Copy ES2015 for package`);
shell.cp(`-Rf`, [ `${TS_COMPILED_DIR}/*` ], `${ESM2015_DIR}`);
/* BUNDLING PACKAGE */
shell.echo(`Start bundling`);
shell.echo(`Rollup package`);
if (shell.exec(`rollup -c rollup.es.config.js -i ${TS_COMPILED_DIR}/${PACKAGE}.js -o ${FESM2015_DIR}/${PACKAGE}.js`).code !== 0) {
shell.echo(chalk.red(`Error: Rollup package failed`));
shell.exit(1);
}
shell.echo(`Produce ESM5/FESM5 versions`);
shell.exec(`ngc -p ${OUT_DIR}/tsconfig-build.json --target es5 -d false --outDir ${OUT_DIR_ESM5} --sourceMap`);
shell.cp(`-Rf`, [ `${OUT_DIR_ESM5}/*` ], `${ESM5_DIR}`);
if (shell.exec(`rollup -c rollup.es.config.js -i ${OUT_DIR_ESM5}/${PACKAGE}.js -o ${FESM5_DIR}/${PACKAGE}.js`).code !== 0) {
shell.echo(chalk.red(`Error: FESM5 version failed`));
shell.exit(1);
}
shell.echo(`Run Rollup conversion on package`);
if (shell.exec(`rollup -c rollup.config.js -i ${FESM5_DIR}/${PACKAGE}.js -o ${BUNDLES_DIR}/${PACKAGE}.umd.js`).code !== 0) {
shell.echo(chalk.red(`Error: Rollup conversion failed`));
shell.exit(1);
}
shell.echo(`Minifying`);
shell.cd(`${BUNDLES_DIR}`);
if (shell.exec(`uglifyjs ${PACKAGE}.umd.js -c --comments -o ${PACKAGE}.umd.min.js --source-map "includeSources=true,filename='${PACKAGE}.umd.min.js.map'"`).code !== 0) {
shell.echo(chalk.red(`Error: Minifying failed`));
shell.exit(1);
}
shell.cd(`..`);
shell.cd(`..`);
shell.echo(chalk.green(`Bundling completed`));
shell.rm(`-Rf`, `${NPM_DIR}/package`);
shell.rm(`-Rf`, `${TS_COMPILED_DIR}/**/*.js`);
shell.rm(`-Rf`, `${TS_COMPILED_DIR}/**/*.js.map`);
shell.rm(`-Rf`, `${ESM2015_DIR}/**/*.d.ts`);
shell.cp(`-Rf`, [`package.json`, `LICENSE`, `README.md`], `${NPM_DIR}`);
shell.sed('-i', `"private": true,`, `"private": false,`, `./${NPM_DIR}/package.json`);
shell.echo(chalk.green(`End building`));
const packagejson = path.join(NPM_DIR, 'package.json');
const json = JSON.parse(fs.readFileSync(packagejson));
if (json['scripts']) {
delete json['scripts']['postinstall'];
}
const searchValue = './' + NPM_DIR + '/';
const replaceValue = './';
replacePropertyValue(json, 'main', searchValue, replaceValue);
replacePropertyValue(json, 'module', searchValue, replaceValue);
replacePropertyValue(json, 'es2015', searchValue, replaceValue);
replacePropertyValue(json, 'typings', searchValue, replaceValue);
replacePropertyValue(json, 'esm5', searchValue, replaceValue);
replacePropertyValue(json, 'esm2015', searchValue, replaceValue);
replacePropertyValue(json, 'fesm5', searchValue, replaceValue);
replacePropertyValue(json, 'fesm2015', searchValue, replaceValue);
fs.writeFileSync(packagejson, JSON.stringify(json, null, 2));
function _generatePackageJson(basePackageJson, distFolder) {
return new Promise((resolve, reject) => {
try {
const json = JSON.parse(fs.readFileSync(basePackageJson));
if (json['scripts']) {
delete json['scripts']['postinstall'];
}
const searchValue = './' + DIST_FOLDER_NAME + '/';
const replaceValue = './';
replacePropertyValue(json, 'main', searchValue, replaceValue);
replacePropertyValue(json, 'module', searchValue, replaceValue);
replacePropertyValue(json, 'es2015', searchValue, replaceValue);
replacePropertyValue(json, 'typings', searchValue, replaceValue);
fs.writeFileSync(path.join(distFolder, 'package.json'), JSON.stringify(json, null, 2));
resolve();
} catch (error) {
reject(error);
}
});
}
function replacePropertyValue(json, property, searchValue, newValue) {
@@ -176,11 +151,3 @@ function replacePropertyValue(json, property, searchValue, newValue) {
json[property] = json[property].replace(searchValue, newValue);
}
}
// Recursively create a dir.
function _recursiveMkDir(dir) {
if (!fs.existsSync(dir)) {
_recursiveMkDir(path.dirname(dir));
fs.mkdirSync(dir);
}
}

View File

@@ -1,119 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const glob = require('glob');
/**
* Simple Promiseify function that takes a Node API and return a version that supports promises.
* We use promises instead of synchronized functions to make the process less I/O bound and
* faster. It also simplifies the code.
*/
function promiseify(fn) {
return function () {
const args = [].slice.call(arguments, 0);
return new Promise((resolve, reject) => {
fn.apply(this, args.concat([function (err, value) {
if (err) {
reject(err);
} else {
resolve(value);
}
}]));
});
};
}
const readFile = promiseify(fs.readFile);
const writeFile = promiseify(fs.writeFile);
/**
* Inline resources in a tsc/ngc compilation.
* @param projectPath {string} Path to the project.
*/
function inlineResources(projectPath) {
// Match only TypeScript files in projectPath.
const files = glob.sync('**/*.ts', {cwd: projectPath});
// For each file, inline the templates and styles under it and write the new file.
return Promise.all(files.map(filePath => {
const fullFilePath = path.join(projectPath, filePath);
return readFile(fullFilePath, 'utf-8')
.then(content => inlineResourcesFromString(content, url => {
// Resolve the template url.
return path.join(path.dirname(fullFilePath), url);
}))
.then(content => writeFile(fullFilePath, content))
.catch(err => {
console.error('An error occured: ', err);
});
}));
}
/**
* Inline resources from a string content.
* @param content {string} The source file's content.
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @returns {string} The content with resources inlined.
*/
function inlineResourcesFromString(content, urlResolver) {
// Curry through the inlining functions.
return [
inlineTemplate,
inlineStyle
].reduce((content, fn) => fn(content, urlResolver), content);
}
/**
* Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and
* replace with `template: ...` (with the content of the file included).
* @param content {string} The source file's content.
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @return {string} The content with all templates inlined.
*/
function inlineTemplate(content, urlResolver) {
return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) {
const templateFile = urlResolver(templateUrl);
const templateContent = fs.readFileSync(templateFile, 'utf-8');
const shortenedTemplate = templateContent
.replace(/([\n\r]\s*)+/gm, ' ')
.replace(/"/g, '\\"');
return `template: "${shortenedTemplate}"`;
});
}
/**
* Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and
* replace with `styles: [...]` (with the content of the file included).
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @param content {string} The source file's content.
* @return {string} The content with all styles inlined.
*/
function inlineStyle(content, urlResolver) {
return content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) {
const urls = eval(styleUrls);
return 'styles: ['
+ urls.map(styleUrl => {
const styleFile = urlResolver(styleUrl);
const styleContent = fs.readFileSync(styleFile, 'utf-8');
const shortenedStyle = styleContent
.replace(/([\n\r]\s*)+/gm, ' ')
.replace(/"/g, '\\"');
return `"${shortenedStyle}"`;
})
.join(',\n')
+ ']';
});
}
module.exports = inlineResources;
module.exports.inlineResourcesFromString = inlineResourcesFromString;
// Run inlineResources if module is being called directly from the CLI with arguments.
if (require.main === module && process.argv.length > 2) {
console.log('Inlining resources from project:', process.argv[2]);
return inlineResources(process.argv[2]);
}

View File

@@ -3,9 +3,13 @@
"version": "0.7.5",
"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",
"module": "./dist/fesm5/spring-flo.js",
"es2015": "./dist/fesm2015/spring-flo.js",
"esm5": "./dist/esm5/spring-flo.js",
"esm2015": "./dist/esm2015/spring-flo.js",
"fesm5": "./dist/fesm5/spring-flo.js",
"fesm2015": "./dist/fesm2015/spring-flo.js",
"typings": "./dist/out-tsc/spring-flo.d.ts",
"author": "",
"license": "Apache-2.0",
"repository": {
@@ -43,6 +47,7 @@
"@types/codemirror": "0.0.45",
"@types/lodash": "4.14.110",
"@types/backbone": "1.3.42",
"@types/jquery": "3.3.4",
"codemirror": "5.28.0",
"jointjs": "2.1.3",
"lodash": "3.10.1",
@@ -59,17 +64,22 @@
"devDependencies": {
"@angular/common": "6.0.7",
"@angular/compiler": "6.0.7",
"@angular/compiler-cli": "4.4.6",
"@angular/compiler-cli": "6.0.7",
"@angular/core": "6.0.7",
"@angular/forms": "6.0.7",
"@angular/platform-browser": "6.0.7",
"@angular/platform-browser-dynamic": "6.0.7",
"@angular/platform-server": "6.0.7",
"@angular/animations": "6.0.7",
"@types/jasmine": "2.5.36",
"@types/node": "6.0.46",
"@types/codemirror": "0.0.45",
"@types/lodash": "4.14.110",
"@types/backbone": "1.3.42",
"@types/jquery": "3.3.4",
"camelcase": "4.0.0",
"concurrently": "3.4.0",
"core-js": "2.4.1",
"core-js": "2.5.7",
"glob": "7.1.1",
"jasmine-core": "2.5.2",
"karma": "1.5.0",
@@ -81,47 +91,52 @@
"lite-server": "2.2.2",
"ngx-bootstrap": "3.0.1",
"rimraf": "2.6.1",
"rollup": "0.51.6",
"rollup": "0.62.0",
"rollup-plugin-sourcemaps": "0.4.2",
"rollup-plugin-uglify": "2.0.1",
"rxjs": "^6.2.1",
"rollup-plugin-node-resolve": "3.3.0",
"rollup-plugin-commonjs": "9.1.3",
"uglify-js": "3.3.23",
"shelljs": "0.8.1",
"rxjs": "6.2.1",
"standard-version": "4.0.0",
"systemjs": "0.21.4",
"tslint": "5.9.1",
"typescript": "2.5.3",
"zone.js": "0.8.18",
"@types/codemirror": "0.0.45",
"@types/lodash": "4.14.73",
"@types/backbone": "1.3.42",
"tslint": "5.10.0",
"typescript": "2.7.2",
"zone.js": "0.8.26",
"codemirror": "5.28.0",
"jointjs": "2.1.3",
"lodash": "3.10.1",
"jquery": "3.1.1",
"ts-disposables": "2.2.3",
"jshint": "2.6.3",
"systemjs-plugin-babel": "0.0.25"
"systemjs-plugin-babel": "0.0.25",
"chalk": "2.4.1",
"node-sass": "4.9.0",
"gulp": "3.9.1",
"gulp-inline-ng2-template": "5.0.1"
},
"buildDependencies": [
"@angular/core",
"@angular/forms",
"@angular/platform-browser",
"@types/codemirror",
"@types/jointjs",
"@types/lodash",
"codemirror",
"jointjs",
"jquery",
"lodash",
"ts-disposables",
"@angular/common",
"@angular/compiler",
"@angular/compiler-cli",
"@angular/platform-browser",
"@angular/platform-browser-dynamic",
"@angular/platform-server",
"@types/codemirror",
"@types/lodash",
"@types/backbone",
"@types/jasmine",
"@types/node",
"codemirror",
"jointjs",
"jquery",
"lodash",
"ts-disposables",
"camelcase",
"chalk",
"concurrently",
"core-js",
"glob",
@@ -134,15 +149,21 @@
"karma-jasmine-html-reporter",
"lite-server",
"ngx-bootstrap",
"node-sass",
"rimraf",
"rollup",
"rollup-plugin-sourcemaps",
"rollup-plugin-uglify",
"rollup-plugin-node-resolve",
"rollup-plugin-commonjs",
"rxjs",
"standard-version",
"systemjs",
"shelljs",
"tslint",
"typescript",
"zone.js"
"uglify-js",
"zone.js",
"gulp",
"gulp-inline-ng2-template"
]
}

90
rollup.config.js Normal file
View File

@@ -0,0 +1,90 @@
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
import commonjs from 'rollup-plugin-commonjs';
/**
* Add here external dependencies that actually you use.
*
* Angular dependencies
* - '@angular/animations' => 'ng.animations'
* - '@angular/animations/browser': 'ng.animations.browser'
* - '@angular/common' => 'ng.common'
* - '@angular/compiler' => 'ng.compiler'
* - '@angular/core' => 'ng.core'
* - '@angular/forms' => 'ng.forms'
* - '@angular/common/http' => 'ng.common.http'
* - '@angular/platform-browser-dynamic' => 'ng.platformBrowserDynamic'
* - '@angular/platform-browser' => 'ng.platformBrowser'
* - '@angular/platform-browser/animations' => 'ng.platformBrowser.animations'
* - '@angular/platform-server' => 'ng.platformServer'
* - '@angular/router' => 'ng.router'
*
* RxJS dependencies
* From RxJS v6 you need only 'rxjs' and 'rxjs.operators'.
*
* Other dependencies
* - Angular libraries: refer to their global namespace
* - TypeScript/JavaScript libraries:
* e.g. lodash: 'lodash' => 'lodash'
*
* Also, if the dependency uses CommonJS modules, such as lodash,
* you should also use a plugin like rollup-plugin-commonjs,
* to explicitly specify unresolvable "named exports".
*
*/
const globals = {
'@angular/core': 'ng.core',
'@angular/common': 'ng.common',
'@angular/forms': 'ng.forms',
'@angular/platform-browser': 'ng.browser',
'rxjs': 'rxjs',
'rxjs/operators': 'rxjs.operators',
'lodash': 'lodash',
'ts-disposables': 'ts-disposables',
'codemirror': 'codemirror',
'jointjs': 'jointjs',
'jquery': 'jquery',
// CodeMirror extensions
'codemirror/mode/meta': 'codemirror/mode/meta',
'codemirror/addon/lint/lint': 'codemirror/addon/lint/lint',
'codemirror/addon/hint/show-hint': 'codemirror/addon/hint/show-hint',
'codemirror/addon/mode/loadmode': 'codemirror/addon/mode/loadmode',
'codemirror/addon/edit/matchbrackets': 'codemirror/addon/edit/matchbrackets',
'codemirror/addon/edit/closebrackets': 'codemirror/addon/edit/closebrackets',
'codemirror/addon/display/placeholder': 'codemirror/addon/edit/closebrackets',
'codemirror/addon/scroll/annotatescrollbar': 'codemirror/addon/scroll/annotatescrollbar',
'codemirror/addon/scroll/simplescrollbars': 'codemirror/addon/scroll/simplescrollbars',
// Lint support
// Unclear how to import this dynamically...
'codemirror/addon/lint/javascript-lint': 'codemirror/addon/lint/javascript-lint',
'codemirror/addon/lint/coffeescript-lint': 'codemirror/addon/lint/coffeescript-lint',
'codemirror/addon/lint/json-lint': 'codemirror/addon/lint/json-lint',
'codemirror/addon/lint/yaml-lint': 'codemirror/addon/lint/yaml-lint',
// TODO: use dynamic import with JS7 in the future. CM autoLoad cannot load it properly - thinks its AMD
// Supported languages until dynamic loading
'codemirror/mode/groovy/groovy': 'codemirror/mode/groovy/groovy',
'codemirror/mode/javascript/javascript': 'codemirror/mode/javascript/javascript',
'codemirror/mode/python/python': 'codemirror/mode/python/python',
'codemirror/mode/ruby/ruby': 'codemirror/mode/ruby/ruby',
'codemirror/mode/clike/clike': 'codemirror/mode/clike/clike',
'codemirror/mode/yaml/yaml': 'codemirror/mode/yaml/yaml',
'codemirror/mode/coffeescript/coffeescript': 'codemirror/mode/coffeescript/coffeescript'
};
export default {
external: Object.keys(globals),
plugins: [resolve(), sourcemaps(), commonjs()],
onwarn: () => { return },
output: {
format: 'umd',
name: 'ng.spring-flo',
globals: globals,
sourcemap: true,
exports: 'named',
amd: { id: 'spring-flo' }
}
}

12
rollup.es.config.js Normal file
View File

@@ -0,0 +1,12 @@
import sourcemaps from 'rollup-plugin-sourcemaps';
export default {
output: {
format: 'es',
sourcemap: true
},
plugins: [
sourcemaps()
],
onwarn: () => { return }
}

View File

@@ -3,7 +3,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import * as CodeMirror from 'codemirror';
import * as _$ from 'jquery';
const $ : any = _$;
const $: any = _$;
// CodeMirror extensions
import 'codemirror/mode/meta';
@@ -37,10 +37,10 @@ import 'codemirror/mode/coffeescript/coffeescript';
selector: 'code-editor',
templateUrl: './code-editor.component.html',
styleUrls: [
'./../../../../node_modules/codemirror/lib/codemirror.css',
'./../../../../node_modules/codemirror/addon/hint/show-hint.css',
'./../../../../node_modules/codemirror/addon/lint/lint.css',
'./../../../../node_modules/codemirror/addon/scroll/simplescrollbars.css',
'./../../../node_modules/codemirror/lib/codemirror.css',
'./../../../node_modules/codemirror/addon/hint/show-hint.css',
'./../../../node_modules/codemirror/addon/lint/lint.css',
'./../../../node_modules/codemirror/addon/scroll/simplescrollbars.css',
'./code-editor.component.scss', ],
encapsulation: ViewEncapsulation.None,
providers: [
@@ -53,11 +53,11 @@ import 'codemirror/mode/coffeescript/coffeescript';
})
export class CodeEditorComponent implements OnInit, OnDestroy, ControlValueAccessor {
private doc : CodeMirror.EditorFromTextArea;
private doc: CodeMirror.EditorFromTextArea;
private _dsl = '';
private _lint : boolean | CodeMirror.LintOptions = false;
private _lint: boolean | CodeMirror.LintOptions = false;
private _language: string;
@@ -70,16 +70,16 @@ export class CodeEditorComponent implements OnInit, OnDestroy, ControlValueAcces
private _onTouchHandler: () => void;
@Input('line-numbers')
private lineNumbers : boolean = false;
private lineNumbers = false;
@Input('line-wrapping')
private lineWrapping : boolean = false;
private lineWrapping = false;
@Input('scrollbar-style')
private scrollbarStyle : string;
private scrollbarStyle: string;
@Input()
private placeholder : string;
private placeholder: string;
@Input('overview-ruler')
private overviewRuler: boolean;
@@ -107,7 +107,7 @@ export class CodeEditorComponent implements OnInit, OnDestroy, ControlValueAcces
constructor(private element: ElementRef) {}
@Input()
set dsl(dsl : string) {
set dsl(dsl: string) {
this._dsl = dsl;
if (this.doc && this._dsl !== this.doc.getValue()) {
let cursorPosition = (<any>this.doc).getCursor();
@@ -126,7 +126,7 @@ export class CodeEditorComponent implements OnInit, OnDestroy, ControlValueAcces
ngOnInit() {
let options : any = {
let options: any = {
value: this._dsl || '',
gutters: ['CodeMirror-lint-markers'],
extraKeys: {'Ctrl-Space': 'autocomplete'},

View File

@@ -1,34 +1,38 @@
import {Directive, Input, Output, EventEmitter, Inject, ElementRef, OnInit, OnDestroy,} from '@angular/core';
import {Directive, Input, Output, EventEmitter, Inject, ElementRef, OnInit, OnDestroy} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser'
import { fromEvent } from "rxjs";
import { fromEvent } from 'rxjs';
import { sampleTime } from 'rxjs/operators';
import { CompositeDisposable, Disposable } from 'ts-disposables';
import * as _$ from 'jquery';
const $ : any = _$;
const $: any = _$;
@Directive({
selector: '[resizer]',
host: {'(mousedown)': 'startDrag()'}
})
export class ResizerDirective implements OnInit, OnDestroy {
private dragInProgress: boolean = false;
private vertical: boolean = true;
private dragInProgress = false;
private vertical = true;
private first: string;
private second: string;
private _size: number;
private _splitSize: number;
private _subscriptions = new CompositeDisposable();
@Input()
maxSplitSize: number;
@Output()
sizeChange = new EventEmitter<number>();
private mouseMoveHandler = (e: any) => {
if (this.dragInProgress) {
this.mousemove(e);
}
};
@Input()
maxSplitSize: number;
@Input()
set splitSize(splitSize : number) {
set splitSize(splitSize: number) {
if (this.maxSplitSize && splitSize > this.maxSplitSize) {
splitSize = this.maxSplitSize;
@@ -66,38 +70,35 @@ export class ResizerDirective implements OnInit, OnDestroy {
this.sizeChange.emit(splitSize);
}
@Output()
sizeChange = new EventEmitter<number>();
@Input()
set resizerWidth(width : number) {
set resizerWidth(width: number) {
this._size = width;
this.vertical = true;
}
@Input()
set resizerHeight(height : number) {
set resizerHeight(height: number) {
this._size = height;
this.vertical = false;
}
@Input()
set resizerLeft(first : string) {
set resizerLeft(first: string) {
this.first = first;
}
@Input()
set resizerTop(first : string) {
set resizerTop(first: string) {
this.first = first;
}
@Input()
set resizerRight(second : string) {
set resizerRight(second: string) {
this.second = second;
}
@Input()
set resizerBottom(second : string) {
set resizerBottom(second: string) {
this.second = second;
}

View File

@@ -2,7 +2,7 @@ import { Component, Input, Output, ElementRef, EventEmitter, OnInit, OnDestroy,
import * as _ from 'lodash';
import * as CodeMirror from 'codemirror';
import * as _$ from 'jquery';
const $ : any = _$;
const $: any = _$;
import 'codemirror/addon/lint/lint';
import 'codemirror/addon/hint/show-hint';
@@ -15,36 +15,36 @@ import 'codemirror/addon/scroll/simplescrollbars';
selector: 'dsl-editor',
templateUrl: './dsl-editor.component.html',
styleUrls: [
'./../../../../node_modules/codemirror/lib/codemirror.css',
'./../../../../node_modules/codemirror/addon/hint/show-hint.css',
'./../../../../node_modules/codemirror/addon/lint/lint.css',
'./../../../node_modules/codemirror/lib/codemirror.css',
'./../../../node_modules/codemirror/addon/hint/show-hint.css',
'./../../../node_modules/codemirror/addon/lint/lint.css',
'./dsl-editor.component.css', ],
encapsulation: ViewEncapsulation.None
})
export class DslEditorComponent implements OnInit, OnDestroy {
private doc : CodeMirror.EditorFromTextArea;
private doc: CodeMirror.EditorFromTextArea;
private _dsl = '';
private _lint : boolean | CodeMirror.LintOptions = false;
private _lint: boolean | CodeMirror.LintOptions = false;
private _hint : any;
private _hint: any;
@Input('line-numbers')
private lineNumbers : boolean = false;
private lineNumbers = false;
@Input('line-wrapping')
private lineWrapping : boolean = false;
private lineWrapping = false;
@Input('scrollbar-style')
private scrollbarStyle : string;
private scrollbarStyle: string;
@Input()
private placeholder : string;
private placeholder: string;
@Input()
private debounce : number = 0;
private debounce = 0;
@Output()
private dslChange = new EventEmitter<string>();
@@ -66,7 +66,7 @@ export class DslEditorComponent implements OnInit, OnDestroy {
constructor(private element: ElementRef) {}
@Input()
set dsl(dsl : string) {
set dsl(dsl: string) {
this._dsl = dsl;
if (this.doc && this._dsl !== this.doc.getValue()) {
let cursorPosition = (<any>this.doc).getCursor();
@@ -76,7 +76,7 @@ export class DslEditorComponent implements OnInit, OnDestroy {
}
@Input()
set lintOptions(lintOptions : boolean | CodeMirror.LintOptions) {
set lintOptions(lintOptions: boolean | CodeMirror.LintOptions) {
this._lint = lintOptions;
if (this.doc) {
this.doc.setOption('lint', this._lint);
@@ -84,7 +84,7 @@ export class DslEditorComponent implements OnInit, OnDestroy {
}
@Input()
set hintOptions(hintOptions : any) {
set hintOptions(hintOptions: any) {
this._hint = hintOptions;
if (this.doc) {
this.doc.setOption('hintOptions', this._hint);
@@ -93,7 +93,7 @@ export class DslEditorComponent implements OnInit, OnDestroy {
ngOnInit() {
let options : CodeMirror.EditorConfiguration = {
let options: CodeMirror.EditorConfiguration = {
value: this._dsl || '',
gutters: ['CodeMirror-lint-markers'],
extraKeys: {'Ctrl-Space': 'autocomplete'},

View File

@@ -1,32 +1,32 @@
import { dia } from 'jointjs';
import { Flo } from '../shared/flo-common';
import * as _ from 'lodash';
const joint : any = Flo.joint;
const joint: any = Flo.joint;
import * as _$ from 'jquery';
const $ : any = _$;
const $: any = _$;
export class Utils {
static fanRoute(graph : dia.Graph, cell : dia.Cell) {
static fanRoute(graph: dia.Graph, cell: dia.Cell) {
if (cell instanceof joint.dia.Element) {
_.chain(graph.getConnectedLinks(cell)).groupBy((link : dia.Link) => {
_.chain(graph.getConnectedLinks(cell)).groupBy((link: dia.Link) => {
// the key of the group is the model id of the link's source or target, but not our cell id.
return _.omit([link.get('source').id, link.get('target').id], cell.id)[0];
}).each((group : any, key : string) => {
}).each((group: any, key: string) => {
// If the member of the group has both source and target model adjust vertices.
let toRoute : any = {};
let toRoute: any = {};
if (key !== undefined) {
group.forEach((link : dia.Link) => {
group.forEach((link: dia.Link) => {
if (link.get('source').id === cell.get('id') && link.get('target').id) {
toRoute[link.get('target').id] = link;
} else if (link.get('target').id === cell.get('id') && link.get('source').id) {
toRoute[link.get('source').id] = link;
}
});
Object.keys(toRoute).forEach(key => {
Utils.fanRoute(graph, toRoute[key]);
Object.keys(toRoute).forEach(k => {
Utils.fanRoute(graph, toRoute[k]);
});
}
});
@@ -115,7 +115,7 @@ export class Utils {
}
}
static isCustomPaperEvent(args : any) : boolean {
static isCustomPaperEvent(args: any): boolean {
return args.length === 5 &&
_.isString(args[0]) &&
(args[0].indexOf('link:') === 0 || args[0].indexOf('element:') === 0) &&

View File

@@ -8,23 +8,74 @@ import { CompositeDisposable, Disposable } from 'ts-disposables';
import * as _$ from 'jquery';
import * as _ from 'lodash';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
const joint : any = Flo.joint;
const $ : any = _$;
const joint: any = Flo.joint;
const $: any = _$;
export interface VisibilityState {
visibility : string;
children : Array<VisibilityState>;
visibility: string;
children: Array<VisibilityState>;
}
@Component({
selector: 'flo-editor',
templateUrl: './editor.component.html',
styleUrls: ['./../../../../node_modules/jointjs/dist/joint.css', './../shared/flo.css'],
styleUrls: ['./../../../node_modules/jointjs/dist/joint.css', './../shared/flo.css'],
encapsulation: ViewEncapsulation.None
})
export class EditorComponent implements OnInit, OnDestroy {
/**
* Joint JS Graph object representing the Graph model
*/
private graph: dia.Graph;
/**
* Joint JS Paper object representing the canvas control containing the graph view
*/
private paper: dia.Paper;
/**
* Currently selected element
*/
private _selection: dia.CellView;
/**
* Current DnD descriptor for frag in progress
*/
private highlighted: Flo.DnDDescriptor;
/**
* Flag specifying whether the Flo-Editor is in read-only mode.
*/
private _readOnlyCanvas = false;
/**
* Grid size
*/
private _gridSize = 1;
private _hiddenPalette = false;
private editorContext: Flo.EditorContext;
private textToGraphEventEmitter = new EventEmitter<void>();
private graphToTextEventEmitter = new EventEmitter<void>();
private _graphToTextSyncEnabled = true;
private validationEventEmitter = new EventEmitter<void>();
private _disposables = new CompositeDisposable();
private _dslText = '';
private textToGraphConversionCompleted = new Subject<void>();
private graphToTextConversionCompleted = new Subject<void>();
private paletteReady = new BehaviorSubject<boolean>(false);
/**
* Metamodel. Retrieves metadata about elements that can be shown in Flo
*/
@@ -53,22 +104,22 @@ export class EditorComponent implements OnInit, OnDestroy {
* Min zoom percent value
*/
@Input()
minZoom: number = 5;
minZoom = 5;
/**
* Max zoom percent value
*/
@Input()
maxZoom: number = 400;
maxZoom = 400;
/**
* Zoom percent increment/decrement step
*/
@Input()
zoomStep: number = 5;
zoomStep = 5;
@Input()
paperPadding : number = 0;
paperPadding = 0;
@Output()
floApi = new EventEmitter<Flo.EditorContext>();
@@ -79,114 +130,60 @@ export class EditorComponent implements OnInit, OnDestroy {
@Output()
contentValidated = new EventEmitter<boolean>();
/**
* Joint JS Graph object representing the Graph model
*/
private graph: dia.Graph;
/**
* Joint JS Paper object representing the canvas control containing the graph view
*/
private paper: dia.Paper;
/**
* Currently selected element
*/
private _selection: dia.CellView;
/**
* Current DnD descriptor for frag in progress
*/
private highlighted: Flo.DnDDescriptor;
/**
* Flag specifying whether the Flo-Editor is in read-only mode.
*/
private _readOnlyCanvas: boolean = false;
/**
* Grid size
*/
private _gridSize: number = 1;
private _hiddenPalette : boolean = false;
private editorContext : Flo.EditorContext;
private _resizeHandler = () => this.autosizePaper();
private textToGraphEventEmitter = new EventEmitter<void>();
private graphToTextEventEmitter = new EventEmitter<void>();
private _graphToTextSyncEnabled = true;
private validationEventEmitter = new EventEmitter<void>();
private _disposables = new CompositeDisposable();
/* DSL Fields */
private _dslText : string = '';
@Output()
private dslChange = new EventEmitter<string>();
private textToGraphConversionCompleted = new Subject<void>();
private graphToTextConversionCompleted = new Subject<void>();
private paletteReady = new BehaviorSubject<boolean>(false);
private _resizeHandler = () => this.autosizePaper();
constructor(private element: ElementRef) {
let self = this;
this.editorContext = new (class DefaultRunnableContext implements Flo.EditorContext {
set zoomPercent(percent : number) {
set zoomPercent(percent: number) {
self.zoomPercent = percent;
}
get zoomPercent() : number {
get zoomPercent(): number {
return self.zoomPercent;
}
set noPalette(noPalette : boolean) {
set noPalette(noPalette: boolean) {
self.noPalette = noPalette;
}
get noPalette() : boolean {
get noPalette(): boolean {
return self.noPalette;
}
set gridSize(gridSize : number) {
set gridSize(gridSize: number) {
self.gridSize = gridSize;
}
get gridSize() : number {
get gridSize(): number {
return self.gridSize;
}
set readOnlyCanvas(readOnly : boolean) {
set readOnlyCanvas(readOnly: boolean) {
self.readOnlyCanvas = readOnly;
}
get readOnlyCanvas() : boolean {
get readOnlyCanvas(): boolean {
return self.readOnlyCanvas;
}
setDsl(dsl : string) {
setDsl(dsl: string) {
self.dsl = dsl;
}
updateGraph() : Promise<any> {
updateGraph(): Promise<any> {
return self.updateGraphRepresentation();
}
updateText() : Promise<any> {
updateText(): Promise<any> {
return self.updateTextRepresentation();
}
performLayout() : Promise<void> {
performLayout(): Promise<void> {
return self.doLayout();
}
@@ -215,11 +212,11 @@ export class EditorComponent implements OnInit, OnDestroy {
return self.paper;
}
get graphToTextSync() : boolean {
get graphToTextSync(): boolean {
return self.graphToTextSync;
}
set graphToTextSync(sync : boolean) {
set graphToTextSync(sync: boolean) {
self.graphToTextSync = sync;
}
@@ -239,29 +236,29 @@ export class EditorComponent implements OnInit, OnDestroy {
self.fitToPage();
}
createNode(metadata : Flo.ElementMetadata, props? : Map<string, any>, position? : dia.Point) : dia.Element {
createNode(metadata: Flo.ElementMetadata, props?: Map<string, any>, position?: dia.Point): dia.Element {
return self.createNode(metadata, props, position);
}
createLink(source : Flo.LinkEnd, target : Flo.LinkEnd, metadata? : Flo.ElementMetadata, props? : Map<string, any>) : dia.Link {
createLink(source: Flo.LinkEnd, target: Flo.LinkEnd, metadata?: Flo.ElementMetadata, props?: Map<string, any>): dia.Link {
return self.createLink(source, target, metadata, props);
}
get selection() : dia.CellView {
get selection(): dia.CellView {
return self.selection;
}
set selection(newSelection : dia.CellView) {
set selection(newSelection: dia.CellView) {
self.selection = newSelection;
}
deleteSelectedNode() : void {
deleteSelectedNode(): void {
if (self.selection) {
if (self.editor && self.editor.preDelete) {
self.editor.preDelete(self.editorContext, self.selection.model);
} else {
if (self.selection.model instanceof joint.dia.Element) {
self.graph.getConnectedLinks(self.selection.model).forEach((l : dia.Link) => l.remove());
self.graph.getConnectedLinks(self.selection.model).forEach((l: dia.Link) => l.remove());
}
}
self.selection.model.remove();
@@ -314,11 +311,11 @@ export class EditorComponent implements OnInit, OnDestroy {
this._disposables.dispose();
}
get noPalette() : boolean {
get noPalette(): boolean {
return this._hiddenPalette;
}
set noPalette(hidden : boolean) {
set noPalette(hidden: boolean) {
this._hiddenPalette = hidden;
// If palette is not shown ensure that canvas starts from the left==0!
if (hidden) {
@@ -326,11 +323,11 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
get graphToTextSync() : boolean {
get graphToTextSync(): boolean {
return this._graphToTextSyncEnabled;
}
set graphToTextSync(sync : boolean) {
set graphToTextSync(sync: boolean) {
this._graphToTextSyncEnabled = sync;
// Try commenting the sync out. Just set the flag but don't kick off graph->text conversion
// this.performGraphToTextSyncing();
@@ -384,7 +381,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
get selection() : dia.CellView {
get selection(): dia.CellView {
return this._selection;
}
@@ -397,7 +394,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
if (newSelection !== this._selection) {
if (this._selection) {
var elementview = this.paper.findViewByModel(this._selection.model);
const elementview = this.paper.findViewByModel(this._selection.model);
if (elementview) { // May have been removed from the graph
this.removeEmbeddedChildrenOfType(elementview.model, joint.shapes.flo.HANDLE_TYPE);
elementview.unhighlight();
@@ -413,11 +410,11 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
get readOnlyCanvas() : boolean {
get readOnlyCanvas(): boolean {
return this._readOnlyCanvas;
}
set readOnlyCanvas(value : boolean) {
set readOnlyCanvas(value: boolean) {
if (this._readOnlyCanvas === value) {
// Nothing to do
return
@@ -427,7 +424,7 @@ export class EditorComponent implements OnInit, OnDestroy {
this.selection = undefined;
}
if (this.graph) {
this.graph.getLinks().forEach((link : dia.Link) => {
this.graph.getLinks().forEach((link: dia.Link) => {
if (value) {
link.attr('.link-tools/display', 'none');
link.attr('.marker-vertices/display', 'none');
@@ -450,11 +447,11 @@ export class EditorComponent implements OnInit, OnDestroy {
* @param dragDescriptor DnD info object. Has on info on graph node being dragged (drag source) and what it is
* being dragged over at the moment (drop target)
*/
showDragFeedback(dragDescriptor : Flo.DnDDescriptor) : void {
showDragFeedback(dragDescriptor: Flo.DnDDescriptor): void {
if (this.editor && this.editor.showDragFeedback) {
this.editor.showDragFeedback(this.editorContext, dragDescriptor);
} else {
let magnet : SVGElement;
let magnet: SVGElement;
if (dragDescriptor.source && dragDescriptor.source.view) {
joint.V(dragDescriptor.source.view.el).addClass('dnd-source-feedback');
if (dragDescriptor.source.cssClassSelector) {
@@ -482,11 +479,11 @@ export class EditorComponent implements OnInit, OnDestroy {
* @param dragDescriptor DnD info object. Has on info on graph node being dragged (drag source) and what it is
* being dragged over at the moment (drop target)
*/
hideDragFeedback(dragDescriptor : Flo.DnDDescriptor) : void {
hideDragFeedback(dragDescriptor: Flo.DnDDescriptor): void {
if (this.editor && this.editor.hideDragFeedback) {
this.editor.hideDragFeedback(this.editorContext, dragDescriptor);
} else {
let magnet : SVGElement;
let magnet: SVGElement;
if (dragDescriptor.source && dragDescriptor.source.view) {
joint.V(dragDescriptor.source.view.el).removeClass('dnd-source-feedback');
if (dragDescriptor.source.cssClassSelector) {
@@ -514,7 +511,7 @@ export class EditorComponent implements OnInit, OnDestroy {
* @param dragDescriptor DnD info object. Has on info on graph node being dragged (drag source) and what it is
* being dragged over at the moment (drop target)
*/
setDragDescriptor(dragDescriptor? : Flo.DnDDescriptor) : void {
setDragDescriptor(dragDescriptor?: Flo.DnDDescriptor): void {
if (this.highlighted === dragDescriptor) {
return;
}
@@ -551,7 +548,7 @@ export class EditorComponent implements OnInit, OnDestroy {
* @param y Y coordinate of the mosue on the canvas
* @param context DnD context (palette or canvas)
*/
handleNodeDragging(draggedView : dia.CellView, targetUnderMouse : dia.CellView, x : number, y : number, sourceComponent : string) {
handleNodeDragging(draggedView: dia.CellView, targetUnderMouse: dia.CellView, x: number, y: number, sourceComponent: string) {
if (this.editor && this.editor.calculateDragDescriptor) {
this.setDragDescriptor(this.editor.calculateDragDescriptor(this.editorContext, draggedView, targetUnderMouse, joint.g.point(x, y), sourceComponent));
}
@@ -572,12 +569,12 @@ export class EditorComponent implements OnInit, OnDestroy {
* @param domNode DOM node to hide
* @returns
*/
private _hideNode(domNode : HTMLElement) : VisibilityState {
let oldVisibility : VisibilityState = {
private _hideNode(domNode: HTMLElement): VisibilityState {
let oldVisibility: VisibilityState = {
visibility: domNode.style ? domNode.style.display : undefined,
children: []
};
for (var i = 0; i < domNode.children.length; i++) {
for (let i = 0; i < domNode.children.length; i++) {
let node = domNode.children.item(i);
if (node instanceof HTMLElement) {
oldVisibility.children.push(this._hideNode(<HTMLElement> node));
@@ -592,14 +589,14 @@ export class EditorComponent implements OnInit, OnDestroy {
* @param domNode DOM node to restore visibility of
* @param oldVisibility original visibility parameter
*/
_restoreNodeVisibility(domNode : HTMLElement, oldVisibility : VisibilityState) {
_restoreNodeVisibility(domNode: HTMLElement, oldVisibility: VisibilityState) {
if (domNode.style) {
domNode.style.display = oldVisibility.visibility;
}
let j = 0;
for (var i = 0; i < domNode.childNodes.length; i++) {
for (let i = 0; i < domNode.childNodes.length; i++) {
if (j < oldVisibility.children.length) {
let node= domNode.children.item(i);
let node = domNode.children.item(i);
if (node instanceof HTMLElement) {
this._restoreNodeVisibility(<HTMLElement> node, oldVisibility.children[j++]);
}
@@ -617,7 +614,7 @@ export class EditorComponent implements OnInit, OnDestroy {
* Excluded views enables you to choose to filter some possible answers (useful in the case where elements are stacked
* - e.g. Drag-n-Drop)
*/
getTargetViewFromEvent(event : MouseEvent, x : number, y : number, excludeViews : Array<dia.CellView> = []) : dia.CellView {
getTargetViewFromEvent(event: MouseEvent, x: number, y: number, excludeViews: Array<dia.CellView> = []): dia.CellView {
if (!x && !y) {
let l = this.paper.snapToGrid({x: event.clientX, y: event.clientY});
x = l.x;
@@ -631,7 +628,7 @@ export class EditorComponent implements OnInit, OnDestroy {
// return underMouse;
// }
let oldVisibility = excludeViews.map(x => this._hideNode(x.el));
let oldVisibility = excludeViews.map(_x => this._hideNode(_x.el));
let targetElement = document.elementFromPoint(event.clientX, event.clientY);
excludeViews.forEach((excluded, i) => {
this._restoreNodeVisibility(excluded.el, oldVisibility[i]);
@@ -639,7 +636,7 @@ export class EditorComponent implements OnInit, OnDestroy {
return this.paper.findView($(targetElement));
}
handleDnDFromPalette(dndEvent : Flo.DnDEvent) {
handleDnDFromPalette(dndEvent: Flo.DnDEvent) {
switch (dndEvent.type) {
case Flo.DnDEventType.DRAG:
this.handleDragFromPalette(dndEvent);
@@ -652,7 +649,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
handleDragFromPalette(dnDEvent : Flo.DnDEvent) {
handleDragFromPalette(dnDEvent: Flo.DnDEvent) {
console.log('Dragging from palette');
if (dnDEvent.view && !this.readOnlyCanvas) {
let location = this.paper.snapToGrid({x: dnDEvent.event.clientX, y: dnDEvent.event.clientY});
@@ -660,7 +657,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
createNode(metadata : Flo.ElementMetadata, props : Map<string, any>, position : dia.Point) : dia.Element {
createNode(metadata: Flo.ElementMetadata, props: Map<string, any>, position: dia.Point): dia.Element {
return Shapes.Factory.createNode({
renderer: this.renderer,
paper: this.paper,
@@ -670,7 +667,7 @@ export class EditorComponent implements OnInit, OnDestroy {
});
}
createLink(source : Flo.LinkEnd, target : Flo.LinkEnd, metadata : Flo.ElementMetadata, props : Map<string, any>) : dia.Link {
createLink(source: Flo.LinkEnd, target: Flo.LinkEnd, metadata: Flo.ElementMetadata, props: Map<string, any>): dia.Link {
return Shapes.Factory.createLink({
renderer: this.renderer,
paper: this.paper,
@@ -681,7 +678,7 @@ export class EditorComponent implements OnInit, OnDestroy {
});
}
handleDropFromPalette(event : Flo.DnDEvent) {
handleDropFromPalette(event: Flo.DnDEvent) {
let cellview = event.view;
let evt = event.event;
if (this.paper.el === evt.target || $.contains(this.paper.el, evt.target)) {
@@ -707,7 +704,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
autosizePaper() : void {
autosizePaper(): void {
let parent = $('#paper-container', this.element.nativeElement);
this.paper.fitToContent({
padding: this.paperPadding,
@@ -716,7 +713,7 @@ export class EditorComponent implements OnInit, OnDestroy {
});
}
fitToPage() : void {
fitToPage(): void {
let parent = $('#paper-container', this.element.nativeElement);
let minScale = this.minZoom / 100;
let maxScale = 2;
@@ -736,11 +733,11 @@ export class EditorComponent implements OnInit, OnDestroy {
this.autosizePaper();
}
get zoomPercent() : number {
get zoomPercent(): number {
return Math.round(joint.V(this.paper.viewport).scale().sx * 100);
}
set zoomPercent(percent : number) {
set zoomPercent(percent: number) {
if (!isNaN(percent)) {
if (percent < this.minZoom) {
percent = this.minZoom;
@@ -751,15 +748,15 @@ export class EditorComponent implements OnInit, OnDestroy {
percent = 0.00001;
}
}
this.paper.scale(percent/100, percent/100);
this.paper.scale(percent / 100, percent / 100);
}
}
get gridSize() : number {
get gridSize(): number {
return this._gridSize;
}
set gridSize(size : number) {
set gridSize(size: number) {
if (!isNaN(size) && size >= 1) {
this._gridSize = size;
if (this.paper) {
@@ -768,7 +765,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
validateContent() : Promise<any> {
validateContent(): Promise<any> {
return new Promise(resolve => {
if (this.editor && this.editor.validate) {
return this.editor
@@ -789,7 +786,7 @@ export class EditorComponent implements OnInit, OnDestroy {
markElement(cell: dia.Cell, markers: Array<Flo.Marker>) {
let errorMessages = markers.map(m => m.message);
let errorCell = cell.getEmbeddedCells().find((e : dia.Cell) => e.attr('./kind') === Constants.ERROR_DECORATION_KIND);
let errorCell = cell.getEmbeddedCells().find((e: dia.Cell) => e.attr('./kind') === Constants.ERROR_DECORATION_KIND);
if (errorCell) {
if (errorMessages.length === 0) {
errorCell.remove();
@@ -805,7 +802,7 @@ export class EditorComponent implements OnInit, OnDestroy {
kind: Constants.ERROR_DECORATION_KIND,
messages: errorMessages
});
let pt : dia.Point;
let pt: dia.Point;
const view = this.paper.findViewByModel(error);
if (cell instanceof joint.dia.Element) {
pt = (<dia.Element> cell).getBBox().topRight().offset(-error.get('size').width, 0);
@@ -818,21 +815,21 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
doLayout() : Promise<void> {
doLayout(): Promise<void> {
if (this.renderer && this.renderer.layout) {
return this.renderer.layout(this.paper);
}
}
@Input()
set dsl(dslText : string) {
set dsl(dslText: string) {
if (this._dslText !== dslText) {
this._dslText = dslText;
this.textToGraphEventEmitter.emit();
}
}
get dsl() : string {
get dsl(): string {
return this._dslText;
}
@@ -840,8 +837,8 @@ export class EditorComponent implements OnInit, OnDestroy {
* Ask the server to parse the supplied text into a JSON graph of nodes and links,
* then update the view based on that new information.
*/
updateGraphRepresentation() : Promise<any> {
console.debug(`Updating graph to represent '${this._dslText}'`);
updateGraphRepresentation(): Promise<any> {
console.log(`Updating graph to represent '${this._dslText}'`);
if (this.metamodel && this.metamodel.textToGraph) {
return this.metamodel.textToGraph(this.editorContext, this._dslText).then(() => {
this.textToGraphConversionCompleted.next();
@@ -853,10 +850,10 @@ export class EditorComponent implements OnInit, OnDestroy {
}
}
updateTextRepresentation() : Promise<any> {
updateTextRepresentation(): Promise<any> {
if (this.metamodel && this.metamodel.graphToText) {
return this.metamodel.graphToText(this.editorContext).then(text => {
if (this._dslText != text) {
if (this._dslText !== text) {
this._dslText = text;
this.dslChange.emit(text);
}
@@ -911,12 +908,12 @@ export class EditorComponent implements OnInit, OnDestroy {
this.graph.set('type', Constants.CANVAS_CONTEXT);
}
handleNodeCreation(node : dia.Element) {
handleNodeCreation(node: dia.Element) {
node.on('change:size', this._resizeHandler);
node.on('change:position', this._resizeHandler);
if (node.attr('metadata')) {
node.on('change:attrs', (cell : dia.Element, attrs : any, changeData : any) => {
node.on('change:attrs', (cell: dia.Element, attrs: any, changeData: any) => {
let propertyPath = changeData ? changeData.propertyPath : undefined;
if (propertyPath) {
let propAttr = propertyPath.substr(propertyPath.indexOf('/') + 1);
@@ -938,38 +935,38 @@ export class EditorComponent implements OnInit, OnDestroy {
* Forwards a link event occurrence to any handlers in the editor service, if they are defined. Event examples
* are 'change:source', 'change:target'.
*/
handleLinkEvent(event : string, link : dia.Link) {
handleLinkEvent(event: string, link: dia.Link) {
if (this.renderer && this.renderer.handleLinkEvent) {
this.renderer.handleLinkEvent(this.editorContext, event, link);
}
}
handleLinkCreation(link : dia.Link) {
handleLinkCreation(link: dia.Link) {
this.handleLinkEvent('add', link);
link.on('change:source', (link : dia.Link) => {
link.on('change:source', (l: dia.Link) => {
this.autosizePaper();
let newSourceId = link.get('source').id;
let oldSourceId = link.previous('source').id;
let newSourceId = l.get('source').id;
let oldSourceId = l.previous('source').id;
if (newSourceId !== oldSourceId) {
this.performGraphToTextSyncing();
}
this.handleLinkEvent('change:source', link);
this.handleLinkEvent('change:source', l);
});
link.on('change:target', (link : dia.Link) => {
link.on('change:target', (l: dia.Link) => {
this.autosizePaper();
let newTargetId = link.get('target').id;
let oldTargetId = link.previous('target').id;
let newTargetId = l.get('target').id;
let oldTargetId = l.previous('target').id;
if (newTargetId !== oldTargetId) {
this.performGraphToTextSyncing();
}
this.handleLinkEvent('change:target', link);
this.handleLinkEvent('change:target', l);
});
link.on('change:vertices', this._resizeHandler);
link.on('change:attrs', (cell : dia.Link, attrs : any, changeData : any) => {
link.on('change:attrs', (cell: dia.Link, attrs: any, changeData: any) => {
let propertyPath = changeData ? changeData.propertyPath : undefined;
if (propertyPath) {
let propAttr = propertyPath.substr(propertyPath.indexOf('/') + 1);
@@ -994,7 +991,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
initGraphListeners() {
this.graph.on('add', (element : dia.Cell) => {
this.graph.on('add', (element: dia.Cell) => {
if (element instanceof joint.dia.Link) {
this.handleLinkCreation(<dia.Link> element);
} else if (element instanceof joint.dia.Element) {
@@ -1006,7 +1003,7 @@ export class EditorComponent implements OnInit, OnDestroy {
this.autosizePaper();
});
this.graph.on('remove', (element : dia.Cell) => {
this.graph.on('remove', (element: dia.Cell) => {
if (element instanceof joint.dia.Link) {
this.handleLinkEvent('remove', <dia.Link> element);
}
@@ -1022,7 +1019,7 @@ export class EditorComponent implements OnInit, OnDestroy {
});
// Set if link is fan-routed. Should be called before routing call
this.graph.on('change:vertices', (link : dia.Link, changed : any, opt : any) => {
this.graph.on('change:vertices', (link: dia.Link, changed: any, opt: any) => {
if (opt.fanRouted) {
link.set('fanRouted', true);
} else {
@@ -1035,7 +1032,7 @@ export class EditorComponent implements OnInit, OnDestroy {
initPaperListeners() {
// http://stackoverflow.com/questions/20463533/how-to-add-an-onclick-event-to-a-joint-js-element
this.paper.on('cell:pointerclick', (cellView : dia.CellView) => {
this.paper.on('cell:pointerclick', (cellView: dia.CellView) => {
if (!this.readOnlyCanvas) {
this.selection = cellView;
}
@@ -1054,7 +1051,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}
});
this.paper.on('dragging-node-over-canvas', (dndEvent : Flo.DnDEvent) => {
this.paper.on('dragging-node-over-canvas', (dndEvent: Flo.DnDEvent) => {
console.log(`Canvas DnD type = ${dndEvent.type}`);
let location = this.paper.snapToGrid({x: dndEvent.event.clientX, y: dndEvent.event.clientY});
switch (dndEvent.type) {
@@ -1075,9 +1072,9 @@ export class EditorComponent implements OnInit, OnDestroy {
});
}
initPaper() : void {
initPaper(): void {
let options : dia.Paper.Options = {
let options: dia.Paper.Options = {
el: $('#paper', this.element.nativeElement),
gridSize: this._gridSize,
drawGrid: true,
@@ -1086,10 +1083,10 @@ export class EditorComponent implements OnInit, OnDestroy {
linkView: this.renderer && this.renderer.getLinkView ? this.renderer.getLinkView() : joint.shapes.flo.LinkView,
// Enable link snapping within 25px lookup radius
snapLinks: { radius: 25 }, // http://www.jointjs.com/tutorial/ports
defaultLink: /*this.renderer && this.renderer.createDefaultLink ? this.renderer.createDefaultLink : new joint.shapes.flo.Link*/
(cellView: dia.ElementView, magnet: SVGElement) => {
defaultLink: /*this.renderer && this.renderer.createDefaultLink ? this.renderer.createDefaultLink: new joint.shapes.flo.Link*/
(cellView: dia.CellView, magnet: SVGElement) => {
if (this.renderer && this.renderer.createLink) {
let linkEnd : Flo.LinkEnd = {
let linkEnd: Flo.LinkEnd = {
id: cellView.model.id
};
if (magnet) {
@@ -1109,7 +1106,7 @@ export class EditorComponent implements OnInit, OnDestroy {
},
// decide whether to create a link if the user clicks a magnet
validateMagnet: (cellView : dia.ElementView, magnet : SVGElement) => {
validateMagnet: (cellView: dia.CellView, magnet: SVGElement) => {
if (this.readOnlyCanvas) {
return false;
} else {
@@ -1154,8 +1151,9 @@ export class EditorComponent implements OnInit, OnDestroy {
}
if (this.editor && this.editor.validateLink) {
options.validateConnection = (cellViewS : dia.ElementView, magnetS : SVGElement, cellViewT : dia.ElementView, magnetT : SVGElement, end: 'source' | 'target', linkView : dia.LinkView) =>
this.editor.validateLink(this.editorContext, cellViewS, magnetS, cellViewT, magnetT, end === 'source', linkView);
const self = this;
options.validateConnection = (cellViewS: dia.CellView, magnetS: SVGElement, cellViewT: dia.CellView, magnetT: SVGElement, end: 'source' | 'target', linkView: dia.LinkView) =>
self!.editor!.validateLink(this.editorContext, cellViewS, magnetS, cellViewT, magnetT, end === 'source', linkView);
}
// The paper is what will represent the graph on the screen

View File

@@ -1,12 +1,12 @@
export { FloModule } from './src/module';
export { Palette } from './src/palette/palette.component';
export { EditorComponent } from './src/editor/editor.component';
export { DslEditorComponent } from './src/dsl-editor/dsl-editor.component';
export { CodeEditorComponent } from './src/code-editor/code-editor.component';
export { PropertiesGroupComponent } from './src/properties/properties.group.component';
export { DynamicFormPropertyComponent } from './src/properties/df.property.component';
export { ResizerDirective } from './src/directives/resizer';
export { FloModule } from './module';
export { Palette } from './palette/palette.component';
export { EditorComponent } from './editor/editor.component';
export { DslEditorComponent } from './dsl-editor/dsl-editor.component';
export { CodeEditorComponent } from './code-editor/code-editor.component';
export { PropertiesGroupComponent } from './properties/properties.group.component';
export { DynamicFormPropertyComponent } from './properties/df.property.component';
export { ResizerDirective } from './directives/resizer';
export * from './src/shared/flo-common';
export * from './src/shared/flo-properties';
export * from './src/shared/shapes';
export * from './shared/flo-common';
export * from './shared/flo-properties';
export * from './shared/shapes';

View File

@@ -3,35 +3,35 @@ import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { dia } from 'jointjs';
import { Flo } from '../shared/flo-common';
import { Shapes, Constants } from './../shared/shapes';
import { Shapes, Constants } from '../shared/shapes';
import { DOCUMENT } from '@angular/platform-browser'
import * as _$ from 'jquery';
const joint : any = Flo.joint;
const $ : any = _$;
const joint: any = Flo.joint;
const $: any = _$;
const DEBOUNCE_TIME : number = 300;
const DEBOUNCE_TIME = 300;
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},
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 },
'rect': { fill: '#34302d', 'stroke-width': 1, stroke: '#6db33f', 'follow-scale': true, width: 80, height: 40 },
'text': {
text:'',
text: '',
fill: '#eeeeee',
'ref-x': 0.5,
'ref-y': 7,
'x-alignment':'middle',
'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
isOpen: true
}, joint.shapes.basic.Generic.prototype.defaults)
});
@@ -43,23 +43,46 @@ joint.shapes.flo.PaletteGroupHeader = joint.shapes.basic.Generic.extend({
})
export class Palette implements OnInit, OnDestroy, OnChanges {
@Input()
metamodel : Flo.Metamodel;
private _metamodelListener: Flo.MetamodelListener = {
metadataError: (data) => {},
metadataAboutToChange: () => {},
metadataChanged: () => this.rebuildPalette()
};
/**
* The names of any groups in the palette that have been deliberately closed (the arrow clicked on)
*/
private closedGroups: Set<string>;
/**
* Model of the clicked element
*/
private clickedElement: dia.Cell | undefined;
private viewBeingDragged: dia.CellView | undefined;
private initialized = false;
private _paletteSize: number;
private _filterText = '';
private paletteGraph: dia.Graph;
private palette: dia.Paper;
private floaterpaper: dia.Paper;
private filterTextModel = new Subject<string>();
@Input()
renderer : Flo.Renderer;
metamodel: Flo.Metamodel;
@Input()
paletteEntryPadding : dia.Size = {width:12, height:12};
renderer: Flo.Renderer;
@Input()
set paletteSize(size : number) {
console.log('Palette Size : ' + size);
if (this._paletteSize != size) {
this._paletteSize = size;
this.rebuildPalette();
}
}
paletteEntryPadding: dia.Size = {width: 12, height: 12};
@Output()
onPaletteEntryDrop = new EventEmitter<Flo.DnDEvent>();
@@ -70,42 +93,19 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
@Output()
paletteFocus = new EventEmitter<void>();
private _paletteSize : number;
private mouseMoveHanlder = (e: any) => this.handleDrag(e);
private mouseUpHanlder = (e: any) => this.handleMouseUp(e);
private _filterText : string = '';
@Input()
set paletteSize(size: number) {
console.log('Palette Size: ' + size);
if (this._paletteSize !== size) {
this._paletteSize = size;
this.rebuildPalette();
}
}
private paletteGraph : dia.Graph;
private palette : dia.Paper;
private floaterpaper : dia.Paper;
private filterTextModel = new Subject<string>();
private mouseMoveHanlder = (e : any) => this.handleDrag(e);
private mouseUpHanlder = (e : any) => this.handleMouseUp(e);
private _metamodelListener : Flo.MetamodelListener = {
metadataError: (data) => {},
metadataAboutToChange: () => {},
metadataChanged: () => this.rebuildPalette()
};
/**
* The names of any groups in the palette that have been deliberately closed (the arrow clicked on)
*/
private closedGroups : Set<string>;
/**
* Model of the clicked element
*/
private clickedElement : dia.Cell;
private viewBeingDragged : dia.CellView;
private initialized = false;
constructor(private element: ElementRef, @Inject(DOCUMENT) private document : any) {
constructor(private element: ElementRef, @Inject(DOCUMENT) private document: any) {
this.paletteGraph = new joint.dia.Graph();
this.paletteGraph.set('type', Constants.PALETTE_CONTEXT);
this._filterText = '';
@@ -122,7 +122,7 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
// Create the paper for the palette using the specified element view
this.palette = new joint.dia.Paper({
el: element,
gridSize:1,
gridSize: 1,
model: this.paletteGraph,
height: $(this.element.nativeElement.parentNode).height(),
width: $(this.element.nativeElement.parentNode).width(),
@@ -130,13 +130,12 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
interactive: false
});
this.palette.on('cell:pointerup', (cellview : dia.CellView, evt : any) => {
console.debug('pointerup');
this.palette.on('cell:pointerup', (cellview: dia.CellView, evt: any) => {
if (this.viewBeingDragged) {
this.trigger({
type: Flo.DnDEventType.DROP,
view: this.viewBeingDragged,
event : evt
event: evt
});
this.viewBeingDragged = undefined;
}
@@ -146,16 +145,16 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
});
// Toggle the header open/closed on a click
this.palette.on('cell:pointerclick', (cellview : dia.CellView, event : any) => {
this.palette.on('cell:pointerclick', (cellview: dia.CellView, event: any) => {
// 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
let element : dia.Cell = cellview.model;
if (cellview.model.attributes.header) {
const cell: dia.Cell = cellview.model;
if (cell.attributes.header) {
// Toggle the header open/closed
if (element.get('isOpen')) {
this.rotateClosed(element);
if (cell.get('isOpen')) {
this.rotateClosed(cell);
} else {
this.rotateOpen(element);
this.rotateOpen(cell);
}
}
// TODO [palette] ensure other mouse handling events do nothing for headers
@@ -196,25 +195,24 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
this.palette.remove();
}
ngOnChanges(changes : SimpleChanges) {
console.log('Changed!!!');
ngOnChanges(changes: SimpleChanges) {
// if (changes.hasOwnProperty('paletteSize') || changes.hasOwnProperty('filterText')) {
// this.metamodel.load().then(metamodel => this.buildPalette(metamodel));
// }
}
private createPaletteGroup(title : string, isOpen : boolean) : dia.Element {
let newGroupHeader = new joint.shapes.flo.PaletteGroupHeader({attrs:{text:{text:title}}});
newGroupHeader.set('header',title);
private createPaletteGroup(title: string, isOpen: boolean): dia.Element {
let 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);
newGroupHeader.attr({'path': {'transform': 'rotate(-90,15,13)'}});
newGroupHeader.set('isOpen', false);
}
this.paletteGraph.addCell(newGroupHeader);
return newGroupHeader;
}
private createPaletteEntry(title : string, metadata : Flo.ElementMetadata) {
private createPaletteEntry(title: string, metadata: Flo.ElementMetadata) {
return Shapes.Factory.createNode({
renderer: this.renderer,
paper: this.palette,
@@ -222,8 +220,8 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
});
}
private buildPalette(metamodel : Map<string, Map<string, Flo.ElementMetadata>>) {
let startTime : number = new Date().getTime();
private buildPalette(metamodel: Map<string, Map<string, Flo.ElementMetadata>>) {
let startTime: number = new Date().getTime();
this.paletteReady.emit(false);
this.paletteGraph.clear();
@@ -233,60 +231,61 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
filterText = filterText.toLowerCase();
}
let paletteNodes : Array<dia.Element> = [];
let groupAdded : Set<string> = new Set<string>();
let paletteNodes: Array<dia.Element> = [];
let groupAdded: Set<string> = new Set<string>();
let parentWidth : number = this._paletteSize;
console.log(`Parent Width : ${parentWidth}`);
let parentWidth: number = this._paletteSize;
console.log(`Parent Width: ${parentWidth}`);
// The field closedGroups tells us which should not be shown
// Work out the list of active groups/nodes based on the filter text
this.metamodel.groups().forEach(group => {
if (metamodel.has(group)) {
Array.from(metamodel.get(group).keys()).sort().forEach(name => {
let node : Flo.ElementMetadata = metamodel.get(group).get(name);
let nodeActive : boolean = !(node.metadata && node.metadata.noPaletteEntry);
if (nodeActive && filterText) {
nodeActive = false;
if (name.toLowerCase().indexOf(filterText) !== -1) {
nodeActive = true;
if (metamodel && metamodel.has(group)) {
Array.from(metamodel.get(group)!.keys()).sort().forEach(name => {
let node = metamodel.get(group)!.get(name);
if (node) {
let nodeActive: boolean = !(node.metadata && node.metadata.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;
// }
// });
// }
}
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.has(group)) {
let header : dia.Element = this.createPaletteGroup(group, !this.closedGroups.has(group));
header.set('size', {width: parentWidth, height: 30});
paletteNodes.push(header);
groupAdded.add(group);
}
if (!this.closedGroups.has(group)) {
paletteNodes.push(this.createPaletteEntry(name, node));
if (nodeActive) {
if (!groupAdded.has(group)) {
let header: dia.Element = this.createPaletteGroup(group, !this.closedGroups.has(group));
header.set('size', {width: parentWidth, height: 30});
paletteNodes.push(header);
groupAdded.add(group);
}
if (!this.closedGroups.has(group)) {
paletteNodes.push(this.createPaletteEntry(name, node));
}
}
}
});
}
});
let cellWidth : number = 0, cellHeight : number = 0;
let cellWidth = 0, cellHeight = 0;
// Determine the size of the palette entry cell (width and height)
paletteNodes.forEach(pnode => {
if (pnode.attr('metadata/name')) {
let dimension : dia.Size = {
let dimension: dia.Size = {
width: pnode.get('size').width,
height: pnode.get('size').height
};
@@ -304,21 +303,21 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
cellHeight += 2 * this.paletteEntryPadding.height;
// Align palette entries row to be at the center
let startX : number = parentWidth >= cellWidth ? (parentWidth - Math.floor(parentWidth / cellWidth) * cellWidth) / 2 : 0;
let xpos : number = startX;
let ypos : number = 0;
let prevNode : dia.Element;
let startX: number = parentWidth >= cellWidth ? (parentWidth - Math.floor(parentWidth / cellWidth) * cellWidth) / 2 : 0;
let xpos = startX;
let ypos = 0;
let prevNode: dia.Element;
// Layout palette entry nodes
paletteNodes.forEach(pnode => {
let dimension : dia.Size = {
let dimension: dia.Size = {
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});
pnode.set('position', {x: 0, y: ypos});
ypos += dimension.height + 5;
} else {
// Palette entry element
@@ -341,7 +340,7 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
});
this.palette.setDimensions(parentWidth, ypos);
this.paletteReady.emit(true);
console.info('buildPalette took '+(new Date().getTime()-startTime)+'ms');
console.log('buildPalette took ' + (new Date().getTime() - startTime) + 'ms');
}
rebuildPalette() {
@@ -350,26 +349,26 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
}
}
set filterText(text : string) {
set filterText(text: string) {
if (this._filterText !== text) {
this._filterText = text;
this.filterTextModel.next(text);
}
}
get filterText() : string {
get filterText(): string {
return this._filterText;
}
private getPaletteView(view : any) : dia.Element {
let self : Palette = this;
private getPaletteView(view: any): dia.Element {
let self: Palette = this;
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)
self.clickedElement = this.model;
if (self.clickedElement.attr('metadata')) {
if (self.clickedElement && self.clickedElement.attr('metadata')) {
$(self.document).on('mousemove', self.mouseMoveHanlder);
}
},
@@ -414,7 +413,7 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
// $(nodeTooltip).append(nodeDescription);
//
// metadata.get('description').then(function(description) {
// $(nodeDescription).text(description ? description : model.attr('metadata/name'));
// $(nodeDescription).text(description ? description: model.attr('metadata/name'));
// }, function() {
// $(nodeDescription).text(model.attr('metadata/name'));
// });
@@ -460,29 +459,28 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
});
}
private handleMouseUp(event : any) {
private handleMouseUp(event: any) {
$(this.document).off('mousemove', this.mouseMoveHanlder);
}
private trigger(event : Flo.DnDEvent) {
console.debug('EVENT: type=' + event.type + ' element=' + event.view.model.attr('metadata/name') + ' x=' + event.event.pageX + ' y=' + event.event.pageY);
private trigger(event: Flo.DnDEvent) {
this.onPaletteEntryDrop.emit(event);
}
private handleDrag(event : any) {
private handleDrag(event: any) {
// TODO offsetX/Y not on firefox
// console.debug("tracking move: x="+event.pageX+",y="+event.pageY);
// console.log('Element = ' + (this.clickedElement ? this.clickedElement.attr('metadata/name') : 'null'));
// console.log('Element = ' + (this.clickedElement ? this.clickedElement.attr('metadata/name'): 'null'));
if (this.clickedElement && this.clickedElement.attr('metadata')) {
if (!this.viewBeingDragged) {
let dataOfClickedElement : Flo.ElementMetadata = this.clickedElement.attr('metadata');
let dataOfClickedElement: Flo.ElementMetadata = this.clickedElement.attr('metadata');
// custom div if not already built.
$('<div>', {
id: 'palette-floater'
}).appendTo($('body'));
let floatergraph : dia.Graph = new joint.dia.Graph();
let floatergraph: dia.Graph = new joint.dia.Graph();
floatergraph.set('type', Constants.FEEDBACK_CONTEXT);
const parent = $('#palette-floater');
@@ -500,22 +498,22 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
// TODO float thing needs to be bigger otherwise icon label is missing
// Initiative drag and drop - create draggable element
let floaternode : dia.Element = Shapes.Factory.createNode({
"renderer": this.renderer,
let floaternode: dia.Element = Shapes.Factory.createNode({
'renderer': this.renderer,
'paper': this.floaterpaper,
'graph': floatergraph,
'metadata': dataOfClickedElement
});
// Only node view expected
let box : dia.BBox = (<dia.ElementView>this.floaterpaper.findViewByModel(floaternode)).getBBox();
let size : dia.Size = floaternode.get('size');
let box: dia.BBox = (<dia.ElementView>this.floaterpaper.findViewByModel(floaternode)).getBBox();
let size: dia.Size = floaternode.get('size');
// Account for node real size including ports
floaternode.translate(box.width - size.width, box.height - size.height);
this.viewBeingDragged = this.floaterpaper.findViewByModel(floaternode);
$('#palette-floater').offset({left:event.pageX+5,top:event.pageY+5});
$('#palette-floater').offset({left: event.pageX + 5, top: event.pageY + 5});
} else {
$('#palette-floater').offset({left:event.pageX+5,top:event.pageY+5});
$('#palette-floater').offset({left: event.pageX + 5, top: event.pageY + 5});
this.trigger({
type: Flo.DnDEventType.DRAG,
view: this.viewBeingDragged,
@@ -528,31 +526,31 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
/*
* Modify the rotation of the arrow in the header from horizontal(closed) to vertical(open)
*/
private rotateOpen(element : dia.Cell) {
private rotateOpen(element: dia.Cell) {
setTimeout(() => this.doRotateOpen(element, 90));
}
private doRotateOpen(element : dia.Cell, angle : number) {
private doRotateOpen(element: dia.Cell, angle: number) {
angle -= 10;
element.attr({'path':{'transform':'rotate(-'+angle+',15,13)'}});
element.attr({'path': {'transform': 'rotate(-' + angle + ',15,13)'}});
if (angle <= 0) {
element.set('isOpen',true);
element.set('isOpen', true);
this.closedGroups.delete(element.get('header'));
this.rebuildPalette();
} else {
setTimeout(() => this.doRotateOpen(element, angle),10);
setTimeout(() => this.doRotateOpen(element, angle), 10);
}
}
private doRotateClose(element : dia.Cell, angle : number) {
angle +=10;
element.attr({'path':{'transform':'rotate(-'+angle+',15,13)'}});
private doRotateClose(element: dia.Cell, angle: number) {
angle += 10;
element.attr({'path': {'transform': 'rotate(-' + angle + ',15,13)'}});
if (angle >= 90) {
element.set('isOpen',false);
element.set('isOpen', false);
this.closedGroups.add(element.get('header'));
this.rebuildPalette();
} else {
setTimeout(() => this.doRotateClose(element, angle),10);
setTimeout(() => this.doRotateClose(element, angle), 10);
}
}
@@ -561,7 +559,7 @@ export class Palette implements OnInit, OnDestroy, OnChanges {
/*
* Modify the rotation of the arrow in the header from vertical(open) to horizontal(closed)
*/
private rotateClosed(element : dia.Cell) {
private rotateClosed(element: dia.Cell) {
setTimeout(() => this.doRotateClose(element, 0));
}

View File

@@ -10,7 +10,7 @@ import { Properties } from '../shared/flo-properties';
export class DynamicFormPropertyComponent {
@Input()
model : Properties.ControlModel<any>;
model: Properties.ControlModel<any>;
@Input() form: FormGroup;
@@ -20,7 +20,7 @@ export class DynamicFormPropertyComponent {
return Properties.InputType;
}
get control() : AbstractControl {
get control(): AbstractControl {
return this.form.controls[this.model.id];
}

View File

@@ -10,10 +10,10 @@ import { Properties } from '../shared/flo-properties';
export class PropertiesGroupComponent implements OnInit {
@Input()
propertiesGroupModel : Properties.PropertiesGroupModel;
propertiesGroupModel: Properties.PropertiesGroupModel;
@Input()
form : FormGroup;
form: FormGroup;
ngOnInit() {
if (this.propertiesGroupModel.isLoading) {

View File

@@ -0,0 +1,245 @@
import { dia, g } from 'jointjs';
import { Observable } from 'rxjs';
import * as _joint from 'jointjs';
import * as _$ from 'jquery';
const $ = _$;
export namespace Flo {
export const joint: any = _joint;
export enum DnDEventType {
DRAG,
DROP
}
export interface DnDEvent {
type: DnDEventType;
view: dia.CellView;
event: MouseEvent;
}
export interface PropertyMetadata {
readonly id: string;
readonly name: string;
readonly description?: string;
readonly defaultValue?: any;
readonly type?: string;
readonly [propName: string]: any;
}
export interface ExtraMetadata {
readonly titleProperty?: string;
readonly noEditableProps?: boolean;
readonly noPaletteEntry?: boolean;
readonly unselectable?: boolean;
readonly [propName: string]: any;
readonly allowAdditionalProperties?: boolean; //TODO: Verify it is still needed
}
export interface ElementMetadata {
readonly name: string;
readonly group: string;
readonly metadata?: ExtraMetadata;
readonly [propName: string]: any;
description?(): Promise<string>;
get(property: String): Promise<PropertyMetadata>;
properties(): Promise<Map<string, PropertyMetadata>>;
}
export interface ViewerDescriptor {
readonly graph?: dia.Graph;
readonly paper?: dia.Paper;
}
export interface MetamodelListener {
metadataError(data: any): void;
metadataAboutToChange(): void;
metadataChanged(): void;
}
export interface Metamodel {
textToGraph(flo: EditorContext, dsl: string): Promise<any>;
graphToText(flo: EditorContext): Promise<string>;
load(): Promise<Map<string, Map<string, ElementMetadata>>>;
groups(): Array<string>;
refresh?(): Promise<Map<string, Map<string, ElementMetadata>>>;
subscribe?(listener: MetamodelListener): void;
unsubscribe?(listener: MetamodelListener): void;
isValidPropertyValue?(element: dia.Element, property: string, value: any): boolean;
}
export interface CreationParams {
metadata?: ElementMetadata;
props?: Map<string, any>;
}
export interface ElementCreationParams extends CreationParams {
position?: dia.Point;
}
export interface LinkCreationParams extends CreationParams {
source: string;
target: string;
}
export interface EmbeddedChildCreationParams extends CreationParams {
parent: dia.Cell;
position?: dia.Point;
}
export interface DecorationCreationParams extends EmbeddedChildCreationParams {
kind: string;
messages: Array<string>;
}
export interface HandleCreationParams extends EmbeddedChildCreationParams {
kind: string;
}
export interface Renderer {
createNode?(metadata: ElementMetadata, props?: Map<string, any>): dia.Element;
createLink?(source: LinkEnd, target: LinkEnd, metadata?: ElementMetadata, props?: Map<string, any>): dia.Link;
createHandle?(kind: string, parent: dia.Cell): dia.Element;
createDecoration?(kind: string, parent: dia.Cell): dia.Element;
initializeNewNode?(node: dia.Element, viewerDescriptor: ViewerDescriptor): void;
initializeNewLink?(link: dia.Link, viewerDescriptor: ViewerDescriptor): void;
initializeNewHandle?(handle: dia.Element, viewerDescriptor: ViewerDescriptor): void;
initializeNewDecoration?(decoration: dia.Element, viewerDescriptor: ViewerDescriptor): void;
getNodeView?(): dia.ElementView;
getLinkView?(): dia.LinkView;
layout?(paper: dia.Paper): Promise<any>;
handleLinkEvent?(context: EditorContext, event: string, link: dia.Link): void;
isSemanticProperty?(propertyPath: string, element: dia.Cell): boolean;
refreshVisuals?(cell: dia.Cell, propertyPath: string, paper: dia.Paper): void;
getLinkAnchorPoint?(linkView: dia.LinkView, view: dia.ElementView, port: SVGElement, reference: dia.Point): dia.Point;
}
export interface EditorContext {
readonly textToGraphConversionObservable: Observable<void>;
readonly graphToTextConversionObservable: Observable<void>;
readonly paletteReady: Observable<boolean>;
zoomPercent: number;
gridSize: number;
readOnlyCanvas: boolean;
selection: dia.CellView;
graphToTextSync: boolean;
noPalette: boolean;
setDsl(dsl: string): void;
updateGraph(): Promise<any>;
updateText(): Promise<any>;
performLayout(): Promise<void>;
clearGraph(): void;
getGraph(): dia.Graph;
getPaper(): dia.Paper;
getMinZoom(): number;
getMaxZoom(): number;
getZoomStep(): number;
fitToPage(): void;
createNode(metadata: ElementMetadata, props?: Map<string, any>, position?: dia.Point): dia.Element;
createLink(source: LinkEnd, target: LinkEnd, metadata?: ElementMetadata, props?: Map<string, any>): dia.Link;
deleteSelectedNode(): void;
[propName: string]: any;
}
export interface LinkEndDescriptor {
view: dia.CellView;
cssClassSelector?: string;
}
export interface DnDDescriptor {
sourceComponent?: string;
range?: number;
source?: LinkEndDescriptor;
target?: LinkEndDescriptor;
}
export interface LinkEnd {
id: string | number;
selector?: string;
port?: string;
}
export enum Severity {
Error,
Warning
}
export interface Marker {
severity: Severity;
message: string;
range?: Range;
}
export interface Position {
ch: number;
line: number;
}
export interface Range {
start: Position;
end: Position;
}
export interface Editor {
interactive?: ((cellView: dia.CellView, event: string) => boolean) | boolean | dia.CellView.InteractivityOptions;
allowLinkVertexEdit?: boolean;
highlighting?: any;
createHandles?(context: EditorContext, createHandle: (owner: dia.CellView, kind: string, action: () => void, location: dia.Point) => void, owner: dia.CellView): void;
validatePort?(context: EditorContext, view: dia.CellView, magnet: SVGElement): boolean;
validateLink?(context: EditorContext, cellViewS: dia.CellView, portS: SVGElement, cellViewT: dia.CellView, portT: SVGElement, isSource: boolean, linkView: dia.LinkView): boolean;
calculateDragDescriptor?(context: EditorContext, draggedView: dia.CellView, targetUnderMouse: dia.CellView, coordinate: g.Point, sourceComponent: string): DnDDescriptor;
handleNodeDropping?(context: EditorContext, dragDescriptor: DnDDescriptor): void;
showDragFeedback?(context: EditorContext, dragDescriptor: DnDDescriptor): void;
hideDragFeedback?(context: EditorContext, dragDescriptor: DnDDescriptor): void;
validate?(graph: dia.Graph, dsl: string, flo: EditorContext): Promise<Map<string | number, Array<Marker>>>;
preDelete?(context: EditorContext, deletedElement: dia.Cell): void;
setDefaultContent?(editorContext: EditorContext, data: Map<string, Map<string, ElementMetadata>>): void;
}
export function findMagnetByClass(view: dia.CellView, className: string): SVGElement | undefined {
if (className && className.startsWith('.')) {
className = className.substr(1);
}
const element = view.$('[magnet]').toArray().find((magnet: any) => magnet.getAttribute('class').split(/\s+/).indexOf(className) >= 0);
if (element) {
return view.findMagnet($(element));
}
}
export function findMagnetByPort(view: dia.CellView, port: string): SVGElement | undefined {
const element = view.$('[magnet]').toArray().find((magnet: HTMLElement) => magnet.getAttribute('port') === port);
if (element) {
return view.findMagnet($(element));
}
}
/**
* Return the metadata for a particular palette entry in a particular group.
* @param name - name of the palette entry
* @param group - group in which the palette entry should exist (e.g. sinks)
* @return
*/
export function getMetadata(metamodel: Map<string, Map<string, ElementMetadata>>, name: string, group: string): ElementMetadata | undefined {
const groupObj = metamodel && group ? metamodel.get(group) : undefined;
if (name && groupObj && groupObj.get(name)) {
return metamodel!.get(group)!.get(name);
} else {
return {
name: name,
group: group,
unresolved: true,
get: (property: string) => new Promise(resolve => resolve()),
properties: () => Promise.resolve(new Map<string, PropertyMetadata>())
};
}
}
}

View File

@@ -18,14 +18,14 @@ export namespace Properties {
}
export interface Property {
readonly id : string;
readonly name : string;
readonly type : string;
readonly description? : string;
readonly defaultValue? : any;
value? : any;
readonly id: string;
readonly name: string;
readonly type?: string;
readonly description?: string;
readonly defaultValue?: any;
value?: any;
readonly valueOptions?: any[]
readonly [propName : string] : any;
readonly [propName: string]: any;
}
export interface SelectOption {
@@ -34,25 +34,25 @@ export namespace Properties {
}
export interface ErrorData {
id : string;
message : string;
id: string;
message: string;
}
export interface Validation {
validator?: ValidatorFn|ValidatorFn[]|null,
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null,
errorData? : Array<ErrorData>;
errorData?: Array<ErrorData>;
}
export interface ControlModel<T> {
readonly type : InputType;
readonly id : string;
value : T;
readonly defaultValue : T;
readonly name? : string;
readonly description? : string;
readonly property : Property
readonly validation? : Validation;
readonly type: InputType;
readonly id: string;
value: T;
readonly defaultValue: T;
readonly name?: string;
readonly description?: string;
readonly property: Property
readonly validation?: Validation;
}
export interface CodeControlModel<T> extends ControlModel<T> {
@@ -61,7 +61,7 @@ export namespace Properties {
export class GenericControlModel<T> implements ControlModel<T> {
constructor(private _property : Property, public type : InputType, public validation? : Validation) {}
constructor(private _property: Property, public type: InputType, public validation?: Validation) {}
get id() {
return this.property.id;
@@ -79,15 +79,15 @@ export namespace Properties {
return this.property.defaultValue;
}
get value() : T {
get value(): T {
return this.getValue();
}
set value(value : T) {
set value(value: T) {
this.setValue(value);
}
get property() : Property {
get property(): Property {
return this._property;
}
@@ -103,7 +103,7 @@ export namespace Properties {
export class CheckBoxControlModel extends GenericControlModel<boolean> {
constructor(_property : Property, validation? : Validation) {
constructor(_property: Property, validation?: Validation) {
super(_property, InputType.CHECKBOX, validation);
}
@@ -119,7 +119,9 @@ export namespace Properties {
export abstract class AbstractCodeControlModel extends GenericControlModel<string> implements CodeControlModel<string> {
constructor(_property : Property, private encode?: (s: string) => string, private decode?: (s: string) => string, validation? : Validation) {
abstract language: string;
constructor(_property: Property, private encode?: (s: string) => string, private decode?: (s: string) => string, validation?: Validation) {
super(_property, InputType.CODE, validation);
}
@@ -140,14 +142,12 @@ export namespace Properties {
}
}
abstract language: string;
}
export class GenericCodeControlModel extends AbstractCodeControlModel {
constructor(_property : Property, public language: string, encode?: (s: string) => string, decode?: (s: string) => string, validation? : Validation) {
constructor(_property: Property, public language: string, encode?: (s: string) => string, decode?: (s: string) => string, validation?: Validation) {
super(_property, encode, decode, validation);
}
@@ -159,7 +159,7 @@ export namespace Properties {
constructor(_property: Properties.Property, private _languagePropertyName: string,
private _groupModel: Properties.PropertiesGroupModel,
encode?: (s: string) => string, decode?: (s: string) => string, validation? : Validation) {
encode?: (s: string) => string, decode?: (s: string) => string, validation?: Validation) {
super(_property, encode, decode, validation);
}
@@ -170,7 +170,9 @@ export namespace Properties {
get languageControlModel(): Properties.ControlModel<any> {
if (!this._langControlModel) {
this._langControlModel = this._groupModel.getControlsModels().find(c => c.id === this._languagePropertyName);
// Cast to Properties.ControlModel<any> from Properties.ControlModel<any> | undefined
// Should not be undefined!
this._langControlModel = <Properties.ControlModel<any>> this._groupModel.getControlsModels().find(c => c.id === this._languagePropertyName);
}
return this._langControlModel;
}
@@ -179,22 +181,22 @@ export namespace Properties {
export class GenericListControlModel extends GenericControlModel<string> {
constructor(property : Property, validation? : Validation) {
constructor(property: Property, validation?: Validation) {
super(property, InputType.TEXT, validation);
}
get value() : string {
get value(): string {
return this.property.value ? this.property.value.join(', ') : '';
}
set value(value : string) {
set value(value: string) {
this.property.value = value && value.trim() ? value.split(/\s*,\s*/) : undefined;
}
}
export class SelectControlModel extends GenericControlModel<any> {
constructor(_property : Property, type : InputType, public options : Array<SelectOption>) {
constructor(_property: Property, type: InputType, public options: Array<SelectOption>) {
super(_property, type);
if (_property.defaultValue === undefined) {
options.unshift({
@@ -212,18 +214,18 @@ export namespace Properties {
export class DefaultCellPropertiesSource implements PropertiesSource {
protected cell : dia.Cell;
protected cell: dia.Cell;
constructor(cell : dia.Cell) {
constructor(cell: dia.Cell) {
this.cell = cell;
}
getProperties() : Promise<Array<Property>> {
let metadata : Flo.ElementMetadata = this.cell.attr('metadata');
getProperties(): Promise<Array<Property>> {
let metadata: Flo.ElementMetadata = this.cell.attr('metadata');
return Promise.resolve(metadata.properties().then(propsMetadata => Array.from(propsMetadata.values()).map(m => this.createProperty(m))));
}
protected createProperty(metadata : Flo.PropertyMetadata) : Property {
protected createProperty(metadata: Flo.PropertyMetadata): Property {
return {
id: metadata.id,
name: metadata.name,
@@ -262,13 +264,13 @@ export namespace Properties {
protected propertiesSource: PropertiesSource;
protected controlModels : Array<ControlModel<any>>;
protected controlModels: Array<ControlModel<any>>;
protected loading : boolean = true;
protected loading = true;
protected _loadedSubject : Subject<boolean>;
protected _loadedSubject: Subject<boolean>;
constructor(propertiesSource : PropertiesSource) {
constructor(propertiesSource: PropertiesSource) {
this.propertiesSource = propertiesSource;
}
@@ -283,7 +285,7 @@ export namespace Properties {
});
}
get isLoading() : boolean {
get isLoading(): boolean {
return this.loading;
}
@@ -295,11 +297,11 @@ export namespace Properties {
return this.controlModels;
}
protected createControlModel(property : Property) : ControlModel<any> {
protected createControlModel(property: Property): ControlModel<any> {
return new GenericControlModel(property, InputType.TEXT);
}
public applyChanges() : void {
public applyChanges(): void {
if (this.loading) {
return;
}
@@ -313,7 +315,7 @@ export namespace Properties {
export namespace Validators {
export function uniqueResource(service : (value : any) => Observable<any>, debounce : number): AsyncValidatorFn {
export function uniqueResource(service: (value: any) => Observable<any>, debounce: number): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors> => {
return new Observable(obs => {
if (control.valueChanges && control.value) {
@@ -323,20 +325,20 @@ export namespace Properties {
obs.next({uniqueResource: true});
obs.complete();
}, () => {
obs.next(null);
obs.next(undefined);
obs.complete();
})
} else {
obs.next(null);
obs.next(undefined);
obs.complete();
}
});
}
}
export function noneOf(excluded : Array<any>) : ValidatorFn {
return (control: AbstractControl) : {[key: string]: any} => {
return excluded.find(e => e === control.value) ? {'noneOf': {value: control.value}} : null;
export function noneOf(excluded: Array<any>): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
return excluded.find(e => e === control.value) ? {'noneOf': {value: control.value}} : {};
};
}

View File

@@ -3,18 +3,18 @@ import { Flo } from './flo-common';
import EditorDescriptor = Flo.ViewerDescriptor;
import * as _ from 'lodash';
import * as _$ from 'jquery';
const joint : any = Flo.joint;
const $ : any = _$;
const joint: any = Flo.joint;
const $: any = _$;
const isChrome : boolean = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
const isFF : boolean = navigator.userAgent.indexOf("Firefox") > 0;
const isChrome: boolean = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
const isFF: boolean = navigator.userAgent.indexOf('Firefox') > 0;
const IMAGE_W : number = 120;
const IMAGE_H : number = 35;
const IMAGE_W = 120;
const IMAGE_H = 35;
const ERROR_MARKER_SIZE : dia.Size = {width: 16, height: 16};
const ERROR_MARKER_SIZE: dia.Size = {width: 16, height: 16};
const HANDLE_SIZE : dia.Size = {width: 10, height: 10};
const HANDLE_SIZE: dia.Size = {width: 10, height: 10};
joint.shapes.flo = {};
@@ -23,61 +23,67 @@ joint.shapes.flo.LINK_TYPE = 'sinspctr.Link';
joint.shapes.flo.DECORATION_TYPE = 'decoration';
joint.shapes.flo.HANDLE_TYPE = 'handle';
const HANDLE_ICON_MAP : Map<string, string>= new Map<string, string>();
const HANDLE_ICON_MAP: Map<string, string> = new Map<string, string>();
const REMOVE = 'remove';
HANDLE_ICON_MAP.set(REMOVE, 'icons/delete.svg');
const DECORATION_ICON_MAP : Map<string, string>= new Map<string, string>();
const DECORATION_ICON_MAP: Map<string, string> = new Map<string, string>();
const ERROR = 'error';
DECORATION_ICON_MAP.set(ERROR, 'icons/error.svg');
joint.util.filter.redscale = (args : Shapes.FilterOptions) => {
joint.util.filter.redscale = (args: Shapes.FilterOptions) => {
let amount = _.isFinite(args.amount) ? args.amount : 1;
let amount = Number.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>', <any>{
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
});
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>',
<any>{
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 = (args : Shapes.FilterOptions) => {
joint.util.filter.orangescale = (args: Shapes.FilterOptions) => {
let amount = _.isFinite(args.amount) ? args.amount : 1;
let amount = Number.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>', <any>{
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
});
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>',
<any>{
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>'+
'<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="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({
@@ -93,7 +99,7 @@ joint.shapes.flo.Node = joint.shapes.basic.Generic.extend({
height: IMAGE_H,
rx: 3,
ry: 3,
'fill-opacity':0, // see through
'fill-opacity': 0, // see through
stroke: '#eeeeee',
'stroke-width': 0
},
@@ -103,7 +109,7 @@ joint.shapes.flo.Node = joint.shapes.basic.Generic.extend({
height: IMAGE_H,
rx: 3,
ry: 3,
//'fill-opacity':0, // see through
//'fill-opacity': 0, // see through
stroke: '#6db33f',
fill: '#eeeeee',
'stroke-width': 1
@@ -113,7 +119,7 @@ joint.shapes.flo.Node = joint.shapes.basic.Generic.extend({
height: 8, width: 8,
magnet: true,
fill: '#eeeeee',
transform: 'translate(' + -4 + ',' + ((IMAGE_H/2)-4) + ')',
transform: 'translate(' + -4 + ',' + ((IMAGE_H / 2 ) - 4) + ')',
stroke: '#34302d',
'stroke-width': 1
},
@@ -122,7 +128,7 @@ joint.shapes.flo.Node = joint.shapes.basic.Generic.extend({
height: 8, width: 8,
magnet: true,
fill: '#eeeeee',
transform: 'translate(' + (IMAGE_W-4) + ',' + ((IMAGE_H/2)-4) + ')',
transform: 'translate(' + (IMAGE_W - 4) + ',' + ((IMAGE_H / 2) - 4) + ')',
stroke: '#34302d',
'stroke-width': 1
},
@@ -169,9 +175,9 @@ joint.shapes.flo.Link = joint.dia.Link.extend({
// '.': { 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': { 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' }
@@ -227,7 +233,9 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
dragMagnetStart: function(evt: any, x: number, y: number) {
if (!this.can('addLinkFromMagnet')) return;
if (!this.can('addLinkFromMagnet')) {
return;
}
this.model.startBatch('add-link');
@@ -271,7 +279,7 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
this.paper.delegateDragEvents(this, evt.data);
},
// pointerdown: function(evt : any, x : number, y : number) {
// pointerdown: function(evt: any, x: number, y: number) {
// // this.canShowTooltip = false;
// // this.hideTooltip();
// this.beingDragged = false;
@@ -317,7 +325,7 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
// joint.dia.CellView.prototype.pointerdown.apply(this, arguments);
// }
// },
drag: function(evt : MouseEvent, x : number, y : number) {
drag: function(evt: MouseEvent, x: number, y: number) {
let interactive = _.isFunction(this.options.interactive) ? this.options.interactive(this, 'pointermove') :
this.options.interactive;
if (interactive !== false) {
@@ -325,13 +333,13 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
}
joint.dia.ElementView.prototype.drag.apply(this, arguments);
},
dragEnd: function(evt : MouseEvent, x : number, y : number) { // jshint ignore:line
dragEnd: function(evt: MouseEvent, x: number, y: number) { // jshint ignore:line
this.paper.trigger('dragging-node-over-canvas', {type: Flo.DnDEventType.DROP, view: this, event: evt});
joint.dia.ElementView.prototype.dragEnd.apply(this, arguments);
},
// events: {
// // Tooltips on the elements in the graph
// 'mouseenter': function(evt : MouseEvent) {
// 'mouseenter': function(evt: MouseEvent) {
// if (this.canShowTooltip) {
// this.showTooltip(evt.pageX, evt.pageY);
// }
@@ -350,21 +358,21 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
// if (isChrome || isFF) {
// this.model.set('z', this._tempZorder);
// var z = this._tempZorder;
// this.model.getEmbeddedCells({breadthFirst: true}).forEach(function(cell : dia.Cell) {
// this.model.getEmbeddedCells({breadthFirst: true}).forEach(function(cell: dia.Cell) {
// cell.set('z', ++z);
// });
// }
// }
// },
// 'mousemove': function(evt : MouseEvent) {
// 'mousemove': function(evt: MouseEvent) {
// this.moveTooltip(evt.pageX, evt.pageY);
// }
// },
// showTooltip: function(x : number, y : number) {
// showTooltip: function(x: number, y: number) {
// var mousex = x + 10;
// var mousey = y + 10;
//
// var nodeTooltip : HTMLElement;
// var nodeTooltip: HTMLElement;
// if (this.model instanceof joint.dia.Element && this.model.attr('metadata')) {
// nodeTooltip = document.createElement('div');
// $(nodeTooltip).addClass('node-tooltip');
@@ -391,9 +399,9 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
// }
// }
//
// model.attr('metadata').get('description').then(function(description : string) {
// model.attr('metadata').get('description').then(function(description: string) {
// $(nodeDescription).text(description);
// }, function(error : any) {
// }, function(error: any) {
// if (error) {
// console.error(error);
// }
@@ -401,7 +409,7 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
//
// // defaultValue
// if (!model.attr('metadata/metadata/hide-tooltip-options')) {
// model.attr('metadata').get('properties').then(function(metaProps : any) {
// model.attr('metadata').get('properties').then(function(metaProps: any) {
// var props = model.attr('props'); // array of {'name':,'value':}
// if (metaProps && props) {
// Object.keys(props).sort().forEach(function(propertyName) {
@@ -427,7 +435,7 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
// // $(nodeTooltip).append(optionRow);
// });
// }
// }, function(error : any) {
// }, function(error: any) {
// if (error) {
// console.error(error);
// }
@@ -458,7 +466,7 @@ joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
// $('.node-tooltip').remove();
// $('.error-tooltip').remove();
// },
// moveTooltip: function(x : number, y : number) {
// moveTooltip: function(x: number, y: number) {
// $('.node-tooltip')
// .css({ top: y + 10, left: x + 10 });
// $('.error-tooltip')
@@ -500,37 +508,37 @@ export namespace Constants {
export namespace Shapes {
export interface CreationParams extends Flo.CreationParams {
renderer? : Flo.Renderer;
paper? : dia.Paper;
graph? : dia.Graph;
renderer?: Flo.Renderer;
paper?: dia.Paper;
graph?: dia.Graph;
}
export interface ElementCreationParams extends CreationParams {
position? : dia.Point;
position?: dia.Point;
}
export interface LinkCreationParams extends CreationParams {
source : Flo.LinkEnd;
target : Flo.LinkEnd;
source: Flo.LinkEnd;
target: Flo.LinkEnd;
}
export interface EmbeddedChildCreationParams extends CreationParams {
parent : dia.Cell;
position? : dia.Point;
parent: dia.Cell;
position?: dia.Point;
}
export interface DecorationCreationParams extends EmbeddedChildCreationParams {
kind : string;
messages : Array<string>;
kind: string;
messages: Array<string>;
}
export interface HandleCreationParams extends EmbeddedChildCreationParams {
kind : string;
kind: string;
}
export interface FilterOptions {
amount : number;
[propName : string] : any;
amount: number;
[propName: string]: any;
}
@@ -539,15 +547,15 @@ export namespace Shapes {
/**
* Create a JointJS node that embeds extra metadata (properties).
*/
static createNode(params : ElementCreationParams) : dia.Element {
let renderer : Flo.Renderer = params.renderer;
let paper : dia.Paper = params.paper;
let metadata : Flo.ElementMetadata = params.metadata;
let position : dia.Point = params.position;
let props : Map<string, any> = params.props;
let graph : dia.Graph = params.graph || (params.paper ? params.paper.model : undefined);
static createNode(params: ElementCreationParams): dia.Element {
let renderer = params.renderer;
let paper = params.paper;
let metadata = params.metadata;
let position = params.position;
let props = params.props;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
let node : dia.Element;
let node: dia.Element;
if (!position) {
position = {x: 0, y: 0};
}
@@ -556,21 +564,23 @@ export namespace Shapes {
node = renderer.createNode(metadata, props);
} else {
node = new joint.shapes.flo.Node();
node.attr('.label/text', metadata.name);
if (metadata) {
node.attr('.label/text', metadata.name);
}
}
node.set('type', joint.shapes.flo.NODE_TYPE);
if (position) {
node.set('position', position);
}
if (props) {
Array.from(props.keys()).forEach(key => node.attr(`props/${key}`, props.get(key)));
Array.from(props.keys()).forEach(key => node.attr(`props/${key}`, props!.get(key)));
}
node.attr('metadata', metadata);
if (graph) {
graph.addCell(node);
}
if (renderer && _.isFunction(renderer.initializeNewNode)) {
let descriptor : Flo.ViewerDescriptor = {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};
@@ -579,16 +589,16 @@ export namespace Shapes {
return node;
}
static createLink(params : LinkCreationParams) : dia.Link {
let renderer : Flo.Renderer = params.renderer;
let paper : dia.Paper = params.paper;
let metadata : Flo.ElementMetadata = params.metadata;
static createLink(params: LinkCreationParams): dia.Link {
let renderer = params.renderer;
let paper = params.paper;
let metadata = params.metadata;
let source = params.source;
let target = params.target;
let props : Map<string, any> = params.props;
let graph : dia.Graph= params.graph || (params.paper ? params.paper.model : undefined);
let props = params.props;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
let link : dia.Link;
let link: dia.Link;
if (renderer && _.isFunction(renderer.createLink)) {
link = renderer.createLink(source, target, metadata, props);
} else {
@@ -605,13 +615,13 @@ export namespace Shapes {
link.attr('metadata', metadata);
}
if (props) {
Array.from(props.keys()).forEach(key => link.attr(`props/${key}`, props.get(key)));
Array.from(props.keys()).forEach(key => link.attr(`props/${key}`, props!.get(key)));
}
if (graph) {
graph.addCell(link);
}
if (renderer && _.isFunction(renderer.initializeNewLink)) {
let descriptor : Flo.ViewerDescriptor = {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};
@@ -622,25 +632,25 @@ export namespace Shapes {
return link;
}
static createDecoration(params : DecorationCreationParams) : dia.Element {
let renderer : Flo.Renderer = params.renderer;
let paper : dia.Paper = params.paper;
let parent : dia.Cell = params.parent;
let kind : string = params.kind;
let messages : Array<string> = params.messages;
let location : dia.Point = params.position;
let graph : dia.Graph = params.graph || (params.paper ? params.paper.model : undefined);
static createDecoration(params: DecorationCreationParams): dia.Element {
let renderer = params.renderer;
let paper = params.paper;
let parent = params.parent;
let kind = params.kind;
let messages = params.messages;
let location = params.position;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
if (!location) {
location = {x: 0, y: 0};
}
let decoration : dia.Element;
let decoration: dia.Element;
if (renderer && _.isFunction(renderer.createDecoration)) {
decoration = renderer.createDecoration(kind, parent);
} else {
decoration = new joint.shapes.flo.ErrorDecoration({
attrs: {
image: { 'xlink:href': DECORATION_ICON_MAP[kind] },
image: { 'xlink:href': DECORATION_ICON_MAP.get(kind) },
}
});
}
@@ -656,7 +666,7 @@ export namespace Shapes {
}
parent.embed(decoration);
if (renderer && _.isFunction(renderer.initializeNewDecoration)) {
let descriptor : Flo.ViewerDescriptor = {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};
@@ -665,15 +675,15 @@ export namespace Shapes {
return decoration;
}
static createHandle(params : HandleCreationParams) : dia.Element {
let renderer : Flo.Renderer = params.renderer;
let paper : dia.Paper = params.paper;
let parent : dia.Cell = params.parent;
let kind : string = params.kind;
let location : dia.Point = params.position;
let graph : dia.Graph = params.graph || (params.paper ? params.paper.model : undefined);
static createHandle(params: HandleCreationParams): dia.Element {
let renderer = params.renderer;
let paper = params.paper;
let parent = params.parent;
let kind = params.kind;
let location = params.position;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
let handle : dia.Element;
let handle: dia.Element;
if (!location) {
location = {x: 0, y: 0};
}
@@ -684,7 +694,7 @@ export namespace Shapes {
size: HANDLE_SIZE,
attrs: {
'image': {
'xlink:href': HANDLE_ICON_MAP[kind]
'xlink:href': HANDLE_ICON_MAP.get(kind)
}
}
});
@@ -700,7 +710,7 @@ export namespace Shapes {
}
parent.embed(handle);
if (renderer && _.isFunction(renderer.initializeNewHandle)) {
let descriptor : Flo.ViewerDescriptor = {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};

View File

@@ -1,244 +0,0 @@
import { dia, g } from 'jointjs';
import { Observable } from "rxjs";
import * as _joint from 'jointjs';
import * as _$ from 'jquery';
const $ = _$;
export namespace Flo {
export const joint : any = _joint;
export enum DnDEventType {
DRAG,
DROP
}
export interface DnDEvent {
type : DnDEventType;
view : dia.CellView;
event : MouseEvent;
}
export interface PropertyMetadata {
readonly id : string;
readonly name : string;
readonly description? : string;
readonly defaultValue? : any;
readonly type? : string;
readonly [propName : string] : any;
}
export interface ExtraMetadata {
readonly titleProperty? : string;
readonly noEditableProps? : boolean;
readonly noPaletteEntry? : boolean;
readonly unselectable? : boolean;
readonly [propName : string] : any;
readonly allowAdditionalProperties? : boolean; //TODO: Verify it is still needed
}
export interface ElementMetadata {
readonly name : string;
readonly group : string;
description?() : Promise<string>;
get(property : String) : Promise<PropertyMetadata>;
properties() : Promise<Map<string, PropertyMetadata>>;
readonly metadata? : ExtraMetadata;
readonly [propName : string] : any;
}
export interface ViewerDescriptor {
readonly graph : dia.Graph;
readonly paper? : dia.Paper;
}
export interface MetamodelListener {
metadataError(data : any) : void;
metadataAboutToChange() : void;
metadataChanged() : void;
}
export interface Metamodel {
textToGraph(flo : EditorContext, dsl : string) : Promise<any>;
graphToText(flo : EditorContext) : Promise<string>;
load() : Promise<Map<string, Map<string, ElementMetadata>>>;
groups() : Array<string>;
refresh?() : Promise<Map<string, Map<string, ElementMetadata>>>;
subscribe?(listener : MetamodelListener) : void;
unsubscribe?(listener : MetamodelListener) : void;
isValidPropertyValue?(element : dia.Element, property : string, value : any) : boolean;
}
export interface CreationParams {
metadata? : ElementMetadata;
props? : Map<string, any>;
}
export interface ElementCreationParams extends CreationParams {
position? : dia.Point;
}
export interface LinkCreationParams extends CreationParams {
source : string;
target : string;
}
export interface EmbeddedChildCreationParams extends CreationParams {
parent : dia.Cell;
position? : dia.Point;
}
export interface DecorationCreationParams extends EmbeddedChildCreationParams {
kind : string;
messages : Array<string>;
}
export interface HandleCreationParams extends EmbeddedChildCreationParams {
kind : string;
}
export interface Renderer {
createNode?(metadata : ElementMetadata, props? : Map<string, any>) : dia.Element;
createLink?(source : LinkEnd, target : LinkEnd, metadata? : ElementMetadata, props? : Map<string, any>) : dia.Link;
createHandle?(kind : string, parent : dia.Cell) : dia.Element;
createDecoration?(kind : string, parent : dia.Cell) : dia.Element;
initializeNewNode?(node : dia.Element, viewerDescriptor : ViewerDescriptor) : void;
initializeNewLink?(link : dia.Link, viewerDescriptor : ViewerDescriptor) : void;
initializeNewHandle?(handle : dia.Element, viewerDescriptor : ViewerDescriptor) : void;
initializeNewDecoration?(decoration : dia.Element, viewerDescriptor : ViewerDescriptor) : void;
getNodeView?() : dia.ElementView;
getLinkView?() : dia.LinkView;
layout?(paper : dia.Paper) : Promise<any>;
handleLinkEvent?(context : EditorContext, event : string, link : dia.Link) : void;
isSemanticProperty?(propertyPath : string, element : dia.Cell) : boolean;
refreshVisuals?(cell : dia.Cell, propertyPath : string, paper : dia.Paper) : void;
getLinkAnchorPoint?(linkView : dia.LinkView, view : dia.ElementView, port : SVGElement, reference : dia.Point) : dia.Point;
}
export interface EditorContext {
zoomPercent : number;
gridSize : number;
readOnlyCanvas : boolean;
selection : dia.CellView;
graphToTextSync : boolean;
noPalette : boolean;
setDsl(dsl : string) : void;
updateGraph() : Promise<any>;
updateText() : Promise<any>;
performLayout() : Promise<void>;
clearGraph() : void;
getGraph() : dia.Graph;
getPaper() : dia.Paper;
getMinZoom() : number;
getMaxZoom() : number;
getZoomStep() : number;
fitToPage() : void;
createNode(metadata : ElementMetadata, props? : Map<string, any>, position? : dia.Point) : dia.Element;
createLink(source : LinkEnd, target : LinkEnd, metadata? : ElementMetadata, props? : Map<string, any>) : dia.Link;
deleteSelectedNode() : void;
readonly textToGraphConversionObservable: Observable<void>;
readonly graphToTextConversionObservable: Observable<void>;
readonly paletteReady: Observable<boolean>;
[propName : string] : any;
}
export interface LinkEndDescriptor {
view : dia.CellView;
cssClassSelector? : string;
}
export interface DnDDescriptor {
sourceComponent? : string;
range?: number;
source? : LinkEndDescriptor;
target? : LinkEndDescriptor;
}
export interface LinkEnd {
id : string | number;
selector? : string;
port? : string;
}
export enum Severity {
Error,
Warning
}
export interface Marker {
severity : Severity;
message : string;
range? : Range;
}
export interface Position {
ch: number;
line: number;
}
export interface Range {
start: Position;
end: Position;
}
export interface Editor {
interactive? : ((cellView: dia.CellView, event: string) => boolean) | boolean | dia.CellView.InteractivityOptions;
allowLinkVertexEdit? : boolean;
highlighting? : any;
createHandles?(context : EditorContext, createHandle : (owner : dia.CellView, kind : string, action : () => void, location : dia.Point) => void, owner : dia.CellView) : void;
validatePort?(context : EditorContext, view : dia.ElementView, magnet : SVGElement) : boolean;
validateLink?(context : EditorContext, cellViewS : dia.ElementView, portS : SVGElement, cellViewT : dia.ElementView, portT : SVGElement, isSource : boolean, linkView : dia.LinkView) : boolean;
calculateDragDescriptor?(context : EditorContext, draggedView : dia.CellView, targetUnderMouse : dia.CellView, coordinate : g.Point, sourceComponent : string) : DnDDescriptor;
handleNodeDropping?(context : EditorContext, dragDescriptor : DnDDescriptor) : void;
showDragFeedback?(context : EditorContext, dragDescriptor : DnDDescriptor) : void;
hideDragFeedback?(context : EditorContext, dragDescriptor : DnDDescriptor) : void;
validate?(graph : dia.Graph, dsl: string, flo: EditorContext) : Promise<Map<string | number, Array<Marker>>>;
preDelete?(context : EditorContext, deletedElement : dia.Cell) : void;
setDefaultContent?(editorContext : EditorContext, data : Map<string, Map<string, ElementMetadata>>) : void;
}
export function findMagnetByClass(view : dia.CellView, className : string) : SVGElement {
if (className && className.startsWith('.')) {
className = className.substr(1);
}
const element = view.$('[magnet]').toArray().find((magnet : any) => magnet.getAttribute('class').split(/\s+/).indexOf(className) >= 0);
if (element) {
return view.findMagnet($(element));
}
}
export function findMagnetByPort(view : dia.CellView, port : string) : SVGElement {
const element = view.$('[magnet]').toArray().find((magnet : HTMLElement) => magnet.getAttribute('port') === port);
if (element) {
return view.findMagnet($(element));
}
}
/**
* Return the metadata for a particular palette entry in a particular group.
* @param name - name of the palette entry
* @param group - group in which the palette entry should exist (e.g. sinks)
* @return
*/
export function getMetadata(metamodel : Map<string, Map<string, ElementMetadata>>, name : string, group : string) : ElementMetadata {
if (name && group && metamodel.get(group) && metamodel.get(group).get(name)) {
return metamodel.get(group).get(name);
} else {
return {
name: name,
group: group,
unresolved: true,
get: (property : string) => new Promise(resolve => resolve()),
properties: () => Promise.resolve(new Map<string, PropertyMetadata>())
};
}
}
}

View File

@@ -1,21 +0,0 @@
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"target": "es5",
"outDir": "../../out-tsc/lib-es5/",
"baseUrl": "",
"types": []
},
"files": [
"./index.ts",
"./typings.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"skipTemplateCodegen": true,
"flatModuleOutFile": "spring-flo.js",
"flatModuleId": "spring-flo",
"genDir": "../../out-tsc/lib-gen-dir/"
}
}

View File

@@ -1,22 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib-es2015/",
"target": "es2015",
"rootDir": "./",
"baseUrl": "",
"types": []
},
"files": [
"./index.ts",
"./typings.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"skipTemplateCodegen": true,
"flatModuleOutFile": "spring-flo.js",
"flatModuleId": "spring-flo",
"genDir": "../../out-tsc/lib-gen-dir/"
}
}

View File

@@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "",
"module": "commonjs",
"declaration": false,
"emitDecoratorMetadata": true
}
}

View File

@@ -1 +0,0 @@
// You can add project typings here.

45
tsconfig-build.json Normal file
View File

@@ -0,0 +1,45 @@
{
"compilerOptions": {
"rootDir": "./lib",
"baseUrl": "./lib",
"paths": {
"@angular/*": [
"../../node_modules/@angular/*"
],
"@rxjs/*": [
"../../node_modules/rxjs/*"
]
},
"outDir": "../out-tsc",
"declaration": true,
"strict": true,
"strictPropertyInitialization": false,
"moduleResolution": "node",
"module": "es2015",
"target": "es2015",
"lib": [
"es2015",
"dom"
],
"skipLibCheck": true,
"types": [],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true,
"inlineSources": true,
"importHelpers": true,
"strictNullChecks": false
},
"include": [
"./lib/**/*",
"../../node_modules/zone.js/dist/zone.js.d.ts"
],
"angularCompilerOptions": {
"enableResourceInlining": false,
"skipTemplateCodegen": true,
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"flatModuleOutFile": "./spring-flo.js",
"flatModuleId": "spring-flo"
}
}

View File

@@ -14,6 +14,7 @@
"lib": [
"es2015",
"dom"
]
],
"strictNullChecks":false
}
}

View File

@@ -2,7 +2,7 @@
"rules": {
"class-name": true,
"comment-format": [
true,
false,
"check-space"
],
"curly": true,
@@ -14,7 +14,7 @@
],
"label-position": true,
"max-line-length": [
true,
false,
140
],
"member-access": false,