Release 1.0.0
parent
cf44a13fbf
commit
aac96f7e04
@ -1,16 +0,0 @@
|
|||||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
|
||||||
# For additional information regarding the format and rule options, please see:
|
|
||||||
# https://github.com/browserslist/browserslist#queries
|
|
||||||
|
|
||||||
# For the full list of supported browsers by the Angular framework, please see:
|
|
||||||
# https://angular.io/guide/browser-support
|
|
||||||
|
|
||||||
# You can see what browsers were selected by your queries by running:
|
|
||||||
# npx browserslist
|
|
||||||
|
|
||||||
last 1 Chrome version
|
|
||||||
last 1 Firefox version
|
|
||||||
last 2 Edge major versions
|
|
||||||
last 2 Safari major versions
|
|
||||||
last 2 iOS major versions
|
|
||||||
Firefox ESR
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# Editor configuration, see https://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
max_line_length = off
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# compiled output
|
|
||||||
/tmp
|
|
||||||
/out-tsc
|
|
||||||
# Only exists if Bazel was run
|
|
||||||
/bazel-out
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
|
|
||||||
# profiling files
|
|
||||||
chrome-profiler-events.json
|
|
||||||
speed-measure-plugin.json
|
|
||||||
|
|
||||||
# IDEs and editors
|
|
||||||
/.idea
|
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
.c9/
|
|
||||||
*.launch
|
|
||||||
.settings/
|
|
||||||
*.sublime-workspace
|
|
||||||
|
|
||||||
# IDE - VSCode
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.history/*
|
|
||||||
|
|
||||||
# misc
|
|
||||||
/.sass-cache
|
|
||||||
/connect.lock
|
|
||||||
/coverage
|
|
||||||
/libpeerconnection.log
|
|
||||||
npm-debug.log
|
|
||||||
yarn-error.log
|
|
||||||
testem.log
|
|
||||||
/typings
|
|
||||||
|
|
||||||
# System Files
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2016 The Thingsboard Authors
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
||||||
"version": 1,
|
|
||||||
"newProjectRoot": "projects",
|
|
||||||
"projects": {
|
|
||||||
"ngx-flowchart-demo": {
|
|
||||||
"root": "",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"projectType": "application",
|
|
||||||
"prefix": "app",
|
|
||||||
"schematics": {
|
|
||||||
"@schematics/angular:component": {
|
|
||||||
"style": "scss"
|
|
||||||
},
|
|
||||||
"@schematics/angular:application": {
|
|
||||||
"strict": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
|
||||||
"options": {
|
|
||||||
"outputPath": "dist/ngx-flowchart-demo",
|
|
||||||
"index": "src/index.html",
|
|
||||||
"main": "src/main.ts",
|
|
||||||
"polyfills": "src/polyfills.ts",
|
|
||||||
"tsConfig": "tsconfig.app.json",
|
|
||||||
"aot": true,
|
|
||||||
"assets": [
|
|
||||||
"src/favicon.ico",
|
|
||||||
"src/assets"
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.scss"
|
|
||||||
],
|
|
||||||
"scripts": [
|
|
||||||
"node_modules/jquery/dist/jquery.min.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"fileReplacements": [
|
|
||||||
{
|
|
||||||
"replace": "src/environments/environment.ts",
|
|
||||||
"with": "src/environments/environment.prod.ts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"optimization": true,
|
|
||||||
"outputHashing": "all",
|
|
||||||
"sourceMap": false,
|
|
||||||
"extractCss": true,
|
|
||||||
"namedChunks": false,
|
|
||||||
"aot": true,
|
|
||||||
"extractLicenses": true,
|
|
||||||
"vendorChunk": false,
|
|
||||||
"buildOptimizer": true,
|
|
||||||
"budgets": [
|
|
||||||
{
|
|
||||||
"type": "initial",
|
|
||||||
"maximumWarning": "2mb",
|
|
||||||
"maximumError": "5mb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "anyComponentStyle",
|
|
||||||
"maximumWarning": "6kb"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serve": {
|
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
|
||||||
"options": {
|
|
||||||
"browserTarget": "ngx-flowchart-demo:build"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"browserTarget": "ngx-flowchart-demo:build:production"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extract-i18n": {
|
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
|
||||||
"options": {
|
|
||||||
"browserTarget": "ngx-flowchart-demo:build"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"main": "src/test.ts",
|
|
||||||
"polyfills": "src/polyfills.ts",
|
|
||||||
"tsConfig": "tsconfig.spec.json",
|
|
||||||
"karmaConfig": "karma.conf.js",
|
|
||||||
"assets": [
|
|
||||||
"src/favicon.ico",
|
|
||||||
"src/assets"
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.scss"
|
|
||||||
],
|
|
||||||
"scripts": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": [
|
|
||||||
"tsconfig.app.json",
|
|
||||||
"tsconfig.spec.json",
|
|
||||||
"e2e/tsconfig.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"**/node_modules/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"e2e": {
|
|
||||||
"builder": "@angular-devkit/build-angular:protractor",
|
|
||||||
"options": {
|
|
||||||
"protractorConfig": "e2e/protractor.conf.js",
|
|
||||||
"devServerTarget": "ngx-flowchart-demo:serve"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"devServerTarget": "ngx-flowchart-demo:serve:production"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ngx-flowchart": {
|
|
||||||
"projectType": "library",
|
|
||||||
"root": "projects/ngx-flowchart",
|
|
||||||
"sourceRoot": "projects/ngx-flowchart/src",
|
|
||||||
"prefix": "fc",
|
|
||||||
"schematics": {
|
|
||||||
"@schematics/angular:component": {
|
|
||||||
"style": "scss"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
|
||||||
"options": {
|
|
||||||
"project": "projects/ngx-flowchart/ng-package.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"tsConfig": "projects/ngx-flowchart/tsconfig.lib.prod.json"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"tsConfig": "projects/ngx-flowchart/tsconfig.lib.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "production"
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"main": "projects/ngx-flowchart/src/test.ts",
|
|
||||||
"tsConfig": "projects/ngx-flowchart/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "projects/ngx-flowchart/karma.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": [
|
|
||||||
"projects/ngx-flowchart/tsconfig.lib.json",
|
|
||||||
"projects/ngx-flowchart/tsconfig.spec.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"**/node_modules/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}},
|
|
||||||
"defaultProject": "ngx-flowchart-demo"
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# NgxFlowchart
|
|
||||||
|
|
||||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.3.
|
|
||||||
|
|
||||||
## Code scaffolding
|
|
||||||
|
|
||||||
Run `ng generate component component-name --project ngx-flowchart` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ngx-flowchart`.
|
|
||||||
> Note: Don't forget to add `--project ngx-flowchart` or else it will be added to the default project in your `angular.json` file.
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
Run `ng build ngx-flowchart` to build the project. The build artifacts will be stored in the `dist/` directory.
|
|
||||||
|
|
||||||
## Publishing
|
|
||||||
|
|
||||||
After building your library with `ng build ngx-flowchart`, go to the dist folder `cd dist/ngx-flowchart` and run `npm publish`.
|
|
||||||
|
|
||||||
## Running unit tests
|
|
||||||
|
|
||||||
Run `ng test ngx-flowchart` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
||||||
|
|
||||||
## Further help
|
|
||||||
|
|
||||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ngx-flowchart",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/common": "^12.2.13",
|
|
||||||
"@angular/core": "^12.2.13",
|
|
||||||
"jquery": "^3.6.0",
|
|
||||||
"typescript": "~4.3.5"
|
|
||||||
},
|
|
||||||
"main": "bundles/ngx-flowchart.umd.js",
|
|
||||||
"module": "fesm2015/ngx-flowchart.js",
|
|
||||||
"es2015": "fesm2015/ngx-flowchart.js",
|
|
||||||
"esm2015": "esm2015/ngx-flowchart.js",
|
|
||||||
"fesm2015": "fesm2015/ngx-flowchart.js",
|
|
||||||
"typings": "ngx-flowchart.d.ts",
|
|
||||||
"sideEffects": false,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
// Protractor configuration file, see link for more information
|
|
||||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
|
||||||
|
|
||||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type { import("protractor").Config }
|
|
||||||
*/
|
|
||||||
exports.config = {
|
|
||||||
allScriptsTimeout: 11000,
|
|
||||||
specs: [
|
|
||||||
'./src/**/*.e2e-spec.ts'
|
|
||||||
],
|
|
||||||
capabilities: {
|
|
||||||
'browserName': 'chrome'
|
|
||||||
},
|
|
||||||
directConnect: true,
|
|
||||||
baseUrl: 'http://localhost:4200/',
|
|
||||||
framework: 'jasmine',
|
|
||||||
jasmineNodeOpts: {
|
|
||||||
showColors: true,
|
|
||||||
defaultTimeoutInterval: 30000,
|
|
||||||
print: function() {}
|
|
||||||
},
|
|
||||||
onPrepare() {
|
|
||||||
require('ts-node').register({
|
|
||||||
project: require('path').join(__dirname, './tsconfig.json')
|
|
||||||
});
|
|
||||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { AppPage } from './app.po';
|
|
||||||
import { browser, logging } from 'protractor';
|
|
||||||
|
|
||||||
describe('workspace-project App', () => {
|
|
||||||
let page: AppPage;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
page = new AppPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display welcome message', () => {
|
|
||||||
page.navigateTo();
|
|
||||||
expect(page.getTitleText()).toEqual('Welcome to ngx-flowchart!');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
// Assert that there are no errors emitted from the browser
|
|
||||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
|
||||||
expect(logs).not.toContain(jasmine.objectContaining({
|
|
||||||
level: logging.Level.SEVERE,
|
|
||||||
} as logging.Entry));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { browser, by, element } from 'protractor';
|
|
||||||
|
|
||||||
export class AppPage {
|
|
||||||
navigateTo() {
|
|
||||||
return browser.get(browser.baseUrl) as Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTitleText() {
|
|
||||||
return element(by.css('app-root h1')).getText() as Promise<string>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../out-tsc/e2e",
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"types": [
|
|
||||||
"jasmine",
|
|
||||||
"jasminewd2",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
// Karma configuration file, see link for more information
|
|
||||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
|
||||||
config.set({
|
|
||||||
basePath: '',
|
|
||||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
|
||||||
plugins: [
|
|
||||||
require('karma-jasmine'),
|
|
||||||
require('karma-chrome-launcher'),
|
|
||||||
require('karma-jasmine-html-reporter'),
|
|
||||||
require('karma-coverage-istanbul-reporter'),
|
|
||||||
require('@angular-devkit/build-angular/plugins/karma')
|
|
||||||
],
|
|
||||||
client: {
|
|
||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
|
||||||
},
|
|
||||||
coverageIstanbulReporter: {
|
|
||||||
dir: require('path').join(__dirname, './coverage/ngx-flowchart'),
|
|
||||||
reports: ['html', 'lcovonly', 'text-summary'],
|
|
||||||
fixWebpackSourcePaths: true
|
|
||||||
},
|
|
||||||
reporters: ['progress', 'kjhtml'],
|
|
||||||
port: 9876,
|
|
||||||
colors: true,
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
autoWatch: true,
|
|
||||||
browsers: ['Chrome'],
|
|
||||||
singleRun: false,
|
|
||||||
restartOnFileChange: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,54 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "ngx-flowchart-demo",
|
"name": "ngx-flowchart",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
|
||||||
"ng": "ng",
|
|
||||||
"start": "ng serve --host 0.0.0.0 --open --port 4300",
|
|
||||||
"build": "ng build ngx-flowchart --configuration production",
|
|
||||||
"test": "ng test ngx-flowchart",
|
|
||||||
"lint": "ng lint",
|
|
||||||
"e2e": "ng e2e"
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"jquery": "^3.6.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@angular-devkit/build-angular": "^12.2.13",
|
|
||||||
"@angular/animations": "^12.2.13",
|
|
||||||
"@angular/cdk": "^12.2.13",
|
|
||||||
"@angular/cli": "^12.2.13",
|
|
||||||
"@angular/common": "^12.2.13",
|
"@angular/common": "^12.2.13",
|
||||||
"@angular/compiler": "^12.2.13",
|
|
||||||
"@angular/compiler-cli": "^12.2.13",
|
|
||||||
"@angular/core": "^12.2.13",
|
"@angular/core": "^12.2.13",
|
||||||
"@angular/forms": "^12.2.13",
|
|
||||||
"@angular/language-service": "^12.2.13",
|
|
||||||
"@angular/platform-browser": "^12.2.13",
|
|
||||||
"@angular/platform-browser-dynamic": "^12.2.13",
|
|
||||||
"@angular/router": "^12.2.13",
|
|
||||||
"@types/jasmine": "~3.10.2",
|
|
||||||
"@types/jasminewd2": "^2.0.10",
|
|
||||||
"@types/jquery": "^3.5.9",
|
|
||||||
"@types/node": "~15.14.9",
|
|
||||||
"codelyzer": "^6.0.2",
|
|
||||||
"jasmine-core": "~3.10.1",
|
|
||||||
"jasmine-spec-reporter": "~7.0.0",
|
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"karma": "~6.3.9",
|
"typescript": "~4.3.5"
|
||||||
"karma-chrome-launcher": "~3.1.0",
|
|
||||||
"karma-coverage-istanbul-reporter": "^3.0.3",
|
|
||||||
"karma-jasmine": "~4.0.1",
|
|
||||||
"karma-jasmine-html-reporter": "^1.7.0",
|
|
||||||
"ng-packagr": "~12.2.5",
|
|
||||||
"protractor": "~7.0.0",
|
|
||||||
"rxjs": "~6.6.7",
|
|
||||||
"ts-node": "^10.4.0",
|
|
||||||
"tslint": "~6.1.3",
|
|
||||||
"typescript": "~4.3.5",
|
|
||||||
"zone.js": "~0.11.4"
|
|
||||||
},
|
},
|
||||||
|
"main": "bundles/ngx-flowchart.umd.js",
|
||||||
|
"module": "fesm2015/ngx-flowchart.js",
|
||||||
|
"es2015": "fesm2015/ngx-flowchart.js",
|
||||||
|
"esm2015": "esm2015/ngx-flowchart.js",
|
||||||
|
"fesm2015": "fesm2015/ngx-flowchart.js",
|
||||||
|
"typings": "ngx-flowchart.d.ts",
|
||||||
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.1"
|
"tslib": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# NgxFlowchart
|
|
||||||
|
|
||||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.3.
|
|
||||||
|
|
||||||
## Code scaffolding
|
|
||||||
|
|
||||||
Run `ng generate component component-name --project ngx-flowchart` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ngx-flowchart`.
|
|
||||||
> Note: Don't forget to add `--project ngx-flowchart` or else it will be added to the default project in your `angular.json` file.
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
Run `ng build ngx-flowchart` to build the project. The build artifacts will be stored in the `dist/` directory.
|
|
||||||
|
|
||||||
## Publishing
|
|
||||||
|
|
||||||
After building your library with `ng build ngx-flowchart`, go to the dist folder `cd dist/ngx-flowchart` and run `npm publish`.
|
|
||||||
|
|
||||||
## Running unit tests
|
|
||||||
|
|
||||||
Run `ng test ngx-flowchart` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
||||||
|
|
||||||
## Further help
|
|
||||||
|
|
||||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
// Karma configuration file, see link for more information
|
|
||||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
|
||||||
config.set({
|
|
||||||
basePath: '',
|
|
||||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
|
||||||
plugins: [
|
|
||||||
require('karma-jasmine'),
|
|
||||||
require('karma-chrome-launcher'),
|
|
||||||
require('karma-jasmine-html-reporter'),
|
|
||||||
require('karma-coverage-istanbul-reporter'),
|
|
||||||
require('@angular-devkit/build-angular/plugins/karma')
|
|
||||||
],
|
|
||||||
client: {
|
|
||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
|
||||||
},
|
|
||||||
coverageIstanbulReporter: {
|
|
||||||
dir: require('path').join(__dirname, '../../coverage/ngx-flowchart'),
|
|
||||||
reports: ['html', 'lcovonly'],
|
|
||||||
fixWebpackSourcePaths: true
|
|
||||||
},
|
|
||||||
reporters: ['progress', 'kjhtml'],
|
|
||||||
port: 9876,
|
|
||||||
colors: true,
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
autoWatch: true,
|
|
||||||
browsers: ['Chrome'],
|
|
||||||
singleRun: false,
|
|
||||||
restartOnFileChange: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
|
||||||
"dest": "../../dist/ngx-flowchart",
|
|
||||||
"deleteDestPath": false,
|
|
||||||
"lib": {
|
|
||||||
"entryFile": "src/public-api.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ngx-flowchart",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/common": "^12.2.13",
|
|
||||||
"@angular/core": "^12.2.13",
|
|
||||||
"jquery": "^3.6.0",
|
|
||||||
"typescript": "~4.3.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/jquery": "^3.5.9"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
import { Directive, ElementRef, HostListener, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
|
||||||
import { FcCallbacks, FcConnector, FcConnectorRectInfo, FcNodeRectInfo, FlowchartConstants } from './ngx-flowchart.models';
|
|
||||||
import { FcModelService } from './model.service';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
// tslint:disable-next-line:directive-selector
|
|
||||||
selector: '[fc-connector]'
|
|
||||||
})
|
|
||||||
export class FcConnectorDirective implements OnInit, OnChanges {
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
callbacks: FcCallbacks;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
modelservice: FcModelService;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
connector: FcConnector;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
nodeRectInfo: FcNodeRectInfo;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
mouseOverConnector: FcConnector;
|
|
||||||
|
|
||||||
constructor(public elementRef: ElementRef<HTMLElement>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const element = $(this.elementRef.nativeElement);
|
|
||||||
element.addClass(FlowchartConstants.connectorClass);
|
|
||||||
if (this.modelservice.isEditable()) {
|
|
||||||
element.attr('draggable', 'true');
|
|
||||||
this.updateConnectorClass();
|
|
||||||
}
|
|
||||||
const connectorRectInfo: FcConnectorRectInfo = {
|
|
||||||
type: this.connector.type,
|
|
||||||
width: this.elementRef.nativeElement.offsetWidth,
|
|
||||||
height: this.elementRef.nativeElement.offsetHeight,
|
|
||||||
nodeRectInfo: this.nodeRectInfo
|
|
||||||
};
|
|
||||||
this.modelservice.connectors.setConnectorRectInfo(this.connector.id, connectorRectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
|
||||||
let updateConnector = false;
|
|
||||||
for (const propName of Object.keys(changes)) {
|
|
||||||
const change = changes[propName];
|
|
||||||
if (!change.firstChange && change.currentValue !== change.previousValue) {
|
|
||||||
if (propName === 'mouseOverConnector') {
|
|
||||||
updateConnector = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (updateConnector && this.modelservice.isEditable()) {
|
|
||||||
this.updateConnectorClass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateConnectorClass() {
|
|
||||||
const element = $(this.elementRef.nativeElement);
|
|
||||||
if (this.connector === this.mouseOverConnector) {
|
|
||||||
element.addClass(FlowchartConstants.hoverClass);
|
|
||||||
} else {
|
|
||||||
element.removeClass(FlowchartConstants.hoverClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragover', ['$event'])
|
|
||||||
dragover(event: Event | any) {
|
|
||||||
// Skip - conflict with magnet
|
|
||||||
/* if (this.modelservice.isEditable()) {
|
|
||||||
return this.callbacks.edgeDragoverConnector(event, this.connector);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('drop', ['$event'])
|
|
||||||
drop(event: Event | any) {
|
|
||||||
if (this.modelservice.isEditable()) {
|
|
||||||
return this.callbacks.edgeDrop(event, this.connector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragend', ['$event'])
|
|
||||||
dragend(event: Event | any) {
|
|
||||||
if (this.modelservice.isEditable()) {
|
|
||||||
this.callbacks.edgeDragend(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragstart', ['$event'])
|
|
||||||
dragstart(event: Event | any) {
|
|
||||||
if (this.modelservice.isEditable()) {
|
|
||||||
this.callbacks.edgeDragstart(event, this.connector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mouseenter', ['$event'])
|
|
||||||
mouseenter(event: MouseEvent) {
|
|
||||||
if (this.modelservice.isEditable()) {
|
|
||||||
this.callbacks.connectorMouseEnter(event, this.connector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mouseleave', ['$event'])
|
|
||||||
mouseleave(event: MouseEvent) {
|
|
||||||
if (this.modelservice.isEditable()) {
|
|
||||||
this.callbacks.connectorMouseLeave(event, this.connector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<div
|
|
||||||
(dblclick)="userNodeCallbacks.doubleClick($event, node)">
|
|
||||||
<div class="{{flowchartConstants.nodeOverlayClass}}"></div>
|
|
||||||
<div class="innerNode">
|
|
||||||
<p>{{ node.name }}</p>
|
|
||||||
|
|
||||||
<div class="{{flowchartConstants.leftConnectorClass}}">
|
|
||||||
<div fc-magnet [connector]="connector" [callbacks]="callbacks"
|
|
||||||
*ngFor="let connector of modelservice.nodes.getConnectorsByType(node, flowchartConstants.leftConnectorType)">
|
|
||||||
<div fc-connector [connector]="connector"
|
|
||||||
[nodeRectInfo]="nodeRectInfo"
|
|
||||||
[mouseOverConnector]="mouseOverConnector"
|
|
||||||
[callbacks]="callbacks"
|
|
||||||
[modelservice]="modelservice"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="{{flowchartConstants.rightConnectorClass}}">
|
|
||||||
<div fc-magnet [connector]="connector" [callbacks]="callbacks"
|
|
||||||
*ngFor="let connector of modelservice.nodes.getConnectorsByType(node, flowchartConstants.rightConnectorType)">
|
|
||||||
<div fc-connector [connector]="connector"
|
|
||||||
[nodeRectInfo]="nodeRectInfo"
|
|
||||||
[mouseOverConnector]="mouseOverConnector"
|
|
||||||
[callbacks]="callbacks"
|
|
||||||
[modelservice]="modelservice"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="modelservice.isEditable() && !node.readonly" class="fc-nodeedit" (click)="userNodeCallbacks.nodeEdit($event, node)">
|
|
||||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="modelservice.isEditable() && !node.readonly" class="fc-nodedelete" (click)="modelservice.nodes.delete(node)">
|
|
||||||
×
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
:host {
|
|
||||||
|
|
||||||
.fc-node-overlay {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #000;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context(.fc-hover) .fc-node-overlay {
|
|
||||||
opacity: 0.25;
|
|
||||||
transition: opacity .2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context(.fc-selected) .fc-node-overlay {
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.innerNode {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 100px;
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
background-color: #F15B26;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
p {
|
|
||||||
padding: 0 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { FcNodeComponent } from './node.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'fc-default-node',
|
|
||||||
templateUrl: './default-node.component.html',
|
|
||||||
styleUrls: ['./default-node.component.scss']
|
|
||||||
})
|
|
||||||
export class DefaultFcNodeComponent extends FcNodeComponent {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,278 +0,0 @@
|
|||||||
import { FcModelService } from './model.service';
|
|
||||||
import { FcConnector, FcCoords, FcEdge, FcModel, FlowchartConstants, ModelvalidationError } from './ngx-flowchart.models';
|
|
||||||
import { FcEdgeDrawingService } from './edge-drawing.service';
|
|
||||||
import { FcModelValidationService } from './modelvalidation.service';
|
|
||||||
|
|
||||||
export class FcEdgeDraggingService {
|
|
||||||
|
|
||||||
edgeDragging: EdgeDragging = {
|
|
||||||
isDragging: false,
|
|
||||||
dragPoint1: null,
|
|
||||||
dragPoint2: null,
|
|
||||||
shadowDragStarted: false
|
|
||||||
};
|
|
||||||
|
|
||||||
private draggedEdgeSource: FcConnector = null;
|
|
||||||
private dragOffset: FcCoords = {};
|
|
||||||
private destinationHtmlElement: HTMLElement = null;
|
|
||||||
private oldDisplayStyle = '';
|
|
||||||
|
|
||||||
private readonly modelValidation: FcModelValidationService;
|
|
||||||
private readonly edgeDrawingService: FcEdgeDrawingService;
|
|
||||||
private readonly modelService: FcModelService;
|
|
||||||
private readonly model: FcModel;
|
|
||||||
private readonly isValidEdgeCallback: (source: FcConnector, destination: FcConnector) => boolean;
|
|
||||||
private readonly applyFunction: <T>(fn: (...args: any[]) => T) => T;
|
|
||||||
private readonly dragAnimation: string;
|
|
||||||
private readonly edgeStyle: string;
|
|
||||||
|
|
||||||
constructor(modelValidation: FcModelValidationService,
|
|
||||||
edgeDrawingService: FcEdgeDrawingService,
|
|
||||||
modelService: FcModelService,
|
|
||||||
model: FcModel,
|
|
||||||
isValidEdgeCallback: (source: FcConnector, destination: FcConnector) => boolean,
|
|
||||||
applyFunction: <T>(fn: (...args: any[]) => T) => T,
|
|
||||||
dragAnimation: string,
|
|
||||||
edgeStyle: string) {
|
|
||||||
this.modelValidation = modelValidation;
|
|
||||||
this.edgeDrawingService = edgeDrawingService;
|
|
||||||
this.modelService = modelService;
|
|
||||||
this.model = model;
|
|
||||||
this.isValidEdgeCallback = isValidEdgeCallback || (() => true);
|
|
||||||
this.applyFunction = applyFunction;
|
|
||||||
this.dragAnimation = dragAnimation;
|
|
||||||
this.edgeStyle = edgeStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragstart(event: Event | any, connector: FcConnector) {
|
|
||||||
let swapConnector: FcConnector;
|
|
||||||
let dragLabel: string;
|
|
||||||
let prevEdge: FcEdge;
|
|
||||||
if (connector.type === FlowchartConstants.leftConnectorType) {
|
|
||||||
for (const edge of this.model.edges) {
|
|
||||||
if (edge.destination === connector.id) {
|
|
||||||
swapConnector = this.modelService.connectors.getConnector(edge.source);
|
|
||||||
dragLabel = edge.label;
|
|
||||||
prevEdge = edge;
|
|
||||||
this.applyFunction(() => {
|
|
||||||
this.modelService.edges.delete(edge);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.edgeDragging.isDragging = true;
|
|
||||||
if (swapConnector !== undefined) {
|
|
||||||
this.draggedEdgeSource = swapConnector;
|
|
||||||
this.edgeDragging.dragPoint1 = this.modelService.connectors.getCenteredCoord(swapConnector.id);
|
|
||||||
this.edgeDragging.dragLabel = dragLabel;
|
|
||||||
this.edgeDragging.prevEdge = prevEdge;
|
|
||||||
} else {
|
|
||||||
this.draggedEdgeSource = connector;
|
|
||||||
this.edgeDragging.dragPoint1 = this.modelService.connectors.getCenteredCoord(connector.id);
|
|
||||||
}
|
|
||||||
const canvas = this.modelService.canvasHtmlElement;
|
|
||||||
if (!canvas) {
|
|
||||||
throw new Error('No canvas while edgedraggingService found.');
|
|
||||||
}
|
|
||||||
this.dragOffset.x = -canvas.getBoundingClientRect().left;
|
|
||||||
this.dragOffset.y = -canvas.getBoundingClientRect().top;
|
|
||||||
|
|
||||||
this.edgeDragging.dragPoint2 = {
|
|
||||||
x: event.clientX + this.dragOffset.x,
|
|
||||||
y: event.clientY + this.dragOffset.y
|
|
||||||
};
|
|
||||||
const originalEvent: Event | any = (event as any).originalEvent || event;
|
|
||||||
|
|
||||||
originalEvent.dataTransfer.setData('Text', 'Just to support firefox');
|
|
||||||
if (originalEvent.dataTransfer.setDragImage) {
|
|
||||||
originalEvent.dataTransfer.setDragImage(this.modelService.getDragImage(), 0, 0);
|
|
||||||
} else {
|
|
||||||
this.destinationHtmlElement = event.target as HTMLElement;
|
|
||||||
this.oldDisplayStyle = this.destinationHtmlElement.style.display;
|
|
||||||
this.destinationHtmlElement.style.display = 'none';
|
|
||||||
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
this.edgeDragging.shadowDragStarted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
if (this.edgeDragging.gElement === undefined) {
|
|
||||||
this.edgeDragging.gElement = $(document.querySelectorAll('.shadow-svg-class'));
|
|
||||||
this.edgeDragging.pathElement = $(document.querySelectorAll('.shadow-svg-class')).find('path');
|
|
||||||
this.edgeDragging.circleElement = $(document.querySelectorAll('.shadow-svg-class')).find('circle');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.edgeDragging.gElement.css('display', 'block');
|
|
||||||
this.edgeDragging.pathElement.attr('d',
|
|
||||||
this.edgeDrawingService.getEdgeDAttribute(this.edgeDragging.dragPoint1, this.edgeDragging.dragPoint2, this.edgeStyle));
|
|
||||||
this.edgeDragging.circleElement.attr('cx', this.edgeDragging.dragPoint2.x);
|
|
||||||
this.edgeDragging.circleElement.attr('cy', this.edgeDragging.dragPoint2.y);
|
|
||||||
}
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragover(event: Event | any) {
|
|
||||||
if (this.edgeDragging.isDragging) {
|
|
||||||
if (!this.edgeDragging.magnetActive && this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
if (this.destinationHtmlElement !== null) {
|
|
||||||
this.destinationHtmlElement.style.display = this.oldDisplayStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.edgeDragging.shadowDragStarted) {
|
|
||||||
this.applyFunction(() => {
|
|
||||||
this.edgeDragging.shadowDragStarted = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.edgeDragging.dragPoint2 = {
|
|
||||||
x: event.clientX + this.dragOffset.x,
|
|
||||||
y: event.clientY + this.dragOffset.y
|
|
||||||
};
|
|
||||||
|
|
||||||
this.edgeDragging.pathElement.attr('d',
|
|
||||||
this.edgeDrawingService.getEdgeDAttribute(this.edgeDragging.dragPoint1, this.edgeDragging.dragPoint2, this.edgeStyle));
|
|
||||||
this.edgeDragging.circleElement.attr('cx', this.edgeDragging.dragPoint2.x);
|
|
||||||
this.edgeDragging.circleElement.attr('cy', this.edgeDragging.dragPoint2.y);
|
|
||||||
|
|
||||||
} else if (this.dragAnimation === FlowchartConstants.dragAnimationRepaint) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
if (this.destinationHtmlElement !== null) {
|
|
||||||
this.destinationHtmlElement.style.display = this.oldDisplayStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.edgeDragging.dragPoint2 = {
|
|
||||||
x: event.clientX + this.dragOffset.x,
|
|
||||||
y: event.clientY + this.dragOffset.y
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragoverConnector(event: Event | any, connector: FcConnector): boolean {
|
|
||||||
if (this.edgeDragging.isDragging) {
|
|
||||||
this.dragover(event);
|
|
||||||
try {
|
|
||||||
this.modelValidation.validateEdges(this.model.edges.concat([{
|
|
||||||
source: this.draggedEdgeSource.id,
|
|
||||||
destination: connector.id
|
|
||||||
}]), this.model.nodes);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof ModelvalidationError) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.isValidEdgeCallback(this.draggedEdgeSource, connector)) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragleaveMagnet(event: Event | any) {
|
|
||||||
this.edgeDragging.magnetActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragoverMagnet(event: Event | any, connector: FcConnector): boolean {
|
|
||||||
if (this.edgeDragging.isDragging) {
|
|
||||||
this.dragover(event);
|
|
||||||
try {
|
|
||||||
this.modelValidation.validateEdges(this.model.edges.concat([{
|
|
||||||
source: this.draggedEdgeSource.id,
|
|
||||||
destination: connector.id
|
|
||||||
}]), this.model.nodes);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof ModelvalidationError) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.isValidEdgeCallback(this.draggedEdgeSource, connector)) {
|
|
||||||
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
|
|
||||||
this.edgeDragging.magnetActive = true;
|
|
||||||
|
|
||||||
this.edgeDragging.dragPoint2 = this.modelService.connectors.getCenteredCoord(connector.id);
|
|
||||||
this.edgeDragging.pathElement.attr('d',
|
|
||||||
this.edgeDrawingService.getEdgeDAttribute(this.edgeDragging.dragPoint1, this.edgeDragging.dragPoint2, this.edgeStyle));
|
|
||||||
this.edgeDragging.circleElement.attr('cx', this.edgeDragging.dragPoint2.x);
|
|
||||||
this.edgeDragging.circleElement.attr('cy', this.edgeDragging.dragPoint2.y);
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
} else if (this.dragAnimation === FlowchartConstants.dragAnimationRepaint) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
this.edgeDragging.dragPoint2 = this.modelService.connectors.getCenteredCoord(connector.id);
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragend(event: Event | any) {
|
|
||||||
if (this.edgeDragging.isDragging) {
|
|
||||||
this.edgeDragging.isDragging = false;
|
|
||||||
this.edgeDragging.dragPoint1 = null;
|
|
||||||
this.edgeDragging.dragPoint2 = null;
|
|
||||||
this.edgeDragging.dragLabel = null;
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
this.edgeDragging.gElement.css('display', 'none');
|
|
||||||
}
|
|
||||||
if (this.edgeDragging.prevEdge) {
|
|
||||||
const edge = this.edgeDragging.prevEdge;
|
|
||||||
this.edgeDragging.prevEdge = null;
|
|
||||||
this.applyFunction(() => {
|
|
||||||
this.modelService.edges.putEdge(edge);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public drop(event: Event | any, targetConnector: FcConnector): boolean {
|
|
||||||
if (this.edgeDragging.isDragging) {
|
|
||||||
try {
|
|
||||||
this.modelValidation.validateEdges(this.model.edges.concat([{
|
|
||||||
source: this.draggedEdgeSource.id,
|
|
||||||
destination: targetConnector.id
|
|
||||||
}]), this.model.nodes);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof ModelvalidationError) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isValidEdgeCallback(this.draggedEdgeSource, targetConnector)) {
|
|
||||||
this.edgeDragging.prevEdge = null;
|
|
||||||
this.modelService.edges._addEdge(event, this.draggedEdgeSource, targetConnector, this.edgeDragging.dragLabel);
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EdgeDragging {
|
|
||||||
isDragging: boolean;
|
|
||||||
shadowDragStarted: boolean;
|
|
||||||
dragPoint1: FcCoords;
|
|
||||||
dragPoint2: FcCoords;
|
|
||||||
dragLabel?: string;
|
|
||||||
prevEdge?: FcEdge;
|
|
||||||
magnetActive?: boolean;
|
|
||||||
gElement?: JQuery<Element>;
|
|
||||||
pathElement?: JQuery<Element>;
|
|
||||||
circleElement?: JQuery<Element>;
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { FcCoords, FlowchartConstants } from './ngx-flowchart.models';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class FcEdgeDrawingService {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEdgeDAttribute(pt1: FcCoords, pt2: FcCoords, style: string): string {
|
|
||||||
let dAddribute = `M ${pt1.x}, ${pt1.y} `;
|
|
||||||
if (style === FlowchartConstants.curvedStyle) {
|
|
||||||
const sourceTangent = this.computeEdgeSourceTangent(pt1, pt2);
|
|
||||||
const destinationTangent = this.computeEdgeDestinationTangent(pt1, pt2);
|
|
||||||
dAddribute += `C ${sourceTangent.x}, ${sourceTangent.y} ${(destinationTangent.x - 50)}, ${destinationTangent.y} ${pt2.x}, ${pt2.y}`;
|
|
||||||
} else {
|
|
||||||
dAddribute += `L ${pt2.x}, ${pt2.y}`;
|
|
||||||
}
|
|
||||||
return dAddribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEdgeCenter(pt1: FcCoords, pt2: FcCoords): FcCoords {
|
|
||||||
return {
|
|
||||||
x: (pt1.x + pt2.x) / 2,
|
|
||||||
y: (pt1.y + pt2.y) / 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private computeEdgeTangentOffset(pt1: FcCoords, pt2: FcCoords): number {
|
|
||||||
return (pt2.y - pt1.y) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private computeEdgeSourceTangent(pt1: FcCoords, pt2: FcCoords): FcCoords {
|
|
||||||
return {
|
|
||||||
x: pt1.x,
|
|
||||||
y: pt1.y + this.computeEdgeTangentOffset(pt1, pt2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private computeEdgeDestinationTangent(pt1: FcCoords, pt2: FcCoords): FcCoords {
|
|
||||||
return {
|
|
||||||
x: pt2.x,
|
|
||||||
y: pt2.y - this.computeEdgeTangentOffset(pt1, pt2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import { Directive, ElementRef, HostListener, Input, OnInit } from '@angular/core';
|
|
||||||
import { FcCallbacks, FcConnector, FlowchartConstants } from './ngx-flowchart.models';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
// tslint:disable-next-line:directive-selector
|
|
||||||
selector: '[fc-magnet]'
|
|
||||||
})
|
|
||||||
export class FcMagnetDirective implements OnInit {
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
callbacks: FcCallbacks;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
connector: FcConnector;
|
|
||||||
|
|
||||||
constructor(public elementRef: ElementRef<HTMLElement>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const element = $(this.elementRef.nativeElement);
|
|
||||||
element.addClass(FlowchartConstants.magnetClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragover', ['$event'])
|
|
||||||
dragover(event: Event | any) {
|
|
||||||
return this.callbacks.edgeDragoverMagnet(event, this.connector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragleave', ['$event'])
|
|
||||||
dragleave(event: Event | any) {
|
|
||||||
this.callbacks.edgeDragleaveMagnet(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('drop', ['$event'])
|
|
||||||
drop(event: Event | any) {
|
|
||||||
return this.callbacks.edgeDrop(event, this.connector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragend', ['$event'])
|
|
||||||
dragend(event: Event | any) {
|
|
||||||
this.callbacks.edgeDragend(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,503 +0,0 @@
|
|||||||
import { FcModelValidationService } from './modelvalidation.service';
|
|
||||||
import {
|
|
||||||
FcConnector,
|
|
||||||
FcConnectorRectInfo,
|
|
||||||
FcCoords,
|
|
||||||
FcEdge,
|
|
||||||
FcItemInfo,
|
|
||||||
FcModel,
|
|
||||||
FcNode,
|
|
||||||
FcRectBox,
|
|
||||||
FlowchartConstants
|
|
||||||
} from './ngx-flowchart.models';
|
|
||||||
import { Observable, of, Subject } from 'rxjs';
|
|
||||||
import { EventEmitter } from '@angular/core';
|
|
||||||
import { debounceTime } from 'rxjs/operators';
|
|
||||||
|
|
||||||
export class FcModelService {
|
|
||||||
|
|
||||||
modelValidation: FcModelValidationService;
|
|
||||||
model: FcModel;
|
|
||||||
private readonly detectChangesSubject: Subject<any>;
|
|
||||||
selectedObjects: any[];
|
|
||||||
|
|
||||||
connectorsRectInfos: ConnectorRectInfoMap = {};
|
|
||||||
nodesHtmlElements: HtmlElementMap = {};
|
|
||||||
canvasHtmlElement: HTMLElement = null;
|
|
||||||
dragImage: HTMLImageElement = null;
|
|
||||||
svgHtmlElement: SVGElement = null;
|
|
||||||
|
|
||||||
dropNode: (event: Event, node: FcNode) => void;
|
|
||||||
createEdge: (event: Event, edge: FcEdge) => Observable<FcEdge>;
|
|
||||||
edgeAddedCallback: (edge: FcEdge) => void;
|
|
||||||
nodeRemovedCallback: (node: FcNode) => void;
|
|
||||||
edgeRemovedCallback: (edge: FcEdge) => void;
|
|
||||||
|
|
||||||
dropTargetId: string;
|
|
||||||
|
|
||||||
private readonly modelChanged: EventEmitter<any>;
|
|
||||||
private readonly debouncer = new Subject<any>();
|
|
||||||
|
|
||||||
connectors: ConnectorsModel;
|
|
||||||
nodes: NodesModel;
|
|
||||||
edges: EdgesModel;
|
|
||||||
|
|
||||||
constructor(modelValidation: FcModelValidationService,
|
|
||||||
model: FcModel,
|
|
||||||
modelChanged: EventEmitter<any>,
|
|
||||||
detectChangesSubject: Subject<any>,
|
|
||||||
selectedObjects: any[],
|
|
||||||
dropNode: (event: Event, node: FcNode) => void,
|
|
||||||
createEdge: (event: Event, edge: FcEdge) => Observable<FcEdge>,
|
|
||||||
edgeAddedCallback: (edge: FcEdge) => void,
|
|
||||||
nodeRemovedCallback: (node: FcNode) => void,
|
|
||||||
edgeRemovedCallback: (edge: FcEdge) => void,
|
|
||||||
canvasHtmlElement: HTMLElement,
|
|
||||||
svgHtmlElement: SVGElement) {
|
|
||||||
|
|
||||||
this.modelValidation = modelValidation;
|
|
||||||
this.model = model;
|
|
||||||
this.modelChanged = modelChanged;
|
|
||||||
this.detectChangesSubject = detectChangesSubject;
|
|
||||||
this.canvasHtmlElement = canvasHtmlElement;
|
|
||||||
this.svgHtmlElement = svgHtmlElement;
|
|
||||||
this.modelValidation.validateModel(this.model);
|
|
||||||
this.selectedObjects = selectedObjects;
|
|
||||||
|
|
||||||
this.dropNode = dropNode || (() => {});
|
|
||||||
this.createEdge = createEdge || ((event, edge) => of({...edge, label: 'label'}));
|
|
||||||
this.edgeAddedCallback = edgeAddedCallback || (() => {});
|
|
||||||
this.nodeRemovedCallback = nodeRemovedCallback || (() => {});
|
|
||||||
this.edgeRemovedCallback = edgeRemovedCallback || (() => {});
|
|
||||||
|
|
||||||
this.connectors = new ConnectorsModel(this);
|
|
||||||
this.nodes = new NodesModel(this);
|
|
||||||
this.edges = new EdgesModel(this);
|
|
||||||
|
|
||||||
this.debouncer
|
|
||||||
.pipe(debounceTime(100))
|
|
||||||
.subscribe(() => this.modelChanged.emit());
|
|
||||||
}
|
|
||||||
|
|
||||||
public notifyModelChanged() {
|
|
||||||
this.debouncer.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
public detectChanges() {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.detectChangesSubject.next();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectObject(object: any) {
|
|
||||||
if (this.isEditable()) {
|
|
||||||
if (this.selectedObjects.indexOf(object) === -1) {
|
|
||||||
this.selectedObjects.push(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public deselectObject(object: any) {
|
|
||||||
if (this.isEditable()) {
|
|
||||||
const index = this.selectedObjects.indexOf(object);
|
|
||||||
if (index === -1) {
|
|
||||||
throw new Error('Tried to deselect an unselected object');
|
|
||||||
}
|
|
||||||
this.selectedObjects.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggleSelectedObject(object: any) {
|
|
||||||
if (this.isSelectedObject(object)) {
|
|
||||||
this.deselectObject(object);
|
|
||||||
} else {
|
|
||||||
this.selectObject(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public isSelectedObject(object: any): boolean {
|
|
||||||
return this.selectedObjects.indexOf(object) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectAll() {
|
|
||||||
this.model.nodes.forEach(node => {
|
|
||||||
if (!node.readonly) {
|
|
||||||
this.nodes.select(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.model.edges.forEach(edge => {
|
|
||||||
this.edges.select(edge);
|
|
||||||
});
|
|
||||||
this.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public deselectAll() {
|
|
||||||
this.selectedObjects.splice(0, this.selectedObjects.length);
|
|
||||||
this.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public isEditObject(object: any): boolean {
|
|
||||||
return this.selectedObjects.length === 1 &&
|
|
||||||
this.selectedObjects.indexOf(object) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private inRectBox(x: number, y: number, rectBox: FcRectBox): boolean {
|
|
||||||
return x >= rectBox.left && x <= rectBox.right &&
|
|
||||||
y >= rectBox.top && y <= rectBox.bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getItemInfoAtPoint(x: number, y: number): FcItemInfo {
|
|
||||||
return {
|
|
||||||
node: this.getNodeAtPoint(x, y),
|
|
||||||
edge: this.getEdgeAtPoint(x, y)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeAtPoint(x: number, y: number): FcNode {
|
|
||||||
for (const node of this.model.nodes) {
|
|
||||||
const element = this.nodes.getHtmlElement(node.id);
|
|
||||||
const nodeElementBox = element.getBoundingClientRect();
|
|
||||||
if (x >= nodeElementBox.left && x <= nodeElementBox.right
|
|
||||||
&& y >= nodeElementBox.top && y <= nodeElementBox.bottom) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEdgeAtPoint(x: number, y: number): FcEdge {
|
|
||||||
const element = document.elementFromPoint(x, y);
|
|
||||||
const id = element.id;
|
|
||||||
let edgeIndex = -1;
|
|
||||||
if (id) {
|
|
||||||
if (id.startsWith('fc-edge-path-')) {
|
|
||||||
edgeIndex = Number(id.substring('fc-edge-path-'.length));
|
|
||||||
} else if (id.startsWith('fc-edge-label-')) {
|
|
||||||
edgeIndex = Number(id.substring('fc-edge-label-'.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (edgeIndex > -1) {
|
|
||||||
return this.model.edges[edgeIndex];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectAllInRect(rectBox: FcRectBox) {
|
|
||||||
this.model.nodes.forEach((value) => {
|
|
||||||
const element = this.nodes.getHtmlElement(value.id);
|
|
||||||
const nodeElementBox = element.getBoundingClientRect();
|
|
||||||
if (!value.readonly) {
|
|
||||||
const x = nodeElementBox.left + nodeElementBox.width / 2;
|
|
||||||
const y = nodeElementBox.top + nodeElementBox.height / 2;
|
|
||||||
if (this.inRectBox(x, y, rectBox)) {
|
|
||||||
this.nodes.select(value);
|
|
||||||
} else {
|
|
||||||
if (this.nodes.isSelected(value)) {
|
|
||||||
this.nodes.deselect(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const canvasElementBox = this.canvasHtmlElement.getBoundingClientRect();
|
|
||||||
this.model.edges.forEach((value) => {
|
|
||||||
const start = this.edges.sourceCoord(value);
|
|
||||||
const end = this.edges.destCoord(value);
|
|
||||||
const x = (start.x + end.x) / 2 + canvasElementBox.left;
|
|
||||||
const y = (start.y + end.y) / 2 + canvasElementBox.top;
|
|
||||||
if (this.inRectBox(x, y, rectBox)) {
|
|
||||||
this.edges.select(value);
|
|
||||||
} else {
|
|
||||||
if (this.edges.isSelected(value)) {
|
|
||||||
this.edges.deselect(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public deleteSelected() {
|
|
||||||
const edgesToDelete = this.edges.getSelectedEdges();
|
|
||||||
edgesToDelete.forEach((edge) => {
|
|
||||||
this.edges.delete(edge);
|
|
||||||
});
|
|
||||||
const nodesToDelete = this.nodes.getSelectedNodes();
|
|
||||||
nodesToDelete.forEach((node) => {
|
|
||||||
this.nodes.delete(node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public isEditable(): boolean {
|
|
||||||
return this.dropTargetId === undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isDropSource(): boolean {
|
|
||||||
return this.dropTargetId !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDragImage(): HTMLImageElement {
|
|
||||||
if (!this.dragImage) {
|
|
||||||
this.dragImage = new Image();
|
|
||||||
this.dragImage.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
||||||
this.dragImage.style.visibility = 'hidden';
|
|
||||||
}
|
|
||||||
return this.dragImage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HtmlElementMap { [id: string]: HTMLElement; }
|
|
||||||
|
|
||||||
interface ConnectorRectInfoMap { [id: string]: FcConnectorRectInfo; }
|
|
||||||
|
|
||||||
abstract class AbstractFcModel<T> {
|
|
||||||
|
|
||||||
modelService: FcModelService;
|
|
||||||
|
|
||||||
protected constructor(modelService: FcModelService) {
|
|
||||||
this.modelService = modelService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public select(object: T) {
|
|
||||||
this.modelService.selectObject(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public deselect(object: T) {
|
|
||||||
this.modelService.deselectObject(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggleSelected(object: T) {
|
|
||||||
this.modelService.toggleSelectedObject(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isSelected(object: T): boolean {
|
|
||||||
return this.modelService.isSelectedObject(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isEdit(object: T): boolean {
|
|
||||||
return this.modelService.isEditObject(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectorsModel extends AbstractFcModel<FcConnector> {
|
|
||||||
|
|
||||||
constructor(modelService: FcModelService) {
|
|
||||||
super(modelService);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnector(connectorId: string): FcConnector {
|
|
||||||
const model = this.modelService.model;
|
|
||||||
for (const node of model.nodes) {
|
|
||||||
for (const connector of node.connectors) {
|
|
||||||
if (connector.id === connectorId) {
|
|
||||||
return connector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnectorRectInfo(connectorId: string): FcConnectorRectInfo {
|
|
||||||
return this.modelService.connectorsRectInfos[connectorId];
|
|
||||||
}
|
|
||||||
|
|
||||||
public setConnectorRectInfo(connectorId: string, connectorRectInfo: FcConnectorRectInfo) {
|
|
||||||
this.modelService.connectorsRectInfos[connectorId] = connectorRectInfo;
|
|
||||||
this.modelService.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getCoords(connectorId: string, centered?: boolean): FcCoords {
|
|
||||||
const connectorRectInfo = this.getConnectorRectInfo(connectorId);
|
|
||||||
const canvas = this.modelService.canvasHtmlElement;
|
|
||||||
if (connectorRectInfo === null || connectorRectInfo === undefined || canvas === null) {
|
|
||||||
return {x: 0, y: 0};
|
|
||||||
}
|
|
||||||
let x = connectorRectInfo.type === FlowchartConstants.leftConnectorType ?
|
|
||||||
connectorRectInfo.nodeRectInfo.left() : connectorRectInfo.nodeRectInfo.right();
|
|
||||||
let y = connectorRectInfo.nodeRectInfo.top() + connectorRectInfo.nodeRectInfo.height() / 2;
|
|
||||||
if (!centered) {
|
|
||||||
x -= connectorRectInfo.width / 2;
|
|
||||||
y -= connectorRectInfo.height / 2;
|
|
||||||
}
|
|
||||||
const coords: FcCoords = {
|
|
||||||
x: Math.round(x),
|
|
||||||
y: Math.round(y)
|
|
||||||
};
|
|
||||||
return coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCoords(connectorId: string): FcCoords {
|
|
||||||
return this._getCoords(connectorId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCenteredCoord(connectorId: string): FcCoords {
|
|
||||||
return this._getCoords(connectorId, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NodesModel extends AbstractFcModel<FcNode> {
|
|
||||||
|
|
||||||
constructor(modelService: FcModelService) {
|
|
||||||
super(modelService);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnectorsByType(node: FcNode, type: string): Array<FcConnector> {
|
|
||||||
return node.connectors.filter((connector) => {
|
|
||||||
return connector.type === type;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addConnector(node: FcNode, connector: FcConnector) {
|
|
||||||
node.connectors.push(connector);
|
|
||||||
try {
|
|
||||||
this.modelService.modelValidation.validateNode(node);
|
|
||||||
} catch (error) {
|
|
||||||
node.connectors.splice(node.connectors.indexOf(connector), 1);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public delete(node: FcNode) {
|
|
||||||
if (this.isSelected(node)) {
|
|
||||||
this.deselect(node);
|
|
||||||
}
|
|
||||||
const model = this.modelService.model;
|
|
||||||
const index = model.nodes.indexOf(node);
|
|
||||||
if (index === -1) {
|
|
||||||
if (node === undefined) {
|
|
||||||
throw new Error('Passed undefined');
|
|
||||||
}
|
|
||||||
throw new Error('Tried to delete not existing node');
|
|
||||||
}
|
|
||||||
const connectorIds = this.getConnectorIds(node);
|
|
||||||
for (let i = 0; i < model.edges.length; i++) {
|
|
||||||
const edge = model.edges[i];
|
|
||||||
if (connectorIds.indexOf(edge.source) !== -1 || connectorIds.indexOf(edge.destination) !== -1) {
|
|
||||||
this.modelService.edges.delete(edge);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
model.nodes.splice(index, 1);
|
|
||||||
this.modelService.notifyModelChanged();
|
|
||||||
this.modelService.nodeRemovedCallback(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSelectedNodes(): Array<FcNode> {
|
|
||||||
const model = this.modelService.model;
|
|
||||||
return model.nodes.filter((node) => {
|
|
||||||
return this.modelService.nodes.isSelected(node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public handleClicked(node: FcNode, ctrlKey?: boolean) {
|
|
||||||
if (ctrlKey) {
|
|
||||||
this.modelService.nodes.toggleSelected(node);
|
|
||||||
} else {
|
|
||||||
this.modelService.deselectAll();
|
|
||||||
this.modelService.nodes.select(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addNode(node: FcNode) {
|
|
||||||
const model = this.modelService.model;
|
|
||||||
try {
|
|
||||||
model.nodes.push(node);
|
|
||||||
this.modelService.modelValidation.validateNodes(model.nodes);
|
|
||||||
} catch (error) {
|
|
||||||
model.nodes.splice(model.nodes.indexOf(node), 1);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnectorIds(node: FcNode): Array<string> {
|
|
||||||
return node.connectors.map((connector) => {
|
|
||||||
return connector.id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeByConnectorId(connectorId: string): FcNode {
|
|
||||||
const model = this.modelService.model;
|
|
||||||
for (const node of model.nodes) {
|
|
||||||
const connectorIds = this.getConnectorIds(node);
|
|
||||||
if (connectorIds.indexOf(connectorId) > -1) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getHtmlElement(nodeId: string): HTMLElement {
|
|
||||||
return this.modelService.nodesHtmlElements[nodeId];
|
|
||||||
}
|
|
||||||
|
|
||||||
public setHtmlElement(nodeId: string, element: HTMLElement) {
|
|
||||||
this.modelService.nodesHtmlElements[nodeId] = element;
|
|
||||||
this.modelService.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class EdgesModel extends AbstractFcModel<FcEdge> {
|
|
||||||
|
|
||||||
constructor(modelService: FcModelService) {
|
|
||||||
super(modelService);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sourceCoord(edge: FcEdge): FcCoords {
|
|
||||||
return this.modelService.connectors.getCenteredCoord(edge.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public destCoord(edge: FcEdge): FcCoords {
|
|
||||||
return this.modelService.connectors.getCenteredCoord(edge.destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
public delete(edge: FcEdge) {
|
|
||||||
const model = this.modelService.model;
|
|
||||||
const index = model.edges.indexOf(edge);
|
|
||||||
if (index === -1) {
|
|
||||||
throw new Error('Tried to delete not existing edge');
|
|
||||||
}
|
|
||||||
if (this.isSelected(edge)) {
|
|
||||||
this.deselect(edge);
|
|
||||||
}
|
|
||||||
model.edges.splice(index, 1);
|
|
||||||
this.modelService.notifyModelChanged();
|
|
||||||
this.modelService.edgeRemovedCallback(edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSelectedEdges(): Array<FcEdge> {
|
|
||||||
const model = this.modelService.model;
|
|
||||||
return model.edges.filter((edge) => {
|
|
||||||
return this.modelService.edges.isSelected(edge);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public handleEdgeMouseClick(edge: FcEdge, ctrlKey?: boolean) {
|
|
||||||
if (ctrlKey) {
|
|
||||||
this.modelService.edges.toggleSelected(edge);
|
|
||||||
} else {
|
|
||||||
this.modelService.deselectAll();
|
|
||||||
this.modelService.edges.select(edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public putEdge(edge: FcEdge) {
|
|
||||||
const model = this.modelService.model;
|
|
||||||
model.edges.push(edge);
|
|
||||||
this.modelService.notifyModelChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public _addEdge(event: Event, sourceConnector: FcConnector, destConnector: FcConnector, label: string) {
|
|
||||||
this.modelService.modelValidation.validateConnector(sourceConnector);
|
|
||||||
this.modelService.modelValidation.validateConnector(destConnector);
|
|
||||||
const edge: FcEdge = {};
|
|
||||||
edge.source = sourceConnector.id;
|
|
||||||
edge.destination = destConnector.id;
|
|
||||||
edge.label = label;
|
|
||||||
const model = this.modelService.model;
|
|
||||||
this.modelService.modelValidation.validateEdges(model.edges.concat([edge]), model.nodes);
|
|
||||||
this.modelService.createEdge(event, edge).subscribe(
|
|
||||||
(created) => {
|
|
||||||
model.edges.push(created);
|
|
||||||
this.modelService.notifyModelChanged();
|
|
||||||
this.modelService.edgeAddedCallback(created);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { FcConnector, FcEdge, FcModel, FcNode, fcTopSort, ModelvalidationError } from './ngx-flowchart.models';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class FcModelValidationService {
|
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
public validateModel(model: FcModel): FcModel {
|
|
||||||
this.validateNodes(model.nodes);
|
|
||||||
this._validateEdges(model.edges, model.nodes);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
public validateNodes(nodes: Array<FcNode>): Array<FcNode> {
|
|
||||||
const ids: string[] = [];
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
this.validateNode(node);
|
|
||||||
if (ids.indexOf(node.id) !== -1) {
|
|
||||||
throw new ModelvalidationError('Id not unique.');
|
|
||||||
}
|
|
||||||
ids.push(node.id);
|
|
||||||
});
|
|
||||||
const connectorIds: string[] = [];
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
node.connectors.forEach((connector) => {
|
|
||||||
if (connectorIds.indexOf(connector.id) !== -1) {
|
|
||||||
throw new ModelvalidationError('Id not unique.');
|
|
||||||
}
|
|
||||||
connectorIds.push(connector.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public validateNode(node: FcNode): FcNode {
|
|
||||||
if (node.id === undefined) {
|
|
||||||
throw new ModelvalidationError('Id not valid.');
|
|
||||||
}
|
|
||||||
if (typeof node.name !== 'string') {
|
|
||||||
throw new ModelvalidationError('Name not valid.');
|
|
||||||
}
|
|
||||||
if (typeof node.x !== 'number' || node.x < 0 || Math.round(node.x) !== node.x) {
|
|
||||||
throw new ModelvalidationError('Coordinates not valid.');
|
|
||||||
}
|
|
||||||
if (typeof node.y !== 'number' || node.y < 0 || Math.round(node.y) !== node.y) {
|
|
||||||
throw new ModelvalidationError('Coordinates not valid.');
|
|
||||||
}
|
|
||||||
if (!Array.isArray(node.connectors)) {
|
|
||||||
throw new ModelvalidationError('Connectors not valid.');
|
|
||||||
}
|
|
||||||
node.connectors.forEach((connector) => {
|
|
||||||
this.validateConnector(connector);
|
|
||||||
});
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _validateEdges(edges: Array<FcEdge>, nodes: Array<FcNode>): Array<FcEdge> {
|
|
||||||
edges.forEach((edge) => {
|
|
||||||
this._validateEdge(edge, nodes);
|
|
||||||
});
|
|
||||||
edges.forEach((edge1, index1) => {
|
|
||||||
edges.forEach((edge2, index2) => {
|
|
||||||
if (index1 !== index2) {
|
|
||||||
if ((edge1.source === edge2.source && edge1.destination === edge2.destination) ||
|
|
||||||
(edge1.source === edge2.destination && edge1.destination === edge2.source)) {
|
|
||||||
throw new ModelvalidationError('Duplicated edge.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (fcTopSort({nodes, edges}) === null) {
|
|
||||||
throw new ModelvalidationError('Graph has a circle.');
|
|
||||||
}
|
|
||||||
return edges;
|
|
||||||
}
|
|
||||||
|
|
||||||
public validateEdges(edges: Array<FcEdge>, nodes: Array<FcNode>): Array<FcEdge> {
|
|
||||||
this.validateNodes(nodes);
|
|
||||||
return this._validateEdges(edges, nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _validateEdge(edge: FcEdge, nodes: Array<FcNode>): FcEdge {
|
|
||||||
if (edge.source === undefined) {
|
|
||||||
throw new ModelvalidationError('Source not valid.');
|
|
||||||
}
|
|
||||||
if (edge.destination === undefined) {
|
|
||||||
throw new ModelvalidationError('Destination not valid.');
|
|
||||||
}
|
|
||||||
if (edge.source === edge.destination) {
|
|
||||||
throw new ModelvalidationError('Edge with same source and destination connectors.');
|
|
||||||
}
|
|
||||||
const sourceNode = nodes.filter((node) => node.connectors.some((connector) => connector.id === edge.source))[0];
|
|
||||||
if (sourceNode === undefined) {
|
|
||||||
throw new ModelvalidationError('Source not valid.');
|
|
||||||
}
|
|
||||||
const destinationNode = nodes.filter((node) => node.connectors.some((connector) => connector.id === edge.destination))[0];
|
|
||||||
if (destinationNode === undefined) {
|
|
||||||
throw new ModelvalidationError('Destination not valid.');
|
|
||||||
}
|
|
||||||
if (sourceNode === destinationNode) {
|
|
||||||
throw new ModelvalidationError('Edge with same source and destination nodes.');
|
|
||||||
}
|
|
||||||
return edge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public validateEdge(edge: FcEdge, nodes: Array<FcNode>): FcEdge {
|
|
||||||
this.validateNodes(nodes);
|
|
||||||
return this._validateEdge(edge, nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public validateConnector(connector: FcConnector): FcConnector {
|
|
||||||
if (connector.id === undefined) {
|
|
||||||
throw new ModelvalidationError('Id not valid.');
|
|
||||||
}
|
|
||||||
if (connector.type === undefined || connector.type === null || typeof connector.type !== 'string') {
|
|
||||||
throw new ModelvalidationError('Type not valid.');
|
|
||||||
}
|
|
||||||
return connector;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import { FcConnector, FcEdge, FcNode } from './ngx-flowchart.models';
|
|
||||||
|
|
||||||
export class FcMouseOverService {
|
|
||||||
|
|
||||||
mouseoverscope: MouseOverScope = {
|
|
||||||
connector: null,
|
|
||||||
edge: null,
|
|
||||||
node: null
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly applyFunction: <T>(fn: (...args: any[]) => T) => T;
|
|
||||||
|
|
||||||
constructor(applyFunction: <T>(fn: (...args: any[]) => T) => T) {
|
|
||||||
this.applyFunction = applyFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public nodeMouseOver(event: MouseEvent, node: FcNode) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
this.mouseoverscope.node = node;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public nodeMouseOut(event: MouseEvent, node: FcNode) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
this.mouseoverscope.node = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectorMouseEnter(event: MouseEvent, connector: FcConnector) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
this.mouseoverscope.connector = connector;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectorMouseLeave(event: MouseEvent, connector: FcConnector) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
this.mouseoverscope.connector = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public edgeMouseEnter(event: MouseEvent, edge: FcEdge) {
|
|
||||||
this.mouseoverscope.edge = edge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public edgeMouseLeave(event: MouseEvent, edge: FcEdge) {
|
|
||||||
this.mouseoverscope.edge = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MouseOverScope {
|
|
||||||
connector: FcConnector;
|
|
||||||
edge: FcEdge;
|
|
||||||
node: FcNode;
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
<div (click)="canvasClick($event)" class="fc-canvas-container">
|
|
||||||
<svg class="fc-canvas-svg">
|
|
||||||
<defs>
|
|
||||||
<marker class="fc-arrow-marker" [attr.id]="arrowDefId" markerWidth="5" markerHeight="5" viewBox="-6 -6 12 12" refX="10" refY="0" markerUnits="strokeWidth" orient="auto">
|
|
||||||
<polygon points="-2,0 -5,5 5,0 -5,-5" stroke="gray" fill="gray" stroke-width="1px"/>
|
|
||||||
</marker>
|
|
||||||
<marker class="fc-arrow-marker-selected" [attr.id]="arrowDefIdSelected" markerWidth="5" markerHeight="5" viewBox="-6 -6 12 12" refX="10" refY="0" markerUnits="strokeWidth" orient="auto">
|
|
||||||
<polygon points="-2,0 -5,5 5,0 -5,-5" stroke="red" fill="red" stroke-width="1px"/>
|
|
||||||
</marker>
|
|
||||||
</defs>
|
|
||||||
<g *ngFor="let edge of model.edges; let $index = index">
|
|
||||||
<path
|
|
||||||
[attr.id]="'fc-edge-path-'+$index"
|
|
||||||
(mousedown)="edgeMouseDown($event, edge)"
|
|
||||||
(click)="edgeClick($event, edge)"
|
|
||||||
(dblclick)="edgeDoubleClick($event, edge)"
|
|
||||||
(mouseover)="edgeMouseOver($event, edge)"
|
|
||||||
(mouseenter)="edgeMouseEnter($event, edge)"
|
|
||||||
(mouseleave)="edgeMouseLeave($event, edge)"
|
|
||||||
[attr.class]="(modelService.edges.isSelected(edge) && flowchartConstants.selectedClass + ' ' + flowchartConstants.edgeClass) ||
|
|
||||||
edge === mouseoverService.mouseoverscope.edge && flowchartConstants.hoverClass + ' ' + flowchartConstants.edgeClass ||
|
|
||||||
edge.active && flowchartConstants.activeClass + ' ' + flowchartConstants.edgeClass ||
|
|
||||||
flowchartConstants.edgeClass"
|
|
||||||
[attr.d]="getEdgeDAttribute(edge)"
|
|
||||||
[attr.marker-end]="'url(#' + (modelService.edges.isSelected(edge) ? arrowDefIdSelected : arrowDefId) + ')'">
|
|
||||||
</path>
|
|
||||||
</g>
|
|
||||||
<g *ngIf="dragAnimation === flowchartConstants.dragAnimationRepaint && edgeDraggingService.edgeDragging.isDragging">
|
|
||||||
<path [attr.class]="flowchartConstants.edgeClass + ' ' + flowchartConstants.draggingClass"
|
|
||||||
[attr.d]="edgeDrawingService.getEdgeDAttribute(edgeDraggingService.edgeDragging.dragPoint1, edgeDraggingService.edgeDragging.dragPoint2, edgeStyle)"></path>
|
|
||||||
<circle class="edge-endpoint" r="4"
|
|
||||||
[attr.cx]="edgeDraggingService.edgeDragging.dragPoint2.x"
|
|
||||||
[attr.cy]="edgeDraggingService.edgeDragging.dragPoint2.y">
|
|
||||||
</circle>
|
|
||||||
</g>
|
|
||||||
<g *ngIf="dragAnimation === flowchartConstants.dragAnimationShadow"
|
|
||||||
class="shadow-svg-class {{ flowchartConstants.edgeClass }} {{ flowchartConstants.draggingClass }}"
|
|
||||||
style="display:none">
|
|
||||||
<path d=""></path>
|
|
||||||
<circle class="edge-endpoint" r="4"></circle>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<ng-container *ngFor="let node of model.nodes">
|
|
||||||
<fc-node
|
|
||||||
[selected]="modelService.nodes.isSelected(node)"
|
|
||||||
[edit]="modelService.nodes.isEdit(node)"
|
|
||||||
[underMouse]="node === mouseoverService.mouseoverscope.node"
|
|
||||||
[node]="node"
|
|
||||||
[mouseOverConnector]="mouseoverService.mouseoverscope.connector"
|
|
||||||
[modelservice]="modelService"
|
|
||||||
[dragging]="nodeDraggingService.isDraggingNode(node)"
|
|
||||||
[callbacks]="callbacks"
|
|
||||||
[userNodeCallbacks]="userNodeCallbacks">
|
|
||||||
</fc-node>
|
|
||||||
</ng-container>
|
|
||||||
<div *ngIf="dragAnimation === flowchartConstants.dragAnimationRepaint && edgeDraggingService.edgeDragging.isDragging"
|
|
||||||
[attr.class]="'fc-noselect ' + flowchartConstants.edgeLabelClass"
|
|
||||||
[ngStyle]="{
|
|
||||||
top: (edgeDrawingService.getEdgeCenter(edgeDraggingService.edgeDragging.dragPoint1, edgeDraggingService.edgeDragging.dragPoint2).y)+'px',
|
|
||||||
left: (edgeDrawingService.getEdgeCenter(edgeDraggingService.edgeDragging.dragPoint1, edgeDraggingService.edgeDragging.dragPoint2).x)+'px'
|
|
||||||
}">
|
|
||||||
<div class="fc-edge-label-text">
|
|
||||||
<span [attr.id]="'fc-edge-label-dragging'" *ngIf="edgeDraggingService.edgeDragging.dragLabel">{{edgeDraggingService.edgeDragging.dragLabel}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
(mousedown)="edgeMouseDown($event, edge)"
|
|
||||||
(click)="edgeClick($event, edge)"
|
|
||||||
(dblclick)="edgeDoubleClick($event, edge)"
|
|
||||||
(mouseover)="edgeMouseOver($event, edge)"
|
|
||||||
(mouseenter)="edgeMouseEnter($event, edge)"
|
|
||||||
(mouseleave)="edgeMouseLeave($event, edge)"
|
|
||||||
[attr.class]="'fc-noselect ' + ((modelService.edges.isEdit(edge) && flowchartConstants.editClass + ' ' + flowchartConstants.edgeLabelClass) ||
|
|
||||||
(modelService.edges.isSelected(edge) && flowchartConstants.selectedClass + ' ' + flowchartConstants.edgeLabelClass) ||
|
|
||||||
edge === mouseoverService.mouseoverscope.edge && flowchartConstants.hoverClass + ' ' + flowchartConstants.edgeLabelClass ||
|
|
||||||
edge.active && flowchartConstants.activeClass + ' ' + flowchartConstants.edgeLabelClass ||
|
|
||||||
flowchartConstants.edgeLabelClass)"
|
|
||||||
[ngStyle]="{
|
|
||||||
top: (edgeDrawingService.getEdgeCenter(modelService.edges.sourceCoord(edge), modelService.edges.destCoord(edge)).y)+'px',
|
|
||||||
left: (edgeDrawingService.getEdgeCenter(modelService.edges.sourceCoord(edge), modelService.edges.destCoord(edge)).x)+'px'
|
|
||||||
}"
|
|
||||||
*ngFor="let edge of model.edges; let $index = index">
|
|
||||||
<div class="fc-edge-label-text">
|
|
||||||
<div *ngIf="modelService.isEditable()" class="fc-noselect fc-nodeedit" (click)="edgeEdit($event, edge)">
|
|
||||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="modelService.isEditable()" class="fc-noselect fc-nodedelete" (click)="edgeRemove($event, edge)">
|
|
||||||
×
|
|
||||||
</div>
|
|
||||||
<span [attr.id]="'fc-edge-label-'+$index" *ngIf="edge.label">{{edge.label}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="select-rectangle" class="fc-select-rectangle" hidden>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-size: 25px 25px;
|
|
||||||
background-image: linear-gradient(to right, rgba(0, 0, 0, .1) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 0, 0, .1) 1px, transparent 1px);
|
|
||||||
background-color: transparent;
|
|
||||||
min-width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
.fc-canvas-container {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
svg.fc-canvas-svg {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-edge {
|
|
||||||
stroke: gray;
|
|
||||||
stroke-width: 4;
|
|
||||||
transition: stroke-width .2s;
|
|
||||||
fill: transparent;
|
|
||||||
&.fc-hover {
|
|
||||||
stroke: gray;
|
|
||||||
stroke-width: 6;
|
|
||||||
fill: transparent;
|
|
||||||
}
|
|
||||||
&.fc-selected {
|
|
||||||
stroke: red;
|
|
||||||
stroke-width: 4;
|
|
||||||
fill: transparent;
|
|
||||||
}
|
|
||||||
&.fc-active {
|
|
||||||
animation: dash 3s linear infinite;
|
|
||||||
stroke-dasharray: 20;
|
|
||||||
}
|
|
||||||
&.fc-dragging {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-arrow-marker polygon {
|
|
||||||
stroke: gray;
|
|
||||||
fill: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-arrow-marker-selected polygon {
|
|
||||||
stroke: red;
|
|
||||||
fill: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edge-endpoint {
|
|
||||||
fill: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-noselect {
|
|
||||||
-webkit-touch-callout: none; /* iOS Safari */
|
|
||||||
-webkit-user-select: none; /* Safari */
|
|
||||||
-khtml-user-select: none; /* Konqueror HTML */
|
|
||||||
-moz-user-select: none; /* Firefox */
|
|
||||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
|
||||||
user-select: none; /* Non-prefixed version, currently
|
|
||||||
supported by Chrome and Opera */
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-edge-label {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0.8;
|
|
||||||
transition: transform .2s;
|
|
||||||
transform-origin: bottom left;
|
|
||||||
margin: 0 auto;
|
|
||||||
.fc-edge-label-text {
|
|
||||||
position: absolute;
|
|
||||||
-webkit-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 16px;
|
|
||||||
span {
|
|
||||||
cursor: default;
|
|
||||||
border: solid #ff3d00;
|
|
||||||
border-radius: 10px;
|
|
||||||
color: #ff3d00;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 3px 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fc-nodeedit {
|
|
||||||
top: -30px;
|
|
||||||
right: 14px;
|
|
||||||
}
|
|
||||||
.fc-nodedelete {
|
|
||||||
top: -30px;
|
|
||||||
right: -13px;
|
|
||||||
}
|
|
||||||
&.fc-hover {
|
|
||||||
transform: scale(1.25);
|
|
||||||
}
|
|
||||||
&.fc-selected,
|
|
||||||
&.fc-edit {
|
|
||||||
.fc-edge-label-text {
|
|
||||||
span {
|
|
||||||
border: solid red;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-select-rectangle {
|
|
||||||
border: 2px dashed #5262ff;
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(20,125,255,0.1);
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes dash {
|
|
||||||
from {
|
|
||||||
stroke-dashoffset: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep {
|
|
||||||
|
|
||||||
.fc-nodeedit {
|
|
||||||
display: none;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-nodedelete {
|
|
||||||
display: none;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-edit {
|
|
||||||
.fc-nodeedit,
|
|
||||||
.fc-nodedelete {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
border: solid 2px #eee;
|
|
||||||
|
|
||||||
border-radius: 50%;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 20px;
|
|
||||||
|
|
||||||
height: 20px;
|
|
||||||
padding-top: 2px;
|
|
||||||
width: 22px;
|
|
||||||
|
|
||||||
background: #494949;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: bottom;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.fc-nodeedit {
|
|
||||||
top: -24px;
|
|
||||||
right: 16px;
|
|
||||||
}
|
|
||||||
.fc-nodedelete {
|
|
||||||
top: -24px;
|
|
||||||
right: -13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,318 +0,0 @@
|
|||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
DoCheck,
|
|
||||||
ElementRef,
|
|
||||||
EventEmitter,
|
|
||||||
HostBinding,
|
|
||||||
HostListener,
|
|
||||||
Input,
|
|
||||||
IterableDiffer,
|
|
||||||
IterableDiffers,
|
|
||||||
NgZone,
|
|
||||||
OnInit,
|
|
||||||
Output
|
|
||||||
} from '@angular/core';
|
|
||||||
import { FcCallbacks, FcEdge, FcModel, FcNode, FlowchartConstants, UserCallbacks, UserNodeCallbacks } from './ngx-flowchart.models';
|
|
||||||
import { FcModelService } from './model.service';
|
|
||||||
import { FcModelValidationService } from './modelvalidation.service';
|
|
||||||
import { FcNodeDraggingService } from './node-dragging.service';
|
|
||||||
import { FcEdgeDrawingService } from './edge-drawing.service';
|
|
||||||
import { FcEdgeDraggingService } from './edge-dragging.service';
|
|
||||||
import { FcMouseOverService } from './mouseover.service';
|
|
||||||
import { FcRectangleSelectService } from './rectangleselect.service';
|
|
||||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
import { debounceTime } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'fc-canvas',
|
|
||||||
templateUrl: './ngx-flowchart.component.html',
|
|
||||||
styleUrls: ['./ngx-flowchart.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class NgxFlowchartComponent implements OnInit, DoCheck {
|
|
||||||
|
|
||||||
@HostBinding('attr.class')
|
|
||||||
get canvasClass(): string {
|
|
||||||
return FlowchartConstants.canvasClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
model: FcModel;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
selectedObjects: any[];
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
edgeStyle: string;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
userCallbacks: UserCallbacks;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
automaticResize: boolean;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
dragAnimation: string;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
nodeWidth: number;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
nodeHeight: number;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
dropTargetId: string;
|
|
||||||
|
|
||||||
@Output()
|
|
||||||
modelChanged = new EventEmitter();
|
|
||||||
|
|
||||||
private fitModelSizeByDefaultValue = true;
|
|
||||||
get fitModelSizeByDefault(): boolean {
|
|
||||||
return this.fitModelSizeByDefaultValue;
|
|
||||||
}
|
|
||||||
@Input()
|
|
||||||
set fitModelSizeByDefault(value: boolean) {
|
|
||||||
this.fitModelSizeByDefaultValue = coerceBooleanProperty(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks: FcCallbacks;
|
|
||||||
|
|
||||||
userNodeCallbacks: UserNodeCallbacks;
|
|
||||||
|
|
||||||
modelService: FcModelService;
|
|
||||||
nodeDraggingService: FcNodeDraggingService;
|
|
||||||
edgeDraggingService: FcEdgeDraggingService;
|
|
||||||
mouseoverService: FcMouseOverService;
|
|
||||||
rectangleSelectService: FcRectangleSelectService;
|
|
||||||
|
|
||||||
arrowDefId: string;
|
|
||||||
arrowDefIdSelected: string;
|
|
||||||
|
|
||||||
flowchartConstants = FlowchartConstants;
|
|
||||||
|
|
||||||
private nodesDiffer: IterableDiffer<FcNode> = this.differs.find([]).create<FcNode>((index, item) => {
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
|
|
||||||
private edgesDiffer: IterableDiffer<FcEdge> = this.differs.find([]).create<FcEdge>((index, item) => {
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
|
|
||||||
private readonly detectChangesSubject = new Subject<any>();
|
|
||||||
|
|
||||||
constructor(private elementRef: ElementRef<HTMLElement>,
|
|
||||||
private differs: IterableDiffers,
|
|
||||||
private modelValidation: FcModelValidationService,
|
|
||||||
public edgeDrawingService: FcEdgeDrawingService,
|
|
||||||
private cd: ChangeDetectorRef,
|
|
||||||
private zone: NgZone) {
|
|
||||||
this.arrowDefId = 'arrow-' + Math.random();
|
|
||||||
this.arrowDefIdSelected = this.arrowDefId + '-selected';
|
|
||||||
this.detectChangesSubject
|
|
||||||
.pipe(debounceTime(50))
|
|
||||||
.subscribe(() => this.cd.detectChanges());
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
if (!this.dropTargetId && this.edgeStyle !== FlowchartConstants.curvedStyle && this.edgeStyle !== FlowchartConstants.lineStyle) {
|
|
||||||
throw new Error('edgeStyle not supported.');
|
|
||||||
}
|
|
||||||
this.nodeHeight = this.nodeHeight || 200;
|
|
||||||
this.nodeWidth = this.nodeWidth || 200;
|
|
||||||
this.dragAnimation = this.dragAnimation || FlowchartConstants.dragAnimationRepaint;
|
|
||||||
this.userCallbacks = this.userCallbacks || {};
|
|
||||||
this.automaticResize = this.automaticResize || false;
|
|
||||||
|
|
||||||
for (const key of Object.keys(this.userCallbacks)) {
|
|
||||||
const callback = this.userCallbacks[key];
|
|
||||||
if (typeof callback !== 'function' && key !== 'nodeCallbacks') {
|
|
||||||
throw new Error('All callbacks should be functions.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userNodeCallbacks = this.userCallbacks.nodeCallbacks;
|
|
||||||
|
|
||||||
const element = $(this.elementRef.nativeElement);
|
|
||||||
|
|
||||||
this.modelService = new FcModelService(this.modelValidation, this.model, this.modelChanged,
|
|
||||||
this.detectChangesSubject, this.selectedObjects,
|
|
||||||
this.userCallbacks.dropNode, this.userCallbacks.createEdge, this.userCallbacks.edgeAdded, this.userCallbacks.nodeRemoved,
|
|
||||||
this.userCallbacks.edgeRemoved, element[0], element[0].querySelector('svg'));
|
|
||||||
|
|
||||||
if (this.dropTargetId) {
|
|
||||||
this.modelService.dropTargetId = this.dropTargetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyFunction = this.zone.run.bind(this.zone);
|
|
||||||
|
|
||||||
this.nodeDraggingService = new FcNodeDraggingService(this.modelService, applyFunction,
|
|
||||||
this.automaticResize, this.dragAnimation);
|
|
||||||
|
|
||||||
this.edgeDraggingService = new FcEdgeDraggingService(this.modelValidation, this.edgeDrawingService, this.modelService,
|
|
||||||
this.model, this.userCallbacks.isValidEdge || null, applyFunction,
|
|
||||||
this.dragAnimation, this.edgeStyle);
|
|
||||||
|
|
||||||
this.mouseoverService = new FcMouseOverService(applyFunction);
|
|
||||||
|
|
||||||
this.rectangleSelectService = new FcRectangleSelectService(this.modelService,
|
|
||||||
element[0].querySelector('#select-rectangle'), applyFunction);
|
|
||||||
|
|
||||||
this.callbacks = {
|
|
||||||
nodeDragstart: this.nodeDraggingService.dragstart.bind(this.nodeDraggingService),
|
|
||||||
nodeDragend: this.nodeDraggingService.dragend.bind(this.nodeDraggingService),
|
|
||||||
edgeDragstart: this.edgeDraggingService.dragstart.bind(this.edgeDraggingService),
|
|
||||||
edgeDragend: this.edgeDraggingService.dragend.bind(this.edgeDraggingService),
|
|
||||||
edgeDrop: this.edgeDraggingService.drop.bind(this.edgeDraggingService),
|
|
||||||
edgeDragoverConnector: this.edgeDraggingService.dragoverConnector.bind(this.edgeDraggingService),
|
|
||||||
edgeDragoverMagnet: this.edgeDraggingService.dragoverMagnet.bind(this.edgeDraggingService),
|
|
||||||
edgeDragleaveMagnet: this.edgeDraggingService.dragleaveMagnet.bind(this.edgeDraggingService),
|
|
||||||
nodeMouseOver: this.mouseoverService.nodeMouseOver.bind(this.mouseoverService),
|
|
||||||
nodeMouseOut: this.mouseoverService.nodeMouseOut.bind(this.mouseoverService),
|
|
||||||
connectorMouseEnter: this.mouseoverService.connectorMouseEnter.bind(this.mouseoverService),
|
|
||||||
connectorMouseLeave: this.mouseoverService.connectorMouseLeave.bind(this.mouseoverService),
|
|
||||||
nodeClicked: (event, node) => {
|
|
||||||
this.modelService.nodes.handleClicked(node, event.ctrlKey);
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.adjustCanvasSize(this.fitModelSizeByDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngDoCheck(): void {
|
|
||||||
if (this.model) {
|
|
||||||
const nodesChange = this.nodesDiffer.diff(this.model.nodes);
|
|
||||||
const edgesChange = this.edgesDiffer.diff(this.model.edges);
|
|
||||||
let nodesChanged = false;
|
|
||||||
let edgesChanged = false;
|
|
||||||
if (nodesChange !== null) {
|
|
||||||
nodesChange.forEachAddedItem(() => {
|
|
||||||
nodesChanged = true;
|
|
||||||
});
|
|
||||||
nodesChange.forEachRemovedItem(() => {
|
|
||||||
nodesChanged = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (edgesChange !== null) {
|
|
||||||
edgesChange.forEachAddedItem(() => {
|
|
||||||
edgesChanged = true;
|
|
||||||
});
|
|
||||||
edgesChange.forEachRemovedItem(() => {
|
|
||||||
edgesChanged = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (nodesChanged) {
|
|
||||||
this.adjustCanvasSize(this.fitModelSizeByDefault);
|
|
||||||
}
|
|
||||||
if (nodesChanged || edgesChanged) {
|
|
||||||
this.detectChangesSubject.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getEdgeDAttribute(edge: FcEdge): string {
|
|
||||||
return this.edgeDrawingService.getEdgeDAttribute(this.modelService.edges.sourceCoord(edge),
|
|
||||||
this.modelService.edges.destCoord(edge), this.edgeStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public adjustCanvasSize(fit?: boolean) {
|
|
||||||
let maxX = 0;
|
|
||||||
let maxY = 0;
|
|
||||||
const element = $(this.elementRef.nativeElement);
|
|
||||||
this.model.nodes.forEach((node) => {
|
|
||||||
maxX = Math.max(node.x + this.nodeWidth, maxX);
|
|
||||||
maxY = Math.max(node.y + this.nodeHeight, maxY);
|
|
||||||
});
|
|
||||||
let width;
|
|
||||||
let height;
|
|
||||||
if (fit) {
|
|
||||||
width = maxX;
|
|
||||||
height = maxY;
|
|
||||||
} else {
|
|
||||||
width = Math.max(maxX, element.prop('offsetWidth'));
|
|
||||||
height = Math.max(maxY, element.prop('offsetHeight'));
|
|
||||||
}
|
|
||||||
element.css('width', width + 'px');
|
|
||||||
element.css('height', height + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
canvasClick(event: MouseEvent) {}
|
|
||||||
|
|
||||||
edgeMouseDown(event: MouseEvent, edge: FcEdge) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeClick(event: MouseEvent, edge: FcEdge) {
|
|
||||||
this.modelService.edges.handleEdgeMouseClick(edge, event.ctrlKey);
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeRemove(event: Event, edge: FcEdge) {
|
|
||||||
this.modelService.edges.delete(edge);
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeEdit(event: Event, edge: FcEdge) {
|
|
||||||
if (this.userCallbacks.edgeEdit) {
|
|
||||||
this.userCallbacks.edgeEdit(event, edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeDoubleClick(event: MouseEvent, edge: FcEdge) {
|
|
||||||
if (this.userCallbacks.edgeDoubleClick) {
|
|
||||||
this.userCallbacks.edgeDoubleClick(event, edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeMouseOver(event: MouseEvent, edge: FcEdge) {
|
|
||||||
if (this.userCallbacks.edgeMouseOver) {
|
|
||||||
this.userCallbacks.edgeMouseOver(event, edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeMouseEnter(event: MouseEvent, edge: FcEdge) {
|
|
||||||
this.mouseoverService.edgeMouseEnter(event, edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeMouseLeave(event: MouseEvent, edge: FcEdge) {
|
|
||||||
this.mouseoverService.edgeMouseLeave(event, edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragover', ['$event'])
|
|
||||||
dragover(event: Event | any) {
|
|
||||||
this.nodeDraggingService.dragover(event);
|
|
||||||
this.edgeDraggingService.dragover(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('drop', ['$event'])
|
|
||||||
drop(event: Event | any) {
|
|
||||||
if (event.preventDefault) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
if (event.stopPropagation) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
this.nodeDraggingService.drop(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mousedown', ['$event'])
|
|
||||||
mousedown(event: MouseEvent) {
|
|
||||||
this.rectangleSelectService.mousedown(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mousemove', ['$event'])
|
|
||||||
mousemove(event: MouseEvent) {
|
|
||||||
this.rectangleSelectService.mousemove(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mouseup', ['$event'])
|
|
||||||
mouseup(event: MouseEvent) {
|
|
||||||
this.rectangleSelectService.mouseup(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
import { Observable } from 'rxjs';
|
|
||||||
import { InjectionToken, Type } from '@angular/core';
|
|
||||||
import { FcNodeComponent } from './node.component';
|
|
||||||
|
|
||||||
export const FC_NODE_COMPONENT_CONFIG = new InjectionToken<FcNodeComponentConfig>('fc-node.component.config');
|
|
||||||
|
|
||||||
export interface FcNodeComponentConfig {
|
|
||||||
nodeComponentType: Type<FcNodeComponent>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const htmlPrefix = 'fc';
|
|
||||||
const leftConnectorType = 'leftConnector';
|
|
||||||
const rightConnectorType = 'rightConnector';
|
|
||||||
|
|
||||||
export const FlowchartConstants = {
|
|
||||||
htmlPrefix,
|
|
||||||
leftConnectorType,
|
|
||||||
rightConnectorType,
|
|
||||||
curvedStyle: 'curved',
|
|
||||||
lineStyle: 'line',
|
|
||||||
dragAnimationRepaint: 'repaint',
|
|
||||||
dragAnimationShadow: 'shadow',
|
|
||||||
canvasClass: htmlPrefix + '-canvas',
|
|
||||||
selectedClass: htmlPrefix + '-selected',
|
|
||||||
editClass: htmlPrefix + '-edit',
|
|
||||||
activeClass: htmlPrefix + '-active',
|
|
||||||
hoverClass: htmlPrefix + '-hover',
|
|
||||||
draggingClass: htmlPrefix + '-dragging',
|
|
||||||
edgeClass: htmlPrefix + '-edge',
|
|
||||||
edgeLabelClass: htmlPrefix + '-edge-label',
|
|
||||||
connectorClass: htmlPrefix + '-connector',
|
|
||||||
magnetClass: htmlPrefix + '-magnet',
|
|
||||||
nodeClass: htmlPrefix + '-node',
|
|
||||||
nodeOverlayClass: htmlPrefix + '-node-overlay',
|
|
||||||
leftConnectorClass: htmlPrefix + '-' + leftConnectorType + 's',
|
|
||||||
rightConnectorClass: htmlPrefix + '-' + rightConnectorType + 's',
|
|
||||||
canvasResizeThreshold: 200,
|
|
||||||
canvasResizeStep: 200
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export interface FcCoords {
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcRectBox {
|
|
||||||
top: number;
|
|
||||||
left: number;
|
|
||||||
right: number;
|
|
||||||
bottom: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcConnector {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcNode extends FcCoords {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
connectors: Array<FcConnector>;
|
|
||||||
readonly?: boolean;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcNodeRectInfo {
|
|
||||||
width(): number;
|
|
||||||
height(): number;
|
|
||||||
top(): number;
|
|
||||||
left(): number;
|
|
||||||
right(): number;
|
|
||||||
bottom(): number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcConnectorRectInfo {
|
|
||||||
type: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
nodeRectInfo: FcNodeRectInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcEdge {
|
|
||||||
label?: string;
|
|
||||||
source?: string;
|
|
||||||
destination?: string;
|
|
||||||
active?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcItemInfo {
|
|
||||||
node?: FcNode;
|
|
||||||
edge?: FcEdge;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcModel {
|
|
||||||
nodes: Array<FcNode>;
|
|
||||||
edges: Array<FcEdge>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserCallbacks {
|
|
||||||
dropNode?: (event: Event, node: FcNode) => void;
|
|
||||||
createEdge?: (event: Event, edge: FcEdge) => Observable<FcEdge>;
|
|
||||||
edgeAdded?: (edge: FcEdge) => void;
|
|
||||||
nodeRemoved?: (node: FcNode) => void;
|
|
||||||
edgeRemoved?: (edge: FcEdge) => void;
|
|
||||||
edgeDoubleClick?: (event: MouseEvent, edge: FcEdge) => void;
|
|
||||||
edgeMouseOver?: (event: MouseEvent, edge: FcEdge) => void;
|
|
||||||
isValidEdge?: (source: FcConnector, destination: FcConnector) => boolean;
|
|
||||||
edgeEdit?: (event: Event, edge: FcEdge) => void;
|
|
||||||
nodeCallbacks?: UserNodeCallbacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserNodeCallbacks {
|
|
||||||
nodeEdit?: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
doubleClick?: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
mouseDown?: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
mouseEnter?: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
mouseLeave?: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcCallbacks {
|
|
||||||
nodeDragstart: (event: Event | any, node: FcNode) => void;
|
|
||||||
nodeDragend: (event: Event | any) => void;
|
|
||||||
edgeDragstart: (event: Event | any, connector: FcConnector) => void;
|
|
||||||
edgeDragend: (event: Event | any) => void;
|
|
||||||
edgeDrop: (event: Event | any, targetConnector: FcConnector) => boolean;
|
|
||||||
edgeDragoverConnector: (event: Event | any, connector: FcConnector) => boolean;
|
|
||||||
edgeDragoverMagnet: (event: Event | any, connector: FcConnector) => boolean;
|
|
||||||
edgeDragleaveMagnet: (event: Event | any) => void;
|
|
||||||
nodeMouseOver: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
nodeMouseOut: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
connectorMouseEnter: (event: MouseEvent, connector: FcConnector) => void;
|
|
||||||
connectorMouseLeave: (event: MouseEvent, connector: FcConnector) => void;
|
|
||||||
nodeClicked: (event: MouseEvent, node: FcNode) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FcAdjacentList {
|
|
||||||
[id: string]: {
|
|
||||||
incoming: number;
|
|
||||||
outgoing: Array<string>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseError {
|
|
||||||
constructor() {
|
|
||||||
Error.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(BaseError, 'prototype', new Error());
|
|
||||||
|
|
||||||
export class ModelvalidationError extends BaseError {
|
|
||||||
constructor(public message: string) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fcTopSort(graph: FcModel): Array<string> | null {
|
|
||||||
const adjacentList: FcAdjacentList = {};
|
|
||||||
graph.nodes.forEach((node) => {
|
|
||||||
adjacentList[node.id] = {incoming: 0, outgoing: []};
|
|
||||||
});
|
|
||||||
graph.edges.forEach((edge) => {
|
|
||||||
const sourceNode = graph.nodes.filter((node) => {
|
|
||||||
return node.connectors.some((connector) => {
|
|
||||||
return connector.id === edge.source;
|
|
||||||
});
|
|
||||||
})[0];
|
|
||||||
const destinationNode = graph.nodes.filter((node) => {
|
|
||||||
return node.connectors.some((connector) => {
|
|
||||||
return connector.id === edge.destination;
|
|
||||||
});
|
|
||||||
})[0];
|
|
||||||
adjacentList[sourceNode.id].outgoing.push(destinationNode.id);
|
|
||||||
adjacentList[destinationNode.id].incoming++;
|
|
||||||
});
|
|
||||||
const orderedNodes: string[] = [];
|
|
||||||
const sourceNodes: string[] = [];
|
|
||||||
for (const node of Object.keys(adjacentList)) {
|
|
||||||
const edges = adjacentList[node];
|
|
||||||
if (edges.incoming === 0) {
|
|
||||||
sourceNodes.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (sourceNodes.length !== 0) {
|
|
||||||
const sourceNode = sourceNodes.pop();
|
|
||||||
for (let i = 0; i < adjacentList[sourceNode].outgoing.length; i++) {
|
|
||||||
const destinationNode = adjacentList[sourceNode].outgoing[i];
|
|
||||||
adjacentList[destinationNode].incoming--;
|
|
||||||
if (adjacentList[destinationNode].incoming === 0) {
|
|
||||||
sourceNodes.push(destinationNode);
|
|
||||||
}
|
|
||||||
adjacentList[sourceNode].outgoing.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
orderedNodes.push(sourceNode);
|
|
||||||
}
|
|
||||||
let hasEdges = false;
|
|
||||||
for (const node of Object.keys(adjacentList)) {
|
|
||||||
const edges = adjacentList[node];
|
|
||||||
if (edges.incoming !== 0) {
|
|
||||||
hasEdges = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasEdges) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return orderedNodes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { NgxFlowchartComponent } from './ngx-flowchart.component';
|
|
||||||
import { FcModelValidationService } from './modelvalidation.service';
|
|
||||||
import { FcEdgeDrawingService } from './edge-drawing.service';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FcMagnetDirective } from './magnet.directive';
|
|
||||||
import { FcConnectorDirective } from './connector.directive';
|
|
||||||
import { FcNodeContainerComponent } from './node.component';
|
|
||||||
import { FC_NODE_COMPONENT_CONFIG } from './ngx-flowchart.models';
|
|
||||||
import { DefaultFcNodeComponent } from './default-node.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
entryComponents: [
|
|
||||||
DefaultFcNodeComponent
|
|
||||||
],
|
|
||||||
declarations: [NgxFlowchartComponent,
|
|
||||||
FcMagnetDirective,
|
|
||||||
FcConnectorDirective,
|
|
||||||
FcNodeContainerComponent,
|
|
||||||
DefaultFcNodeComponent],
|
|
||||||
providers: [
|
|
||||||
FcModelValidationService,
|
|
||||||
FcEdgeDrawingService,
|
|
||||||
{
|
|
||||||
provide: FC_NODE_COMPONENT_CONFIG,
|
|
||||||
useValue: {
|
|
||||||
nodeComponentType: DefaultFcNodeComponent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
CommonModule
|
|
||||||
],
|
|
||||||
exports: [NgxFlowchartComponent,
|
|
||||||
FcMagnetDirective,
|
|
||||||
FcConnectorDirective,
|
|
||||||
DefaultFcNodeComponent]
|
|
||||||
})
|
|
||||||
export class NgxFlowchartModule { }
|
|
||||||
@ -1,341 +0,0 @@
|
|||||||
import { FcModelService } from './model.service';
|
|
||||||
import { FcCoords, FcNode, FlowchartConstants } from './ngx-flowchart.models';
|
|
||||||
|
|
||||||
const nodeDropScope: NodeDropScope = {
|
|
||||||
dropElement: null
|
|
||||||
};
|
|
||||||
|
|
||||||
export class FcNodeDraggingService {
|
|
||||||
|
|
||||||
nodeDraggingScope: NodeDraggingScope = {
|
|
||||||
shadowDragStarted: false,
|
|
||||||
dropElement: null,
|
|
||||||
draggedNodes: [],
|
|
||||||
shadowElements: []
|
|
||||||
};
|
|
||||||
|
|
||||||
private dragOffsets: FcCoords[] = [];
|
|
||||||
private draggedElements: HTMLElement[] = [];
|
|
||||||
|
|
||||||
private destinationHtmlElements: HTMLElement[] = [];
|
|
||||||
private oldDisplayStyles: string[] = [];
|
|
||||||
|
|
||||||
private readonly modelService: FcModelService;
|
|
||||||
private readonly automaticResize: boolean;
|
|
||||||
private readonly dragAnimation: string;
|
|
||||||
private readonly applyFunction: <T>(fn: (...args: any[]) => T) => T;
|
|
||||||
|
|
||||||
constructor(modelService: FcModelService,
|
|
||||||
applyFunction: <T>(fn: (...args: any[]) => T) => T,
|
|
||||||
automaticResize: boolean, dragAnimation: string) {
|
|
||||||
this.modelService = modelService;
|
|
||||||
this.automaticResize = automaticResize;
|
|
||||||
this.dragAnimation = dragAnimation;
|
|
||||||
this.applyFunction = applyFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCoordinate(coordinate: number, max: number): number {
|
|
||||||
coordinate = Math.max(coordinate, 0);
|
|
||||||
coordinate = Math.min(coordinate, max);
|
|
||||||
return coordinate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getXCoordinate(x: number): number {
|
|
||||||
return this.getCoordinate(x, this.modelService.canvasHtmlElement.offsetWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getYCoordinate(y: number): number {
|
|
||||||
return this.getCoordinate(y, this.modelService.canvasHtmlElement.offsetHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private resizeCanvas(draggedNode: FcNode, nodeElement: HTMLElement) {
|
|
||||||
if (this.automaticResize && !this.modelService.isDropSource()) {
|
|
||||||
const canvasElement = this.modelService.canvasHtmlElement;
|
|
||||||
if (canvasElement.offsetWidth < draggedNode.x + nodeElement.offsetWidth + FlowchartConstants.canvasResizeThreshold) {
|
|
||||||
canvasElement.style.width = canvasElement.offsetWidth + FlowchartConstants.canvasResizeStep + 'px';
|
|
||||||
}
|
|
||||||
if (canvasElement.offsetHeight < draggedNode.y + nodeElement.offsetHeight + FlowchartConstants.canvasResizeThreshold) {
|
|
||||||
canvasElement.style.height = canvasElement.offsetHeight + FlowchartConstants.canvasResizeStep + 'px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public isDraggingNode(node: FcNode): boolean {
|
|
||||||
return this.nodeDraggingScope.draggedNodes.includes(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragstart(event: Event | any, node: FcNode) {
|
|
||||||
if (node.readonly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.dragOffsets.length = 0;
|
|
||||||
this.draggedElements.length = 0;
|
|
||||||
this.nodeDraggingScope.draggedNodes.length = 0;
|
|
||||||
this.nodeDraggingScope.shadowElements.length = 0;
|
|
||||||
this.destinationHtmlElements.length = 0;
|
|
||||||
this.oldDisplayStyles.length = 0;
|
|
||||||
const elements: Array<JQuery<HTMLElement>> = [];
|
|
||||||
const nodes: Array<FcNode> = [];
|
|
||||||
if (this.modelService.nodes.isSelected(node)) {
|
|
||||||
const selectedNodes = this.modelService.nodes.getSelectedNodes();
|
|
||||||
for (const selectedNode of selectedNodes) {
|
|
||||||
const element = $(this.modelService.nodes.getHtmlElement(selectedNode.id));
|
|
||||||
elements.push(element);
|
|
||||||
nodes.push(selectedNode);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
elements.push($(event.target as HTMLElement));
|
|
||||||
nodes.push(node);
|
|
||||||
}
|
|
||||||
const offsetsX: number[] = [];
|
|
||||||
const offsetsY: number[] = [];
|
|
||||||
for (const element of elements) {
|
|
||||||
offsetsX.push(parseInt(element.css('left'), 10) - event.clientX);
|
|
||||||
offsetsY.push(parseInt(element.css('top'), 10) - event.clientY);
|
|
||||||
}
|
|
||||||
const originalEvent: Event | any = (event as any).originalEvent || event;
|
|
||||||
if (this.modelService.isDropSource()) {
|
|
||||||
if (nodeDropScope.dropElement) {
|
|
||||||
nodeDropScope.dropElement.parentNode.removeChild(nodeDropScope.dropElement);
|
|
||||||
nodeDropScope.dropElement = null;
|
|
||||||
}
|
|
||||||
nodeDropScope.dropElement = elements[0][0].cloneNode(true) as NodeDropElement;
|
|
||||||
const offset = $(this.modelService.canvasHtmlElement).offset();
|
|
||||||
nodeDropScope.dropElement.offsetInfo = {
|
|
||||||
offsetX: Math.round(offsetsX[0] + offset.left),
|
|
||||||
offsetY: Math.round(offsetsY[0] + offset.top)
|
|
||||||
};
|
|
||||||
nodeDropScope.dropElement.style.position = 'absolute';
|
|
||||||
nodeDropScope.dropElement.style.pointerEvents = 'none';
|
|
||||||
nodeDropScope.dropElement.style.zIndex = '9999';
|
|
||||||
|
|
||||||
document.body.appendChild(nodeDropScope.dropElement);
|
|
||||||
const dropNodeInfo: DropNodeInfo = {
|
|
||||||
node,
|
|
||||||
dropTargetId: this.modelService.dropTargetId,
|
|
||||||
offsetX: Math.round(offsetsX[0] + offset.left),
|
|
||||||
offsetY: Math.round(offsetsY[0] + offset.top)
|
|
||||||
};
|
|
||||||
originalEvent.dataTransfer.setData('text', JSON.stringify(dropNodeInfo));
|
|
||||||
|
|
||||||
if (originalEvent.dataTransfer.setDragImage) {
|
|
||||||
originalEvent.dataTransfer.setDragImage(this.modelService.getDragImage(), 0, 0);
|
|
||||||
} else {
|
|
||||||
const target: HTMLElement = event.target as HTMLElement;
|
|
||||||
const cloneNode = target.cloneNode(true);
|
|
||||||
target.parentNode.insertBefore(cloneNode, target);
|
|
||||||
target.style.visibility = 'collapse';
|
|
||||||
setTimeout(() => {
|
|
||||||
target.parentNode.removeChild(cloneNode);
|
|
||||||
target.style.visibility = 'visible';
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.nodeDraggingScope.draggedNodes = nodes;
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
|
||||||
this.draggedElements.push(elements[i][0]);
|
|
||||||
this.dragOffsets.push(
|
|
||||||
{
|
|
||||||
x: offsetsX[i],
|
|
||||||
y: offsetsY[i]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
for (let i = 0; i < this.draggedElements.length; i++) {
|
|
||||||
const dragOffset = this.dragOffsets[i];
|
|
||||||
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
||||||
const shadowElement = $(`<div style="position: absolute; opacity: 0.7; ` +
|
|
||||||
`top: ${this.getYCoordinate(dragOffset.y + event.clientY)}px; ` +
|
|
||||||
`left: ${this.getXCoordinate(dragOffset.x + event.clientX)}px; ">` +
|
|
||||||
`<div class="innerNode"><p style="padding: 0 15px;">${draggedNode.name}</p> </div></div>`);
|
|
||||||
const targetInnerNode = $(this.draggedElements[i]).children()[0];
|
|
||||||
shadowElement.children()[0].style.backgroundColor = targetInnerNode.style.backgroundColor;
|
|
||||||
this.nodeDraggingScope.shadowElements.push(shadowElement);
|
|
||||||
this.modelService.canvasHtmlElement.appendChild(this.nodeDraggingScope.shadowElements[i][0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
originalEvent.dataTransfer.setData('text', 'Just to support firefox');
|
|
||||||
if (originalEvent.dataTransfer.setDragImage) {
|
|
||||||
originalEvent.dataTransfer.setDragImage(this.modelService.getDragImage(), 0, 0);
|
|
||||||
} else {
|
|
||||||
this.draggedElements.forEach((draggedElement) => {
|
|
||||||
const cloneNode = draggedElement.cloneNode(true);
|
|
||||||
draggedElement.parentNode.insertBefore(cloneNode, draggedElement);
|
|
||||||
draggedElement.style.visibility = 'collapse';
|
|
||||||
setTimeout(() => {
|
|
||||||
draggedElement.parentNode.removeChild(cloneNode);
|
|
||||||
draggedElement.style.visibility = 'visible';
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
for (let i = 0; i < this.draggedElements.length; i++) {
|
|
||||||
this.destinationHtmlElements.push(this.draggedElements[i]);
|
|
||||||
this.oldDisplayStyles.push(this.destinationHtmlElements[i].style.display);
|
|
||||||
this.destinationHtmlElements[i].style.display = 'none';
|
|
||||||
}
|
|
||||||
this.nodeDraggingScope.shadowDragStarted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public drop(event: Event | any): boolean {
|
|
||||||
if (this.modelService.isDropSource()) {
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let dropNode: FcNode = null;
|
|
||||||
const originalEvent: Event | any = (event as any).originalEvent || event;
|
|
||||||
const infoText = originalEvent.dataTransfer.getData('text');
|
|
||||||
if (infoText) {
|
|
||||||
let dropNodeInfo: DropNodeInfo = null;
|
|
||||||
try {
|
|
||||||
dropNodeInfo = JSON.parse(infoText);
|
|
||||||
} catch (e) {}
|
|
||||||
if (dropNodeInfo && dropNodeInfo.dropTargetId) {
|
|
||||||
if (this.modelService.canvasHtmlElement.id &&
|
|
||||||
this.modelService.canvasHtmlElement.id === dropNodeInfo.dropTargetId) {
|
|
||||||
dropNode = dropNodeInfo.node;
|
|
||||||
const offset = $(this.modelService.canvasHtmlElement).offset();
|
|
||||||
const x = event.clientX - offset.left;
|
|
||||||
const y = event.clientY - offset.top;
|
|
||||||
dropNode.x = Math.round(this.getXCoordinate(dropNodeInfo.offsetX + x));
|
|
||||||
dropNode.y = Math.round(this.getYCoordinate(dropNodeInfo.offsetY + y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dropNode) {
|
|
||||||
this.modelService.dropNode(event, dropNode);
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
} else if (this.nodeDraggingScope.draggedNodes.length) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
||||||
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
||||||
const dragOffset = this.dragOffsets[i];
|
|
||||||
draggedNode.x = Math.round(this.getXCoordinate(dragOffset.x + event.clientX));
|
|
||||||
draggedNode.y = Math.round(this.getYCoordinate(dragOffset.y + event.clientY));
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
this.modelService.notifyModelChanged();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragover(event: Event | any) {
|
|
||||||
if (nodeDropScope.dropElement) {
|
|
||||||
const offsetInfo = nodeDropScope.dropElement.offsetInfo;
|
|
||||||
nodeDropScope.dropElement.style.left = (offsetInfo.offsetX + event.clientX) + 'px';
|
|
||||||
nodeDropScope.dropElement.style.top = (offsetInfo.offsetY + event.clientY) + 'px';
|
|
||||||
if (this.nodeDraggingScope.shadowDragStarted) {
|
|
||||||
this.applyFunction(() => {
|
|
||||||
this.destinationHtmlElements[0].style.display = this.oldDisplayStyles[0];
|
|
||||||
this.nodeDraggingScope.shadowDragStarted = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.modelService.isDropSource()) {
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.nodeDraggingScope.draggedNodes.length) {
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.dragAnimation === FlowchartConstants.dragAnimationRepaint) {
|
|
||||||
if (this.nodeDraggingScope.draggedNodes.length) {
|
|
||||||
return this.applyFunction(() => {
|
|
||||||
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
||||||
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
||||||
const dragOffset = this.dragOffsets[i];
|
|
||||||
draggedNode.x = this.getXCoordinate(dragOffset.x + event.clientX);
|
|
||||||
draggedNode.y = this.getYCoordinate(dragOffset.y + event.clientY);
|
|
||||||
this.resizeCanvas(draggedNode, this.draggedElements[i]);
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
this.modelService.notifyModelChanged();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
||||||
if (this.nodeDraggingScope.draggedNodes.length) {
|
|
||||||
if (this.nodeDraggingScope.shadowDragStarted) {
|
|
||||||
this.applyFunction(() => {
|
|
||||||
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
||||||
this.destinationHtmlElements[i].style.display = this.oldDisplayStyles[i];
|
|
||||||
}
|
|
||||||
this.nodeDraggingScope.shadowDragStarted = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
||||||
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
||||||
const dragOffset = this.dragOffsets[i];
|
|
||||||
this.nodeDraggingScope.shadowElements[i].css('left', this.getXCoordinate(dragOffset.x + event.clientX) + 'px');
|
|
||||||
this.nodeDraggingScope.shadowElements[i].css('top', this.getYCoordinate(dragOffset.y + event.clientY) + 'px');
|
|
||||||
this.resizeCanvas(draggedNode, this.draggedElements[i]);
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public dragend(event: Event | any) {
|
|
||||||
this.applyFunction(() => {
|
|
||||||
if (nodeDropScope.dropElement) {
|
|
||||||
nodeDropScope.dropElement.parentNode.removeChild(nodeDropScope.dropElement);
|
|
||||||
nodeDropScope.dropElement = null;
|
|
||||||
}
|
|
||||||
if (this.modelService.isDropSource()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.nodeDraggingScope.shadowElements.length) {
|
|
||||||
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
||||||
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
||||||
const shadowElement = this.nodeDraggingScope.shadowElements[i];
|
|
||||||
draggedNode.x = parseInt(shadowElement.css('left').replace('px', ''), 10);
|
|
||||||
draggedNode.y = parseInt(shadowElement.css('top').replace('px', ''), 10);
|
|
||||||
this.modelService.canvasHtmlElement.removeChild(shadowElement[0]);
|
|
||||||
}
|
|
||||||
this.nodeDraggingScope.shadowElements.length = 0;
|
|
||||||
this.modelService.notifyModelChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.nodeDraggingScope.draggedNodes.length) {
|
|
||||||
this.nodeDraggingScope.draggedNodes.length = 0;
|
|
||||||
this.draggedElements.length = 0;
|
|
||||||
this.dragOffsets.length = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeDraggingScope {
|
|
||||||
draggedNodes: Array<FcNode>;
|
|
||||||
shadowElements: Array<JQuery<HTMLElement>>;
|
|
||||||
shadowDragStarted: boolean;
|
|
||||||
dropElement: HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeDropElement extends HTMLElement {
|
|
||||||
offsetInfo?: {
|
|
||||||
offsetX: number;
|
|
||||||
offsetY: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeDropScope {
|
|
||||||
dropElement: NodeDropElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DropNodeInfo {
|
|
||||||
node: FcNode;
|
|
||||||
dropTargetId: string;
|
|
||||||
offsetX: number;
|
|
||||||
offsetY: number;
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
:host {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
&.fc-dragging {
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep {
|
|
||||||
.fc-leftConnectors, .fc-rightConnectors {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
z-index: -10;
|
|
||||||
.fc-magnet {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-leftConnectors {
|
|
||||||
left: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-rightConnectors {
|
|
||||||
right: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-magnet {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
height: 60px;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-connector {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
|
|
||||||
border: 10px solid transparent;
|
|
||||||
-moz-background-clip: padding; /* Firefox 3.6 */
|
|
||||||
-webkit-background-clip: padding; /* Safari 4? Chrome 6? */
|
|
||||||
background-clip: padding-box;
|
|
||||||
border-radius: 50% 50%;
|
|
||||||
background-color: #F7A789;
|
|
||||||
color: #fff;
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
&.fc-hover {
|
|
||||||
background-color: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,270 +0,0 @@
|
|||||||
import {
|
|
||||||
AfterViewInit,
|
|
||||||
Component,
|
|
||||||
ComponentFactoryResolver,
|
|
||||||
Directive,
|
|
||||||
ElementRef,
|
|
||||||
HostBinding,
|
|
||||||
HostListener,
|
|
||||||
Inject,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
OnInit,
|
|
||||||
SimpleChanges,
|
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef
|
|
||||||
} from '@angular/core';
|
|
||||||
import {
|
|
||||||
FC_NODE_COMPONENT_CONFIG,
|
|
||||||
FcCallbacks,
|
|
||||||
FcConnector,
|
|
||||||
FcNode,
|
|
||||||
FcNodeComponentConfig,
|
|
||||||
FcNodeRectInfo,
|
|
||||||
FlowchartConstants,
|
|
||||||
UserNodeCallbacks
|
|
||||||
} from './ngx-flowchart.models';
|
|
||||||
import { FcModelService } from './model.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'fc-node',
|
|
||||||
template: '<ng-template #nodeContent></ng-template>',
|
|
||||||
styleUrls: ['./node.component.scss']
|
|
||||||
})
|
|
||||||
export class FcNodeContainerComponent implements OnInit, AfterViewInit, OnChanges {
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
callbacks: FcCallbacks;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
userNodeCallbacks: UserNodeCallbacks;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
node: FcNode;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
selected: boolean;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
edit: boolean;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
underMouse: boolean;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
mouseOverConnector: FcConnector;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
modelservice: FcModelService;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
dragging: boolean;
|
|
||||||
|
|
||||||
@HostBinding('attr.id')
|
|
||||||
get nodeId(): string {
|
|
||||||
return this.node.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostBinding('style.top')
|
|
||||||
get top(): string {
|
|
||||||
return this.node.y + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostBinding('style.left')
|
|
||||||
get left(): string {
|
|
||||||
return this.node.x + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeComponent: FcNodeComponent;
|
|
||||||
|
|
||||||
@ViewChild('nodeContent', {read: ViewContainerRef, static: true}) nodeContentContainer: ViewContainerRef;
|
|
||||||
|
|
||||||
constructor(@Inject(FC_NODE_COMPONENT_CONFIG) private nodeComponentConfig: FcNodeComponentConfig,
|
|
||||||
private elementRef: ElementRef<HTMLElement>,
|
|
||||||
private componentFactoryResolver: ComponentFactoryResolver) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (!this.userNodeCallbacks) {
|
|
||||||
this.userNodeCallbacks = {};
|
|
||||||
}
|
|
||||||
this.userNodeCallbacks.nodeEdit = this.userNodeCallbacks.nodeEdit || (() => {});
|
|
||||||
this.userNodeCallbacks.doubleClick = this.userNodeCallbacks.doubleClick || (() => {});
|
|
||||||
this.userNodeCallbacks.mouseDown = this.userNodeCallbacks.mouseDown || (() => {});
|
|
||||||
this.userNodeCallbacks.mouseEnter = this.userNodeCallbacks.mouseEnter || (() => {});
|
|
||||||
this.userNodeCallbacks.mouseLeave = this.userNodeCallbacks.mouseLeave || (() => {});
|
|
||||||
|
|
||||||
const element = $(this.elementRef.nativeElement);
|
|
||||||
element.addClass(FlowchartConstants.nodeClass);
|
|
||||||
if (!this.node.readonly) {
|
|
||||||
element.attr('draggable', 'true');
|
|
||||||
}
|
|
||||||
this.updateNodeClass();
|
|
||||||
this.modelservice.nodes.setHtmlElement(this.node.id, element[0]);
|
|
||||||
this.nodeContentContainer.clear();
|
|
||||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.nodeComponentConfig.nodeComponentType);
|
|
||||||
const componentRef = this.nodeContentContainer.createComponent(componentFactory);
|
|
||||||
this.nodeComponent = componentRef.instance;
|
|
||||||
this.nodeComponent.callbacks = this.callbacks;
|
|
||||||
this.nodeComponent.userNodeCallbacks = this.userNodeCallbacks;
|
|
||||||
this.nodeComponent.node = this.node;
|
|
||||||
this.nodeComponent.modelservice = this.modelservice;
|
|
||||||
this.updateNodeComponent();
|
|
||||||
this.nodeComponent.width = this.elementRef.nativeElement.offsetWidth;
|
|
||||||
this.nodeComponent.height = this.elementRef.nativeElement.offsetHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
|
||||||
this.nodeComponent.width = this.elementRef.nativeElement.offsetWidth;
|
|
||||||
this.nodeComponent.height = this.elementRef.nativeElement.offsetHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
|
||||||
let updateNode = false;
|
|
||||||
for (const propName of Object.keys(changes)) {
|
|
||||||
const change = changes[propName];
|
|
||||||
if (!change.firstChange && change.currentValue !== change.previousValue) {
|
|
||||||
if (['selected', 'edit', 'underMouse', 'mouseOverConnector', 'dragging'].includes(propName)) {
|
|
||||||
updateNode = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (updateNode) {
|
|
||||||
this.updateNodeClass();
|
|
||||||
this.updateNodeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateNodeClass() {
|
|
||||||
const element = $(this.elementRef.nativeElement);
|
|
||||||
this.toggleClass(element, FlowchartConstants.selectedClass, this.selected);
|
|
||||||
this.toggleClass(element, FlowchartConstants.editClass, this.edit);
|
|
||||||
this.toggleClass(element, FlowchartConstants.hoverClass, this.underMouse);
|
|
||||||
this.toggleClass(element, FlowchartConstants.draggingClass, this.dragging);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateNodeComponent() {
|
|
||||||
this.nodeComponent.selected = this.selected;
|
|
||||||
this.nodeComponent.edit = this.edit;
|
|
||||||
this.nodeComponent.underMouse = this.underMouse;
|
|
||||||
this.nodeComponent.mouseOverConnector = this.mouseOverConnector;
|
|
||||||
this.nodeComponent.dragging = this.dragging;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toggleClass(element: JQuery<HTMLElement>, clazz: string, set: boolean) {
|
|
||||||
if (set) {
|
|
||||||
element.addClass(clazz);
|
|
||||||
} else {
|
|
||||||
element.removeClass(clazz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mousedown', ['$event'])
|
|
||||||
mousedown(event: MouseEvent) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragstart', ['$event'])
|
|
||||||
dragstart(event: Event | any) {
|
|
||||||
if (!this.node.readonly) {
|
|
||||||
this.callbacks.nodeDragstart(event, this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragend', ['$event'])
|
|
||||||
dragend(event: Event | any) {
|
|
||||||
if (!this.node.readonly) {
|
|
||||||
this.callbacks.nodeDragend(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
|
||||||
click(event: MouseEvent) {
|
|
||||||
if (!this.node.readonly) {
|
|
||||||
this.callbacks.nodeClicked(event, this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mouseover', ['$event'])
|
|
||||||
mouseover(event: MouseEvent) {
|
|
||||||
if (!this.node.readonly) {
|
|
||||||
this.callbacks.nodeMouseOver(event, this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('mouseout', ['$event'])
|
|
||||||
mouseout(event: MouseEvent) {
|
|
||||||
if (!this.node.readonly) {
|
|
||||||
this.callbacks.nodeMouseOut(event, this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive()
|
|
||||||
// tslint:disable-next-line:directive-class-suffix
|
|
||||||
export abstract class FcNodeComponent implements OnInit {
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
callbacks: FcCallbacks;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
userNodeCallbacks: UserNodeCallbacks;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
node: FcNode;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
selected: boolean;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
edit: boolean;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
underMouse: boolean;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
mouseOverConnector: FcConnector;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
modelservice: FcModelService;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
dragging: boolean;
|
|
||||||
|
|
||||||
flowchartConstants = FlowchartConstants;
|
|
||||||
|
|
||||||
width: number;
|
|
||||||
|
|
||||||
height: number;
|
|
||||||
|
|
||||||
nodeRectInfo: FcNodeRectInfo = {
|
|
||||||
top: () => {
|
|
||||||
return this.node.y;
|
|
||||||
},
|
|
||||||
|
|
||||||
left: () => {
|
|
||||||
return this.node.x;
|
|
||||||
},
|
|
||||||
|
|
||||||
bottom: () => {
|
|
||||||
return this.node.y + this.height;
|
|
||||||
},
|
|
||||||
|
|
||||||
right: () => {
|
|
||||||
return this.node.x + this.width;
|
|
||||||
},
|
|
||||||
|
|
||||||
width: () => {
|
|
||||||
return this.width;
|
|
||||||
},
|
|
||||||
|
|
||||||
height: () => {
|
|
||||||
return this.height;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import { FcModelService } from './model.service';
|
|
||||||
import { FcRectBox } from './ngx-flowchart.models';
|
|
||||||
import scrollparent from './scrollparent';
|
|
||||||
|
|
||||||
interface Rectangle {
|
|
||||||
x1: number;
|
|
||||||
x2: number;
|
|
||||||
y1: number;
|
|
||||||
y2: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FcRectangleSelectService {
|
|
||||||
|
|
||||||
private readonly selectRect: Rectangle = {
|
|
||||||
x1: 0,
|
|
||||||
x2: 0,
|
|
||||||
y1: 0,
|
|
||||||
y2: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly modelService: FcModelService;
|
|
||||||
private readonly selectElement: HTMLElement;
|
|
||||||
private readonly $canvasElement: JQuery<HTMLElement>;
|
|
||||||
private readonly $scrollParent: JQuery<HTMLElement>;
|
|
||||||
private readonly applyFunction: <T>(fn: (...args: any[]) => T) => T;
|
|
||||||
|
|
||||||
constructor(modelService: FcModelService,
|
|
||||||
selectElement: HTMLElement,
|
|
||||||
applyFunction: <T>(fn: (...args: any[]) => T) => T) {
|
|
||||||
this.modelService = modelService;
|
|
||||||
this.selectElement = selectElement;
|
|
||||||
this.$canvasElement = $(this.modelService.canvasHtmlElement);
|
|
||||||
this.$scrollParent = $(scrollparent(this.modelService.canvasHtmlElement));
|
|
||||||
this.applyFunction = applyFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public mousedown(e: MouseEvent) {
|
|
||||||
if (this.modelService.isEditable() && !e.ctrlKey && !e.metaKey && e.button === 0
|
|
||||||
&& this.selectElement.hidden) {
|
|
||||||
this.selectElement.hidden = false;
|
|
||||||
const offset = this.$canvasElement.offset();
|
|
||||||
this.selectRect.x1 = Math.round(e.pageX - offset.left);
|
|
||||||
this.selectRect.y1 = Math.round(e.pageY - offset.top);
|
|
||||||
this.selectRect.x2 = this.selectRect.x1;
|
|
||||||
this.selectRect.y2 = this.selectRect.y1;
|
|
||||||
this.updateSelectRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public mousemove(e: MouseEvent) {
|
|
||||||
if (this.modelService.isEditable() && !e.ctrlKey && !e.metaKey && e.button === 0
|
|
||||||
&& !this.selectElement.hidden) {
|
|
||||||
const offset = this.$canvasElement.offset();
|
|
||||||
this.selectRect.x2 = Math.round(e.pageX - offset.left);
|
|
||||||
this.selectRect.y2 = Math.round(e.pageY - offset.top);
|
|
||||||
this.updateScroll(offset);
|
|
||||||
this.updateSelectRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateScroll(offset: JQuery.Coordinates) {
|
|
||||||
const rect = this.$scrollParent[0].getBoundingClientRect();
|
|
||||||
const bottom = rect.bottom - offset.top;
|
|
||||||
const right = rect.right - offset.left;
|
|
||||||
const top = rect.top - offset.top;
|
|
||||||
const left = rect.left - offset.left;
|
|
||||||
if (this.selectRect.y2 - top < 25) {
|
|
||||||
const topScroll = 25 - (this.selectRect.y2 - top);
|
|
||||||
const scroll = this.$scrollParent.scrollTop();
|
|
||||||
this.$scrollParent.scrollTop(scroll - topScroll);
|
|
||||||
} else if (bottom - this.selectRect.y2 < 40) {
|
|
||||||
const bottomScroll = 40 - (bottom - this.selectRect.y2);
|
|
||||||
const scroll = this.$scrollParent.scrollTop();
|
|
||||||
this.$scrollParent.scrollTop(scroll + bottomScroll);
|
|
||||||
}
|
|
||||||
if (this.selectRect.x2 - left < 25) {
|
|
||||||
const leftScroll = 25 - (this.selectRect.x2 - left);
|
|
||||||
const scroll = this.$scrollParent.scrollLeft();
|
|
||||||
this.$scrollParent.scrollLeft(scroll - leftScroll);
|
|
||||||
} else if (right - this.selectRect.x2 < 40) {
|
|
||||||
const rightScroll = 40 - (right - this.selectRect.x2);
|
|
||||||
const scroll = this.$scrollParent.scrollLeft();
|
|
||||||
this.$scrollParent.scrollLeft(scroll + rightScroll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public mouseup(e: MouseEvent) {
|
|
||||||
if (this.modelService.isEditable() && !e.ctrlKey && !e.metaKey && e.button === 0
|
|
||||||
&& !this.selectElement.hidden) {
|
|
||||||
const rectBox = this.selectElement.getBoundingClientRect() as FcRectBox;
|
|
||||||
this.selectElement.hidden = true;
|
|
||||||
this.selectObjects(rectBox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateSelectRect() {
|
|
||||||
const x3 = Math.min(this.selectRect.x1, this.selectRect.x2);
|
|
||||||
const x4 = Math.max(this.selectRect.x1, this.selectRect.x2);
|
|
||||||
const y3 = Math.min(this.selectRect.y1, this.selectRect.y2);
|
|
||||||
const y4 = Math.max(this.selectRect.y1, this.selectRect.y2);
|
|
||||||
this.selectElement.style.left = x3 + 'px';
|
|
||||||
this.selectElement.style.top = y3 + 'px';
|
|
||||||
this.selectElement.style.width = x4 - x3 + 'px';
|
|
||||||
this.selectElement.style.height = y4 - y3 + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
private selectObjects(rectBox: FcRectBox) {
|
|
||||||
this.applyFunction(() => {
|
|
||||||
this.modelService.selectAllInRect(rectBox);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
const regex = /(auto|scroll)/;
|
|
||||||
|
|
||||||
const style = (node: Element, prop: string): string =>
|
|
||||||
getComputedStyle(node, null).getPropertyValue(prop);
|
|
||||||
|
|
||||||
const scroll = (node: Element) =>
|
|
||||||
regex.test(
|
|
||||||
style(node, 'overflow') +
|
|
||||||
style(node, 'overflow-y') +
|
|
||||||
style(node, 'overflow-x'));
|
|
||||||
|
|
||||||
const scrollparent = (node: HTMLElement): HTMLElement =>
|
|
||||||
!node || node === document.body
|
|
||||||
? document.body
|
|
||||||
: scroll(node)
|
|
||||||
? node
|
|
||||||
: scrollparent(node.parentNode as HTMLElement);
|
|
||||||
|
|
||||||
export default scrollparent;
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
* Public API Surface of ngx-flowchart
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './lib/ngx-flowchart.component';
|
|
||||||
export * from './lib/ngx-flowchart.module';
|
|
||||||
export * from './lib/ngx-flowchart.models';
|
|
||||||
export { FcNodeComponent } from './lib/node.component';
|
|
||||||
export { FcMagnetDirective } from './lib/magnet.directive';
|
|
||||||
export { FcConnectorDirective } from './lib/connector.directive';
|
|
||||||
export { DefaultFcNodeComponent } from './lib/default-node.component';
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
|
||||||
|
|
||||||
import 'zone.js/dist/zone';
|
|
||||||
import 'zone.js/dist/zone-testing';
|
|
||||||
import { getTestBed } from '@angular/core/testing';
|
|
||||||
import {
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting
|
|
||||||
} from '@angular/platform-browser-dynamic/testing';
|
|
||||||
|
|
||||||
declare const require: any;
|
|
||||||
|
|
||||||
// First, initialize the Angular testing environment.
|
|
||||||
getTestBed().initTestEnvironment(
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting()
|
|
||||||
);
|
|
||||||
// Then we find all the tests.
|
|
||||||
const context = require.context('./', true, /\.spec\.ts$/);
|
|
||||||
// And load the modules.
|
|
||||||
context.keys().map(context);
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../../out-tsc/lib",
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"inlineSources": true,
|
|
||||||
"types": ["jquery"]
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"src/test.ts",
|
|
||||||
"**/*.spec.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.lib.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"declarationMap": false
|
|
||||||
},
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"compilationMode": "partial"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../../out-tsc/spec",
|
|
||||||
"types": [
|
|
||||||
"jasmine",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"src/test.ts"
|
|
||||||
],
|
|
||||||
"include": [
|
|
||||||
"**/*.spec.ts",
|
|
||||||
"**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tslint.json",
|
|
||||||
"rules": {
|
|
||||||
"directive-selector": [
|
|
||||||
true,
|
|
||||||
"attribute",
|
|
||||||
"fc",
|
|
||||||
"camelCase"
|
|
||||||
],
|
|
||||||
"component-selector": [
|
|
||||||
true,
|
|
||||||
"element",
|
|
||||||
"fc",
|
|
||||||
"kebab-case"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
<div class="fc-container">
|
|
||||||
<div class="fc-left-pane">
|
|
||||||
<fc-canvas [model]="nodeTypesModel" [selectedObjects]="nodeTypesFlowchartselected"
|
|
||||||
[automaticResize]="false"
|
|
||||||
[dropTargetId]="'fc-target-canvas'">
|
|
||||||
</fc-canvas>
|
|
||||||
</div>
|
|
||||||
<div class="fc-divider" style="background-color: gray;">
|
|
||||||
</div>
|
|
||||||
<div class="fc-right-pane">
|
|
||||||
<div class="button-overlay">
|
|
||||||
<button (click)="addNewNode()" title="Add a new node to then chart">Add Node</button>
|
|
||||||
<button (click)="deleteSelected()"
|
|
||||||
[disabled]="!fcCanvas.modelService || (fcCanvas.modelService.nodes.getSelectedNodes().length === 0 &&
|
|
||||||
fcCanvas.modelService.edges.getSelectedEdges().length === 0)"
|
|
||||||
title="Delete selected nodes and connections">Delete Selected</button>
|
|
||||||
<button (click)="activateWorkflow()">
|
|
||||||
Activate Workflow
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<fc-canvas #fcCanvas
|
|
||||||
id="fc-target-canvas"
|
|
||||||
[model]="model"
|
|
||||||
[selectedObjects]="flowchartselected"
|
|
||||||
[edgeStyle]="flowchartConstants.curvedStyle"
|
|
||||||
[userCallbacks]="callbacks"
|
|
||||||
[automaticResize]="true"
|
|
||||||
[dragAnimation]="flowchartConstants.dragAnimationRepaint">
|
|
||||||
</fc-canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
.fc-container {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: row;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-left-pane {
|
|
||||||
flex: 0.15;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-divider {
|
|
||||||
flex: 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-right-pane {
|
|
||||||
flex: 0.84;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 40px;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-overlay button {
|
|
||||||
display: block;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 20px;
|
|
||||||
background-color: #F15B26;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-overlay button:hover:not(:disabled) {
|
|
||||||
border: 4px solid #b03911;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: -4px;
|
|
||||||
margin-bottom: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-overlay button:disabled {
|
|
||||||
-webkit-filter: brightness(70%);
|
|
||||||
filter: brightness(70%);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,280 +0,0 @@
|
|||||||
import { AfterViewInit, Component, HostBinding, HostListener, ViewChild } from '@angular/core';
|
|
||||||
import { FcModel, FcNode, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart-dev';
|
|
||||||
import { of } from 'rxjs';
|
|
||||||
import { DELETE } from '@angular/cdk/keycodes';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-root',
|
|
||||||
templateUrl: './app.component.html',
|
|
||||||
styleUrls: ['./app.component.scss']
|
|
||||||
})
|
|
||||||
export class AppComponent implements AfterViewInit {
|
|
||||||
|
|
||||||
@HostBinding('attr.tabindex')
|
|
||||||
get tabindex(): string {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
flowchartConstants = FlowchartConstants;
|
|
||||||
|
|
||||||
nodeTypesFlowchartselected = [];
|
|
||||||
nodeTypesModel: FcModel = {
|
|
||||||
nodes: [],
|
|
||||||
edges: []
|
|
||||||
};
|
|
||||||
|
|
||||||
flowchartselected = [];
|
|
||||||
model: FcModel = {
|
|
||||||
nodes: [],
|
|
||||||
edges: []
|
|
||||||
};
|
|
||||||
nextNodeID = 10;
|
|
||||||
nextConnectorID = 20;
|
|
||||||
|
|
||||||
callbacks: UserCallbacks = {
|
|
||||||
edgeDoubleClick: (event, edge) => {
|
|
||||||
console.log('Edge double clicked.');
|
|
||||||
},
|
|
||||||
edgeEdit: (event, edge) => {
|
|
||||||
const label = prompt('Enter a link label:', edge.label);
|
|
||||||
if (label) {
|
|
||||||
edge.label = label;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edgeMouseOver: event => {
|
|
||||||
console.log('mouserover');
|
|
||||||
},
|
|
||||||
isValidEdge: (source, destination) => {
|
|
||||||
return source.type === FlowchartConstants.rightConnectorType && destination.type === FlowchartConstants.leftConnectorType;
|
|
||||||
},
|
|
||||||
createEdge: (event, edge) => {
|
|
||||||
if (!edge.label) {
|
|
||||||
const label = prompt('Enter a link label:', 'New label');
|
|
||||||
edge.label = label;
|
|
||||||
}
|
|
||||||
return of(edge);
|
|
||||||
},
|
|
||||||
dropNode: (event, node) => {
|
|
||||||
const name = prompt('Enter a node name:', node.name);
|
|
||||||
if (name) {
|
|
||||||
node.name = name;
|
|
||||||
node.id = (this.nextNodeID++) + '';
|
|
||||||
node.connectors = [
|
|
||||||
{
|
|
||||||
id: (this.nextConnectorID++) + '',
|
|
||||||
type: FlowchartConstants.leftConnectorType
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: (this.nextConnectorID++) + '',
|
|
||||||
type: FlowchartConstants.rightConnectorType
|
|
||||||
}
|
|
||||||
];
|
|
||||||
this.model.nodes.push(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edgeAdded: edge => {
|
|
||||||
console.log('edge added');
|
|
||||||
console.log(edge);
|
|
||||||
},
|
|
||||||
nodeRemoved: node => {
|
|
||||||
console.log('node removed');
|
|
||||||
console.log(node);
|
|
||||||
},
|
|
||||||
edgeRemoved: edge => {
|
|
||||||
console.log('edge removed');
|
|
||||||
console.log(edge);
|
|
||||||
},
|
|
||||||
nodeCallbacks: {
|
|
||||||
doubleClick: event => {
|
|
||||||
console.log('Node was doubleclicked.');
|
|
||||||
},
|
|
||||||
nodeEdit: (event, node) => {
|
|
||||||
const name = prompt('Enter a node name:', node.name);
|
|
||||||
if (name) {
|
|
||||||
node.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@ViewChild('fcCanvas', {static: true}) fcCanvas: NgxFlowchartComponent;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.initData();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
|
||||||
console.log(this.fcCanvas.modelService);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initData() {
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
const node: FcNode = {
|
|
||||||
name: 'type' + i,
|
|
||||||
id: (i + 1) + '',
|
|
||||||
x: 50,
|
|
||||||
y: 100 * (i + 1),
|
|
||||||
connectors: [
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.leftConnectorType,
|
|
||||||
id: (i * 2 + 1) + ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.rightConnectorType,
|
|
||||||
id: (i * 2 + 2) + ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
this.nodeTypesModel.nodes.push(node);
|
|
||||||
}
|
|
||||||
this.model.nodes.push(...
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'ngxFlowchart',
|
|
||||||
readonly: true,
|
|
||||||
id: '2',
|
|
||||||
x: 300,
|
|
||||||
y: 100,
|
|
||||||
color: '#000',
|
|
||||||
borderColor: '#000',
|
|
||||||
connectors: [
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.leftConnectorType,
|
|
||||||
id: '1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.rightConnectorType,
|
|
||||||
id: '2'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Implemented with Angular',
|
|
||||||
id: '3',
|
|
||||||
x: 600,
|
|
||||||
y: 100,
|
|
||||||
color: '#F15B26',
|
|
||||||
connectors: [
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.leftConnectorType,
|
|
||||||
id: '3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.rightConnectorType,
|
|
||||||
id: '4'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Easy Integration',
|
|
||||||
id: '4',
|
|
||||||
x: 1000,
|
|
||||||
y: 100,
|
|
||||||
color: '#000',
|
|
||||||
borderColor: '#000',
|
|
||||||
connectors: [
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.leftConnectorType,
|
|
||||||
id: '5'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.rightConnectorType,
|
|
||||||
id: '6'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Customizable templates',
|
|
||||||
id: '5',
|
|
||||||
x: 1300,
|
|
||||||
y: 100,
|
|
||||||
color: '#000',
|
|
||||||
borderColor: '#000',
|
|
||||||
connectors: [
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.leftConnectorType,
|
|
||||||
id: '7'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: FlowchartConstants.rightConnectorType,
|
|
||||||
id: '8'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
this.model.edges.push(...
|
|
||||||
[
|
|
||||||
{
|
|
||||||
source: '2',
|
|
||||||
destination: '3',
|
|
||||||
label: 'label1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: '4',
|
|
||||||
destination: '5',
|
|
||||||
label: 'label2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: '6',
|
|
||||||
destination: '7',
|
|
||||||
label: 'label3'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('keydown.control.a', ['$event'])
|
|
||||||
public onCtrlA(event: KeyboardEvent) {
|
|
||||||
this.fcCanvas.modelService.selectAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('keydown.esc', ['$event'])
|
|
||||||
public onEsc(event: KeyboardEvent) {
|
|
||||||
this.fcCanvas.modelService.deselectAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('keydown', ['$event'])
|
|
||||||
public onKeydown(event: KeyboardEvent) {
|
|
||||||
if (event.keyCode === DELETE) {
|
|
||||||
this.fcCanvas.modelService.deleteSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public addNewNode() {
|
|
||||||
const nodeName = prompt('Enter a node name:', 'New node');
|
|
||||||
if (!nodeName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newNode: FcNode = {
|
|
||||||
name: nodeName,
|
|
||||||
id: (this.nextNodeID++) + '',
|
|
||||||
x: 200,
|
|
||||||
y: 100,
|
|
||||||
color: '#F15B26',
|
|
||||||
connectors: [
|
|
||||||
{
|
|
||||||
id: (this.nextConnectorID++) + '',
|
|
||||||
type: FlowchartConstants.leftConnectorType
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: (this.nextConnectorID++) + '',
|
|
||||||
type: FlowchartConstants.rightConnectorType
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
this.model.nodes.push(newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public activateWorkflow() {
|
|
||||||
this.model.edges.forEach((edge) => {
|
|
||||||
edge.active = !edge.active;
|
|
||||||
});
|
|
||||||
this.fcCanvas.modelService.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public deleteSelected() {
|
|
||||||
this.fcCanvas.modelService.deleteSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import { FC_NODE_COMPONENT_CONFIG, NgxFlowchartModule } from 'ngx-flowchart-dev';
|
|
||||||
import { TestFcNodeComponent } from './test-node.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
entryComponents: [
|
|
||||||
TestFcNodeComponent
|
|
||||||
],
|
|
||||||
/*providers: [
|
|
||||||
{
|
|
||||||
provide: FC_NODE_COMPONENT_CONFIG,
|
|
||||||
useValue: {
|
|
||||||
nodeComponentType: TestFcNodeComponent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],*/
|
|
||||||
declarations: [
|
|
||||||
AppComponent,
|
|
||||||
TestFcNodeComponent
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
BrowserModule,
|
|
||||||
NgxFlowchartModule
|
|
||||||
],
|
|
||||||
bootstrap: [AppComponent]
|
|
||||||
})
|
|
||||||
export class AppModule { }
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<div
|
|
||||||
(dblclick)="userNodeCallbacks.doubleClick($event, node)">
|
|
||||||
{{ node | json }}
|
|
||||||
</div>
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { FcNodeComponent } from 'ngx-flowchart-dev';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-default-node',
|
|
||||||
templateUrl: './test-node.component.html',
|
|
||||||
styleUrls: []
|
|
||||||
})
|
|
||||||
export class TestFcNodeComponent extends FcNodeComponent {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export const environment = {
|
|
||||||
production: true
|
|
||||||
};
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
// This file can be replaced during build by using the `fileReplacements` array.
|
|
||||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
|
||||||
// The list of file replacements can be found in `angular.json`.
|
|
||||||
|
|
||||||
export const environment = {
|
|
||||||
production: false
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For easier debugging in development mode, you can import the following file
|
|
||||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
|
||||||
*
|
|
||||||
* This import should be commented out in production mode because it will have a negative impact
|
|
||||||
* on performance if an error is thrown.
|
|
||||||
*/
|
|
||||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
@ -1,15 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>NgxFlowchart</title>
|
|
||||||
<base href="/">
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" type="text/css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<app-root></app-root>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { enableProdMode } from '@angular/core';
|
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|
||||||
|
|
||||||
import { AppModule } from './app/app.module';
|
|
||||||
import { environment } from './environments/environment';
|
|
||||||
|
|
||||||
if (environment.production) {
|
|
||||||
enableProdMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
|
||||||
.catch(err => console.error(err));
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
|
||||||
* You can add your own extra polyfills to this file.
|
|
||||||
*
|
|
||||||
* This file is divided into 2 sections:
|
|
||||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
|
||||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
|
||||||
* file.
|
|
||||||
*
|
|
||||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
|
||||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
|
||||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
|
||||||
*
|
|
||||||
* Learn more in https://angular.io/guide/browser-support
|
|
||||||
*/
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
|
||||||
* BROWSER POLYFILLS
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
|
||||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Web Animations `@angular/platform-browser/animations`
|
|
||||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
|
||||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
|
||||||
*/
|
|
||||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
|
||||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
|
||||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
|
||||||
* will put import in the top of bundle, so user need to create a separate file
|
|
||||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
|
||||||
* into that file, and then add the following code before importing zone.js.
|
|
||||||
* import './zone-flags.ts';
|
|
||||||
*
|
|
||||||
* The flags allowed in zone-flags.ts are listed here.
|
|
||||||
*
|
|
||||||
* The following flags will work for all browsers.
|
|
||||||
*
|
|
||||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
|
||||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
|
||||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
|
||||||
*
|
|
||||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
|
||||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
|
||||||
*
|
|
||||||
* (window as any).__Zone_enable_cross_context_check = true;
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
|
||||||
* Zone JS is required by default for Angular itself.
|
|
||||||
*/
|
|
||||||
import 'zone.js'; // Included with Angular CLI.
|
|
||||||
import 'core-js/es/array';
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
|
||||||
* APPLICATION IMPORTS
|
|
||||||
*/
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
|
||||||
|
|
||||||
import 'zone.js/dist/zone-testing';
|
|
||||||
import { getTestBed } from '@angular/core/testing';
|
|
||||||
import {
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting
|
|
||||||
} from '@angular/platform-browser-dynamic/testing';
|
|
||||||
|
|
||||||
declare const require: any;
|
|
||||||
|
|
||||||
// First, initialize the Angular testing environment.
|
|
||||||
getTestBed().initTestEnvironment(
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting()
|
|
||||||
);
|
|
||||||
// Then we find all the tests.
|
|
||||||
const context = require.context('./', true, /\.spec\.ts$/);
|
|
||||||
// And load the modules.
|
|
||||||
context.keys().map(context);
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./out-tsc/app",
|
|
||||||
"types": ["jquery"]
|
|
||||||
},
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"fullTemplateTypeCheck": true
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"src/main.ts",
|
|
||||||
"src/polyfills.ts"
|
|
||||||
],
|
|
||||||
"include": [
|
|
||||||
"src/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"compileOnSave": false,
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "./",
|
|
||||||
"outDir": "./dist/out-tsc",
|
|
||||||
"sourceMap": true,
|
|
||||||
"declaration": false,
|
|
||||||
"module": "es2020",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"importHelpers": true,
|
|
||||||
"target": "es2017",
|
|
||||||
"typeRoots": [
|
|
||||||
"node_modules/@types"
|
|
||||||
],
|
|
||||||
"lib": [
|
|
||||||
"es2018",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"ngx-flowchart": [
|
|
||||||
"dist/ngx-flowchart"
|
|
||||||
],
|
|
||||||
"ngx-flowchart/*": [
|
|
||||||
"dist/ngx-flowchart/*"
|
|
||||||
],
|
|
||||||
"ngx-flowchart-dev": [
|
|
||||||
"projects/ngx-flowchart/src/public-api"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"enableI18nLegacyMessageIdFormat": false,
|
|
||||||
"strictInjectionParameters": true,
|
|
||||||
"strictInputAccessModifiers": true,
|
|
||||||
"strictTemplates": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./out-tsc/spec",
|
|
||||||
"types": [
|
|
||||||
"jasmine",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"src/test.ts",
|
|
||||||
"src/polyfills.ts"
|
|
||||||
],
|
|
||||||
"include": [
|
|
||||||
"src/**/*.spec.ts",
|
|
||||||
"src/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue