Commit aac96f7e authored by Igor Kulikov's avatar Igor Kulikov

Release 1.0.0

parent cf44a13f
# 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
# 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
# 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
This diff is collapsed.
# NgxFlowchart
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.1.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
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` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
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` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
Run `ng build ngx-flowchart` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
## Publishing
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
After building your library with `ng build ngx-flowchart`, go to the dist folder `cd dist/ngx-flowchart` and run `npm publish`.
## Running end-to-end tests
## Running unit tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Run `ng test ngx-flowchart` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
......
{
"$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"
}
# 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).
{
"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"
}
}
\ No newline at end of file
// @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 } }));
}
};
\ No newline at end of file
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));
});
});
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>;
}
}
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
// 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
});
};
{
"name": "ngx-flowchart-demo",
"version": "0.0.0",
"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,
"name": "ngx-flowchart",
"version": "0.0.1",
"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/compiler": "^12.2.13",
"@angular/compiler-cli": "^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",
"karma": "~6.3.9",
"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"
"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.3.1"
"tslib": "^2.2.0"
}
}
\ No newline at end of file
# 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).
// 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
});
};
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/ngx-flowchart",
"deleteDestPath": false,
"lib": {
"entryFile": "src/public-api.ts"
}
}
{
"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"
}
}
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);
}
}
}
<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)">
&times;
</div>
</div>
: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;
}
}
}
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();
}
}
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)
};
}
}
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);
}
}
This diff is collapsed.
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;
}
}
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;
}
<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)">
&times;
</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>
: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;
}
}
}
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;
}
}
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 { }
: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;
}
}
}
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 {
}
}
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);
});
}
}
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;
/*
* 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';
// 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);
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": ["jquery"]
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"fc",
"camelCase"
],
"component-selector": [
true,
"element",
"fc",
"kebab-case"
]
}
}
<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>
: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%);
}
}
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();
}
}
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 { }
<div
(dblclick)="userNodeCallbacks.doubleClick($event, node)">
{{ node | json }}
</div>
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();
}
}
export const environment = {
production: true
};
// 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.
<!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>
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));
/**
* 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
*/
/* 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;
}
// 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);
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": ["jquery"]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
{
"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
}
}
{
"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"
]
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment