mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-01-18 02:39:50 +00:00
Merge branch 'master' into custom-html-adbutler-ads
This commit is contained in:
commit
ebbeac6e0b
@ -14,7 +14,7 @@ init:
|
|||||||
- git config --global core.autocrlf input
|
- git config --global core.autocrlf input
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- ps: Install-Product node 8 x64
|
- ps: Install-Product node 11 x64
|
||||||
- yarn install
|
- yarn install
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
|
@ -8,6 +8,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Install nodejs
|
||||||
|
command: |
|
||||||
|
brew upgrade node
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Set timezone and check current datetime
|
name: Set timezone and check current datetime
|
||||||
command: |
|
command: |
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- '10' # use node for latest
|
- '11' # use node for latest
|
||||||
|
|
||||||
# Issue with Travis: https://github.com/travis-ci/travis-ci/issues/8836#issuecomment-356362524
|
# Issue with Travis: https://github.com/travis-ci/travis-ci/issues/8836#issuecomment-356362524
|
||||||
sudo: required
|
sudo: required
|
||||||
|
17
README.md
17
README.md
@ -93,24 +93,21 @@ If you would like to bump prepatch just type:
|
|||||||
|
|
||||||
### Distribute release
|
### Distribute release
|
||||||
|
|
||||||
We have got configured CircleCI, TravisCI and AppVeyor for distributing application for particular platform. In order to release you need to tag&push your changes.
|
We have got configured CircleCI, TravisCI and AppVeyor for distributing application for particular platform. In order to release you need to tag&push your changes from master.
|
||||||
|
|
||||||
Using `bump`:
|
First of all please remove `dev` from version in `package.json` (for instance `2019.2.0-alpha.4dev` to `2019.2.0-alpha.4`). Commit & push changes with message `Release 2019.2.0-alpha.4` . Next step is to tag repository and push to origin:
|
||||||
|
|
||||||
bump --patch --tag --push
|
|
||||||
|
|
||||||
Or manually:
|
git tag v2019.2.0-alpha.4
|
||||||
|
git push origin v2019.2.0-alpha.4
|
||||||
git tag v0.0.1
|
|
||||||
git push origin v0.0.1
|
|
||||||
|
|
||||||
|
|
||||||
When artifacts are made you can see draft release here: [gns3-web-ui releases](https://github.com/GNS3/gns3-web-ui/releases) which is waiting to be published.
|
When artifacts are made you can see draft release here: [gns3-web-ui releases](https://github.com/GNS3/gns3-web-ui/releases) which is waiting to be published.
|
||||||
After release please change current version in `package.json` to `X.X.X-beta.0`'. Otherwise artifacts will be overwritten during the next commit.
|
After release please change current version in `package.json` to `2019.2.0-alpha.5dev`'. Otherwise artifacts will be overwritten during the next commit. Don't forget to commit & push changes.
|
||||||
|
|
||||||
You may use `bump` to achieve that:
|
#### Updating gns3server
|
||||||
|
|
||||||
bump --prepatch
|
Checkout the latest master of `gns3server`. Run command `./scripts/update-bundled-web-ui.sh --tag=v2019.2.0-alpha.5`. Commit & push changes with message `Release 2019.2.0-alpha.4`.
|
||||||
|
|
||||||
### Staging release
|
### Staging release
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
"polyfills": "src/polyfills.ts",
|
"polyfills": "src/polyfills.ts",
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/assets",
|
"src/assets",
|
||||||
"src/favicon.ico"
|
"src/favicon.ico",
|
||||||
|
"src/ReleaseNotes.txt"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
|
82
package.json
82
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gns3-web-ui",
|
"name": "gns3-web-ui",
|
||||||
"version": "2019.2.0-alpha.3dev",
|
"version": "2019.2.0-alpha.6dev",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "GNS3 Technology Inc.",
|
"name": "GNS3 Technology Inc.",
|
||||||
"email": "developers@gns3.com"
|
"email": "developers@gns3.com"
|
||||||
@ -38,73 +38,73 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^7.2.14",
|
"@angular/animations": "^8.1.2",
|
||||||
"@angular/cdk": "^7.3.7",
|
"@angular/cdk": "^8.1.1",
|
||||||
"@angular/common": "^7.2.14",
|
"@angular/common": "^8.1.2",
|
||||||
"@angular/compiler": "^7.2.14",
|
"@angular/compiler": "^8.1.2",
|
||||||
"@angular/core": "^7.2.14",
|
"@angular/core": "^8.1.2",
|
||||||
"@angular/forms": "^7.2.14",
|
"@angular/forms": "^8.1.2",
|
||||||
"@angular/http": "^7.2.14",
|
"@angular/http": "^7.2.15",
|
||||||
"@angular/material": "^7.3.7",
|
"@angular/material": "^8.1.1",
|
||||||
"@angular/platform-browser": "^7.2.14",
|
"@angular/platform-browser": "^8.1.2",
|
||||||
"@angular/platform-browser-dynamic": "^7.2.14",
|
"@angular/platform-browser-dynamic": "^8.1.2",
|
||||||
"@angular/router": "^7.2.14",
|
"@angular/router": "^8.1.2",
|
||||||
"angular-persistence": "^1.0.1",
|
"angular-persistence": "^1.0.1",
|
||||||
"angular2-hotkeys": "^2.1.4",
|
"angular2-hotkeys": "^2.1.4",
|
||||||
"angular2-indexeddb": "^1.2.3",
|
"angular2-indexeddb": "^1.2.3",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"command-exists": "^1.2.8",
|
"command-exists": "^1.2.8",
|
||||||
"core-js": "^3.0.1",
|
"core-js": "^3.1.4",
|
||||||
"css-tree": "^1.0.0-alpha.29",
|
"css-tree": "^1.0.0-alpha.33",
|
||||||
"d3-ng2-service": "^2.1.0",
|
"d3-ng2-service": "^2.1.0",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"ini": "^1.3.5",
|
"ini": "^1.3.5",
|
||||||
"material-design-icons": "^3.0.1",
|
"material-design-icons": "^3.0.1",
|
||||||
"ng2-file-upload": "^1.3.0",
|
"ng2-file-upload": "^1.3.0",
|
||||||
"ngx-electron": "^2.1.1",
|
"ngx-electron": "^2.1.1",
|
||||||
"node-fetch": "^2.4.1",
|
"node-fetch": "^2.6.0",
|
||||||
"notosans-fontface": "^1.1.0",
|
"notosans-fontface": "^1.1.0",
|
||||||
"raven-js": "^3.27.0",
|
"raven-js": "^3.27.2",
|
||||||
"rxjs": "^6.5.1",
|
"rxjs": "^6.5.2",
|
||||||
"rxjs-compat": "^6.5.1",
|
"rxjs-compat": "^6.5.2",
|
||||||
"tree-kill": "^1.2.1",
|
"tree-kill": "^1.2.1",
|
||||||
"typeface-roboto": "^0.0.54",
|
"typeface-roboto": "^0.0.75",
|
||||||
"yargs": "^13.2.2",
|
"yargs": "^13.3.0",
|
||||||
"zone.js": "^0.9.0"
|
"zone.js": "^0.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.13.8",
|
"@angular-devkit/build-angular": "~0.801.2",
|
||||||
"@angular/cli": "^7.3.8",
|
"@angular/cli": "^8.1.2",
|
||||||
"@angular/compiler-cli": "^7.2.14",
|
"@angular/compiler-cli": "^8.1.2",
|
||||||
"@angular/language-service": "^7.2.14",
|
"@angular/language-service": "^8.1.2",
|
||||||
"@sentry/cli": "^1.41.2",
|
"@sentry/cli": "^1.47.0",
|
||||||
"@sentry/electron": "^0.17.1",
|
"@sentry/electron": "^0.17.3",
|
||||||
"@types/jasmine": "~3.3.12",
|
"@types/jasmine": "~3.3.15",
|
||||||
"@types/jasminewd2": "~2.0.6",
|
"@types/jasminewd2": "~2.0.6",
|
||||||
"@types/node": "~12.0.0",
|
"@types/node": "~12.6.8",
|
||||||
"codelyzer": "~5.0.1",
|
"codelyzer": "~5.1.0",
|
||||||
"electron": "5.0.1",
|
"electron": "5.0.8",
|
||||||
"electron-builder": "20.39.0",
|
"electron-builder": "21.1.5",
|
||||||
"jasmine-core": "~3.4.0",
|
"jasmine-core": "~3.4.0",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"jquery": "^3.4.0",
|
"jquery": "^3.4.1",
|
||||||
"karma": "~4.1.0",
|
"karma": "~4.2.0",
|
||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~3.0.0",
|
||||||
"karma-cli": "~2.0.0",
|
"karma-cli": "~2.0.0",
|
||||||
"karma-coverage-istanbul-reporter": "^2.0.5",
|
"karma-coverage-istanbul-reporter": "^2.1.0",
|
||||||
"karma-jasmine": "~2.0.1",
|
"karma-jasmine": "~2.0.1",
|
||||||
"karma-jasmine-html-reporter": "^1.4.2",
|
"karma-jasmine-html-reporter": "^1.4.2",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.12.0",
|
||||||
"popper.js": "^1.15.0",
|
"popper.js": "^1.15.0",
|
||||||
"prettier": "^1.17.0",
|
"prettier": "^1.18.2",
|
||||||
"protractor": "~5.4.2",
|
"protractor": "~5.4.2",
|
||||||
"replace": "^1.1.0",
|
"replace": "^1.1.0",
|
||||||
"ts-mockito": "^2.3.1",
|
"ts-mockito": "^2.4.2",
|
||||||
"ts-node": "~8.1.0",
|
"ts-node": "~8.3.0",
|
||||||
"tslint": "~5.16.0",
|
"tslint": "~5.18.0",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typescript": "<3.3.0"
|
"typescript": ">=3.4.0 <3.5.0"
|
||||||
},
|
},
|
||||||
"greenkeeper": {
|
"greenkeeper": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
|
@ -44,7 +44,7 @@ DEPENDENCIES = {
|
|||||||
'ubridge': {
|
'ubridge': {
|
||||||
'type': 'github',
|
'type': 'github',
|
||||||
'releases': 'https://api.github.com/repos/GNS3/ubridge/releases',
|
'releases': 'https://api.github.com/repos/GNS3/ubridge/releases',
|
||||||
'version': '0.9.15', # possible to use LATEST as value
|
'version': '0.9.16', # possible to use LATEST as value
|
||||||
'files': {
|
'files': {
|
||||||
'windows': [
|
'windows': [
|
||||||
'cygwin1.dll',
|
'cygwin1.dll',
|
||||||
|
16
src/ReleaseNotes.txt
Normal file
16
src/ReleaseNotes.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
GNS3 WebUI is web implementation of user interface for GNS3 software.
|
||||||
|
|
||||||
|
Current version: 2019.2.0
|
||||||
|
|
||||||
|
What's New
|
||||||
|
- Help section added with information about third party components
|
||||||
|
- Showing progress when server starting
|
||||||
|
- Possibility to edit interface & node labels by using context menu
|
||||||
|
- Enhancements in moving elements on map
|
||||||
|
- Context menu extended with option to duplicate
|
||||||
|
- Main menu extended with option to lock all items on map
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
- Removing issues with positioning interface labels while adding link between nodes on map
|
||||||
|
- Context menu now is correctly placed
|
||||||
|
- Entered text in text & style editor is now validated
|
@ -52,6 +52,7 @@ import { CopyDockerTemplateComponent } from './components/preferences/docker/cop
|
|||||||
import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component';
|
import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component';
|
||||||
import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
|
import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
|
||||||
import { ConsoleComponent } from './components/settings/console/console.component';
|
import { ConsoleComponent } from './components/settings/console/console.component';
|
||||||
|
import { HelpComponent } from './components/help/help.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -62,6 +63,7 @@ const routes: Routes = [
|
|||||||
{ path: 'servers', component: ServersComponent },
|
{ path: 'servers', component: ServersComponent },
|
||||||
{ path: 'bundled', component: BundledServerFinderComponent },
|
{ path: 'bundled', component: BundledServerFinderComponent },
|
||||||
{ path: 'server/:server_id/projects', component: ProjectsComponent },
|
{ path: 'server/:server_id/projects', component: ProjectsComponent },
|
||||||
|
{ path: 'help', component: HelpComponent },
|
||||||
{ path: 'settings', component: SettingsComponent },
|
{ path: 'settings', component: SettingsComponent },
|
||||||
{ path: 'settings/console', component: ConsoleComponent },
|
{ path: 'settings/console', component: ConsoleComponent },
|
||||||
{ path: 'installed-software', component: InstalledSoftwareComponent },
|
{ path: 'installed-software', component: InstalledSoftwareComponent },
|
||||||
|
@ -180,11 +180,18 @@ import { StartCaptureDialogComponent } from './components/project-map/packet-cap
|
|||||||
import { SuspendLinkActionComponent } from './components/project-map/context-menu/actions/suspend-link/suspend-link-action.component';
|
import { SuspendLinkActionComponent } from './components/project-map/context-menu/actions/suspend-link/suspend-link-action.component';
|
||||||
import { ResumeLinkActionComponent } from './components/project-map/context-menu/actions/resume-link-action/resume-link-action.component';
|
import { ResumeLinkActionComponent } from './components/project-map/context-menu/actions/resume-link-action/resume-link-action.component';
|
||||||
import { StopCaptureActionComponent } from './components/project-map/context-menu/actions/stop-capture/stop-capture-action.component';
|
import { StopCaptureActionComponent } from './components/project-map/context-menu/actions/stop-capture/stop-capture-action.component';
|
||||||
|
import { MapScaleService } from './services/mapScale.service';
|
||||||
import { AdbutlerComponent } from './components/adbutler/adbutler.component';
|
import { AdbutlerComponent } from './components/adbutler/adbutler.component';
|
||||||
import { ConsoleService } from './services/settings/console.service';
|
import { ConsoleService } from './services/settings/console.service';
|
||||||
import { DefaultConsoleService } from './services/settings/default-console.service';
|
import { DefaultConsoleService } from './services/settings/default-console.service';
|
||||||
import { NodeCreatedLabelStylesFixer } from './components/project-map/helpers/node-created-label-styles-fixer';
|
import { NodeCreatedLabelStylesFixer } from './components/project-map/helpers/node-created-label-styles-fixer';
|
||||||
import { NotificationBoxComponent } from './components/notification-box/notification-box.component';
|
import { NotificationBoxComponent } from './components/notification-box/notification-box.component';
|
||||||
|
import { NonNegativeValidator } from './validators/non-negative-validator';
|
||||||
|
import { RotationValidator } from './validators/rotation-validator';
|
||||||
|
import { DuplicateActionComponent } from './components/project-map/context-menu/actions/duplicate-action/duplicate-action.component';
|
||||||
|
import { MapSettingService } from './services/mapsettings.service';
|
||||||
|
import { ProjectMapMenuComponent } from './components/project-map/project-map-menu/project-map-menu.component';
|
||||||
|
import { HelpComponent } from './components/help/help.component';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
||||||
@ -219,6 +226,7 @@ if (environment.production) {
|
|||||||
EditStyleActionComponent,
|
EditStyleActionComponent,
|
||||||
EditTextActionComponent,
|
EditTextActionComponent,
|
||||||
DeleteActionComponent,
|
DeleteActionComponent,
|
||||||
|
DuplicateActionComponent,
|
||||||
PacketFiltersActionComponent,
|
PacketFiltersActionComponent,
|
||||||
StartCaptureActionComponent,
|
StartCaptureActionComponent,
|
||||||
StopCaptureActionComponent,
|
StopCaptureActionComponent,
|
||||||
@ -304,7 +312,9 @@ if (environment.production) {
|
|||||||
ConsoleDeviceActionComponent,
|
ConsoleDeviceActionComponent,
|
||||||
ConsoleComponent,
|
ConsoleComponent,
|
||||||
NodesMenuComponent,
|
NodesMenuComponent,
|
||||||
NotificationBoxComponent
|
NotificationBoxComponent,
|
||||||
|
ProjectMapMenuComponent,
|
||||||
|
HelpComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -374,9 +384,13 @@ if (environment.production) {
|
|||||||
IouConfigurationService,
|
IouConfigurationService,
|
||||||
RecentlyOpenedProjectService,
|
RecentlyOpenedProjectService,
|
||||||
ServerManagementService,
|
ServerManagementService,
|
||||||
|
MapScaleService,
|
||||||
ConsoleService,
|
ConsoleService,
|
||||||
DefaultConsoleService,
|
DefaultConsoleService,
|
||||||
NodeCreatedLabelStylesFixer
|
NodeCreatedLabelStylesFixer,
|
||||||
|
NonNegativeValidator,
|
||||||
|
RotationValidator,
|
||||||
|
MapSettingService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddServerDialogComponent,
|
AddServerDialogComponent,
|
||||||
|
@ -55,6 +55,9 @@ import { RectangleElementFactory } from './helpers/drawings-factory/rectangle-el
|
|||||||
import { LineElementFactory } from './helpers/drawings-factory/line-element-factory';
|
import { LineElementFactory } from './helpers/drawings-factory/line-element-factory';
|
||||||
import { TextEditorComponent } from './components/text-editor/text-editor.component';
|
import { TextEditorComponent } from './components/text-editor/text-editor.component';
|
||||||
import { DrawingAddingComponent } from './components/drawing-adding/drawing-adding.component';
|
import { DrawingAddingComponent } from './components/drawing-adding/drawing-adding.component';
|
||||||
|
import { MovingEventSource } from './events/moving-event-source';
|
||||||
|
import { MovingCanvasDirective } from './directives/moving-canvas.directive';
|
||||||
|
import { ZoomingCanvasDirective } from './directives/zooming-canvas.directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, MatMenuModule, MatIconModule],
|
imports: [CommonModule, MatMenuModule, MatIconModule],
|
||||||
@ -67,7 +70,9 @@ import { DrawingAddingComponent } from './components/drawing-adding/drawing-addi
|
|||||||
...ANGULAR_MAP_DECLARATIONS,
|
...ANGULAR_MAP_DECLARATIONS,
|
||||||
SelectionControlComponent,
|
SelectionControlComponent,
|
||||||
SelectionSelectComponent,
|
SelectionSelectComponent,
|
||||||
DraggableSelectionComponent
|
DraggableSelectionComponent,
|
||||||
|
MovingCanvasDirective,
|
||||||
|
ZoomingCanvasDirective
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
CssFixer,
|
CssFixer,
|
||||||
@ -87,6 +92,7 @@ import { DrawingAddingComponent } from './components/drawing-adding/drawing-addi
|
|||||||
DrawingsEventSource,
|
DrawingsEventSource,
|
||||||
NodesEventSource,
|
NodesEventSource,
|
||||||
LinksEventSource,
|
LinksEventSource,
|
||||||
|
MovingEventSource,
|
||||||
MapDrawingToSvgConverter,
|
MapDrawingToSvgConverter,
|
||||||
DrawingToMapDrawingConverter,
|
DrawingToMapDrawingConverter,
|
||||||
LabelToMapLabelConverter,
|
LabelToMapLabelConverter,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<svg #svg class="map" preserveAspectRatio="none">
|
<svg #svg class="map" preserveAspectRatio="none" movingCanvas zoomingCanvas>
|
||||||
<filter id="grayscale"><feColorMatrix id="feGrayscale" type="saturate" values="0" /></filter>
|
<filter id="grayscale"><feColorMatrix id="feGrayscale" type="saturate" values="0" /></filter>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
@ -6,5 +6,5 @@
|
|||||||
<app-drawing-resizing></app-drawing-resizing>
|
<app-drawing-resizing></app-drawing-resizing>
|
||||||
<app-selection-control></app-selection-control>
|
<app-selection-control></app-selection-control>
|
||||||
<app-selection-select></app-selection-select>
|
<app-selection-select></app-selection-select>
|
||||||
<app-text-editor #textEditor [svg]="svg"></app-text-editor>
|
<app-text-editor #textEditor [server]="server" [svg]="svg"></app-text-editor>
|
||||||
<app-draggable-selection [svg]="svg"></app-draggable-selection>
|
<app-draggable-selection [svg]="svg"></app-draggable-selection>
|
||||||
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 517 B |
@ -31,6 +31,7 @@ import { MapSettingsManager } from '../../managers/map-settings-manager';
|
|||||||
import { Server } from '../../../models/server';
|
import { Server } from '../../../models/server';
|
||||||
import { ToolsService } from '../../../services/tools.service';
|
import { ToolsService } from '../../../services/tools.service';
|
||||||
import { TextEditorComponent } from '../text-editor/text-editor.component';
|
import { TextEditorComponent } from '../text-editor/text-editor.component';
|
||||||
|
import { MapScaleService } from '../../../services/mapScale.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-d3-map',
|
selector: 'app-d3-map',
|
||||||
@ -47,8 +48,8 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input() width = 1500;
|
@Input() width = 1500;
|
||||||
@Input() height = 600;
|
@Input() height = 600;
|
||||||
|
|
||||||
@ViewChild('svg') svgRef: ElementRef;
|
@ViewChild('svg', {static: false}) svgRef: ElementRef;
|
||||||
@ViewChild('textEditor') textEditor: TextEditorComponent;
|
@ViewChild('textEditor', {static: false}) textEditor: TextEditorComponent;
|
||||||
|
|
||||||
private parentNativeElement: any;
|
private parentNativeElement: any;
|
||||||
private svg: Selection<SVGSVGElement, any, null, undefined>;
|
private svg: Selection<SVGSVGElement, any, null, undefined>;
|
||||||
@ -73,7 +74,8 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
protected selectionToolWidget: SelectionTool,
|
protected selectionToolWidget: SelectionTool,
|
||||||
protected movingToolWidget: MovingTool,
|
protected movingToolWidget: MovingTool,
|
||||||
public graphLayout: GraphLayout,
|
public graphLayout: GraphLayout,
|
||||||
private toolsService: ToolsService
|
private toolsService: ToolsService,
|
||||||
|
private mapScaleService: MapScaleService
|
||||||
) {
|
) {
|
||||||
this.parentNativeElement = element.nativeElement;
|
this.parentNativeElement = element.nativeElement;
|
||||||
}
|
}
|
||||||
@ -119,9 +121,12 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.subscriptions.push(
|
||||||
|
this.mapScaleService.scaleChangeEmitter.subscribe((value: number) => this.redraw())
|
||||||
|
);
|
||||||
|
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
this.toolsService.isMovingToolActivated.subscribe((value: boolean) => {
|
this.toolsService.isMovingToolActivated.subscribe((value: boolean) => {
|
||||||
this.movingToolWidget.setEnabled(value);
|
|
||||||
this.mapChangeDetectorRef.detectChanges();
|
this.mapChangeDetectorRef.detectChanges();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -180,7 +185,8 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.graphDataManager.setLinks(this.links);
|
this.graphDataManager.setLinks(this.links);
|
||||||
this.graphDataManager.setDrawings(this.drawings);
|
this.graphDataManager.setDrawings(this.drawings);
|
||||||
this.graphLayout.draw(this.svg, this.context);
|
this.graphLayout.draw(this.svg, this.context);
|
||||||
this.textEditor.activateTextEditing();
|
this.textEditor.activateTextEditingForDrawings();
|
||||||
|
this.textEditor.activateTextEditingForNodeLabels();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
|
@ -21,6 +21,7 @@ import { MapLabel } from '../../models/map/map-label';
|
|||||||
import { MapLinkNode } from '../../models/map/map-link-node';
|
import { MapLinkNode } from '../../models/map/map-link-node';
|
||||||
import { select } from 'd3-selection';
|
import { select } from 'd3-selection';
|
||||||
import { MapLink } from '../../models/map/map-link';
|
import { MapLink } from '../../models/map/map-link';
|
||||||
|
import { MapSettingService } from '../../../services/mapsettings.service';
|
||||||
|
|
||||||
describe('DraggableSelectionComponent', () => {
|
describe('DraggableSelectionComponent', () => {
|
||||||
let component: DraggableSelectionComponent;
|
let component: DraggableSelectionComponent;
|
||||||
@ -121,7 +122,8 @@ describe('DraggableSelectionComponent', () => {
|
|||||||
{ provide: NodesEventSource, useValue: nodesEventSourceStub },
|
{ provide: NodesEventSource, useValue: nodesEventSourceStub },
|
||||||
{ provide: DrawingsEventSource, useValue: drawingsEventSourceStub },
|
{ provide: DrawingsEventSource, useValue: drawingsEventSourceStub },
|
||||||
{ provide: GraphDataManager, useValue: mockedGraphDataManager },
|
{ provide: GraphDataManager, useValue: mockedGraphDataManager },
|
||||||
{ provide: LinksEventSource, useValue: linksEventSourceStub }
|
{ provide: LinksEventSource, useValue: linksEventSourceStub },
|
||||||
|
{ provide: MapSettingService, useClass: MapSettingService }
|
||||||
],
|
],
|
||||||
declarations: [DraggableSelectionComponent]
|
declarations: [DraggableSelectionComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@ -17,6 +17,7 @@ import { LabelWidget } from '../../widgets/label';
|
|||||||
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
||||||
import { MapLinkNode } from '../../models/map/map-link-node';
|
import { MapLinkNode } from '../../models/map/map-link-node';
|
||||||
import { LinksEventSource } from '../../events/links-event-source';
|
import { LinksEventSource } from '../../events/links-event-source';
|
||||||
|
import { MapSettingService } from '../../../services/mapsettings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-draggable-selection',
|
selector: 'app-draggable-selection',
|
||||||
@ -27,6 +28,8 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
|||||||
private start: Subscription;
|
private start: Subscription;
|
||||||
private drag: Subscription;
|
private drag: Subscription;
|
||||||
private end: Subscription;
|
private end: Subscription;
|
||||||
|
private mapSettingsSubscription: Subscription;
|
||||||
|
private isMapLocked: boolean = false;
|
||||||
|
|
||||||
@Input('svg') svg: SVGSVGElement;
|
@Input('svg') svg: SVGSVGElement;
|
||||||
|
|
||||||
@ -40,12 +43,17 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
|||||||
private nodesEventSource: NodesEventSource,
|
private nodesEventSource: NodesEventSource,
|
||||||
private drawingsEventSource: DrawingsEventSource,
|
private drawingsEventSource: DrawingsEventSource,
|
||||||
private graphDataManager: GraphDataManager,
|
private graphDataManager: GraphDataManager,
|
||||||
private linksEventSource: LinksEventSource
|
private linksEventSource: LinksEventSource,
|
||||||
|
private mapSettingsService: MapSettingService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const svg = select(this.svg);
|
const svg = select(this.svg);
|
||||||
|
|
||||||
|
this.mapSettingsSubscription = this.mapSettingsService.isMapLocked.subscribe((value) => {
|
||||||
|
this.isMapLocked = value;
|
||||||
|
});
|
||||||
|
|
||||||
this.start = merge(
|
this.start = merge(
|
||||||
this.nodesWidget.draggable.start,
|
this.nodesWidget.draggable.start,
|
||||||
this.drawingsWidget.draggable.start,
|
this.drawingsWidget.draggable.start,
|
||||||
@ -84,6 +92,7 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
|||||||
this.labelWidget.draggable.drag,
|
this.labelWidget.draggable.drag,
|
||||||
this.interfaceWidget.draggable.drag
|
this.interfaceWidget.draggable.drag
|
||||||
).subscribe((evt: DraggableDrag<any>) => {
|
).subscribe((evt: DraggableDrag<any>) => {
|
||||||
|
if (!this.isMapLocked) {
|
||||||
const selected = this.selectionManager.getSelected();
|
const selected = this.selectionManager.getSelected();
|
||||||
const selectedNodes = selected.filter(item => item instanceof MapNode);
|
const selectedNodes = selected.filter(item => item instanceof MapNode);
|
||||||
// update nodes
|
// update nodes
|
||||||
@ -153,6 +162,7 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.linksWidget.redrawLink(svg, link);
|
this.linksWidget.redrawLink(svg, link);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.end = merge(
|
this.end = merge(
|
||||||
@ -161,6 +171,7 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
|||||||
this.labelWidget.draggable.end,
|
this.labelWidget.draggable.end,
|
||||||
this.interfaceWidget.draggable.end
|
this.interfaceWidget.draggable.end
|
||||||
).subscribe((evt: DraggableEnd<any>) => {
|
).subscribe((evt: DraggableEnd<any>) => {
|
||||||
|
if (!this.isMapLocked) {
|
||||||
const selected = this.selectionManager.getSelected();
|
const selected = this.selectionManager.getSelected();
|
||||||
const selectedNodes = selected.filter(item => item instanceof MapNode);
|
const selectedNodes = selected.filter(item => item instanceof MapNode);
|
||||||
|
|
||||||
@ -194,6 +205,7 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
|
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,5 +213,6 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
|||||||
this.start.unsubscribe();
|
this.start.unsubscribe();
|
||||||
this.drag.unsubscribe();
|
this.drag.unsubscribe();
|
||||||
this.end.unsubscribe();
|
this.end.unsubscribe();
|
||||||
|
this.mapSettingsSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@ export class DrawingAddingComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
let listener = (event: MouseEvent) => {
|
let listener = (event: MouseEvent) => {
|
||||||
let x = event.pageX - (this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x);
|
let x = (event.pageX - (this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x))/this.context.transformation.k;
|
||||||
let y = event.pageY - (this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y);
|
let y = (event.pageY - (this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y))/this.context.transformation.k;
|
||||||
|
|
||||||
this.drawingsEventSource.pointToAddSelected.emit(new AddedDataEvent(x, y));
|
this.drawingsEventSource.pointToAddSelected.emit(new AddedDataEvent(x, y));
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
|
@ -13,7 +13,7 @@ export class TextComponent implements OnInit, DoCheck {
|
|||||||
|
|
||||||
@Input('app-text') text: TextElement;
|
@Input('app-text') text: TextElement;
|
||||||
|
|
||||||
@ViewChild('text') textRef: ElementRef;
|
@ViewChild('text', {static: false}) textRef: ElementRef;
|
||||||
|
|
||||||
lines: string[] = [];
|
lines: string[] = [];
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ export class ExperimentalMapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input() width = 1500;
|
@Input() width = 1500;
|
||||||
@Input() height = 600;
|
@Input() height = 600;
|
||||||
|
|
||||||
@ViewChild('svg') svg: ElementRef;
|
@ViewChild('svg', {static: false}) svg: ElementRef;
|
||||||
|
|
||||||
private changesDetected: Subscription;
|
private changesDetected: Subscription;
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { CssFixer } from '../../../helpers/css-fixer';
|
|||||||
export class InterfaceLabelComponent implements OnInit {
|
export class InterfaceLabelComponent implements OnInit {
|
||||||
@Input('app-interface-label') ignore: any;
|
@Input('app-interface-label') ignore: any;
|
||||||
|
|
||||||
@ViewChild('textSvg') textRef: ElementRef;
|
@ViewChild('textSvg', {static: false}) textRef: ElementRef;
|
||||||
|
|
||||||
private label = {
|
private label = {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
@ -26,7 +26,7 @@ export class LinkComponent implements OnInit, OnDestroy {
|
|||||||
@Input('node-changed') nodeChanged: EventEmitter<Node>;
|
@Input('node-changed') nodeChanged: EventEmitter<Node>;
|
||||||
@Input('show-interface-labels') showInterfaceLabels: boolean;
|
@Input('show-interface-labels') showInterfaceLabels: boolean;
|
||||||
|
|
||||||
@ViewChild('path') path: ElementRef;
|
@ViewChild('path', {static: false}) path: ElementRef;
|
||||||
|
|
||||||
private ethernetLinkStrategy = new EthernetLinkStrategy();
|
private ethernetLinkStrategy = new EthernetLinkStrategy();
|
||||||
private serialLinkStrategy = new SerialLinkStrategy();
|
private serialLinkStrategy = new SerialLinkStrategy();
|
||||||
|
@ -30,8 +30,8 @@ import { DraggedDataEvent } from '../../../events/event-source';
|
|||||||
export class NodeComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
|
export class NodeComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
|
||||||
static NODE_LABEL_MARGIN = 3;
|
static NODE_LABEL_MARGIN = 3;
|
||||||
|
|
||||||
@ViewChild('label') label: ElementRef;
|
@ViewChild('label', {static: false}) label: ElementRef;
|
||||||
@ViewChild('image') imageRef: ElementRef;
|
@ViewChild('image', {static: false}) imageRef: ElementRef;
|
||||||
|
|
||||||
@Input('app-node') node: MapNode;
|
@Input('app-node') node: MapNode;
|
||||||
@Input('symbols') symbols: Symbol[];
|
@Input('symbols') symbols: Symbol[];
|
||||||
|
@ -5,10 +5,18 @@ import { DrawingsEventSource } from '../../events/drawings-event-source';
|
|||||||
import { ToolsService } from '../../../services/tools.service';
|
import { ToolsService } from '../../../services/tools.service';
|
||||||
import { Context } from '../../models/context';
|
import { Context } from '../../models/context';
|
||||||
import { Renderer2 } from '@angular/core';
|
import { Renderer2 } from '@angular/core';
|
||||||
|
import { MapScaleService } from '../../../services/mapScale.service';
|
||||||
|
import { LinkService } from '../../../services/link.service';
|
||||||
|
import { NodesDataSource } from '../../datasources/nodes-datasource';
|
||||||
|
import { LinksDataSource } from '../../datasources/links-datasource';
|
||||||
|
import { SelectionManager } from '../../managers/selection-manager';
|
||||||
|
import { FontFixer } from '../../helpers/font-fixer';
|
||||||
|
import { MockedLinkService } from '../../../components/project-map/project-map.component.spec';
|
||||||
|
|
||||||
describe('TemporaryTextElementComponent', () => {
|
describe('TextEditorComponent', () => {
|
||||||
let component: TextEditorComponent;
|
let component: TextEditorComponent;
|
||||||
let fixture: ComponentFixture<TextEditorComponent>;
|
let fixture: ComponentFixture<TextEditorComponent>;
|
||||||
|
let mockedLinkService: MockedLinkService = new MockedLinkService();
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -17,7 +25,13 @@ describe('TemporaryTextElementComponent', () => {
|
|||||||
{ provide: DrawingsEventSource, useClass: DrawingsEventSource },
|
{ provide: DrawingsEventSource, useClass: DrawingsEventSource },
|
||||||
{ provide: ToolsService, useClass: ToolsService },
|
{ provide: ToolsService, useClass: ToolsService },
|
||||||
{ provide: Context, useClass: Context },
|
{ provide: Context, useClass: Context },
|
||||||
{ provide: Renderer2, useClass: Renderer2 }
|
{ provide: Renderer2, useClass: Renderer2 },
|
||||||
|
{ provide: MapScaleService, useClass: MapScaleService },
|
||||||
|
{ provide: LinkService, useValue: mockedLinkService },
|
||||||
|
{ provide: NodesDataSource, useClass: NodesDataSource },
|
||||||
|
{ provide: LinksDataSource, useClass: LinksDataSource },
|
||||||
|
{ provide: SelectionManager, useClass: SelectionManager },
|
||||||
|
{ provide: FontFixer, useClass: FontFixer }
|
||||||
],
|
],
|
||||||
declarations: [TextEditorComponent]
|
declarations: [TextEditorComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@ -6,6 +6,20 @@ import { select } from 'd3-selection';
|
|||||||
import { TextElement } from '../../models/drawings/text-element';
|
import { TextElement } from '../../models/drawings/text-element';
|
||||||
import { Context } from '../../models/context';
|
import { Context } from '../../models/context';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { MapScaleService } from '../../../services/mapScale.service';
|
||||||
|
import { MapLabel } from '../../models/map/map-label';
|
||||||
|
import { MapNode } from '../../models/map/map-node';
|
||||||
|
import { NodesDataSource } from '../../datasources/nodes-datasource';
|
||||||
|
import { Node } from '../../models/node';
|
||||||
|
import { SelectionManager } from '../../managers/selection-manager';
|
||||||
|
import { Server } from '../../../models/server';
|
||||||
|
import { MapLinkNode } from '../../models/map/map-link-node';
|
||||||
|
import { LinkService } from '../../../services/link.service';
|
||||||
|
import { LinksDataSource } from '../../datasources/links-datasource';
|
||||||
|
import { Link } from '../../../models/link';
|
||||||
|
import { StyleProperty } from '../../../components/project-map/drawings-editors/text-editor/text-editor.component';
|
||||||
|
import { FontFixer } from '../../helpers/font-fixer';
|
||||||
|
import { Font } from '../../models/font';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-text-editor',
|
selector: 'app-text-editor',
|
||||||
@ -13,8 +27,9 @@ import { Subscription } from 'rxjs';
|
|||||||
styleUrls: ['./text-editor.component.scss']
|
styleUrls: ['./text-editor.component.scss']
|
||||||
})
|
})
|
||||||
export class TextEditorComponent implements OnInit, OnDestroy {
|
export class TextEditorComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild('temporaryTextElement') temporaryTextElement: ElementRef;
|
@ViewChild('temporaryTextElement', {static: false}) temporaryTextElement: ElementRef;
|
||||||
@Input('svg') svg: SVGSVGElement;
|
@Input('svg') svg: SVGSVGElement;
|
||||||
|
@Input('server') server: Server;
|
||||||
|
|
||||||
leftPosition: string = '0px';
|
leftPosition: string = '0px';
|
||||||
topPosition: string = '0px';
|
topPosition: string = '0px';
|
||||||
@ -22,6 +37,8 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private editingDrawingId: string;
|
private editingDrawingId: string;
|
||||||
private editedElement: any;
|
private editedElement: any;
|
||||||
|
private editedLink: MapLinkNode;
|
||||||
|
private editedNode: Node;
|
||||||
|
|
||||||
private mapListener: Function;
|
private mapListener: Function;
|
||||||
private textListener: Function;
|
private textListener: Function;
|
||||||
@ -32,7 +49,13 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
private drawingsEventSource: DrawingsEventSource,
|
private drawingsEventSource: DrawingsEventSource,
|
||||||
private toolsService: ToolsService,
|
private toolsService: ToolsService,
|
||||||
private context: Context,
|
private context: Context,
|
||||||
private renderer: Renderer2
|
private renderer: Renderer2,
|
||||||
|
private mapScaleService: MapScaleService,
|
||||||
|
private linkService: LinkService,
|
||||||
|
private linksDataSource: LinksDataSource,
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private selectionManager: SelectionManager,
|
||||||
|
private fontFixer: FontFixer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -40,7 +63,8 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
isActive ? this.activateTextAdding() : this.deactivateTextAdding();
|
isActive ? this.activateTextAdding() : this.deactivateTextAdding();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.activateTextEditing();
|
this.activateTextEditingForDrawings();
|
||||||
|
this.activateTextEditingForNodeLabels();
|
||||||
}
|
}
|
||||||
|
|
||||||
activateTextAdding() {
|
activateTextAdding() {
|
||||||
@ -48,14 +72,15 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
this.leftPosition = event.pageX.toString() + 'px';
|
this.leftPosition = event.pageX.toString() + 'px';
|
||||||
this.topPosition = event.pageY.toString() + 'px';
|
this.topPosition = event.pageY.toString() + 'px';
|
||||||
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'initial');
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'initial');
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'transform', `scale(${this.mapScaleService.getScale()})`);
|
||||||
this.temporaryTextElement.nativeElement.focus();
|
this.temporaryTextElement.nativeElement.focus();
|
||||||
|
|
||||||
let textListener = () => {
|
let textListener = () => {
|
||||||
this.drawingsEventSource.textAdded.emit(
|
this.drawingsEventSource.textAdded.emit(
|
||||||
new TextAddedDataEvent(
|
new TextAddedDataEvent(
|
||||||
this.temporaryTextElement.nativeElement.innerText.replace(/\n$/, ''),
|
this.temporaryTextElement.nativeElement.innerText.replace(/\n$/, ''),
|
||||||
event.pageX - this.context.transformation.x,
|
event.pageX,
|
||||||
event.pageY - this.context.transformation.y
|
event.pageY
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
this.deactivateTextAdding();
|
this.deactivateTextAdding();
|
||||||
@ -77,23 +102,90 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
this.svg.removeEventListener('click', this.mapListener as EventListenerOrEventListenerObject);
|
this.svg.removeEventListener('click', this.mapListener as EventListenerOrEventListenerObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
activateTextEditing() {
|
activateTextEditingForNodeLabels() {
|
||||||
|
const rootElement = select(this.svg);
|
||||||
|
|
||||||
|
rootElement
|
||||||
|
.selectAll<SVGGElement, MapLinkNode>('g.interface_label_container')
|
||||||
|
.select<SVGTextElement>('text.interface_label')
|
||||||
|
.on('dblclick', (elem, index, textElements) => {
|
||||||
|
this.selectionManager.setSelected([]);
|
||||||
|
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'initial');
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'transform', `scale(${this.mapScaleService.getScale()})`);
|
||||||
|
this.editedLink = elem;
|
||||||
|
|
||||||
|
select(textElements[index]).attr('visibility', 'hidden');
|
||||||
|
select(textElements[index]).classed('editingMode', true);
|
||||||
|
|
||||||
|
this.editedNode = this.nodesDataSource.get(elem.nodeId);
|
||||||
|
this.editedLink = elem;
|
||||||
|
let x = ((elem.label.originalX + this.editedNode.x - 1) * this.context.transformation.k) + this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x;
|
||||||
|
let y = ((elem.label.originalY + this.editedNode.y + 4) * this.context.transformation.k) + this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y;
|
||||||
|
this.leftPosition = x.toString() + 'px';
|
||||||
|
this.topPosition = y.toString() + 'px';
|
||||||
|
this.temporaryTextElement.nativeElement.innerText = elem.label.text;
|
||||||
|
|
||||||
|
let styleProperties: StyleProperty[] = [];
|
||||||
|
for (let property of elem.label.style.split(";")){
|
||||||
|
styleProperties.push({
|
||||||
|
property: property.split(": ")[0],
|
||||||
|
value: property.split(": ")[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let font: Font = {
|
||||||
|
font_family: styleProperties.find(p => p.property === 'font-family') ? styleProperties.find(p => p.property === 'font-family').value : 'TypeWriter',
|
||||||
|
font_size: styleProperties.find(p => p.property === 'font-size') ? Number(styleProperties.find(p => p.property === 'font-size').value) : 10.0,
|
||||||
|
font_weight: styleProperties.find(p => p.property === 'font-weight') ? styleProperties.find(p => p.property === 'font-weight').value : 'normal'
|
||||||
|
};
|
||||||
|
font = this.fontFixer.fix(font);
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'color', styleProperties.find(p => p.property === 'fill') ? styleProperties.find(p => p.property === 'fill').value : '#000000');
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'font-family', font.font_family);
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'font-size', `${font.font_size}pt`);
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'font-weight', font.font_weight);
|
||||||
|
|
||||||
|
let listener = () => {
|
||||||
|
let innerText = this.temporaryTextElement.nativeElement.innerText;
|
||||||
|
let link: Link = this.linksDataSource.get(this.editedLink.linkId);
|
||||||
|
link.nodes.find(n => n.node_id === this.editedNode.node_id).label.text = innerText;
|
||||||
|
|
||||||
|
this.linkService.updateLink(this.server, link).subscribe((link: Link) => {
|
||||||
|
rootElement
|
||||||
|
.selectAll<SVGTextElement, TextElement>('text.editingMode')
|
||||||
|
.attr('visibility', 'visible')
|
||||||
|
.classed('editingMode', false);
|
||||||
|
|
||||||
|
this.innerText = '';
|
||||||
|
this.temporaryTextElement.nativeElement.innerText = '';
|
||||||
|
this.temporaryTextElement.nativeElement.removeEventListener('focusout', this.textListener);
|
||||||
|
|
||||||
|
this.clearStyle();
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'none');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.textListener = listener;
|
||||||
|
this.temporaryTextElement.nativeElement.addEventListener('focusout', this.textListener);
|
||||||
|
this.temporaryTextElement.nativeElement.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
activateTextEditingForDrawings() {
|
||||||
const rootElement = select(this.svg);
|
const rootElement = select(this.svg);
|
||||||
|
|
||||||
rootElement
|
rootElement
|
||||||
.selectAll<SVGTextElement, TextElement>('text.text_element')
|
.selectAll<SVGTextElement, TextElement>('text.text_element')
|
||||||
.on('dblclick', (elem, index, textElements) => {
|
.on('dblclick', (elem, index, textElements) => {
|
||||||
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'initial');
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'initial');
|
||||||
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'transform', `scale(${this.mapScaleService.getScale()})`);
|
||||||
this.editedElement = elem;
|
this.editedElement = elem;
|
||||||
|
|
||||||
select(textElements[index]).attr('visibility', 'hidden');
|
select(textElements[index]).attr('visibility', 'hidden');
|
||||||
|
|
||||||
select(textElements[index]).classed('editingMode', true);
|
select(textElements[index]).classed('editingMode', true);
|
||||||
|
|
||||||
this.editingDrawingId = textElements[index].parentElement.parentElement.getAttribute('drawing_id');
|
this.editingDrawingId = textElements[index].parentElement.parentElement.getAttribute('drawing_id');
|
||||||
var transformData = textElements[index].parentElement.getAttribute('transform').split(/\(|\)/);
|
var transformData = textElements[index].parentElement.getAttribute('transform').split(/\(|\)/);
|
||||||
var x = Number(transformData[1].split(/,/)[0]) + this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x;
|
var x = (Number(transformData[1].split(/,/)[0]) * this.context.transformation.k) + this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x;
|
||||||
var y = Number(transformData[1].split(/,/)[1]) + this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y;
|
var y = (Number(transformData[1].split(/,/)[1]) * this.context.transformation.k) + this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y;
|
||||||
this.leftPosition = x.toString() + 'px';
|
this.leftPosition = x.toString() + 'px';
|
||||||
this.topPosition = y.toString() + 'px';
|
this.topPosition = y.toString() + 'px';
|
||||||
this.temporaryTextElement.nativeElement.innerText = elem.text;
|
this.temporaryTextElement.nativeElement.innerText = elem.text;
|
||||||
|
169
src/app/cartography/directives/moving-canvas.directive.spec.ts
Normal file
169
src/app/cartography/directives/moving-canvas.directive.spec.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import { ComponentFixture, TestBed, async, tick, fakeAsync } from '@angular/core/testing';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { Context } from '../models/context';
|
||||||
|
import { MovingEventSource } from '../events/moving-event-source';
|
||||||
|
import { MovingCanvasDirective } from './moving-canvas.directive';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `<svg #svg class="map" preserveAspectRatio="none" movingCanvas><g class="canvas" transform="translate(0, 0) scale(1)"></g></svg>`
|
||||||
|
})
|
||||||
|
class DummyComponent {
|
||||||
|
constructor(){}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MovingCanvasDirective', () => {
|
||||||
|
let component: DummyComponent;
|
||||||
|
let fixture: ComponentFixture<DummyComponent>;
|
||||||
|
let movingEventSource = new MovingEventSource();
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: MovingEventSource, useValue: movingEventSource },
|
||||||
|
{ provide: Context, useClass: Context }
|
||||||
|
],
|
||||||
|
declarations: [DummyComponent, MovingCanvasDirective]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DummyComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move canvas if moving mode is activated', fakeAsync(() => {
|
||||||
|
movingEventSource.movingModeState.emit(true);
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let xMovement: number = 200;
|
||||||
|
let yMovement: number = 200;
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousedown', {
|
||||||
|
bubbles: true,
|
||||||
|
clientX: 0,
|
||||||
|
clientY: 0,
|
||||||
|
screenY: 0,
|
||||||
|
screenX: 0,
|
||||||
|
view: window
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousemove', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
movementX: xMovement,
|
||||||
|
movementY: yMovement
|
||||||
|
} as MouseEventInit));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(${xMovement}, ${yMovement}) scale(1)`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not move canvas if moving mode is not activated', fakeAsync(() => {
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousedown', {
|
||||||
|
bubbles: true,
|
||||||
|
clientX: 0,
|
||||||
|
clientY: 0,
|
||||||
|
screenY: 0,
|
||||||
|
screenX: 0,
|
||||||
|
view: window
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousemove', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
movementX: 1000,
|
||||||
|
movementY: 1000
|
||||||
|
} as MouseEventInit));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual('translate(0, 0) scale(1)');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not move canvas after mouseup event', fakeAsync(() => {
|
||||||
|
movingEventSource.movingModeState.emit(true);
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let xMovement: number = 200;
|
||||||
|
let yMovement: number = 200;
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousedown', {
|
||||||
|
bubbles: true,
|
||||||
|
clientX: 0,
|
||||||
|
clientY: 0,
|
||||||
|
screenY: 0,
|
||||||
|
screenX: 0,
|
||||||
|
view: window
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousemove', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
movementX: xMovement,
|
||||||
|
movementY: yMovement
|
||||||
|
} as MouseEventInit));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(${xMovement}, ${yMovement}) scale(1)`);
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mouseup', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
movementX: 1000,
|
||||||
|
movementY: 1000
|
||||||
|
} as MouseEventInit));
|
||||||
|
tick();
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousemove', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
movementX: xMovement,
|
||||||
|
movementY: yMovement
|
||||||
|
} as MouseEventInit));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(${xMovement}, ${yMovement}) scale(1)`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not move canvas after deactivation of moving mode', fakeAsync(() => {
|
||||||
|
movingEventSource.movingModeState.emit(true);
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let xMovement: number = 200;
|
||||||
|
let yMovement: number = 200;
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousedown', {
|
||||||
|
bubbles: true,
|
||||||
|
clientX: 0,
|
||||||
|
clientY: 0,
|
||||||
|
screenY: 0,
|
||||||
|
screenX: 0,
|
||||||
|
view: window
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousemove', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
movementX: xMovement,
|
||||||
|
movementY: yMovement
|
||||||
|
} as MouseEventInit));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(${xMovement}, ${yMovement}) scale(1)`);
|
||||||
|
|
||||||
|
movingEventSource.movingModeState.emit(false);
|
||||||
|
canvas.dispatchEvent(new MouseEvent('mousemove', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
movementX: 1000,
|
||||||
|
movementY: 1000
|
||||||
|
} as MouseEventInit));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(${xMovement}, ${yMovement}) scale(1)`);
|
||||||
|
}));
|
||||||
|
});
|
65
src/app/cartography/directives/moving-canvas.directive.ts
Normal file
65
src/app/cartography/directives/moving-canvas.directive.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { HostListener, ElementRef, Renderer, Directive, Input, OnInit, OnDestroy } from '@angular/core'
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { MovingEventSource } from '../events/moving-event-source';
|
||||||
|
import { Context } from '../models/context';
|
||||||
|
import { select } from 'd3-selection';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[movingCanvas]',
|
||||||
|
})
|
||||||
|
export class MovingCanvasDirective implements OnInit, OnDestroy {
|
||||||
|
private mouseupListener: Function;
|
||||||
|
private mousemoveListener: Function;
|
||||||
|
private movingModeState: Subscription;
|
||||||
|
private activated: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private element: ElementRef,
|
||||||
|
private movingEventSource: MovingEventSource,
|
||||||
|
private context: Context
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.movingModeState = this.movingEventSource.movingModeState.subscribe((event: boolean) => {
|
||||||
|
this.activated = event;
|
||||||
|
if (!event) this.removelisteners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.movingModeState.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('mousedown', ['$event'])
|
||||||
|
onMouseDown(event: MouseEvent) {
|
||||||
|
if (this.activated) {
|
||||||
|
this.mousemoveListener = (event: MouseEvent) => {
|
||||||
|
const view = select(this.element.nativeElement);
|
||||||
|
const canvas = view.selectAll<SVGGElement, Context>('g.canvas').data([this.context]);
|
||||||
|
|
||||||
|
canvas.attr('transform', () => {
|
||||||
|
this.context.transformation.x = this.context.transformation.x + event.movementX;
|
||||||
|
this.context.transformation.y = this.context.transformation.y + event.movementY;
|
||||||
|
|
||||||
|
const xTrans = this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x;
|
||||||
|
const yTrans = this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y;
|
||||||
|
const kTrans = this.context.transformation.k;
|
||||||
|
|
||||||
|
return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mouseupListener = (event: MouseEvent) => {
|
||||||
|
this.removelisteners();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.element.nativeElement.addEventListener('mouseup', this.mouseupListener as EventListenerOrEventListenerObject);
|
||||||
|
this.element.nativeElement.addEventListener('mousemove', this.mousemoveListener as EventListenerOrEventListenerObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removelisteners() {
|
||||||
|
this.element.nativeElement.removeEventListener('mouseup', this.mouseupListener as EventListenerOrEventListenerObject);
|
||||||
|
this.element.nativeElement.removeEventListener('mousemove', this.mousemoveListener as EventListenerOrEventListenerObject);
|
||||||
|
}
|
||||||
|
}
|
139
src/app/cartography/directives/zooming-canvas.directive.spec.ts
Normal file
139
src/app/cartography/directives/zooming-canvas.directive.spec.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { ComponentFixture, TestBed, async, tick, fakeAsync } from '@angular/core/testing';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { Context } from '../models/context';
|
||||||
|
import { MovingEventSource } from '../events/moving-event-source';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ZoomingCanvasDirective } from './zooming-canvas.directive';
|
||||||
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `<svg #svg class="map" preserveAspectRatio="none" zoomingCanvas><g class="canvas" transform="translate(0, 0) scale(1)"></g></svg>`
|
||||||
|
})
|
||||||
|
class DummyComponent {
|
||||||
|
constructor(){}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ZoomingCanvasDirective', () => {
|
||||||
|
let component: DummyComponent;
|
||||||
|
let fixture: ComponentFixture<DummyComponent>;
|
||||||
|
let movingEventSource = new MovingEventSource();
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: MovingEventSource, useValue: movingEventSource },
|
||||||
|
{ provide: Context, useClass: Context },
|
||||||
|
{ provide: MapScaleService, useClass: MapScaleService }
|
||||||
|
],
|
||||||
|
declarations: [DummyComponent, ZoomingCanvasDirective]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DummyComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should zoom in canvas if moving mode is activated', fakeAsync(() => {
|
||||||
|
movingEventSource.movingModeState.emit(true);
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let deltaMode: number = 0;
|
||||||
|
let zoom: number = -1000;
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new WheelEvent('wheel', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
deltaMode: deltaMode,
|
||||||
|
deltaY: zoom
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(0, 0) scale(2)`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should zoom out canvas if moving mode is activated', fakeAsync(() => {
|
||||||
|
movingEventSource.movingModeState.emit(true);
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let deltaMode: number = 0;
|
||||||
|
let zoom: number = 100;
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new WheelEvent('wheel', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
deltaMode: deltaMode,
|
||||||
|
deltaY: zoom
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(0, 0) scale(0.9)`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not zoom in/out canvas if moving mode is not activated', fakeAsync(() => {
|
||||||
|
movingEventSource.movingModeState.emit(true);
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let deltaMode: number = 0;
|
||||||
|
let zoom: number = -1000;
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new WheelEvent('wheel', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
deltaMode: deltaMode,
|
||||||
|
deltaY: zoom
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(0, 0) scale(2)`);
|
||||||
|
|
||||||
|
movingEventSource.movingModeState.emit(false);
|
||||||
|
canvas.dispatchEvent(new WheelEvent('wheel', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
deltaMode: deltaMode,
|
||||||
|
deltaY: zoom
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(0, 0) scale(2)`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not zoom in/out canvas after deactivation of moving mode', fakeAsync(() => {
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let deltaMode: number = 0;
|
||||||
|
let zoom: number = -1000;
|
||||||
|
|
||||||
|
canvas.dispatchEvent(new WheelEvent('wheel', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
deltaMode: deltaMode,
|
||||||
|
deltaY: zoom
|
||||||
|
}));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(canvas.getAttribute('transform')).toEqual(`translate(0, 0) scale(1)`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should prevent from default wheel behaviour when moving mode activated', fakeAsync(() => {
|
||||||
|
movingEventSource.movingModeState.emit(true);
|
||||||
|
const canvas: HTMLElement = fixture.debugElement.nativeElement.querySelector('.canvas');
|
||||||
|
let deltaMode: number = 0;
|
||||||
|
let zoom: number = -1000;
|
||||||
|
const event = new WheelEvent('wheel', {
|
||||||
|
bubbles: true,
|
||||||
|
relatedTarget: canvas,
|
||||||
|
deltaMode: deltaMode,
|
||||||
|
deltaY: zoom
|
||||||
|
});
|
||||||
|
spyOn(event, 'preventDefault');
|
||||||
|
|
||||||
|
canvas.dispatchEvent(event);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
});
|
61
src/app/cartography/directives/zooming-canvas.directive.ts
Normal file
61
src/app/cartography/directives/zooming-canvas.directive.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { ElementRef, Directive, OnInit, OnDestroy } from '@angular/core'
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { MovingEventSource } from '../events/moving-event-source';
|
||||||
|
import { Context } from '../models/context';
|
||||||
|
import { select } from 'd3-selection';
|
||||||
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[zoomingCanvas]',
|
||||||
|
})
|
||||||
|
export class ZoomingCanvasDirective implements OnInit, OnDestroy {
|
||||||
|
private wheelListener: Function;
|
||||||
|
private movingModeState: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private element: ElementRef,
|
||||||
|
private movingEventSource: MovingEventSource,
|
||||||
|
private context: Context,
|
||||||
|
private mapsScaleService: MapScaleService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.movingModeState = this.movingEventSource.movingModeState.subscribe((event: boolean) => {
|
||||||
|
event ? this.addListener() : this.removeListener();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.movingModeState.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener() {
|
||||||
|
this.wheelListener = (event: WheelEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let zoom = event.deltaY;
|
||||||
|
zoom = event.deltaMode === 0 ? zoom/100 : zoom/3;
|
||||||
|
|
||||||
|
const view = select(this.element.nativeElement);
|
||||||
|
const canvas = view.selectAll<SVGGElement, Context>('g.canvas').data([this.context]);
|
||||||
|
|
||||||
|
canvas.attr('transform', () => {
|
||||||
|
this.context.transformation.k = this.context.transformation.k - zoom/10;
|
||||||
|
|
||||||
|
const xTrans = this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x;
|
||||||
|
const yTrans = this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y;
|
||||||
|
const kTrans = this.context.transformation.k;
|
||||||
|
this.mapsScaleService.setScale(kTrans);
|
||||||
|
|
||||||
|
return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.element.nativeElement.addEventListener('wheel', this.wheelListener as EventListenerOrEventListenerObject, {passive: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener() {
|
||||||
|
this.element.nativeElement.removeEventListener('wheel', this.wheelListener as EventListenerOrEventListenerObject);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import { TextElement } from '../models/drawings/text-element';
|
import { TextElement } from '../models/drawings/text-element';
|
||||||
import { MapDrawing } from '../models/map/map-drawing';
|
import { MapDrawing } from '../models/map/map-drawing';
|
||||||
import { MapLink } from '../models/map/map-link';
|
import { MapLink } from '../models/map/map-link';
|
||||||
|
import { MapLinkNode } from '../models/map/map-link-node';
|
||||||
|
import { MapLabel } from '../models/map/map-label';
|
||||||
|
|
||||||
export class DataEventSource<T> {
|
export class DataEventSource<T> {
|
||||||
constructor(public datum: T, public dx: number, public dy: number) {}
|
constructor(public datum: T, public dx: number, public dy: number) {}
|
||||||
@ -35,3 +37,11 @@ export class DrawingContextMenu {
|
|||||||
export class LinkContextMenu {
|
export class LinkContextMenu {
|
||||||
constructor(public event: any, public link: MapLink) {}
|
constructor(public event: any, public link: MapLink) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InterfaceLabelContextMenu {
|
||||||
|
constructor(public event: any, public interfaceLabel: MapLinkNode) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LabelContextMenu {
|
||||||
|
constructor(public event: any, public label: MapLabel) {}
|
||||||
|
}
|
||||||
|
6
src/app/cartography/events/moving-event-source.ts
Normal file
6
src/app/cartography/events/moving-event-source.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Injectable, EventEmitter } from "@angular/core";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MovingEventSource {
|
||||||
|
public movingModeState = new EventEmitter<boolean>();
|
||||||
|
}
|
@ -100,7 +100,9 @@ export class SelectionTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private moveSelection(start, move) {
|
private moveSelection(start, move) {
|
||||||
this.path.attr('d', this.rect(start[0], start[1], move[0] - start[0], move[1] - start[1]));
|
let x = start[0]/this.context.transformation.k;
|
||||||
|
let y = start[1]/this.context.transformation.k;
|
||||||
|
this.path.attr('d', this.rect(x, y, move[0]/this.context.transformation.k - x, move[1]/this.context.transformation.k - y));
|
||||||
this.selectedEvent(start, move);
|
this.selectedEvent(start, move);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
|
|
||||||
import { SVGSelection } from '../models/types';
|
import { SVGSelection } from '../models/types';
|
||||||
import { CssFixer } from '../helpers/css-fixer';
|
import { CssFixer } from '../helpers/css-fixer';
|
||||||
@ -10,9 +10,11 @@ import { MapLinkNode } from '../models/map/map-link-node';
|
|||||||
import { MapNode } from '../models/map/map-node';
|
import { MapNode } from '../models/map/map-node';
|
||||||
import { Draggable } from '../events/draggable';
|
import { Draggable } from '../events/draggable';
|
||||||
import { MapSettingsManager } from '../managers/map-settings-manager';
|
import { MapSettingsManager } from '../managers/map-settings-manager';
|
||||||
|
import { InterfaceLabelContextMenu } from '../events/event-source';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InterfaceLabelWidget {
|
export class InterfaceLabelWidget {
|
||||||
|
public onContextMenu = new EventEmitter<InterfaceLabelContextMenu>();
|
||||||
public draggable = new Draggable<SVGGElement, MapLinkNode>();
|
public draggable = new Draggable<SVGGElement, MapLinkNode>();
|
||||||
|
|
||||||
static SURROUNDING_TEXT_BORDER = 5;
|
static SURROUNDING_TEXT_BORDER = 5;
|
||||||
@ -30,6 +32,8 @@ export class InterfaceLabelWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
draw(selection: SVGSelection) {
|
draw(selection: SVGSelection) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
const link_node_position = selection
|
const link_node_position = selection
|
||||||
.selectAll<SVGGElement, MapLinkNode>('g.link_node_position')
|
.selectAll<SVGGElement, MapLinkNode>('g.link_node_position')
|
||||||
.data((link: MapLink) => [[link.source, link.nodes[0]], [link.target, link.nodes[1]]]);
|
.data((link: MapLink) => [[link.source, link.nodes[0]], [link.target, link.nodes[1]]]);
|
||||||
@ -68,7 +72,12 @@ export class InterfaceLabelWidget {
|
|||||||
.attr('class', 'interface_label noselect')
|
.attr('class', 'interface_label noselect')
|
||||||
.attr('interface_label_id', (i: MapLinkNode) => `${i.id}`);
|
.attr('interface_label_id', (i: MapLinkNode) => `${i.id}`);
|
||||||
|
|
||||||
const merge = labels.merge(enter);
|
const merge = labels
|
||||||
|
.merge(enter)
|
||||||
|
.on('contextmenu', (n: MapLinkNode, i: number) => {
|
||||||
|
event.preventDefault();
|
||||||
|
self.onContextMenu.emit(new InterfaceLabelContextMenu(event, n));
|
||||||
|
});
|
||||||
|
|
||||||
// update label
|
// update label
|
||||||
merge
|
merge
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
|
|
||||||
import { Widget } from './widget';
|
import { Widget } from './widget';
|
||||||
import { SVGSelection } from '../models/types';
|
import { SVGSelection } from '../models/types';
|
||||||
@ -10,9 +10,12 @@ import { SelectionManager } from '../managers/selection-manager';
|
|||||||
import { Draggable } from '../events/draggable';
|
import { Draggable } from '../events/draggable';
|
||||||
import { MapLabel } from '../models/map/map-label';
|
import { MapLabel } from '../models/map/map-label';
|
||||||
import { MapSettingsManager } from '../managers/map-settings-manager';
|
import { MapSettingsManager } from '../managers/map-settings-manager';
|
||||||
|
import { LabelContextMenu } from '../events/event-source';
|
||||||
|
import { TextElement } from '../models/drawings/text-element';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LabelWidget implements Widget {
|
export class LabelWidget implements Widget {
|
||||||
|
public onContextMenu = new EventEmitter<LabelContextMenu>();
|
||||||
public draggable = new Draggable<SVGGElement, MapLabel>();
|
public draggable = new Draggable<SVGGElement, MapLabel>();
|
||||||
|
|
||||||
static NODE_LABEL_MARGIN = 3;
|
static NODE_LABEL_MARGIN = 3;
|
||||||
@ -29,6 +32,7 @@ export class LabelWidget implements Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
|
const self = this;
|
||||||
const label_view = view.selectAll<SVGGElement, MapLabel>('g.label_container').data((node: MapNode) => {
|
const label_view = view.selectAll<SVGGElement, MapLabel>('g.label_container').data((node: MapNode) => {
|
||||||
return [node.label];
|
return [node.label];
|
||||||
});
|
});
|
||||||
@ -39,7 +43,12 @@ export class LabelWidget implements Widget {
|
|||||||
.attr('class', 'label_container')
|
.attr('class', 'label_container')
|
||||||
.attr('label_id', (label: MapLabel) => label.id);
|
.attr('label_id', (label: MapLabel) => label.id);
|
||||||
|
|
||||||
const merge = label_view.merge(label_enter);
|
const merge = label_view
|
||||||
|
.merge(label_enter)
|
||||||
|
.on('contextmenu', (n: MapLabel, i: number) => {
|
||||||
|
event.preventDefault();
|
||||||
|
self.onContextMenu.emit(new LabelContextMenu(event, n));
|
||||||
|
});
|
||||||
|
|
||||||
this.drawLabel(merge);
|
this.drawLabel(merge);
|
||||||
|
|
||||||
@ -63,7 +72,8 @@ export class LabelWidget implements Widget {
|
|||||||
|
|
||||||
label_body_enter.append<SVGRectElement>('rect').attr('class', 'label_selection');
|
label_body_enter.append<SVGRectElement>('rect').attr('class', 'label_selection');
|
||||||
|
|
||||||
const label_body_merge = label_body.merge(label_body_enter);
|
const label_body_merge = label_body
|
||||||
|
.merge(label_body_enter);
|
||||||
|
|
||||||
label_body_merge
|
label_body_merge
|
||||||
.select<SVGTextElement>('text.label')
|
.select<SVGTextElement>('text.label')
|
||||||
@ -82,7 +92,9 @@ export class LabelWidget implements Widget {
|
|||||||
|
|
||||||
label_body_merge
|
label_body_merge
|
||||||
.select<SVGRectElement>('rect.label_selection')
|
.select<SVGRectElement>('rect.label_selection')
|
||||||
.attr('visibility', (l: MapLabel) => (this.selectionManager.isSelected(l) ? 'visible' : 'hidden'))
|
.attr('visibility', (l: MapLabel) => {
|
||||||
|
return (this.selectionManager.isSelected(l) ? 'visible' : 'hidden')
|
||||||
|
})
|
||||||
.attr('stroke', 'black')
|
.attr('stroke', 'black')
|
||||||
.attr('stroke-dasharray', '3,3')
|
.attr('stroke-dasharray', '3,3')
|
||||||
.attr('stroke-width', '0.5')
|
.attr('stroke-width', '0.5')
|
||||||
|
@ -38,17 +38,17 @@ export class NodeWidget implements Widget {
|
|||||||
const node_body_merge = node_body
|
const node_body_merge = node_body
|
||||||
.merge(node_body_enter)
|
.merge(node_body_enter)
|
||||||
.classed('selected', (n: MapNode) => this.selectionManager.isSelected(n))
|
.classed('selected', (n: MapNode) => this.selectionManager.isSelected(n))
|
||||||
.on('contextmenu', function(n: MapNode, i: number) {
|
|
||||||
event.preventDefault();
|
|
||||||
self.onContextMenu.emit(new NodeContextMenu(event, n));
|
|
||||||
})
|
|
||||||
.on('click', (node: MapNode) => {
|
.on('click', (node: MapNode) => {
|
||||||
this.nodesEventSource.clicked.emit(new ClickedDataEvent<MapNode>(node, event.clientX, event.clientY));
|
this.nodesEventSource.clicked.emit(new ClickedDataEvent<MapNode>(node, event.pageX, event.pageY));
|
||||||
});
|
});
|
||||||
|
|
||||||
// update image of node
|
// update image of node
|
||||||
node_body_merge
|
node_body_merge
|
||||||
.select<SVGImageElement>('image')
|
.select<SVGImageElement>('image')
|
||||||
|
.on('contextmenu', function(n: MapNode, i: number) {
|
||||||
|
event.preventDefault();
|
||||||
|
self.onContextMenu.emit(new NodeContextMenu(event, n));
|
||||||
|
})
|
||||||
.attr('xnode:href', (n: MapNode) => n.symbolUrl)
|
.attr('xnode:href', (n: MapNode) => n.symbolUrl)
|
||||||
.attr('width', (n: MapNode) => n.width)
|
.attr('width', (n: MapNode) => n.width)
|
||||||
.attr('height', (n: MapNode) => n.height)
|
.attr('height', (n: MapNode) => n.height)
|
||||||
|
@ -5,7 +5,7 @@ import { MatIconModule, MatProgressSpinnerModule } from '@angular/material';
|
|||||||
import { ProgressService } from './progress.service';
|
import { ProgressService } from './progress.service';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
class MockedRouter {
|
class MockedRouter {
|
||||||
events: BehaviorSubject<boolean>;
|
events: BehaviorSubject<boolean>;
|
||||||
@ -13,18 +13,20 @@ class MockedRouter {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.events = new BehaviorSubject(true);
|
this.events = new BehaviorSubject(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigateByUrl() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ProgressComponent', () => {
|
describe('ProgressComponent', () => {
|
||||||
let component: ProgressComponent;
|
let component: ProgressComponent;
|
||||||
let fixture: ComponentFixture<ProgressComponent>;
|
let fixture: ComponentFixture<ProgressComponent>;
|
||||||
let progressService: ProgressService;
|
let progressService: ProgressService;
|
||||||
let router: MockedRouter;
|
let router: MockedRouter = new MockedRouter();
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [RouterTestingModule, MatProgressSpinnerModule, MatIconModule],
|
imports: [RouterTestingModule, MatProgressSpinnerModule, MatIconModule],
|
||||||
providers: [ProgressService, { provide: Router, useClass: MockedRouter }],
|
providers: [ProgressService, { provide: Router, useValue: router }],
|
||||||
declarations: [ProgressComponent]
|
declarations: [ProgressComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
@ -71,4 +73,12 @@ describe('ProgressComponent', () => {
|
|||||||
|
|
||||||
expect(progressService.clear).toHaveBeenCalled();
|
expect(progressService.clear).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should reload page after clicking refresh", () => {
|
||||||
|
spyOn(router, 'navigateByUrl');
|
||||||
|
|
||||||
|
component.refresh();
|
||||||
|
|
||||||
|
expect(router.navigateByUrl).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -36,8 +36,7 @@ export class ProgressComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
// unfortunately we need to use global var
|
this.router.navigateByUrl(this.router.url);
|
||||||
location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
@ -9,9 +9,8 @@ import { AdButlerResponse } from '../../models/adbutler';
|
|||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class AdbutlerComponent implements OnInit {
|
export class AdbutlerComponent implements OnInit {
|
||||||
@ViewChild('ad') ad: ElementRef;
|
@ViewChild('ad', {static: false}) ad: ElementRef;
|
||||||
@Input() theme: string;
|
@Input() theme: string;
|
||||||
|
|
||||||
htmlCode: string;
|
htmlCode: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -35,12 +35,48 @@ export class LinkCreatedComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLinkCreated(linkCreated: MapLinkCreated) {
|
onLinkCreated(linkCreated: MapLinkCreated) {
|
||||||
|
const xLength = Math.abs(linkCreated.sourceNode.x - linkCreated.targetNode.x);
|
||||||
|
const yLength = Math.abs(linkCreated.sourceNode.y - linkCreated.targetNode.y);
|
||||||
|
const zLength = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2));
|
||||||
|
//from law of sines
|
||||||
|
const sinY = yLength/zLength;
|
||||||
|
|
||||||
|
const x = (45 / zLength) * xLength;
|
||||||
|
const y = (45 / zLength) * yLength;
|
||||||
|
|
||||||
|
let xLabelSourceNode = 0;
|
||||||
|
let yLabelSourceNode = 0;
|
||||||
|
let xLabelTargetNode = 0;
|
||||||
|
let yLabelTargetNode = 0;
|
||||||
|
|
||||||
|
if ((linkCreated.sourceNode.x <= linkCreated.targetNode.x) && (linkCreated.sourceNode.y <= linkCreated.targetNode.y)) {
|
||||||
|
xLabelSourceNode = Math.floor(linkCreated.sourceNode.width/2) + Math.round(x) + 5;
|
||||||
|
yLabelSourceNode = Math.floor(linkCreated.sourceNode.height/2) + Math.round(y) + 5;
|
||||||
|
xLabelTargetNode = Math.floor(linkCreated.targetNode.width/2) - Math.round(x) - 5 - Math.round(20 * sinY);
|
||||||
|
yLabelTargetNode = Math.floor(linkCreated.targetNode.height/2) - Math.round(y) + 5 - Math.round(20 * sinY);
|
||||||
|
} else if ((linkCreated.sourceNode.x > linkCreated.targetNode.x) && (linkCreated.sourceNode.y < linkCreated.targetNode.y)) {
|
||||||
|
xLabelSourceNode = Math.floor(linkCreated.sourceNode.width/2) - Math.round(x) - 5 - Math.round(20 * sinY);
|
||||||
|
yLabelSourceNode = Math.floor(linkCreated.sourceNode.height/2) + Math.round(y) + 5 - Math.round(20 * sinY);
|
||||||
|
xLabelTargetNode = Math.floor(linkCreated.targetNode.width/2) + Math.round(x) + 5;
|
||||||
|
yLabelTargetNode = Math.floor(linkCreated.targetNode.height/2) - Math.round(y) - 5;
|
||||||
|
} else if ((linkCreated.sourceNode.x < linkCreated.targetNode.x) && (linkCreated.sourceNode.y > linkCreated.targetNode.y)) {
|
||||||
|
xLabelSourceNode = Math.floor(linkCreated.sourceNode.width/2) + Math.round(x) + 5 - Math.round(20 * sinY);
|
||||||
|
yLabelSourceNode = Math.floor(linkCreated.sourceNode.height/2) - Math.round(y) - 5 - Math.round(20 * sinY);
|
||||||
|
xLabelTargetNode = Math.floor(linkCreated.targetNode.width/2) - Math.round(x) - 5;
|
||||||
|
yLabelTargetNode = Math.floor(linkCreated.targetNode.height/2) + Math.round(y) + 5;
|
||||||
|
} else if ((linkCreated.sourceNode.x >= linkCreated.targetNode.x) && (linkCreated.sourceNode.y >= linkCreated.targetNode.y)) {
|
||||||
|
xLabelSourceNode = Math.floor(linkCreated.sourceNode.width/2) - Math.round(x) - 5 - Math.round(20 * sinY);
|
||||||
|
yLabelSourceNode = Math.floor(linkCreated.sourceNode.height/2) - Math.round(y) + 5 - Math.round(20 * sinY);
|
||||||
|
xLabelTargetNode = Math.floor(linkCreated.targetNode.width/2) + Math.round(x) + 5;
|
||||||
|
yLabelTargetNode = Math.floor(linkCreated.targetNode.height/2) + Math.round(y) + 5;
|
||||||
|
}
|
||||||
|
|
||||||
const sourceNode = this.mapNodeToNode.convert(linkCreated.sourceNode);
|
const sourceNode = this.mapNodeToNode.convert(linkCreated.sourceNode);
|
||||||
const sourcePort = this.mapPortToPort.convert(linkCreated.sourcePort);
|
const sourcePort = this.mapPortToPort.convert(linkCreated.sourcePort);
|
||||||
const targetNode = this.mapNodeToNode.convert(linkCreated.targetNode);
|
const targetNode = this.mapNodeToNode.convert(linkCreated.targetNode);
|
||||||
const targetPort = this.mapPortToPort.convert(linkCreated.targetPort);
|
const targetPort = this.mapPortToPort.convert(linkCreated.targetPort);
|
||||||
|
|
||||||
this.linkService.createLink(this.server, sourceNode, sourcePort, targetNode, targetPort).subscribe(() => {
|
this.linkService.createLink(this.server, sourceNode, sourcePort, targetNode, targetPort, xLabelSourceNode, yLabelSourceNode, xLabelTargetNode, yLabelTargetNode).subscribe(() => {
|
||||||
this.projectService.links(this.server, this.project.project_id).subscribe((links: Link[]) => {
|
this.projectService.links(this.server, this.project.project_id).subscribe((links: Link[]) => {
|
||||||
this.linksDataSource.set(links);
|
this.linksDataSource.set(links);
|
||||||
});
|
});
|
||||||
|
@ -45,8 +45,8 @@ export class TextAddedComponent implements OnInit, OnDestroy {
|
|||||||
.add(
|
.add(
|
||||||
this.server,
|
this.server,
|
||||||
this.project.project_id,
|
this.project.project_id,
|
||||||
evt.x - this.context.getZeroZeroTransformationPoint().x,
|
(evt.x - (this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x))/this.context.transformation.k,
|
||||||
evt.y - this.context.getZeroZeroTransformationPoint().y,
|
(evt.y - (this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y))/this.context.transformation.k,
|
||||||
svgText
|
svgText
|
||||||
)
|
)
|
||||||
.subscribe((serverDrawing: Drawing) => {
|
.subscribe((serverDrawing: Drawing) => {
|
||||||
|
21
src/app/components/help/help.component.html
Normal file
21
src/app/components/help/help.component.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<div class="content">
|
||||||
|
<div class="default-header"><h1>Help</h1></div>
|
||||||
|
<div class="default-content">
|
||||||
|
<div class="container mat-elevation-z8">
|
||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title> Third party components </mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<div [innerHTML]="thirdpartylicenses"></div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title> Release notes </mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<div [innerHTML]="releasenotes"></div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
0
src/app/components/help/help.component.scss
Normal file
0
src/app/components/help/help.component.scss
Normal file
0
src/app/components/help/help.component.spec.ts
Normal file
0
src/app/components/help/help.component.spec.ts
Normal file
33
src/app/components/help/help.component.ts
Normal file
33
src/app/components/help/help.component.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-help',
|
||||||
|
templateUrl: './help.component.html',
|
||||||
|
styleUrls: ['./help.component.scss']
|
||||||
|
})
|
||||||
|
export class HelpComponent implements OnInit {
|
||||||
|
thirdpartylicenses = '';
|
||||||
|
releasenotes = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private httpClient: HttpClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.httpClient.get(window.location.href + '/3rdpartylicenses.txt', {responseType: 'text'})
|
||||||
|
.subscribe(data => {
|
||||||
|
this.thirdpartylicenses = data.replace(new RegExp('\n', 'g'), "<br />")
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (error.status === 404) {
|
||||||
|
this.thirdpartylicenses = 'File not found';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpClient.get('ReleaseNotes.txt', {responseType: 'text'})
|
||||||
|
.subscribe(data => {
|
||||||
|
this.releasenotes = data.replace(new RegExp('\n', 'g'), "<br />")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@
|
|||||||
<mat-card class="matCard">
|
<mat-card class="matCard">
|
||||||
<form [formGroup]="formGroup">
|
<form [formGroup]="formGroup">
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input formControlName="templateName" matInput type="text" [(ngModel)]="templateName" placeholder="Template name">
|
<input formControlName="templateName" matInput type="text" placeholder="Template name">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -54,7 +54,7 @@ export class CloudNodesAddTemplateComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cloudTemplate.template_id = uuid();
|
cloudTemplate.template_id = uuid();
|
||||||
cloudTemplate.name = this.templateName;
|
cloudTemplate.name = this.formGroup.get('templateName').value;
|
||||||
|
|
||||||
this.builtInTemplatesService.addTemplate(this.server, cloudTemplate).subscribe((cloudNodeTemplate) => {
|
this.builtInTemplatesService.addTemplate(this.server, cloudTemplate).subscribe((cloudNodeTemplate) => {
|
||||||
this.goBack();
|
this.goBack();
|
||||||
|
@ -15,7 +15,7 @@ import { DeleteTemplateComponent } from '../../../common/delete-template-compone
|
|||||||
export class CloudNodesTemplatesComponent implements OnInit {
|
export class CloudNodesTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
cloudNodesTemplates: CloudTemplate[] = [];
|
cloudNodesTemplates: CloudTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
<mat-card class="matCard">
|
<mat-card class="matCard">
|
||||||
<form [formGroup]="formGroup">
|
<form [formGroup]="formGroup">
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input formControlName="templateName" matInput type="text" [(ngModel)]="templateName" placeholder="Template name">
|
<input formControlName="templateName" matInput type="text" placeholder="Template name">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input formControlName="numberOfPorts" matInput type="text" [(ngModel)]="numberOfPorts" placeholder="Number of ports">
|
<input formControlName="numberOfPorts" matInput type="text" placeholder="Number of ports">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -75,7 +75,6 @@ describe('EthernetHubsAddTemplateComponent', () => {
|
|||||||
it('should call add template', () => {
|
it('should call add template', () => {
|
||||||
spyOn(mockedBuiltInTemplatesService, 'addTemplate').and.returnValue(of({} as EthernetHubTemplate));
|
spyOn(mockedBuiltInTemplatesService, 'addTemplate').and.returnValue(of({} as EthernetHubTemplate));
|
||||||
component.templateName = "sample name";
|
component.templateName = "sample name";
|
||||||
component.numberOfPorts = 2;
|
|
||||||
component.server = {id: 1} as Server;
|
component.server = {id: 1} as Server;
|
||||||
component.formGroup.controls['templateName'].setValue('template name');
|
component.formGroup.controls['templateName'].setValue('template name');
|
||||||
component.formGroup.controls['numberOfPorts'].setValue('1');
|
component.formGroup.controls['numberOfPorts'].setValue('1');
|
||||||
@ -89,7 +88,6 @@ describe('EthernetHubsAddTemplateComponent', () => {
|
|||||||
spyOn(mockedBuiltInTemplatesService, 'addTemplate').and.returnValue(of({} as EthernetHubTemplate));
|
spyOn(mockedBuiltInTemplatesService, 'addTemplate').and.returnValue(of({} as EthernetHubTemplate));
|
||||||
spyOn(mockedToasterService, 'error');
|
spyOn(mockedToasterService, 'error');
|
||||||
component.templateName = "";
|
component.templateName = "";
|
||||||
component.numberOfPorts = 2;
|
|
||||||
component.server = {id: 1} as Server;
|
component.server = {id: 1} as Server;
|
||||||
|
|
||||||
component.addTemplate();
|
component.addTemplate();
|
||||||
|
@ -17,7 +17,6 @@ import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'
|
|||||||
})
|
})
|
||||||
export class EthernetHubsAddTemplateComponent implements OnInit {
|
export class EthernetHubsAddTemplateComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
numberOfPorts: number;
|
|
||||||
templateName: string = '';
|
templateName: string = '';
|
||||||
formGroup: FormGroup;
|
formGroup: FormGroup;
|
||||||
|
|
||||||
@ -56,9 +55,9 @@ export class EthernetHubsAddTemplateComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ethernetHubTemplate.template_id = uuid();
|
ethernetHubTemplate.template_id = uuid();
|
||||||
ethernetHubTemplate.name = this.templateName;
|
ethernetHubTemplate.name = this.formGroup.get('templateName').value;
|
||||||
|
|
||||||
for(let i=0; i<this.numberOfPorts; i++){
|
for(let i=0; i<this.formGroup.get('numberOfPorts').value; i++){
|
||||||
ethernetHubTemplate.ports_mapping.push({
|
ethernetHubTemplate.ports_mapping.push({
|
||||||
name: `Ethernet${i}`,
|
name: `Ethernet${i}`,
|
||||||
port_number: i
|
port_number: i
|
||||||
|
@ -15,7 +15,7 @@ import { DeleteTemplateComponent } from '../../../common/delete-template-compone
|
|||||||
export class EthernetHubsTemplatesComponent implements OnInit {
|
export class EthernetHubsTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
ethernetHubsTemplates: EthernetHubTemplate[] = [];
|
ethernetHubsTemplates: EthernetHubTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -8,7 +8,10 @@
|
|||||||
<mat-card class="matCard">
|
<mat-card class="matCard">
|
||||||
<form [formGroup]="formGroup">
|
<form [formGroup]="formGroup">
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input formControlName="templateName" matInput type="text" [(ngModel)]="templateName" placeholder="Template name">
|
<input formControlName="templateName" matInput type="text" placeholder="Template name">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="form-field">
|
||||||
|
<input formControlName="numberOfPorts" matInput type="text" placeholder="Number of ports">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -75,7 +75,6 @@ describe('EthernetSwitchesAddTemplateComponent', () => {
|
|||||||
it('should call add template', () => {
|
it('should call add template', () => {
|
||||||
spyOn(mockedBuiltInTemplatesService, 'addTemplate').and.returnValue(of({} as EthernetSwitchTemplate));
|
spyOn(mockedBuiltInTemplatesService, 'addTemplate').and.returnValue(of({} as EthernetSwitchTemplate));
|
||||||
component.templateName = "sample name";
|
component.templateName = "sample name";
|
||||||
component.numberOfPorts = 2;
|
|
||||||
component.server = {id: 1} as Server;
|
component.server = {id: 1} as Server;
|
||||||
component.formGroup.controls['templateName'].setValue('template name');
|
component.formGroup.controls['templateName'].setValue('template name');
|
||||||
component.formGroup.controls['numberOfPorts'].setValue('1');
|
component.formGroup.controls['numberOfPorts'].setValue('1');
|
||||||
|
@ -17,7 +17,6 @@ import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms'
|
|||||||
})
|
})
|
||||||
export class EthernetSwitchesAddTemplateComponent implements OnInit {
|
export class EthernetSwitchesAddTemplateComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
numberOfPorts: number;
|
|
||||||
templateName: string = '';
|
templateName: string = '';
|
||||||
formGroup: FormGroup;
|
formGroup: FormGroup;
|
||||||
|
|
||||||
@ -56,9 +55,9 @@ export class EthernetSwitchesAddTemplateComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ethernetSwitchTemplate.template_id = uuid();
|
ethernetSwitchTemplate.template_id = uuid();
|
||||||
ethernetSwitchTemplate.name = this.templateName;
|
ethernetSwitchTemplate.name = this.formGroup.get('templateName').value;
|
||||||
|
|
||||||
for(let i=0; i<this.numberOfPorts; i++){
|
for(let i=0; i<this.formGroup.get('numberOfPorts').value; i++){
|
||||||
ethernetSwitchTemplate.ports_mapping.push({
|
ethernetSwitchTemplate.ports_mapping.push({
|
||||||
ethertype: '',
|
ethertype: '',
|
||||||
name: `Ethernet${i}`,
|
name: `Ethernet${i}`,
|
||||||
|
@ -15,7 +15,7 @@ import { DeleteTemplateComponent } from '../../../common/delete-template-compone
|
|||||||
export class EthernetSwitchesTemplatesComponent implements OnInit {
|
export class EthernetSwitchesTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
ethernetSwitchesTemplates: EthernetSwitchTemplate[] = [];
|
ethernetSwitchesTemplates: EthernetSwitchTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
matInput
|
matInput
|
||||||
class="filename"
|
class="filename"
|
||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="dockerTemplate.image"
|
|
||||||
formControlName="filename"
|
formControlName="filename"
|
||||||
placeholder="Image name"/>
|
placeholder="Image name"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -48,7 +47,6 @@
|
|||||||
matInput
|
matInput
|
||||||
class="templatename"
|
class="templatename"
|
||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="dockerTemplate.name"
|
|
||||||
formControlName="templateName"
|
formControlName="templateName"
|
||||||
placeholder="Container name"/>
|
placeholder="Container name"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -61,7 +59,6 @@
|
|||||||
matInput
|
matInput
|
||||||
class="networkadapter"
|
class="networkadapter"
|
||||||
type="number"
|
type="number"
|
||||||
[(ngModel)]="dockerTemplate.adapters"
|
|
||||||
formControlName="adapters"
|
formControlName="adapters"
|
||||||
placeholder="Adapters"/>
|
placeholder="Adapters"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -87,6 +87,9 @@ export class AddDockerTemplateComponent implements OnInit {
|
|||||||
addTemplate() {
|
addTemplate() {
|
||||||
if ((!this.virtualMachineForm.invalid || !this.newImageSelected) && !this.containerNameForm.invalid && !this.networkAdaptersForm.invalid) {
|
if ((!this.virtualMachineForm.invalid || !this.newImageSelected) && !this.containerNameForm.invalid && !this.networkAdaptersForm.invalid) {
|
||||||
this.dockerTemplate.template_id = uuid();
|
this.dockerTemplate.template_id = uuid();
|
||||||
|
this.dockerTemplate.image = this.virtualMachineForm.get('filename').value;
|
||||||
|
this.dockerTemplate.name = this.containerNameForm.get('templateName').value;
|
||||||
|
this.dockerTemplate.adapters = this.networkAdaptersForm.get('adapters').value;
|
||||||
|
|
||||||
this.dockerService.addTemplate(this.server, this.dockerTemplate).subscribe((template: DockerTemplate) => {
|
this.dockerService.addTemplate(this.server, this.dockerTemplate).subscribe((template: DockerTemplate) => {
|
||||||
this.goBack();
|
this.goBack();
|
||||||
|
@ -15,7 +15,7 @@ import { DockerService } from '../../../../services/docker.service';
|
|||||||
export class DockerTemplatesComponent implements OnInit {
|
export class DockerTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
dockerTemplates: DockerTemplate[] = [];
|
dockerTemplates: DockerTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input
|
<input
|
||||||
matInput type="text"
|
matInput type="text"
|
||||||
[(ngModel)]="iosTemplate.image"
|
|
||||||
formControlName="imageName"
|
formControlName="imageName"
|
||||||
placeholder="IOS image"/>
|
placeholder="IOS image"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -23,7 +22,6 @@
|
|||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input
|
<input
|
||||||
matInput type="text"
|
matInput type="text"
|
||||||
[(ngModel)]="iosTemplate.name"
|
|
||||||
formControlName="templateName"
|
formControlName="templateName"
|
||||||
placeholder="Name"/>
|
placeholder="Name"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -31,8 +29,7 @@
|
|||||||
<mat-select
|
<mat-select
|
||||||
placeholder="Platform"
|
placeholder="Platform"
|
||||||
(selectionChange)="onPlatformChosen($event)"
|
(selectionChange)="onPlatformChosen($event)"
|
||||||
formControlName="platform"
|
formControlName="platform">
|
||||||
[(ngModel)]="iosTemplate.platform">
|
|
||||||
<mat-option *ngFor="let platform of platforms" [value]="platform">
|
<mat-option *ngFor="let platform of platforms" [value]="platform">
|
||||||
{{platform}}
|
{{platform}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@ -42,8 +39,7 @@
|
|||||||
<mat-select
|
<mat-select
|
||||||
placeholder="Chassis"
|
placeholder="Chassis"
|
||||||
(selectionChange)="onChassisChosen($event)"
|
(selectionChange)="onChassisChosen($event)"
|
||||||
formControlName="chassis"
|
formControlName="chassis">
|
||||||
[(ngModel)]="iosTemplate.chassis">
|
|
||||||
<mat-option *ngFor="let chassis of chassis[iosTemplate.platform]" [value]="chassis">
|
<mat-option *ngFor="let chassis of chassis[iosTemplate.platform]" [value]="chassis">
|
||||||
{{chassis}}
|
{{chassis}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@ -61,7 +57,6 @@
|
|||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input
|
<input
|
||||||
matInput type="number"
|
matInput type="number"
|
||||||
[(ngModel)]="iosTemplate.ram"
|
|
||||||
formControlName="memory"
|
formControlName="memory"
|
||||||
value="defaultRam[iosTemplate.platform]"
|
value="defaultRam[iosTemplate.platform]"
|
||||||
placeholder="Default RAM"/>
|
placeholder="Default RAM"/>
|
||||||
|
@ -90,6 +90,11 @@ export class AddIosTemplateComponent implements OnInit {
|
|||||||
addTemplate() {
|
addTemplate() {
|
||||||
if (!this.iosImageForm.invalid && !this.iosNameForm.invalid && !this.iosMemoryForm.invalid) {
|
if (!this.iosImageForm.invalid && !this.iosNameForm.invalid && !this.iosMemoryForm.invalid) {
|
||||||
this.iosTemplate.template_id = uuid();
|
this.iosTemplate.template_id = uuid();
|
||||||
|
this.iosTemplate.image = this.iosImageForm.get("imageName").value;
|
||||||
|
this.iosTemplate.name = this.iosNameForm.get('templateName').value;
|
||||||
|
this.iosTemplate.platform = this.iosNameForm.get('platform').value;
|
||||||
|
this.iosTemplate.chassis = this.iosNameForm.get('chassis').value;
|
||||||
|
this.iosTemplate.ram = this.iosMemoryForm.get('memory').value;
|
||||||
|
|
||||||
if (this.isEtherSwitchRouter) {
|
if (this.isEtherSwitchRouter) {
|
||||||
this.iosTemplate.symbol = ":/symbols/multilayer_switch.svg";
|
this.iosTemplate.symbol = ":/symbols/multilayer_switch.svg";
|
||||||
|
@ -16,7 +16,7 @@ import { VpcsTemplate } from '../../../../models/templates/vpcs-template';
|
|||||||
export class IosTemplatesComponent implements OnInit {
|
export class IosTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
iosTemplates: IosTemplate[] = [];
|
iosTemplates: IosTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<mat-step label="Name">
|
<mat-step label="Name">
|
||||||
<form [formGroup]="templateNameForm">
|
<form [formGroup]="templateNameForm">
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input matInput placeholder="Name" type="text" [(ngModel)]="iouTemplate.name" formControlName="templateName"/>
|
<input matInput placeholder="Name" type="text" formControlName="templateName"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
@ -59,7 +59,6 @@
|
|||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="iouTemplate.path"
|
|
||||||
formControlName="imageName"
|
formControlName="imageName"
|
||||||
placeholder="IOU image"/>
|
placeholder="IOU image"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -82,6 +82,8 @@ export class AddIouTemplateComponent implements OnInit {
|
|||||||
addTemplate() {
|
addTemplate() {
|
||||||
if (!this.templateNameForm.invalid && ((this.newImageSelected && !this.imageForm.invalid) || (!this.newImageSelected && this.iouTemplate.path))) {
|
if (!this.templateNameForm.invalid && ((this.newImageSelected && !this.imageForm.invalid) || (!this.newImageSelected && this.iouTemplate.path))) {
|
||||||
this.iouTemplate.template_id = uuid();
|
this.iouTemplate.template_id = uuid();
|
||||||
|
this.iouTemplate.name = this.templateNameForm.get("templateName").value;
|
||||||
|
this.iouTemplate.path = this.imageForm.get("imageName").value;
|
||||||
|
|
||||||
this.iouService.addTemplate(this.server, this.iouTemplate).subscribe((template: IouTemplate) => {
|
this.iouService.addTemplate(this.server, this.iouTemplate).subscribe((template: IouTemplate) => {
|
||||||
this.goBack();
|
this.goBack();
|
||||||
|
@ -15,7 +15,7 @@ import { IouService } from '../../../../services/iou.service';
|
|||||||
export class IouTemplatesComponent implements OnInit {
|
export class IouTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
iouTemplates: IouTemplate[] = [];
|
iouTemplates: IouTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<input
|
<input
|
||||||
matInput type="text"
|
matInput type="text"
|
||||||
[(ngModel)]="qemuTemplate.name"
|
|
||||||
formControlName="templateName"
|
formControlName="templateName"
|
||||||
placeholder="Please choose a descriptive name for your new QEMU virtual machine"
|
placeholder="Please choose a descriptive name for your new QEMU virtual machine"
|
||||||
ngDefaultContro/>
|
ngDefaultContro/>
|
||||||
@ -28,7 +27,7 @@
|
|||||||
<mat-select
|
<mat-select
|
||||||
placeholder="Qemu binary"
|
placeholder="Qemu binary"
|
||||||
[(ngModel)]="selectedBinary"
|
[(ngModel)]="selectedBinary"
|
||||||
formControlName="binary" >
|
[ngModelOptions]="{standalone: true}">
|
||||||
<mat-option *ngFor="let binary of qemuBinaries" [value]="binary">
|
<mat-option *ngFor="let binary of qemuBinaries" [value]="binary">
|
||||||
{{binary.path}}
|
{{binary.path}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@ -38,7 +37,6 @@
|
|||||||
<input
|
<input
|
||||||
matInput type="number"
|
matInput type="number"
|
||||||
placeholder="RAM"
|
placeholder="RAM"
|
||||||
[(ngModel)]="ramMemory"
|
|
||||||
formControlName="ramMemory"
|
formControlName="ramMemory"
|
||||||
ngDefaultContro/>
|
ngDefaultContro/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -51,7 +51,6 @@ export class AddQemuVmTemplateComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.memoryForm = this.formBuilder.group({
|
this.memoryForm = this.formBuilder.group({
|
||||||
binary: new FormControl('', Validators.required),
|
|
||||||
ramMemory: new FormControl('', Validators.required)
|
ramMemory: new FormControl('', Validators.required)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,7 +94,7 @@ export class AddQemuVmTemplateComponent implements OnInit {
|
|||||||
|
|
||||||
addTemplate() {
|
addTemplate() {
|
||||||
if (!this.nameForm.invalid && !this.memoryForm.invalid && (this.selectedImage || this.chosenImage)) {
|
if (!this.nameForm.invalid && !this.memoryForm.invalid && (this.selectedImage || this.chosenImage)) {
|
||||||
this.qemuTemplate.ram = this.ramMemory;
|
this.qemuTemplate.ram = this.memoryForm.get("ramMemory").value;
|
||||||
this.qemuTemplate.qemu_path = this.selectedBinary.path;
|
this.qemuTemplate.qemu_path = this.selectedBinary.path;
|
||||||
if (this.newImageSelected) {
|
if (this.newImageSelected) {
|
||||||
this.qemuTemplate.hda_disk_image = this.chosenImage;
|
this.qemuTemplate.hda_disk_image = this.chosenImage;
|
||||||
@ -103,6 +102,7 @@ export class AddQemuVmTemplateComponent implements OnInit {
|
|||||||
this.qemuTemplate.hda_disk_image = this.selectedImage.path;
|
this.qemuTemplate.hda_disk_image = this.selectedImage.path;
|
||||||
}
|
}
|
||||||
this.qemuTemplate.template_id = uuid();
|
this.qemuTemplate.template_id = uuid();
|
||||||
|
this.qemuTemplate.name = this.nameForm.get("templateName").value;
|
||||||
|
|
||||||
this.qemuService.addTemplate(this.server, this.qemuTemplate).subscribe((template: QemuTemplate) => {
|
this.qemuService.addTemplate(this.server, this.qemuTemplate).subscribe((template: QemuTemplate) => {
|
||||||
this.goBack();
|
this.goBack();
|
||||||
|
@ -34,7 +34,7 @@ export class QemuVmTemplateDetailsComponent implements OnInit {
|
|||||||
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type'];
|
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type'];
|
||||||
generalSettingsForm: FormGroup;
|
generalSettingsForm: FormGroup;
|
||||||
|
|
||||||
@ViewChild("customAdaptersConfigurator")
|
@ViewChild("customAdaptersConfigurator", {static: false})
|
||||||
customAdaptersConfigurator: CustomAdaptersComponent;
|
customAdaptersConfigurator: CustomAdaptersComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -15,7 +15,7 @@ import { DeleteTemplateComponent } from '../../common/delete-template-component/
|
|||||||
export class QemuVmTemplatesComponent implements OnInit {
|
export class QemuVmTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
qemuTemplates: QemuTemplate[] = [];
|
qemuTemplates: QemuTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -29,7 +29,7 @@ export class VirtualBoxTemplateDetailsComponent implements OnInit {
|
|||||||
generalSettingsForm: FormGroup;
|
generalSettingsForm: FormGroup;
|
||||||
networkForm: FormGroup
|
networkForm: FormGroup
|
||||||
|
|
||||||
@ViewChild("customAdaptersConfigurator")
|
@ViewChild("customAdaptersConfigurator", {static: false})
|
||||||
customAdaptersConfigurator: CustomAdaptersComponent;
|
customAdaptersConfigurator: CustomAdaptersComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -16,7 +16,7 @@ import { VpcsTemplate } from '../../../../models/templates/vpcs-template';
|
|||||||
export class VirtualBoxTemplatesComponent implements OnInit {
|
export class VirtualBoxTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
virtualBoxTemplates: VirtualBoxTemplate[] = [];
|
virtualBoxTemplates: VirtualBoxTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -28,7 +28,7 @@ export class VmwareTemplateDetailsComponent implements OnInit {
|
|||||||
onCloseOptions = [];
|
onCloseOptions = [];
|
||||||
networkTypes = [];
|
networkTypes = [];
|
||||||
|
|
||||||
@ViewChild("customAdaptersConfigurator")
|
@ViewChild("customAdaptersConfigurator", {static: false})
|
||||||
customAdaptersConfigurator: CustomAdaptersComponent;
|
customAdaptersConfigurator: CustomAdaptersComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -15,7 +15,7 @@ import { DeleteTemplateComponent } from '../../common/delete-template-component/
|
|||||||
export class VmwareTemplatesComponent implements OnInit {
|
export class VmwareTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
vmwareTemplates: VmwareTemplate[] = [];
|
vmwareTemplates: VmwareTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -14,7 +14,7 @@ import { DeleteTemplateComponent } from '../../common/delete-template-component/
|
|||||||
export class VpcsTemplatesComponent implements OnInit {
|
export class VpcsTemplatesComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
vpcsTemplates: VpcsTemplate[] = [];
|
vpcsTemplates: VpcsTemplate[] = [];
|
||||||
@ViewChild(DeleteTemplateComponent) deleteComponent: DeleteTemplateComponent;
|
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -103,10 +103,10 @@ describe('ConsoleDeviceActionComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show message when command is not defined', async () => {
|
it('should set command when it is not defined', async () => {
|
||||||
mockedSettingsService.set('console_command', undefined);
|
mockedSettingsService.set('console_command', undefined);
|
||||||
await component.console();
|
await component.console();
|
||||||
expect(component.openConsole).not.toHaveBeenCalled();
|
expect(component.openConsole).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show message when there is no started nodes', async () => {
|
it('should show message when there is no started nodes', async () => {
|
||||||
|
@ -25,11 +25,10 @@ export class ConsoleDeviceActionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async console() {
|
async console() {
|
||||||
const consoleCommand = this.settingsService.get<string>('console_command');
|
let consoleCommand = this.settingsService.get<string>('console_command');
|
||||||
|
|
||||||
if(consoleCommand === undefined) {
|
if(consoleCommand === undefined) {
|
||||||
this.toasterService.error('Console command is not defined. Please change it in the Settings.');
|
consoleCommand = `putty.exe -telnet \%h \%p -wt \"\%d\" -gns3 5 -skin 4`;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const startedNodes = this.nodes.filter(node => node.status === 'started');
|
const startedNodes = this.nodes.filter(node => node.status === 'started');
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<button mat-menu-item (click)="duplicate()">
|
||||||
|
<mat-icon>filter_none</mat-icon>
|
||||||
|
<span>Duplicate</span>
|
||||||
|
</button>
|
@ -0,0 +1,82 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MatIconModule, MatMenuModule } from '@angular/material';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
|
||||||
|
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
|
||||||
|
import { NodeService } from '../../../../../services/node.service';
|
||||||
|
import { DrawingService } from '../../../../../services/drawing.service';
|
||||||
|
import { MockedDrawingService, MockedNodeService } from '../../../project-map.component.spec';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { Drawing } from '../../../../../cartography/models/drawing';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { DuplicateActionComponent } from './duplicate-action.component';
|
||||||
|
import { ToasterService } from '../../../../../services/toaster.service';
|
||||||
|
import { MockedToasterService } from '../../../../../services/toaster.service.spec';
|
||||||
|
|
||||||
|
describe('DuplicateActionComponent', () => {
|
||||||
|
let component: DuplicateActionComponent;
|
||||||
|
let fixture: ComponentFixture<DuplicateActionComponent>;
|
||||||
|
let mockedNodeService: MockedNodeService = new MockedNodeService();
|
||||||
|
let mockedDrawingService: MockedDrawingService = new MockedDrawingService();
|
||||||
|
let mockedToasterService = new MockedToasterService;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [MatIconModule, MatMenuModule, NoopAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: NodesDataSource, useClass: NodesDataSource },
|
||||||
|
{ provide: DrawingsDataSource, useClass: DrawingsDataSource },
|
||||||
|
{ provide: NodeService, useValue: mockedNodeService },
|
||||||
|
{ provide: DrawingService, useValue: mockedDrawingService },
|
||||||
|
{ provide: ToasterService, useValue: mockedToasterService }
|
||||||
|
],
|
||||||
|
declarations: [DuplicateActionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DuplicateActionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call duplicate action in drawing service', () => {
|
||||||
|
let drawing = { drawing_id: '1' } as Drawing;
|
||||||
|
component.drawings = [drawing];
|
||||||
|
component.nodes = [];
|
||||||
|
spyOn(mockedDrawingService, 'duplicate').and.returnValue(of());
|
||||||
|
|
||||||
|
component.duplicate();
|
||||||
|
|
||||||
|
expect(mockedDrawingService.duplicate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call duplicate action in node service', () => {
|
||||||
|
let node = { node_id: '1', status: 'stopped'} as Node;
|
||||||
|
component.nodes = [node];
|
||||||
|
component.drawings = [];
|
||||||
|
spyOn(mockedNodeService, 'duplicate').and.returnValue(of());
|
||||||
|
|
||||||
|
component.duplicate();
|
||||||
|
|
||||||
|
expect(mockedNodeService.duplicate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call duplicate action in both services', () => {
|
||||||
|
let drawing = { drawing_id: '1' } as Drawing;
|
||||||
|
component.drawings = [drawing];
|
||||||
|
let node = { node_id: '1', status: 'stopped' } as Node;
|
||||||
|
component.nodes = [node];
|
||||||
|
spyOn(mockedDrawingService, 'duplicate').and.returnValue(of());
|
||||||
|
spyOn(mockedNodeService, 'duplicate').and.returnValue(of());
|
||||||
|
|
||||||
|
component.duplicate();
|
||||||
|
|
||||||
|
expect(mockedDrawingService.duplicate).toHaveBeenCalled();
|
||||||
|
expect(mockedNodeService.duplicate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,51 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Server } from '../../../../../models/server';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { Drawing } from '../../../../../cartography/models/drawing';
|
||||||
|
import { Project } from '../../../../../models/project';
|
||||||
|
import { NodeService } from '../../../../../services/node.service';
|
||||||
|
import { DrawingService } from '../../../../../services/drawing.service';
|
||||||
|
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
|
||||||
|
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
|
||||||
|
import { ToasterService } from '../../../../../services/toaster.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-duplicate-action',
|
||||||
|
templateUrl: './duplicate-action.component.html'
|
||||||
|
})
|
||||||
|
export class DuplicateActionComponent {
|
||||||
|
@Input() server: Server;
|
||||||
|
@Input() project: Project;
|
||||||
|
@Input() drawings: Drawing[];
|
||||||
|
@Input() nodes: Node[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private nodeService: NodeService,
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private drawingService: DrawingService,
|
||||||
|
private drawingsDataSource: DrawingsDataSource,
|
||||||
|
private toasterService: ToasterService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
duplicate() {
|
||||||
|
let runningNodes: string = '';
|
||||||
|
for(let node of this.nodes) {
|
||||||
|
if (node.status === 'stopped') {
|
||||||
|
this.nodeService.duplicate(this.server, node).subscribe((node: Node) => {
|
||||||
|
this.nodesDataSource.add(node);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
runningNodes += `${node.name}, `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let drawing of this.drawings) {
|
||||||
|
this.drawingService.duplicate(this.server, drawing.project_id, drawing).subscribe((drawing: Drawing) => {
|
||||||
|
this.drawingsDataSource.add(drawing);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runningNodes = runningNodes.substring(0, runningNodes.length-2);
|
||||||
|
this.toasterService.error(`Cannot duplicate node data for nodes: ${runningNodes}`);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,10 @@ import { Project } from '../../../../../models/project';
|
|||||||
import { Drawing } from '../../../../../cartography/models/drawing';
|
import { Drawing } from '../../../../../cartography/models/drawing';
|
||||||
import { MatDialog } from '@angular/material';
|
import { MatDialog } from '@angular/material';
|
||||||
import { TextEditorDialogComponent } from '../../../drawings-editors/text-editor/text-editor.component';
|
import { TextEditorDialogComponent } from '../../../drawings-editors/text-editor/text-editor.component';
|
||||||
|
import { Label } from '../../../../../cartography/models/label';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { Link } from '../../../../../models/link';
|
||||||
|
import { LinkNode } from '../../../../../models/link-node';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit-text-action',
|
selector: 'app-edit-text-action',
|
||||||
@ -13,6 +17,10 @@ export class EditTextActionComponent implements OnInit {
|
|||||||
@Input() server: Server;
|
@Input() server: Server;
|
||||||
@Input() project: Project;
|
@Input() project: Project;
|
||||||
@Input() drawing: Drawing;
|
@Input() drawing: Drawing;
|
||||||
|
@Input() node: Node;
|
||||||
|
@Input() label: Label;
|
||||||
|
@Input() link: Link;
|
||||||
|
@Input() linkNode: LinkNode;
|
||||||
|
|
||||||
constructor(private dialog: MatDialog) {}
|
constructor(private dialog: MatDialog) {}
|
||||||
|
|
||||||
@ -27,5 +35,9 @@ export class EditTextActionComponent implements OnInit {
|
|||||||
instance.server = this.server;
|
instance.server = this.server;
|
||||||
instance.project = this.project;
|
instance.project = this.project;
|
||||||
instance.drawing = this.drawing;
|
instance.drawing = this.drawing;
|
||||||
|
instance.node = this.node;
|
||||||
|
instance.label = this.label;
|
||||||
|
instance.link = this.link;
|
||||||
|
instance.linkNode = this.linkNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,45 @@
|
|||||||
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition">
|
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition">
|
||||||
<span [matMenuTriggerFor]="contextMenu"></span>
|
<span [matMenuTriggerFor]="contextMenu"></span>
|
||||||
<mat-menu #contextMenu="matMenu" class="context-menu-items">
|
<mat-menu #contextMenu="matMenu" class="context-menu-items">
|
||||||
<app-start-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-start-node-action>
|
<app-start-node-action *ngIf="nodes.length && labels.length===0" [server]="server" [nodes]="nodes"></app-start-node-action>
|
||||||
<app-stop-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-stop-node-action>
|
<app-stop-node-action *ngIf="nodes.length && labels.length===0" [server]="server" [nodes]="nodes"></app-stop-node-action>
|
||||||
<app-console-device-action
|
<app-console-device-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && nodes.length && isElectronApp"
|
*ngIf="!projectService.isReadOnly(project) && nodes.length && isElectronApp"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
></app-console-device-action>
|
></app-console-device-action>
|
||||||
<app-edit-style-action *ngIf="drawings.length===1 && !hasTextCapabilities"
|
<app-duplicate-action *ngIf="drawings.length>0 || nodes.length>0"
|
||||||
|
[server]="server"
|
||||||
|
[project]="project"
|
||||||
|
[nodes]="nodes"
|
||||||
|
[drawings]="drawings"
|
||||||
|
></app-duplicate-action>
|
||||||
|
<app-edit-style-action *ngIf="!projectService.isReadOnly(project) && drawings.length===1 && !hasTextCapabilities"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[project]="project"
|
[project]="project"
|
||||||
[drawing]="drawings[0]"
|
[drawing]="drawings[0]"
|
||||||
></app-edit-style-action>
|
></app-edit-style-action>
|
||||||
<app-edit-text-action
|
<app-edit-text-action
|
||||||
*ngIf="drawings.length===1 && hasTextCapabilities"
|
*ngIf="!projectService.isReadOnly(project) &&
|
||||||
|
(drawings.length===1 && hasTextCapabilities && labels.length===0 && linkNodes.length===0 ||
|
||||||
|
labels.length===1 && linkNodes.length===0 && drawings.length===0 ||
|
||||||
|
linkNodes.length===1 && labels.length===0 && drawings.length===0)"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[project]="project"
|
[project]="project"
|
||||||
[drawing]="drawings[0]"
|
[drawing]="drawings[0]"
|
||||||
|
[node]="nodes[0]"
|
||||||
|
[label]="labels[0]"
|
||||||
|
[link]="links[0]"
|
||||||
|
[linkNode]="linkNodes[0]"
|
||||||
></app-edit-text-action>
|
></app-edit-text-action>
|
||||||
<app-move-layer-up-action
|
<app-move-layer-up-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && (drawings.length || nodes.length)"
|
*ngIf="!projectService.isReadOnly(project) && (drawings.length || nodes.length) && labels.length===0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
[drawings]="drawings"
|
[drawings]="drawings"
|
||||||
></app-move-layer-up-action>
|
></app-move-layer-up-action>
|
||||||
<app-move-layer-down-action
|
<app-move-layer-down-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && (drawings.length || nodes.length)"
|
*ngIf="!projectService.isReadOnly(project) && (drawings.length || nodes.length) && labels.length===0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
[drawings]="drawings"
|
[drawings]="drawings"
|
||||||
@ -39,28 +52,28 @@
|
|||||||
></app-start-capture-action>
|
></app-start-capture-action>
|
||||||
<app-stop-capture-action
|
<app-stop-capture-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && isBundledServer
|
*ngIf="!projectService.isReadOnly(project) && isBundledServer
|
||||||
&& drawings.length===0 && nodes.length===0 && links.length===1"
|
&& drawings.length===0 && nodes.length===0 && links.length===1 && linkNodes.length === 0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[link]="links[0]"
|
[link]="links[0]"
|
||||||
></app-stop-capture-action>
|
></app-stop-capture-action>
|
||||||
<app-packet-filters-action
|
<app-packet-filters-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1"
|
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1 && linkNodes.length === 0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[project]="project"
|
[project]="project"
|
||||||
[link]="links[0]"
|
[link]="links[0]"
|
||||||
></app-packet-filters-action>
|
></app-packet-filters-action>
|
||||||
<app-resume-link-action
|
<app-resume-link-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1"
|
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1 && linkNodes.length === 0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[link]="links[0]"
|
[link]="links[0]"
|
||||||
></app-resume-link-action>
|
></app-resume-link-action>
|
||||||
<app-suspend-link-action
|
<app-suspend-link-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1"
|
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1 && linkNodes.length === 0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[link]="links[0]"
|
[link]="links[0]"
|
||||||
></app-suspend-link-action>
|
></app-suspend-link-action>
|
||||||
<app-delete-action
|
<app-delete-action
|
||||||
*ngIf="!projectService.isReadOnly(project)"
|
*ngIf="!projectService.isReadOnly(project) && (drawings.length>0 || nodes.length>0 || links.length>0) && linkNodes.length === 0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
[drawings]="drawings"
|
[drawings]="drawings"
|
||||||
|
@ -47,15 +47,6 @@ describe('ContextMenuComponent', () => {
|
|||||||
expect(component.isElectronApp).toBeTruthy();
|
expect(component.isElectronApp).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset capabilities while opening menu for node', () => {
|
|
||||||
component.contextMenu = { openMenu() {} } as MatMenuTrigger;
|
|
||||||
var spy = spyOn<any>(component, 'resetCapabilities');
|
|
||||||
|
|
||||||
component.openMenuForNode(null, 0, 0);
|
|
||||||
|
|
||||||
expect(spy.calls.any()).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset capabilities while opening menu for drawing', () => {
|
it('should reset capabilities while opening menu for drawing', () => {
|
||||||
component.contextMenu = { openMenu() {} } as MatMenuTrigger;
|
component.contextMenu = { openMenu() {} } as MatMenuTrigger;
|
||||||
let drawing = {} as Drawing;
|
let drawing = {} as Drawing;
|
||||||
|
@ -10,6 +10,7 @@ import { TextElement } from '../../../cartography/models/drawings/text-element';
|
|||||||
import { Label } from '../../../cartography/models/label';
|
import { Label } from '../../../cartography/models/label';
|
||||||
import { Link } from '../../../models/link';
|
import { Link } from '../../../models/link';
|
||||||
import { ElectronService } from 'ngx-electron';
|
import { ElectronService } from 'ngx-electron';
|
||||||
|
import { LinkNode } from '../../../models/link-node';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -21,7 +22,7 @@ export class ContextMenuComponent implements OnInit {
|
|||||||
@Input() project: Project;
|
@Input() project: Project;
|
||||||
@Input() server: Server;
|
@Input() server: Server;
|
||||||
|
|
||||||
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
|
@ViewChild(MatMenuTrigger, {static: false}) contextMenu: MatMenuTrigger;
|
||||||
|
|
||||||
topPosition;
|
topPosition;
|
||||||
leftPosition;
|
leftPosition;
|
||||||
@ -30,6 +31,7 @@ export class ContextMenuComponent implements OnInit {
|
|||||||
nodes: Node[] = [];
|
nodes: Node[] = [];
|
||||||
labels: Label[] = [];
|
labels: Label[] = [];
|
||||||
links: Link[] = [];
|
links: Link[] = [];
|
||||||
|
linkNodes: LinkNode[] = [];
|
||||||
|
|
||||||
hasTextCapabilities = false;
|
hasTextCapabilities = false;
|
||||||
isElectronApp = false;
|
isElectronApp = false;
|
||||||
@ -74,10 +76,21 @@ export class ContextMenuComponent implements OnInit {
|
|||||||
this.contextMenu.openMenu();
|
this.contextMenu.openMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
public openMenuForLabel(label: Label, top: number, left: number) {
|
public openMenuForLabel(label: Label, node: Node, top: number, left: number) {
|
||||||
this.resetCapabilities();
|
this.resetCapabilities();
|
||||||
|
|
||||||
this.labels = [label];
|
this.labels = [label];
|
||||||
|
this.nodes = [node];
|
||||||
|
this.setPosition(top, left);
|
||||||
|
|
||||||
|
this.contextMenu.openMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public openMenuForInterfaceLabel(linkNode: LinkNode, link: Link, top: number, left: number) {
|
||||||
|
this.resetCapabilities();
|
||||||
|
|
||||||
|
this.linkNodes = [linkNode];
|
||||||
|
this.links = [link];
|
||||||
this.setPosition(top, left);
|
this.setPosition(top, left);
|
||||||
|
|
||||||
this.contextMenu.openMenu();
|
this.contextMenu.openMenu();
|
||||||
@ -99,6 +112,8 @@ export class ContextMenuComponent implements OnInit {
|
|||||||
this.drawings = [];
|
this.drawings = [];
|
||||||
this.nodes = [];
|
this.nodes = [];
|
||||||
this.labels = [];
|
this.labels = [];
|
||||||
|
this.linkNodes = [];
|
||||||
|
this.links = [];
|
||||||
this.hasTextCapabilities = false;
|
this.hasTextCapabilities = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import { NodeToMapNodeConverter } from '../../../cartography/converters/map/node
|
|||||||
})
|
})
|
||||||
export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
||||||
@Input() links: Link[];
|
@Input() links: Link[];
|
||||||
@ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
|
@ViewChild(NodeSelectInterfaceComponent, {static: false}) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
|
||||||
|
|
||||||
private nodeClicked$: Subscription;
|
private nodeClicked$: Subscription;
|
||||||
|
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
<h1 mat-dialog-title>Style editor</h1>
|
<h1 mat-dialog-title>Style editor</h1>
|
||||||
|
|
||||||
<div class="modal-form-container">
|
<div class="modal-form-container">
|
||||||
<mat-form-field>
|
<form [formGroup]="formGroup">
|
||||||
<input matInput placeholder="Fill color" type="color" [(ngModel)]="element.fill">
|
<mat-form-field class="form-field">
|
||||||
|
<input matInput [ngModelOptions]="{standalone: true}" placeholder="Fill color" type="color" [(ngModel)]="element.fill">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field class="form-field">
|
||||||
<input matInput placeholder="Fill color" type="color" [(ngModel)]="element.stroke">
|
<input matInput [ngModelOptions]="{standalone: true}" placeholder="Border color" type="color" [(ngModel)]="element.stroke">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field class="form-field">
|
||||||
<input matInput placeholder="Border width" type="text" [(ngModel)]="element.stroke_width">
|
<input matInput formControlName="borderWidth" placeholder="Border width" type="number">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field *ngIf="element.stroke_dasharray">
|
<mat-form-field class="form-field" *ngIf="element.stroke_dasharray">
|
||||||
<input matInput placeholder="Border style" type="text" [(ngModel)]="element.stroke_dasharray">
|
<input matInput [ngModelOptions]="{standalone: true}" placeholder="Border style" type="text" [(ngModel)]="element.stroke_dasharray">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field class="form-field">
|
||||||
<input matInput placeholder="Rotation" type="text" [(ngModel)]="rotation">
|
<input matInput formControlName="rotation" placeholder="Rotation" type="number">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
|
@ -47,3 +47,7 @@ input[type="color"]::-webkit-color-swatch {
|
|||||||
.modal-form-container > * {
|
.modal-form-container > * {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, Injectable } from '@angular/core';
|
||||||
import { Server } from '../../../../models/server';
|
import { Server } from '../../../../models/server';
|
||||||
import { Project } from '../../../../models/project';
|
import { Project } from '../../../../models/project';
|
||||||
import { Drawing } from '../../../../cartography/models/drawing';
|
import { Drawing } from '../../../../cartography/models/drawing';
|
||||||
@ -10,6 +10,10 @@ import { DrawingsDataSource } from '../../../../cartography/datasources/drawings
|
|||||||
import { EllipseElement } from '../../../../cartography/models/drawings/ellipse-element';
|
import { EllipseElement } from '../../../../cartography/models/drawings/ellipse-element';
|
||||||
import { LineElement } from '../../../../cartography/models/drawings/line-element';
|
import { LineElement } from '../../../../cartography/models/drawings/line-element';
|
||||||
import { RectElement } from '../../../../cartography/models/drawings/rect-element';
|
import { RectElement } from '../../../../cartography/models/drawings/rect-element';
|
||||||
|
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
|
||||||
|
import { ToasterService } from '../../../../services/toaster.service';
|
||||||
|
import { NonNegativeValidator } from '../../../../validators/non-negative-validator';
|
||||||
|
import { RotationValidator } from '../../../../validators/rotation-validator';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-style-editor',
|
selector: 'app-style-editor',
|
||||||
@ -21,19 +25,27 @@ export class StyleEditorDialogComponent implements OnInit {
|
|||||||
project: Project;
|
project: Project;
|
||||||
drawing: Drawing;
|
drawing: Drawing;
|
||||||
element: ElementData;
|
element: ElementData;
|
||||||
rotation: string;
|
formGroup: FormGroup;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<StyleEditorDialogComponent>,
|
public dialogRef: MatDialogRef<StyleEditorDialogComponent>,
|
||||||
private drawingToMapDrawingConverter: DrawingToMapDrawingConverter,
|
private drawingToMapDrawingConverter: DrawingToMapDrawingConverter,
|
||||||
private mapDrawingToSvgConverter: MapDrawingToSvgConverter,
|
private mapDrawingToSvgConverter: MapDrawingToSvgConverter,
|
||||||
private drawingService: DrawingService,
|
private drawingService: DrawingService,
|
||||||
private drawingsDataSource: DrawingsDataSource
|
private drawingsDataSource: DrawingsDataSource,
|
||||||
) {}
|
private formBuilder: FormBuilder,
|
||||||
|
private toasterService: ToasterService,
|
||||||
|
private nonNegativeValidator: NonNegativeValidator,
|
||||||
|
private rotationValidator: RotationValidator
|
||||||
|
) {
|
||||||
|
this.formGroup = this.formBuilder.group({
|
||||||
|
borderWidth: new FormControl('', [Validators.required, nonNegativeValidator.get]),
|
||||||
|
rotation: new FormControl('', [Validators.required, rotationValidator.get])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.element = new ElementData();
|
this.element = new ElementData();
|
||||||
this.rotation = this.drawing.rotation.toString();
|
|
||||||
|
|
||||||
if (this.drawing.element instanceof RectElement || this.drawing.element instanceof EllipseElement) {
|
if (this.drawing.element instanceof RectElement || this.drawing.element instanceof EllipseElement) {
|
||||||
this.element.fill = this.drawing.element.fill;
|
this.element.fill = this.drawing.element.fill;
|
||||||
@ -45,6 +57,10 @@ export class StyleEditorDialogComponent implements OnInit {
|
|||||||
this.element.stroke_dasharray = this.drawing.element.stroke_dasharray;
|
this.element.stroke_dasharray = this.drawing.element.stroke_dasharray;
|
||||||
this.element.stroke_width = this.drawing.element.stroke_width;
|
this.element.stroke_width = this.drawing.element.stroke_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.element.stroke_width === undefined) this.element.stroke_width = 0;
|
||||||
|
this.formGroup.controls['borderWidth'].setValue(this.element.stroke_width);
|
||||||
|
this.formGroup.controls['rotation'].setValue(this.drawing.rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
onNoClick() {
|
onNoClick() {
|
||||||
@ -52,7 +68,11 @@ export class StyleEditorDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onYesClick() {
|
onYesClick() {
|
||||||
this.drawing.rotation = +this.rotation;
|
if (this.formGroup.valid) {
|
||||||
|
|
||||||
|
this.element.stroke_width = this.formGroup.get('borderWidth').value;
|
||||||
|
this.drawing.rotation = this.formGroup.get('rotation').value;
|
||||||
|
|
||||||
if (this.drawing.element instanceof RectElement || this.drawing.element instanceof EllipseElement) {
|
if (this.drawing.element instanceof RectElement || this.drawing.element instanceof EllipseElement) {
|
||||||
this.drawing.element.fill = this.element.fill;
|
this.drawing.element.fill = this.element.fill;
|
||||||
this.drawing.element.stroke = this.element.stroke;
|
this.drawing.element.stroke = this.element.stroke;
|
||||||
@ -73,6 +93,9 @@ export class StyleEditorDialogComponent implements OnInit {
|
|||||||
this.drawingsDataSource.update(serverDrawing);
|
this.drawingsDataSource.update(serverDrawing);
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.toasterService.error(`Entered data is incorrect`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<h1 mat-dialog-title>Text editor</h1>
|
<h1 mat-dialog-title>Text editor</h1>
|
||||||
|
|
||||||
<div class="modal-form-container">
|
<div class="modal-form-container">
|
||||||
<mat-form-field>
|
<mat-form-field class="form-field">
|
||||||
<input matInput placeholder="Fill color" type="color" (ngModelChange)="changeTextColor($event)" [(ngModel)]="element.fill">
|
<input matInput placeholder="Fill color" type="color" (ngModelChange)="changeTextColor($event)" [(ngModel)]="element.fill">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<form [formGroup]="formGroup">
|
||||||
<input matInput placeholder="Rotation" type="text" [(ngModel)]="rotation">
|
<mat-form-field class="form-field">
|
||||||
|
<input formControlName="rotation" matInput placeholder="Rotation" type="text">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
|
||||||
<textarea #textArea id="textArea" class="text" [(ngModel)]="element.text"> </textarea>
|
<textarea #textArea id="textArea" class="text" [(ngModel)]="element.text" [readonly]="!isTextEditable"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
|
@ -52,3 +52,7 @@ input[type="color"]::-webkit-color-swatch {
|
|||||||
.modal-form-container > * {
|
.modal-form-container > * {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@ -8,6 +8,19 @@ import { MapDrawingToSvgConverter } from '../../../../cartography/converters/map
|
|||||||
import { DrawingService } from '../../../../services/drawing.service';
|
import { DrawingService } from '../../../../services/drawing.service';
|
||||||
import { DrawingsDataSource } from '../../../../cartography/datasources/drawings-datasource';
|
import { DrawingsDataSource } from '../../../../cartography/datasources/drawings-datasource';
|
||||||
import { TextElement } from '../../../../cartography/models/drawings/text-element';
|
import { TextElement } from '../../../../cartography/models/drawings/text-element';
|
||||||
|
import { Label } from '../../../../cartography/models/label';
|
||||||
|
import { NodeService } from '../../../../services/node.service';
|
||||||
|
import { Node } from '../../../../cartography/models/node';
|
||||||
|
import { NodesDataSource } from '../../../../cartography/datasources/nodes-datasource';
|
||||||
|
import { Link } from '../../../../models/link';
|
||||||
|
import { LinkNode } from '../../../../models/link-node';
|
||||||
|
import { LinkService } from '../../../../services/link.service';
|
||||||
|
import { LinksDataSource } from '../../../../cartography/datasources/links-datasource';
|
||||||
|
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
|
||||||
|
import { ToasterService } from '../../../../services/toaster.service';
|
||||||
|
import { RotationValidator } from '../../../../validators/rotation-validator';
|
||||||
|
import { Font } from '../../../../cartography/models/font';
|
||||||
|
import { FontFixer } from '../../../../cartography/helpers/font-fixer';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-text-editor',
|
selector: 'app-text-editor',
|
||||||
@ -15,13 +28,19 @@ import { TextElement } from '../../../../cartography/models/drawings/text-elemen
|
|||||||
styleUrls: ['./text-editor.component.scss']
|
styleUrls: ['./text-editor.component.scss']
|
||||||
})
|
})
|
||||||
export class TextEditorDialogComponent implements OnInit {
|
export class TextEditorDialogComponent implements OnInit {
|
||||||
@ViewChild('textArea') textArea: ElementRef;
|
@ViewChild('textArea', {static: true}) textArea: ElementRef;
|
||||||
|
|
||||||
server: Server;
|
server: Server;
|
||||||
project: Project;
|
project: Project;
|
||||||
drawing: Drawing;
|
drawing: Drawing;
|
||||||
|
node: Node;
|
||||||
|
label: Label;
|
||||||
|
link: Link;
|
||||||
|
linkNode: LinkNode;
|
||||||
element: TextElement;
|
element: TextElement;
|
||||||
rotation: string;
|
rotation: string;
|
||||||
|
isTextEditable: boolean;
|
||||||
|
formGroup: FormGroup;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private dialogRef: MatDialogRef<TextEditorDialogComponent>,
|
private dialogRef: MatDialogRef<TextEditorDialogComponent>,
|
||||||
@ -29,17 +48,74 @@ export class TextEditorDialogComponent implements OnInit {
|
|||||||
private mapDrawingToSvgConverter: MapDrawingToSvgConverter,
|
private mapDrawingToSvgConverter: MapDrawingToSvgConverter,
|
||||||
private drawingService: DrawingService,
|
private drawingService: DrawingService,
|
||||||
private drawingsDataSource: DrawingsDataSource,
|
private drawingsDataSource: DrawingsDataSource,
|
||||||
private renderer: Renderer2
|
private renderer: Renderer2,
|
||||||
|
private nodeService: NodeService,
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private linkService: LinkService,
|
||||||
|
private linksDataSource: LinksDataSource,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private toasterService: ToasterService,
|
||||||
|
private rotationValidator: RotationValidator,
|
||||||
|
private fontFixer: FontFixer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.rotation = this.drawing.rotation.toString();
|
this.formGroup = this.formBuilder.group({
|
||||||
|
rotation: new FormControl('', [Validators.required, this.rotationValidator.get])
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.label && this.node) {
|
||||||
|
this.isTextEditable = false;
|
||||||
|
this.rotation = this.label.rotation.toString();
|
||||||
|
this.element = this.getTextElementFromLabel();
|
||||||
|
} else if (this.linkNode && this.link) {
|
||||||
|
this.isTextEditable = true;
|
||||||
|
this.label = this.link.nodes.find(n => n.node_id === this.linkNode.node_id).label;
|
||||||
|
this.rotation = this.label.rotation.toString();
|
||||||
|
this.element = this.getTextElementFromLabel();
|
||||||
|
} else if (this.drawing) {
|
||||||
|
this.isTextEditable = true;
|
||||||
|
this.rotation = this.drawing.rotation.toString();
|
||||||
this.element = this.drawing.element as TextElement;
|
this.element = this.drawing.element as TextElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
let font: Font = {
|
||||||
|
font_family: this.element.font_family,
|
||||||
|
font_size: this.element.font_size,
|
||||||
|
font_weight: this.element.font_weight
|
||||||
|
};
|
||||||
|
font = this.fontFixer.fix(font);
|
||||||
|
|
||||||
|
this.formGroup.controls['rotation'].setValue(this.rotation);
|
||||||
this.renderer.setStyle(this.textArea.nativeElement, 'color', this.element.fill);
|
this.renderer.setStyle(this.textArea.nativeElement, 'color', this.element.fill);
|
||||||
this.renderer.setStyle(this.textArea.nativeElement, 'font-family', this.element.font_family);
|
this.renderer.setStyle(this.textArea.nativeElement, 'font-family', font.font_family);
|
||||||
this.renderer.setStyle(this.textArea.nativeElement, 'font-size', `${this.element.font_size}pt`);
|
this.renderer.setStyle(this.textArea.nativeElement, 'font-size', `${font.font_size}pt`);
|
||||||
this.renderer.setStyle(this.textArea.nativeElement, 'font-weight', this.element.font_weight);
|
this.renderer.setStyle(this.textArea.nativeElement, 'font-weight', font.font_weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTextElementFromLabel(): TextElement{
|
||||||
|
var styleProperties: StyleProperty[] = [];
|
||||||
|
var textElement = new TextElement();
|
||||||
|
|
||||||
|
for (var property of this.label.style.split(";")){
|
||||||
|
styleProperties.push({
|
||||||
|
property: property.split(": ")[0],
|
||||||
|
value: property.split(": ")[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
textElement.text = this.label.text ? this.label.text : '';
|
||||||
|
textElement.font_family = styleProperties.find(p => p.property === 'font-family') ? styleProperties.find(p => p.property === 'font-family').value : 'TypeWriter';
|
||||||
|
textElement.font_size = styleProperties.find(p => p.property === 'font-size') ? +styleProperties.find(p => p.property === 'font-size').value : 10.0;
|
||||||
|
textElement.font_weight = styleProperties.find(p => p.property === 'font-weight') ? styleProperties.find(p => p.property === 'font-weight').value : 'normal';
|
||||||
|
textElement.fill = styleProperties.find(p => p.property === 'fill') ? styleProperties.find(p => p.property === 'fill').value : '#000000';
|
||||||
|
textElement.fill_opacity = styleProperties.find(p => p.property === 'fill-opacity') ? +styleProperties.find(p => p.property === 'fill-opacity').value : 1.0;
|
||||||
|
|
||||||
|
return textElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStyleFromTextElement(): string{
|
||||||
|
return `font-family: ${this.element.font_family};font-size: ${this.element.font_size};font-weight: ${this.element.font_weight};fill: ${this.element.fill};fill-opacity: ${this.element.fill_opacity};`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onNoClick() {
|
onNoClick() {
|
||||||
@ -47,6 +123,27 @@ export class TextEditorDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onYesClick() {
|
onYesClick() {
|
||||||
|
if (this.formGroup.valid) {
|
||||||
|
this.rotation = this.formGroup.get('rotation').value;
|
||||||
|
|
||||||
|
if (this.label && this.node) {
|
||||||
|
this.node.label.style = this.getStyleFromTextElement();
|
||||||
|
this.node.label.rotation = +this.rotation;
|
||||||
|
|
||||||
|
this.nodeService.updateLabel(this.server, this.node, this.node.label).subscribe((node: Node) => {
|
||||||
|
this.nodesDataSource.update(node);
|
||||||
|
this.dialogRef.close();
|
||||||
|
});
|
||||||
|
} else if (this.linkNode && this.link) {
|
||||||
|
this.label.style = this.getStyleFromTextElement();
|
||||||
|
this.label.rotation = +this.rotation;
|
||||||
|
this.label.text = this.element.text;
|
||||||
|
|
||||||
|
this.linkService.updateLink(this.server, this.link).subscribe((link: Link) => {
|
||||||
|
this.linksDataSource.update(link);
|
||||||
|
this.dialogRef.close();
|
||||||
|
});
|
||||||
|
} else if (this.drawing) {
|
||||||
this.drawing.rotation = +this.rotation;
|
this.drawing.rotation = +this.rotation;
|
||||||
this.drawing.element = this.element;
|
this.drawing.element = this.element;
|
||||||
|
|
||||||
@ -59,9 +156,18 @@ export class TextEditorDialogComponent implements OnInit {
|
|||||||
this.drawingsDataSource.update(serverDrawing);
|
this.drawingsDataSource.update(serverDrawing);
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.toasterService.error(`Entered data is incorrect`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTextColor(changedColor) {
|
changeTextColor(changedColor) {
|
||||||
this.renderer.setStyle(this.textArea.nativeElement, 'color', changedColor);
|
this.renderer.setStyle(this.textArea.nativeElement, 'color', changedColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StyleProperty {
|
||||||
|
property: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@ export class NodeSelectInterfaceComponent implements OnInit {
|
|||||||
@Input() links: Link[];
|
@Input() links: Link[];
|
||||||
@Output() onChooseInterface = new EventEmitter<any>();
|
@Output() onChooseInterface = new EventEmitter<any>();
|
||||||
|
|
||||||
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
|
@ViewChild(MatMenuTrigger, {static: false}) contextMenu: MatMenuTrigger;
|
||||||
|
|
||||||
protected topPosition;
|
protected topPosition;
|
||||||
protected leftPosition;
|
protected leftPosition;
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
<button
|
||||||
|
matTooltip="Add a note"
|
||||||
|
mat-icon-button
|
||||||
|
class="menu-button"
|
||||||
|
[color]="drawTools.isTextChosen ? 'primary' : 'basic'"
|
||||||
|
(click)="addDrawing('text')">
|
||||||
|
<mat-icon>create</mat-icon>
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".svg, .bmp, .jpeg, .jpg, .gif, .png"
|
||||||
|
class="non-visible"
|
||||||
|
#file
|
||||||
|
(change)="uploadImageFile($event)"/>
|
||||||
|
<button
|
||||||
|
matTooltip="Insert a picture"
|
||||||
|
mat-icon-button
|
||||||
|
class="menu-button"
|
||||||
|
(click)="file.click()">
|
||||||
|
<mat-icon>image</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
matTooltip="Draw a rectangle"
|
||||||
|
mat-icon-button
|
||||||
|
class="menu-button"
|
||||||
|
[color]="drawTools.isRectangleChosen ? 'primary' : 'basic'"
|
||||||
|
(click)="addDrawing('rectangle')">
|
||||||
|
<mat-icon>crop_3_2</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
matTooltip="Draw an ellipse"
|
||||||
|
mat-icon-button
|
||||||
|
class="menu-button"
|
||||||
|
[color]="drawTools.isEllipseChosen ? 'primary' : 'basic'"
|
||||||
|
(click)="addDrawing('ellipse')">
|
||||||
|
<mat-icon>panorama_fish_eye</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
matTooltip="Draw a line"
|
||||||
|
mat-icon-button class="menu-button"
|
||||||
|
(click)="addDrawing('line')">
|
||||||
|
<svg height="40" width="40">
|
||||||
|
<line
|
||||||
|
[ngClass]="{ selected: drawTools.isLineChosen }"
|
||||||
|
x1="30"
|
||||||
|
y1="10"
|
||||||
|
x2="10"
|
||||||
|
y2="30"
|
||||||
|
style="stroke:white;stroke-width:2"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
matTooltip="Lock or unlock all items"
|
||||||
|
mat-icon-button
|
||||||
|
class="menu-button"
|
||||||
|
[color]="isLocked ? 'primary' : 'basic'"
|
||||||
|
(click)="changeLockValue()">
|
||||||
|
<mat-icon>lock</mat-icon>
|
||||||
|
</button>
|
||||||
|
<app-drawing-added
|
||||||
|
[server]="server"
|
||||||
|
[project]="project"
|
||||||
|
[selectedDrawing]="selectedDrawing"
|
||||||
|
(drawingSaved)="onDrawingSaved()">
|
||||||
|
</app-drawing-added>
|
@ -0,0 +1,24 @@
|
|||||||
|
.menu-button {
|
||||||
|
outline: 0 !important;
|
||||||
|
transition: 0.5s;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
width: 40px;
|
||||||
|
margin-right: 12px !important;
|
||||||
|
margin-left: 12px !important;
|
||||||
|
background: #263238;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-divider.divider {
|
||||||
|
height: 40px;
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: 7px;
|
||||||
|
width: 10px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.non-visible {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
import { ProjectMapMenuComponent } from "./project-map-menu.component";
|
||||||
|
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockedDrawingService } from '../project-map.component.spec';
|
||||||
|
import { MapSettingService } from '../../../services/mapsettings.service';
|
||||||
|
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule } from '@angular/material';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { DrawingService } from '../../../services/drawing.service';
|
||||||
|
import { ToolsService } from '../../../services/tools.service';
|
||||||
|
import { D3MapComponent } from '../../../cartography/components/d3-map/d3-map.component';
|
||||||
|
import { ANGULAR_MAP_DECLARATIONS } from '../../../cartography/angular-map.imports';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('ProjectMapMenuComponent', () => {
|
||||||
|
let component: ProjectMapMenuComponent;
|
||||||
|
let fixture: ComponentFixture<ProjectMapMenuComponent>;
|
||||||
|
let drawingService = new MockedDrawingService();
|
||||||
|
let mapSettingService = new MapSettingService();
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: DrawingService, useValue: drawingService },
|
||||||
|
{ provide: ToolsService },
|
||||||
|
{ provide: MapSettingService, useValue: mapSettingService }
|
||||||
|
],
|
||||||
|
declarations: [ProjectMapMenuComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ProjectMapMenuComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset choice on draw menu after saving drawing', () => {
|
||||||
|
spyOn(component, 'resetDrawToolChoice');
|
||||||
|
|
||||||
|
component.onDrawingSaved();
|
||||||
|
|
||||||
|
expect(component.resetDrawToolChoice).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call map settings service when lock value was changed', () => {
|
||||||
|
spyOn(mapSettingService, 'changeMapLockValue');
|
||||||
|
|
||||||
|
component.changeLockValue();
|
||||||
|
|
||||||
|
expect(mapSettingService.changeMapLockValue).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call map settings service with proper value', () => {
|
||||||
|
spyOn(mapSettingService, 'changeMapLockValue');
|
||||||
|
|
||||||
|
component.changeLockValue();
|
||||||
|
expect(mapSettingService.changeMapLockValue).toHaveBeenCalledWith(true);
|
||||||
|
|
||||||
|
component.changeLockValue();
|
||||||
|
expect(mapSettingService.changeMapLockValue).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,110 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
||||||
|
import { Project } from '../../../models/project';
|
||||||
|
import { Server } from '../../../models/server';
|
||||||
|
import { ToolsService } from '../../../services/tools.service';
|
||||||
|
import { MapSettingService } from '../../../services/mapsettings.service';
|
||||||
|
import { DrawingService } from '../../../services/drawing.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-project-map-menu',
|
||||||
|
templateUrl: './project-map-menu.component.html',
|
||||||
|
styleUrls: ['./project-map-menu.component.scss']
|
||||||
|
})
|
||||||
|
export class ProjectMapMenuComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() project: Project;
|
||||||
|
@Input() server: Server;
|
||||||
|
|
||||||
|
public selectedDrawing: string;
|
||||||
|
public drawTools = {
|
||||||
|
isRectangleChosen: false,
|
||||||
|
isEllipseChosen: false,
|
||||||
|
isLineChosen: false,
|
||||||
|
isTextChosen: false
|
||||||
|
};
|
||||||
|
public isLocked: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private toolsService: ToolsService,
|
||||||
|
private mapSettingsService: MapSettingService,
|
||||||
|
private drawingService: DrawingService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
public addDrawing(selectedObject: string) {
|
||||||
|
switch (selectedObject) {
|
||||||
|
case 'rectangle':
|
||||||
|
this.drawTools.isTextChosen = false;
|
||||||
|
this.drawTools.isEllipseChosen = false;
|
||||||
|
this.drawTools.isRectangleChosen = !this.drawTools.isRectangleChosen;
|
||||||
|
this.drawTools.isLineChosen = false;
|
||||||
|
break;
|
||||||
|
case 'ellipse':
|
||||||
|
this.drawTools.isTextChosen = false;
|
||||||
|
this.drawTools.isEllipseChosen = !this.drawTools.isEllipseChosen;
|
||||||
|
this.drawTools.isRectangleChosen = false;
|
||||||
|
this.drawTools.isLineChosen = false;
|
||||||
|
break;
|
||||||
|
case 'line':
|
||||||
|
this.drawTools.isTextChosen = false;
|
||||||
|
this.drawTools.isEllipseChosen = false;
|
||||||
|
this.drawTools.isRectangleChosen = false;
|
||||||
|
this.drawTools.isLineChosen = !this.drawTools.isLineChosen;
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
this.drawTools.isTextChosen = !this.drawTools.isTextChosen;
|
||||||
|
this.drawTools.isEllipseChosen = false;
|
||||||
|
this.drawTools.isRectangleChosen = false;
|
||||||
|
this.drawTools.isLineChosen = false;
|
||||||
|
this.toolsService.textAddingToolActivation(this.drawTools.isTextChosen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedDrawing = this.selectedDrawing === selectedObject ? '' : selectedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDrawingSaved() {
|
||||||
|
this.resetDrawToolChoice();
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetDrawToolChoice() {
|
||||||
|
this.drawTools.isRectangleChosen = false;
|
||||||
|
this.drawTools.isEllipseChosen = false;
|
||||||
|
this.drawTools.isLineChosen = false;
|
||||||
|
this.drawTools.isTextChosen = false;
|
||||||
|
this.selectedDrawing = '';
|
||||||
|
this.toolsService.textAddingToolActivation(this.drawTools.isTextChosen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public changeLockValue() {
|
||||||
|
this.isLocked = !this.isLocked;
|
||||||
|
this.mapSettingsService.changeMapLockValue(this.isLocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadImageFile(event) {
|
||||||
|
this.readImageFile(event.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readImageFile(fileInput) {
|
||||||
|
let file: File = fileInput.files[0];
|
||||||
|
let fileReader: FileReader = new FileReader();
|
||||||
|
let imageToUpload = new Image();
|
||||||
|
|
||||||
|
fileReader.onloadend = () => {
|
||||||
|
let image = fileReader.result;
|
||||||
|
let svg = this.createSvgFileForImage(image, imageToUpload);
|
||||||
|
this.drawingService.add(this.server, this.project.project_id, -(imageToUpload.width/2), -(imageToUpload.height/2), svg).subscribe(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
imageToUpload.onload = () => { fileReader.readAsDataURL(file) };
|
||||||
|
imageToUpload.src = window.URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSvgFileForImage(image: string|ArrayBuffer, imageToUpload: HTMLImageElement) {
|
||||||
|
return `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" height=\"${imageToUpload.height}\"
|
||||||
|
width=\"${imageToUpload.width}\">\n<image height=\"${imageToUpload.height}\" width=\"${imageToUpload.width}\" xlink:href=\"${image}\"/>\n</svg>`
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
<div *ngIf="project" class="project-map">
|
<div *ngIf="project" class="project-map">
|
||||||
<app-d3-map
|
<app-d3-map
|
||||||
*ngIf="!settings.angular_map"
|
*ngIf="!settings.angular_map"
|
||||||
|
[server]="server"
|
||||||
[symbols]="symbols"
|
[symbols]="symbols"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
[links]="links"
|
[links]="links"
|
||||||
@ -87,86 +88,30 @@
|
|||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="show-menu-wrapper" [ngClass]="{ shadowed: !drawTools.visibility }" *ngIf="!readonly">
|
<div id="show-menu-wrapper" [ngClass]="{ shadowed: !isProjectMapMenuVisible }" *ngIf="!readonly">
|
||||||
<button class="arrow-button" mat-icon-button (click)="showMenu()"><mat-icon>keyboard_arrow_right</mat-icon></button>
|
<button class="arrow-button" mat-icon-button (click)="showMenu()"><mat-icon>keyboard_arrow_right</mat-icon></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="menu-wrapper" [ngClass]="{ extended: drawTools.visibility }">
|
<div id="menu-wrapper" [ngClass]="{ extended: isProjectMapMenuVisible }">
|
||||||
<app-nodes-menu [server]="server" [project]="project"></app-nodes-menu>
|
<app-nodes-menu [server]="server" [project]="project"></app-nodes-menu>
|
||||||
<mat-divider class="divider" [vertical]="true"></mat-divider>
|
<mat-divider class="divider" [vertical]="true"></mat-divider>
|
||||||
<button
|
<app-project-map-menu [server]="server" [project]="project"></app-project-map-menu>
|
||||||
matTooltip="Add a note"
|
|
||||||
mat-icon-button
|
|
||||||
class="menu-button"
|
|
||||||
[color]="drawTools.isTextChosen ? 'primary' : 'basic'"
|
|
||||||
(click)="addDrawing('text')"
|
|
||||||
>
|
|
||||||
<mat-icon>create</mat-icon>
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".svg, .bmp, .jpeg, .jpg, .gif, .png"
|
|
||||||
class="non-visible"
|
|
||||||
#file
|
|
||||||
(change)="uploadImageFile($event)"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
matTooltip="Insert a picture"
|
|
||||||
mat-icon-button
|
|
||||||
class="menu-button"
|
|
||||||
(click)="file.click()"
|
|
||||||
>
|
|
||||||
<mat-icon>image</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
matTooltip="Draw a rectangle"
|
|
||||||
mat-icon-button
|
|
||||||
class="menu-button"
|
|
||||||
[color]="drawTools.isRectangleChosen ? 'primary' : 'basic'"
|
|
||||||
(click)="addDrawing('rectangle')"
|
|
||||||
>
|
|
||||||
<mat-icon>crop_3_2</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
matTooltip="Draw an ellipse"
|
|
||||||
mat-icon-button
|
|
||||||
class="menu-button"
|
|
||||||
[color]="drawTools.isEllipseChosen ? 'primary' : 'basic'"
|
|
||||||
(click)="addDrawing('ellipse')"
|
|
||||||
>
|
|
||||||
<mat-icon>panorama_fish_eye</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button matTooltip="Draw a line" mat-icon-button class="menu-button" (click)="addDrawing('line')">
|
|
||||||
<svg height="40" width="40">
|
|
||||||
<line
|
|
||||||
[ngClass]="{ selected: drawTools.isLineChosen }"
|
|
||||||
x1="30"
|
|
||||||
y1="10"
|
|
||||||
x2="10"
|
|
||||||
y2="30"
|
|
||||||
style="stroke:white;stroke-width:2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="arrow-button" mat-icon-button (click)="hideMenu()"><mat-icon>keyboard_arrow_left</mat-icon></button>
|
<button class="arrow-button" mat-icon-button (click)="hideMenu()"><mat-icon>keyboard_arrow_left</mat-icon></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-context-menu [project]="project" [server]="server"></app-context-menu>
|
<app-context-menu [project]="project" [server]="server"></app-context-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="zoom-buttons">
|
||||||
|
<button class="zoom-button" (click)="zoomIn()">+</button>
|
||||||
|
<button class="zoom-button" (click)="resetZoom()"><mat-icon>adjust</mat-icon></button>
|
||||||
|
<button class="zoom-button" (click)="zoomOut()">-</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<app-progress></app-progress>
|
<app-progress></app-progress>
|
||||||
|
|
||||||
<app-project-map-shortcuts *ngIf="project" [project]="project" [server]="server"></app-project-map-shortcuts>
|
<app-project-map-shortcuts *ngIf="project" [project]="project" [server]="server"></app-project-map-shortcuts>
|
||||||
|
|
||||||
<app-draw-link-tool [links]="links" *ngIf="tools.draw_link"></app-draw-link-tool>
|
<app-draw-link-tool [links]="links" *ngIf="tools.draw_link"></app-draw-link-tool>
|
||||||
|
|
||||||
<app-drawing-added
|
|
||||||
[server]="server"
|
|
||||||
[project]="project"
|
|
||||||
[selectedDrawing]="selectedDrawing"
|
|
||||||
(drawingSaved)="onDrawingSaved()"
|
|
||||||
>
|
|
||||||
</app-drawing-added>
|
|
||||||
<app-drawing-dragged [server]="server"></app-drawing-dragged>
|
<app-drawing-dragged [server]="server"></app-drawing-dragged>
|
||||||
<app-drawing-resized [server]="server"></app-drawing-resized>
|
<app-drawing-resized [server]="server"></app-drawing-resized>
|
||||||
<app-interface-label-dragged [server]="server"></app-interface-label-dragged>
|
<app-interface-label-dragged [server]="server"></app-interface-label-dragged>
|
||||||
|
@ -80,7 +80,7 @@ g.node:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.extended {
|
.extended {
|
||||||
width: 640px !important;
|
width: 700px !important;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -93,6 +93,44 @@ mat-divider.divider {
|
|||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#zoom-buttons {
|
||||||
|
position: fixed;
|
||||||
|
background: #263238;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
.zoom-button {
|
||||||
|
outline: none;
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
background: #263238;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-button-white {
|
||||||
|
outline: none;
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
color: #263238;
|
||||||
|
border: none;
|
||||||
|
background: white;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
@-moz-document url-prefix() {
|
||||||
/** fixes gray background of drawing menu on Firefox **/
|
/** fixes gray background of drawing menu on Firefox **/
|
||||||
.mat-drawer-content {
|
.mat-drawer-content {
|
||||||
|
@ -40,9 +40,18 @@ import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProje
|
|||||||
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
||||||
import { Link } from '../../models/link';
|
import { Link } from '../../models/link';
|
||||||
import { Project } from '../../models/project';
|
import { Project } from '../../models/project';
|
||||||
|
import { MovingEventSource } from '../../cartography/events/moving-event-source';
|
||||||
import { CapturingSettings } from '../../models/capturingSettings';
|
import { CapturingSettings } from '../../models/capturingSettings';
|
||||||
import { LinkWidget } from '../../cartography/widgets/link';
|
import { LinkWidget } from '../../cartography/widgets/link';
|
||||||
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
||||||
|
import { LabelWidget } from '../../cartography/widgets/label';
|
||||||
|
import { InterfaceLabelWidget } from '../../cartography/widgets/interface-label';
|
||||||
|
import { MapLinkNodeToLinkNodeConverter } from '../../cartography/converters/map/map-link-node-to-link-node-converter';
|
||||||
|
import { MapSettingService } from '../../services/mapsettings.service';
|
||||||
|
import { ProjectMapMenuComponent } from './project-map-menu/project-map-menu.component';
|
||||||
|
import { MockedToasterService } from '../../services/toaster.service.spec';
|
||||||
|
import { ToasterService } from '../../services/toaster.service';
|
||||||
|
|
||||||
export class MockedProgressService {
|
export class MockedProgressService {
|
||||||
public activate() {}
|
public activate() {}
|
||||||
@ -81,6 +90,10 @@ export class MockedNodeService {
|
|||||||
reloadAll(server: Server, project: Project) {
|
reloadAll(server: Server, project: Project) {
|
||||||
return of();
|
return of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
duplicate(server: Server, node: Node) {
|
||||||
|
return of(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockedDrawingService {
|
export class MockedDrawingService {
|
||||||
@ -91,6 +104,10 @@ export class MockedDrawingService {
|
|||||||
return of(this.drawing);
|
return of(this.drawing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
duplicate(server: Server, project_id: string, drawing: Drawing) {
|
||||||
|
return of(drawing);
|
||||||
|
}
|
||||||
|
|
||||||
updatePosition(_server: Server, _drawing: Drawing, _x: number, _y: number) {
|
updatePosition(_server: Server, _drawing: Drawing, _x: number, _y: number) {
|
||||||
return of(this.drawing);
|
return of(this.drawing);
|
||||||
}
|
}
|
||||||
@ -183,6 +200,7 @@ describe('ProjectMapComponent', () => {
|
|||||||
let drawingsDataSource = new MockedDrawingsDataSource();
|
let drawingsDataSource = new MockedDrawingsDataSource();
|
||||||
let nodesDataSource = new MockedNodesDataSource();
|
let nodesDataSource = new MockedNodesDataSource();
|
||||||
let linksDataSource = new MockedLinksDataSource();
|
let linksDataSource = new MockedLinksDataSource();
|
||||||
|
let mockedToasterService = new MockedToasterService();
|
||||||
let nodeCreatedLabelStylesFixer;
|
let nodeCreatedLabelStylesFixer;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@ -205,10 +223,13 @@ describe('ProjectMapComponent', () => {
|
|||||||
{ provide: NodeWidget },
|
{ provide: NodeWidget },
|
||||||
{ provide: LinkWidget },
|
{ provide: LinkWidget },
|
||||||
{ provide: DrawingsWidget },
|
{ provide: DrawingsWidget },
|
||||||
|
{ provide: LabelWidget },
|
||||||
|
{ provide: InterfaceLabelWidget },
|
||||||
{ provide: MapNodeToNodeConverter },
|
{ provide: MapNodeToNodeConverter },
|
||||||
{ provide: MapDrawingToDrawingConverter },
|
{ provide: MapDrawingToDrawingConverter },
|
||||||
{ provide: MapLabelToLabelConverter },
|
{ provide: MapLabelToLabelConverter },
|
||||||
{ provide: MapLinkToLinkConverter },
|
{ provide: MapLinkToLinkConverter },
|
||||||
|
{ provide: MapLinkNodeToLinkNodeConverter },
|
||||||
{ provide: NodesDataSource, useValue: nodesDataSource },
|
{ provide: NodesDataSource, useValue: nodesDataSource },
|
||||||
{ provide: LinksDataSource, useValue: linksDataSource },
|
{ provide: LinksDataSource, useValue: linksDataSource },
|
||||||
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
|
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
|
||||||
@ -216,13 +237,17 @@ describe('ProjectMapComponent', () => {
|
|||||||
{ provide: ToolsService },
|
{ provide: ToolsService },
|
||||||
{ provide: SelectionManager },
|
{ provide: SelectionManager },
|
||||||
{ provide: SelectionTool },
|
{ provide: SelectionTool },
|
||||||
|
{ provide: MovingEventSource },
|
||||||
{
|
{
|
||||||
provide: RecentlyOpenedProjectService,
|
provide: RecentlyOpenedProjectService,
|
||||||
useClass: RecentlyOpenedProjectService
|
useClass: RecentlyOpenedProjectService
|
||||||
},
|
},
|
||||||
{ provide: NodeCreatedLabelStylesFixer, useValue: nodeCreatedLabelStylesFixer}
|
{ provide: NodeCreatedLabelStylesFixer, useValue: nodeCreatedLabelStylesFixer},
|
||||||
|
{ provide: MapScaleService },
|
||||||
|
{ provide: NodeCreatedLabelStylesFixer, useValue: nodeCreatedLabelStylesFixer},
|
||||||
|
{ provide: ToasterService, useValue: mockedToasterService }
|
||||||
],
|
],
|
||||||
declarations: [ProjectMapComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS],
|
declarations: [ProjectMapComponent, ProjectMapMenuComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
@ -230,6 +255,13 @@ describe('ProjectMapComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(ProjectMapComponent);
|
fixture = TestBed.createComponent(ProjectMapComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
component.projectMapMenuComponent = {
|
||||||
|
resetDrawToolChoice(){}
|
||||||
|
} as ProjectMapMenuComponent;
|
||||||
|
|
||||||
|
component.ws = {
|
||||||
|
OPEN: 0,
|
||||||
|
} as WebSocket;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -245,18 +277,10 @@ describe('ProjectMapComponent', () => {
|
|||||||
document.getElementsByClassName = jasmine.createSpy('HTML element').and.callFake(() => {
|
document.getElementsByClassName = jasmine.createSpy('HTML element').and.callFake(() => {
|
||||||
return [dummyElement];
|
return [dummyElement];
|
||||||
});
|
});
|
||||||
spyOn(component, 'resetDrawToolChoice');
|
spyOn(component.projectMapMenuComponent, 'resetDrawToolChoice').and.returnValue();
|
||||||
|
|
||||||
component.hideMenu();
|
component.hideMenu();
|
||||||
|
|
||||||
expect(component.resetDrawToolChoice).toHaveBeenCalled();
|
expect(component.projectMapMenuComponent.resetDrawToolChoice).toHaveBeenCalled();
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset choice on draw menu after saving drawing', () => {
|
|
||||||
spyOn(component, 'resetDrawToolChoice');
|
|
||||||
|
|
||||||
component.onDrawingSaved();
|
|
||||||
|
|
||||||
expect(component.resetDrawToolChoice).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@ import { MapNodeToNodeConverter } from '../../cartography/converters/map/map-nod
|
|||||||
import { SettingsService, Settings } from '../../services/settings.service';
|
import { SettingsService, Settings } from '../../services/settings.service';
|
||||||
import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
|
import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
|
||||||
import { ToolsService } from '../../services/tools.service';
|
import { ToolsService } from '../../services/tools.service';
|
||||||
import { DrawingContextMenu, LinkContextMenu } from '../../cartography/events/event-source';
|
import { DrawingContextMenu, LinkContextMenu, LabelContextMenu, InterfaceLabelContextMenu } from '../../cartography/events/event-source';
|
||||||
import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter';
|
import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter';
|
||||||
import { SelectionManager } from '../../cartography/managers/selection-manager';
|
import { SelectionManager } from '../../cartography/managers/selection-manager';
|
||||||
import { SelectionTool } from '../../cartography/tools/selection-tool';
|
import { SelectionTool } from '../../cartography/tools/selection-tool';
|
||||||
@ -42,8 +42,15 @@ import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-l
|
|||||||
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
|
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
|
||||||
import { MapLink } from '../../cartography/models/map/map-link';
|
import { MapLink } from '../../cartography/models/map/map-link';
|
||||||
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
||||||
|
import { MovingEventSource } from '../../cartography/events/moving-event-source';
|
||||||
import { LinkWidget } from '../../cartography/widgets/link';
|
import { LinkWidget } from '../../cartography/widgets/link';
|
||||||
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
||||||
|
import { InterfaceLabelWidget } from '../../cartography/widgets/interface-label';
|
||||||
|
import { LabelWidget } from '../../cartography/widgets/label';
|
||||||
|
import { MapLinkNodeToLinkNodeConverter } from '../../cartography/converters/map/map-link-node-to-link-node-converter';
|
||||||
|
import { ProjectMapMenuComponent } from './project-map-menu/project-map-menu.component';
|
||||||
|
import { ToasterService } from '../../services/toaster.service';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -59,8 +66,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
public symbols: Symbol[] = [];
|
public symbols: Symbol[] = [];
|
||||||
public project: Project;
|
public project: Project;
|
||||||
public server: Server;
|
public server: Server;
|
||||||
public selectedDrawing: string;
|
public ws: WebSocket;
|
||||||
private ws: Subject<any>;
|
public isProjectMapMenuVisible: boolean = false;
|
||||||
|
|
||||||
tools = {
|
tools = {
|
||||||
selection: true,
|
selection: true,
|
||||||
@ -70,19 +77,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
protected settings: Settings;
|
protected settings: Settings;
|
||||||
|
|
||||||
protected drawTools = {
|
|
||||||
isRectangleChosen: false,
|
|
||||||
isEllipseChosen: false,
|
|
||||||
isLineChosen: false,
|
|
||||||
isTextChosen: false,
|
|
||||||
visibility: false
|
|
||||||
};
|
|
||||||
|
|
||||||
private inReadOnlyMode = false;
|
private inReadOnlyMode = false;
|
||||||
|
|
||||||
@ViewChild(ContextMenuComponent) contextMenu: ContextMenuComponent;
|
@ViewChild(ContextMenuComponent, {static: false}) contextMenu: ContextMenuComponent;
|
||||||
@ViewChild(D3MapComponent) mapChild: D3MapComponent;
|
@ViewChild(D3MapComponent, {static: false}) mapChild: D3MapComponent;
|
||||||
|
@ViewChild(ProjectMapMenuComponent, {static: false}) projectMapMenuComponent: ProjectMapMenuComponent;
|
||||||
|
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
@ -98,10 +97,13 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
private nodeWidget: NodeWidget,
|
private nodeWidget: NodeWidget,
|
||||||
private drawingsWidget: DrawingsWidget,
|
private drawingsWidget: DrawingsWidget,
|
||||||
private linkWidget: LinkWidget,
|
private linkWidget: LinkWidget,
|
||||||
|
private labelWidget: LabelWidget,
|
||||||
|
private interfaceLabelWidget: InterfaceLabelWidget,
|
||||||
private mapNodeToNode: MapNodeToNodeConverter,
|
private mapNodeToNode: MapNodeToNodeConverter,
|
||||||
private mapDrawingToDrawing: MapDrawingToDrawingConverter,
|
private mapDrawingToDrawing: MapDrawingToDrawingConverter,
|
||||||
private mapLabelToLabel: MapLabelToLabelConverter,
|
private mapLabelToLabel: MapLabelToLabelConverter,
|
||||||
private mapLinkToLink: MapLinkToLinkConverter,
|
private mapLinkToLink: MapLinkToLinkConverter,
|
||||||
|
private mapLinkNodeToLinkNode: MapLinkNodeToLinkNodeConverter,
|
||||||
private nodesDataSource: NodesDataSource,
|
private nodesDataSource: NodesDataSource,
|
||||||
private linksDataSource: LinksDataSource,
|
private linksDataSource: LinksDataSource,
|
||||||
private drawingsDataSource: DrawingsDataSource,
|
private drawingsDataSource: DrawingsDataSource,
|
||||||
@ -110,7 +112,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
private selectionManager: SelectionManager,
|
private selectionManager: SelectionManager,
|
||||||
private selectionTool: SelectionTool,
|
private selectionTool: SelectionTool,
|
||||||
private recentlyOpenedProjectService: RecentlyOpenedProjectService,
|
private recentlyOpenedProjectService: RecentlyOpenedProjectService,
|
||||||
private nodeCreatedLabelStylesFixer: NodeCreatedLabelStylesFixer
|
private movingEventSource: MovingEventSource,
|
||||||
|
private mapScaleService: MapScaleService,
|
||||||
|
private nodeCreatedLabelStylesFixer: NodeCreatedLabelStylesFixer,
|
||||||
|
private toasterService: ToasterService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -184,6 +189,18 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
this.mapChangeDetectorRef.detectChanges();
|
this.mapChangeDetectorRef.detectChanges();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.addKeyboardListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
addKeyboardListeners() {
|
||||||
|
Mousetrap.bind('ctrl++', (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
Mousetrap.bind('ctrl+-', (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});;
|
||||||
}
|
}
|
||||||
|
|
||||||
onProjectLoad(project: Project) {
|
onProjectLoad(project: Project) {
|
||||||
@ -213,9 +230,15 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setUpWS(project: Project) {
|
setUpWS(project: Project) {
|
||||||
this.ws = webSocket(this.projectService.notificationsPath(this.server, project.project_id));
|
this.ws = new WebSocket(this.projectService.notificationsPath(this.server, project.project_id));
|
||||||
|
|
||||||
this.subscriptions.push(this.projectWebServiceHandler.connect(this.ws));
|
this.ws.onmessage = (event: MessageEvent) => {
|
||||||
|
this.projectWebServiceHandler.handleMessage(JSON.parse(event.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onerror = (event: MessageEvent) => {
|
||||||
|
this.toasterService.error('Connection to host lost.');
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setUpMapCallbacks() {
|
setUpMapCallbacks() {
|
||||||
@ -238,9 +261,21 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.pageY, eventDrawing.event.pageX);
|
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.pageY, eventDrawing.event.pageX);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onLabelContextMenu = this.labelWidget.onContextMenu.subscribe((eventLabel: LabelContextMenu) => {
|
||||||
|
const label = this.mapLabelToLabel.convert(eventLabel.label);
|
||||||
|
const node = this.nodes.find(n => n.node_id === eventLabel.label.nodeId);
|
||||||
|
this.contextMenu.openMenuForLabel(label, node, eventLabel.event.pageY, eventLabel.event.pageX);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onInterfaceLabelContextMenu = this.interfaceLabelWidget.onContextMenu.subscribe((eventInterfaceLabel: InterfaceLabelContextMenu) => {
|
||||||
|
const linkNode = this.mapLinkNodeToLinkNode.convert(eventInterfaceLabel.interfaceLabel);
|
||||||
|
const link = this.links.find(l => l.link_id === eventInterfaceLabel.interfaceLabel.linkId);
|
||||||
|
this.contextMenu.openMenuForInterfaceLabel(linkNode, link, eventInterfaceLabel.event.pageY, eventInterfaceLabel.event.pageX);
|
||||||
|
});
|
||||||
|
|
||||||
const onContextMenu = this.selectionTool.contextMenuOpened.subscribe((event) => {
|
const onContextMenu = this.selectionTool.contextMenuOpened.subscribe((event) => {
|
||||||
const selectedItems = this.selectionManager.getSelected();
|
const selectedItems = this.selectionManager.getSelected();
|
||||||
if (selectedItems.length === 0 || !(event instanceof MouseEvent)) return;
|
if (selectedItems.length < 2 || !(event instanceof MouseEvent)) return;
|
||||||
|
|
||||||
let drawings: Drawing[] = [];
|
let drawings: Drawing[] = [];
|
||||||
let nodes: Node[] = [];
|
let nodes: Node[] = [];
|
||||||
@ -266,6 +301,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
this.subscriptions.push(onNodeContextMenu);
|
this.subscriptions.push(onNodeContextMenu);
|
||||||
this.subscriptions.push(onDrawingContextMenu);
|
this.subscriptions.push(onDrawingContextMenu);
|
||||||
this.subscriptions.push(onContextMenu);
|
this.subscriptions.push(onContextMenu);
|
||||||
|
this.subscriptions.push(onLabelContextMenu);
|
||||||
|
this.subscriptions.push(onInterfaceLabelContextMenu);
|
||||||
this.mapChangeDetectorRef.detectChanges();
|
this.mapChangeDetectorRef.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +325,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onDrawingSaved() {
|
public onDrawingSaved() {
|
||||||
this.resetDrawToolChoice();
|
this.projectMapMenuComponent.resetDrawToolChoice();
|
||||||
}
|
}
|
||||||
|
|
||||||
public set readonly(value) {
|
public set readonly(value) {
|
||||||
@ -308,7 +345,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
public toggleMovingMode() {
|
public toggleMovingMode() {
|
||||||
this.tools.moving = !this.tools.moving;
|
this.tools.moving = !this.tools.moving;
|
||||||
this.toolsService.movingToolActivation(this.tools.moving);
|
this.movingEventSource.movingModeState.emit(this.tools.moving);
|
||||||
|
|
||||||
if (!this.readonly) {
|
if (!this.readonly) {
|
||||||
this.tools.selection = !this.tools.moving;
|
this.tools.selection = !this.tools.moving;
|
||||||
this.toolsService.selectionToolActivation(this.tools.selection);
|
this.toolsService.selectionToolActivation(this.tools.selection);
|
||||||
@ -324,54 +362,29 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
this.project.show_interface_labels = enabled;
|
this.project.show_interface_labels = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addDrawing(selectedObject: string) {
|
|
||||||
switch (selectedObject) {
|
|
||||||
case 'rectangle':
|
|
||||||
this.drawTools.isTextChosen = false;
|
|
||||||
this.drawTools.isEllipseChosen = false;
|
|
||||||
this.drawTools.isRectangleChosen = !this.drawTools.isRectangleChosen;
|
|
||||||
this.drawTools.isLineChosen = false;
|
|
||||||
break;
|
|
||||||
case 'ellipse':
|
|
||||||
this.drawTools.isTextChosen = false;
|
|
||||||
this.drawTools.isEllipseChosen = !this.drawTools.isEllipseChosen;
|
|
||||||
this.drawTools.isRectangleChosen = false;
|
|
||||||
this.drawTools.isLineChosen = false;
|
|
||||||
break;
|
|
||||||
case 'line':
|
|
||||||
this.drawTools.isTextChosen = false;
|
|
||||||
this.drawTools.isEllipseChosen = false;
|
|
||||||
this.drawTools.isRectangleChosen = false;
|
|
||||||
this.drawTools.isLineChosen = !this.drawTools.isLineChosen;
|
|
||||||
break;
|
|
||||||
case 'text':
|
|
||||||
this.drawTools.isTextChosen = !this.drawTools.isTextChosen;
|
|
||||||
this.drawTools.isEllipseChosen = false;
|
|
||||||
this.drawTools.isRectangleChosen = false;
|
|
||||||
this.drawTools.isLineChosen = false;
|
|
||||||
this.toolsService.textAddingToolActivation(this.drawTools.isTextChosen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedDrawing = this.selectedDrawing === selectedObject ? '' : selectedObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetDrawToolChoice() {
|
|
||||||
this.drawTools.isRectangleChosen = false;
|
|
||||||
this.drawTools.isEllipseChosen = false;
|
|
||||||
this.drawTools.isLineChosen = false;
|
|
||||||
this.drawTools.isTextChosen = false;
|
|
||||||
this.selectedDrawing = '';
|
|
||||||
this.toolsService.textAddingToolActivation(this.drawTools.isTextChosen);
|
|
||||||
}
|
|
||||||
|
|
||||||
public hideMenu() {
|
public hideMenu() {
|
||||||
this.resetDrawToolChoice();
|
this.projectMapMenuComponent.resetDrawToolChoice()
|
||||||
this.drawTools.visibility = false;
|
this.isProjectMapMenuVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public showMenu() {
|
public showMenu() {
|
||||||
this.drawTools.visibility = true;
|
this.isProjectMapMenuVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomIn() {
|
||||||
|
this.mapScaleService.setScale(this.mapScaleService.getScale() + 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut() {
|
||||||
|
let currentScale = this.mapScaleService.getScale();
|
||||||
|
|
||||||
|
if ((currentScale - 0.1) > 0) {
|
||||||
|
this.mapScaleService.setScale(currentScale - 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetZoom() {
|
||||||
|
this.mapScaleService.resetToDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadImageFile(event) {
|
public uploadImageFile(event) {
|
||||||
@ -400,8 +413,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
this.nodesDataSource.clear();
|
this.nodesDataSource.clear();
|
||||||
this.linksDataSource.clear();
|
this.linksDataSource.clear();
|
||||||
|
|
||||||
if (this.ws) {
|
if (this.ws.OPEN) {
|
||||||
this.ws.unsubscribe();
|
this.ws.close();
|
||||||
}
|
}
|
||||||
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
|
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ export class ProjectsComponent implements OnInit {
|
|||||||
displayedColumns = ['name', 'actions'];
|
displayedColumns = ['name', 'actions'];
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort, {static: true}) sort: MatSort;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row">
|
<mat-cell *matCellDef="let row">
|
||||||
<a [routerLink]="['/server', row.id, 'projects']" class="table-link">{{ row.name }}</a></mat-cell
|
<a *ngIf="getServerStatus(row) === 'running' || row.location === 'remote' || row.location === 'bundled'" [routerLink]="['/server', row.id, 'projects']" class="table-link">{{ row.name }}</a>
|
||||||
>
|
<span *ngIf="getServerStatus(row) != 'running' && row.location !== 'remote' && row.location !== 'bundled'">{{ row.name }}</span>
|
||||||
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="location">
|
<ng-container matColumnDef="location">
|
||||||
@ -43,6 +44,8 @@
|
|||||||
<mat-icon aria-label="Stop server">stop</mat-icon>
|
<mat-icon aria-label="Stop server">stop</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<mat-spinner [diameter]="24" *ngIf="row.location === 'local' && getServerStatus(row) === 'starting'"></mat-spinner>
|
||||||
|
|
||||||
<button mat-icon-button (click)="deleteServer(row)">
|
<button mat-icon-button (click)="deleteServer(row)">
|
||||||
<mat-icon aria-label="Remove server">delete</mat-icon>
|
<mat-icon aria-label="Remove server">delete</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
@ -49,6 +49,9 @@ export class ServersComponent implements OnInit, OnDestroy {
|
|||||||
if(!server) {
|
if(!server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(serverStatus.status === 'starting') {
|
||||||
|
server.status = 'starting';
|
||||||
|
}
|
||||||
if(serverStatus.status === 'stopped') {
|
if(serverStatus.status === 'stopped') {
|
||||||
server.status = 'stopped';
|
server.status = 'stopped';
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export class TemplateListDialogComponent implements OnInit {
|
|||||||
dataSource: TemplateDataSource;
|
dataSource: TemplateDataSource;
|
||||||
displayedColumns = ['name'];
|
displayedColumns = ['name'];
|
||||||
|
|
||||||
@ViewChild('filter') filter: ElementRef;
|
@ViewChild('filter', {static: false}) filter: ElementRef;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<TemplateListDialogComponent>,
|
public dialogRef: MatDialogRef<TemplateListDialogComponent>,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user