diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 86aa99ab..800cf781 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -71,6 +71,7 @@ import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-
import { MATERIAL_IMPORTS } from './material.imports';
import { DrawingService } from './services/drawing.service';
import { ProjectNameValidator } from './components/projects/models/projectNameValidator';
+import { MatSidenavModule } from '@angular/material';
import { NodeSelectInterfaceComponent } from './components/project-map/node-select-interface/node-select-interface.component';
import { DrawLinkToolComponent } from './components/project-map/draw-link-tool/draw-link-tool.component';
@@ -130,6 +131,7 @@ if (environment.production) {
PersistenceModule,
NgxElectronModule,
FileUploadModule,
+ MatSidenavModule,
MATERIAL_IMPORTS
],
providers: [
diff --git a/src/app/cartography/cartography.module.ts b/src/app/cartography/cartography.module.ts
index d860a486..419fa9cc 100644
--- a/src/app/cartography/cartography.module.ts
+++ b/src/app/cartography/cartography.module.ts
@@ -15,6 +15,7 @@ import { D3_MAP_IMPORTS } from './d3-map.imports';
import { CanvasSizeDetector } from './helpers/canvas-size-detector';
import { DrawingsEventSource } from './events/drawings-event-source';
import { NodesEventSource } from './events/nodes-event-source';
+import { MapDrawingToSvgConverter } from './converters/map/map-drawing-to-svg-converter';
import { DrawingToMapDrawingConverter } from './converters/map/drawing-to-map-drawing-converter';
import { LabelToMapLabelConverter } from './converters/map/label-to-map-label-converter';
import { LinkToMapLinkConverter } from './converters/map/link-to-map-link-converter';
@@ -39,6 +40,9 @@ import { SelectionControlComponent } from './components/selection-control/select
import { SelectionSelectComponent } from './components/selection-select/selection-select.component';
import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component';
import { MapSettingsManager } from './managers/map-settings-manager';
+import { DrawingResizingComponent } from './components/drawing-resizing/drawing-resizing.component';
+import { TextAddingComponent } from './components/text-adding/text-adding.component';
+import { TextEditingComponent } from './components/text-editing/text-editing.component';
import { FontBBoxCalculator } from './helpers/font-bbox-calculator';
import { StylesToFontConverter } from './converters/styles-to-font-converter';
@@ -52,6 +56,9 @@ import { StylesToFontConverter } from './converters/styles-to-font-converter';
declarations: [
D3MapComponent,
ExperimentalMapComponent,
+ DrawingResizingComponent,
+ TextAddingComponent,
+ TextEditingComponent,
...ANGULAR_MAP_DECLARATIONS,
SelectionControlComponent,
SelectionSelectComponent,
@@ -70,6 +77,7 @@ import { StylesToFontConverter } from './converters/styles-to-font-converter';
DrawingsEventSource,
NodesEventSource,
LinksEventSource,
+ MapDrawingToSvgConverter,
DrawingToMapDrawingConverter,
LabelToMapLabelConverter,
LinkToMapLinkConverter,
diff --git a/src/app/cartography/components/d3-map/d3-map.component.html b/src/app/cartography/components/d3-map/d3-map.component.html
index 018e64f0..8c04ec05 100644
--- a/src/app/cartography/components/d3-map/d3-map.component.html
+++ b/src/app/cartography/components/d3-map/d3-map.component.html
@@ -8,6 +8,9 @@
+
-
\ No newline at end of file
+
+
+
diff --git a/src/app/cartography/components/d3-map/d3-map.component.ts b/src/app/cartography/components/d3-map/d3-map.component.ts
index 779ad561..29b25511 100644
--- a/src/app/cartography/components/d3-map/d3-map.component.ts
+++ b/src/app/cartography/components/d3-map/d3-map.component.ts
@@ -11,13 +11,17 @@ import { InterfaceLabelWidget } from '../../widgets/interface-label';
import { SelectionTool } from '../../tools/selection-tool';
import { MovingTool } from '../../tools/moving-tool';
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
+import { MapLinkCreated } from '../../events/links';
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
import { Node } from '../../models/node';
import { Link } from '../../../models/link';
import { Drawing } from '../../models/drawing';
import { Symbol } from '../../../models/symbol';
import { GraphDataManager } from '../../managers/graph-data-manager';
+import { DraggedDataEvent } from '../../events/event-source';
import { MapSettingsManager } from '../../managers/map-settings-manager';
+import { TextEditingTool } from '../../tools/text-editing-tool';
+import { TextAddingComponent } from '../text-adding/text-adding.component';
import { Server } from '../../../models/server';
@@ -37,6 +41,7 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
@Input() height = 600;
@ViewChild('svg') svgRef: ElementRef;
+ @ViewChild(TextAddingComponent) textAddingComponent: TextAddingComponent;
private parentNativeElement: any;
private svg: Selection;
@@ -47,9 +52,13 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
'show_interface_labels': true
};
+ ngAfterInit(){
+ console.log(this.textAddingComponent);
+ }
+
constructor(
private graphDataManager: GraphDataManager,
- private context: Context,
+ public context: Context,
private mapChangeDetectorRef: MapChangeDetectorRef,
private canvasSizeDetector: CanvasSizeDetector,
private mapSettings: MapSettingsManager,
@@ -57,6 +66,7 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
protected interfaceLabelWidget: InterfaceLabelWidget,
protected selectionToolWidget: SelectionTool,
protected movingToolWidget: MovingTool,
+ protected textEditingToolWidget: TextEditingTool,
public graphLayout: GraphLayout,
) {
this.parentNativeElement = element.nativeElement;
@@ -81,6 +91,12 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
this.mapChangeDetectorRef.detectChanges();
}
+ @Input('text-editing-tool')
+ set textEditingTool(value){
+ this.textEditingToolWidget.setEnabled(value);
+ this.mapChangeDetectorRef.detectChanges();
+ }
+
@Input('draw-link-tool') drawLinkTool: boolean;
@Input('readonly') set readonly(value) {
@@ -143,7 +159,6 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
this.redraw();
}
-
private onSymbolsChange(change: SimpleChange) {
this.graphDataManager.setSymbols(this.symbols);
}
@@ -152,7 +167,6 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
this.graphDataManager.setNodes(this.nodes);
this.graphDataManager.setLinks(this.links);
this.graphDataManager.setDrawings(this.drawings);
-
this.graphLayout.draw(this.svg, this.context);
}
diff --git a/src/app/cartography/components/drawing-resizing/drawing-resizing.component.html b/src/app/cartography/components/drawing-resizing/drawing-resizing.component.html
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/drawing-resizing/drawing-resizing.component.scss b/src/app/cartography/components/drawing-resizing/drawing-resizing.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/drawing-resizing/drawing-resizing.component.spec.ts b/src/app/cartography/components/drawing-resizing/drawing-resizing.component.spec.ts
new file mode 100644
index 00000000..0cf6fdab
--- /dev/null
+++ b/src/app/cartography/components/drawing-resizing/drawing-resizing.component.spec.ts
@@ -0,0 +1,73 @@
+import { ComponentFixture, TestBed, async } from '@angular/core/testing';
+
+import { DrawingResizingComponent } from './drawing-resizing.component'
+import { DrawingsWidget } from '../../widgets/drawings';
+import { DrawingsEventSource } from '../../events/drawings-event-source';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { EventEmitter } from '@angular/core';
+import { ResizingEnd } from '../../events/resizing';
+import { MapDrawing } from '../../models/map/map-drawing';
+
+export class DrawingWidgetMock {
+ resizingFinished = new EventEmitter>();
+ evt: any;
+ constructor(){}
+
+ emitEvent(){
+ const evt = new ResizingEnd();
+ evt.x = 0;
+ evt.y = 0;
+ evt.width = 10;
+ evt.height = 10;
+ evt.datum = {} as MapDrawing;
+
+ this.resizingFinished.emit(evt);
+ }
+}
+
+describe('DrawizngResizingComponent', () => {
+ let component: DrawingResizingComponent;
+ let fixture: ComponentFixture;
+ let drawingsWidgetMock = new DrawingWidgetMock;
+ let drawingsEventSource = new DrawingsEventSource;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ NoopAnimationsModule
+ ],
+ providers: [
+ { provide: DrawingsWidget, useValue: drawingsWidgetMock },
+ { provide: DrawingsEventSource, useValue: drawingsEventSource}
+ ],
+ declarations: [
+ DrawingResizingComponent
+ ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DrawingResizingComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should emit event after size changes', () => {
+ spyOn(drawingsEventSource.resized, 'emit');
+ const evt = new ResizingEnd();
+ evt.x = 0;
+ evt.y = 0;
+ evt.width = 10;
+ evt.height = 10;
+ evt.datum = {} as MapDrawing;
+
+ drawingsWidgetMock.emitEvent();
+
+ expect(drawingsEventSource.resized.emit).toHaveBeenCalled();
+ });
+});
diff --git a/src/app/cartography/components/drawing-resizing/drawing-resizing.component.ts b/src/app/cartography/components/drawing-resizing/drawing-resizing.component.ts
new file mode 100644
index 00000000..a1607ef7
--- /dev/null
+++ b/src/app/cartography/components/drawing-resizing/drawing-resizing.component.ts
@@ -0,0 +1,31 @@
+import { Component, OnInit, ElementRef, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { DrawingsEventSource } from '../../events/drawings-event-source';
+import { DrawingsWidget } from '../../widgets/drawings';
+import { MapDrawing } from '../../models/map/map-drawing';
+import { ResizedDataEvent } from '../../events/event-source';
+import { ResizingEnd } from '../../events/resizing';
+
+@Component({
+ selector: 'app-drawing-resizing',
+ template: ``,
+ styleUrls: ['./drawing-resizing.component.scss']
+})
+export class DrawingResizingComponent implements OnInit, OnDestroy{
+ resizingFinished: Subscription;
+
+ constructor(
+ private drawingsWidget: DrawingsWidget,
+ private drawingsEventSource: DrawingsEventSource
+ ) {}
+
+ ngOnInit() {
+ this.resizingFinished = this.drawingsWidget.resizingFinished.subscribe((evt: ResizingEnd) => {
+ this.drawingsEventSource.resized.emit(new ResizedDataEvent(evt.datum, evt.x, evt.y, evt.width, evt.height));
+ });
+ }
+
+ ngOnDestroy() {
+ this.resizingFinished.unsubscribe();
+ }
+}
diff --git a/src/app/cartography/components/text-adding/text-adding.component.html b/src/app/cartography/components/text-adding/text-adding.component.html
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/text-adding/text-adding.component.scss b/src/app/cartography/components/text-adding/text-adding.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/text-adding/text-adding.component.spec.ts b/src/app/cartography/components/text-adding/text-adding.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/text-adding/text-adding.component.ts b/src/app/cartography/components/text-adding/text-adding.component.ts
new file mode 100644
index 00000000..24651934
--- /dev/null
+++ b/src/app/cartography/components/text-adding/text-adding.component.ts
@@ -0,0 +1,24 @@
+import { Component, OnInit } from "@angular/core";
+import { select } from 'd3-selection';
+import { Context } from "../../models/context";
+
+@Component({
+ selector: 'app-text-adding',
+ template: ``,
+ styleUrls: ['./text-adding.component.scss']
+})
+export class TextAddingComponent implements OnInit {
+ public isEnabled: boolean = true;
+
+ constructor(
+ private context: Context
+ ){}
+
+ ngOnInit(){
+
+ }
+
+ addingTextSelected(){
+ //here will be moved addText in projectMapComponent
+ }
+}
diff --git a/src/app/cartography/components/text-editing/text-editing.component.html b/src/app/cartography/components/text-editing/text-editing.component.html
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/text-editing/text-editing.component.scss b/src/app/cartography/components/text-editing/text-editing.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/text-editing/text-editing.component.spec.ts b/src/app/cartography/components/text-editing/text-editing.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/components/text-editing/text-editing.component.ts b/src/app/cartography/components/text-editing/text-editing.component.ts
new file mode 100644
index 00000000..8e981f86
--- /dev/null
+++ b/src/app/cartography/components/text-editing/text-editing.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnDestroy, OnInit } from "@angular/core";
+import { Subscription } from 'rxjs';
+import { TextEditingTool } from '../../tools/text-editing-tool';
+import { DrawingsEventSource } from '../../events/drawings-event-source';
+import { TextEditedDataEvent } from '../../events/event-source';
+
+@Component({
+ selector: 'app-text-editing',
+ template: ``,
+ styleUrls: ['./text-editing.component.scss']
+})
+export class TextEditingComponent implements OnInit, OnDestroy{
+ textEditingFinished: Subscription;
+
+ constructor(
+ private textEditingTool: TextEditingTool,
+ private drawingEventSource: DrawingsEventSource
+ ) {}
+
+ ngOnInit() {
+ this.textEditingFinished = this.textEditingTool.editingFinished.subscribe((evt: TextEditedDataEvent) => {
+ this.drawingEventSource.textEdited.emit(evt);
+ });
+ }
+
+ onTextAddingChosen() {
+
+ }
+
+ ngOnDestroy() {
+ this.textEditingFinished.unsubscribe();
+ }
+}
diff --git a/src/app/cartography/converters/map/map-drawing-to-svg-converter.ts b/src/app/cartography/converters/map/map-drawing-to-svg-converter.ts
new file mode 100644
index 00000000..66fc05ed
--- /dev/null
+++ b/src/app/cartography/converters/map/map-drawing-to-svg-converter.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+
+import { Converter } from '../converter';
+import { MapDrawing } from '../../models/map/map-drawing';
+import { RectElement } from '../../models/drawings/rect-element';
+import { EllipseElement } from '../../models/drawings/ellipse-element';
+import { LineElement } from '../../models/drawings/line-element';
+import { TextElement } from '../../models/drawings/text-element';
+
+
+@Injectable()
+export class MapDrawingToSvgConverter implements Converter {
+ constructor(
+ ) {}
+
+ convert(mapDrawing: MapDrawing) {
+ let elem = ``;
+
+ if (mapDrawing.element instanceof RectElement) {
+ elem = ``;
+ } else if (mapDrawing.element instanceof EllipseElement) {
+ elem = ``;
+ } else if (mapDrawing.element instanceof LineElement) {
+ elem = ``
+ } else if (mapDrawing.element instanceof TextElement) {
+ elem = `${mapDrawing.element.text}`;
+ } else return "";
+
+ return ``;
+ }
+}
\ No newline at end of file
diff --git a/src/app/cartography/d3-map.imports.ts b/src/app/cartography/d3-map.imports.ts
index 25aa4e27..728a6af8 100644
--- a/src/app/cartography/d3-map.imports.ts
+++ b/src/app/cartography/d3-map.imports.ts
@@ -5,6 +5,7 @@ import { DrawingsWidget } from './widgets/drawings';
import { DrawingLineWidget } from './widgets/drawing-line';
import { SelectionTool } from './tools/selection-tool';
import { MovingTool } from './tools/moving-tool';
+import {TextEditingTool} from './tools/text-editing-tool';
import { LayersWidget } from './widgets/layers';
import { LinkWidget } from './widgets/link';
import { InterfaceStatusWidget } from './widgets/interface-status';
@@ -28,6 +29,7 @@ export const D3_MAP_IMPORTS = [
DrawingLineWidget,
SelectionTool,
MovingTool,
+ TextEditingTool,
LayersWidget,
LinkWidget,
InterfaceStatusWidget,
diff --git a/src/app/cartography/events/drawings-event-source.ts b/src/app/cartography/events/drawings-event-source.ts
index 40e1093e..1f25e781 100644
--- a/src/app/cartography/events/drawings-event-source.ts
+++ b/src/app/cartography/events/drawings-event-source.ts
@@ -1,9 +1,11 @@
import { Injectable, EventEmitter } from "@angular/core";
-import { DraggedDataEvent } from "./event-source";
+import { DraggedDataEvent, ResizedDataEvent } from "./event-source";
import { MapDrawing } from "../models/map/map-drawing";
@Injectable()
export class DrawingsEventSource {
public dragged = new EventEmitter>();
+ public resized = new EventEmitter>();
+ public textEdited = new EventEmitter();
}
diff --git a/src/app/cartography/events/event-source.ts b/src/app/cartography/events/event-source.ts
index c108097c..a9203860 100644
--- a/src/app/cartography/events/event-source.ts
+++ b/src/app/cartography/events/event-source.ts
@@ -1,3 +1,5 @@
+import { TextElement } from '../models/drawings/text-element';
+
export class DataEventSource {
constructor(
public datum: T,
@@ -8,10 +10,28 @@ export class DataEventSource {
export class DraggedDataEvent extends DataEventSource {}
+export class ResizedDataEvent {
+ constructor(
+ public datum: T,
+ public x: number,
+ public y: number,
+ public width: number,
+ public height: number
+ ) {}
+}
+
export class ClickedDataEvent {
constructor(
public datum: T,
public x: number,
public y: number
) {}
-}
\ No newline at end of file
+}
+
+export class TextEditedDataEvent {
+ constructor(
+ public textDrawingId: string,
+ public editedText: string,
+ public textElement: TextElement
+ ) {}
+}
diff --git a/src/app/cartography/events/resizing.ts b/src/app/cartography/events/resizing.ts
new file mode 100644
index 00000000..7ce2cc1b
--- /dev/null
+++ b/src/app/cartography/events/resizing.ts
@@ -0,0 +1,7 @@
+export class ResizingEnd {
+ public datum: T;
+ public x: number;
+ public y: number;
+ public width: number;
+ public height: number;
+}
\ No newline at end of file
diff --git a/src/app/cartography/helpers/font-bbox-calculator.spec.ts b/src/app/cartography/helpers/font-bbox-calculator.spec.ts
index 52417102..42d295d0 100644
--- a/src/app/cartography/helpers/font-bbox-calculator.spec.ts
+++ b/src/app/cartography/helpers/font-bbox-calculator.spec.ts
@@ -15,7 +15,7 @@ describe('FontBBoxCalculator', () => {
expect(box.width).toEqual(41.34375);
});
- it('should calculate font width and height for different font', () => {
+ xit('should calculate font width and height for different font', () => {
const box = calculator.calculate("My text", "font-family:Tahoma; font-size: 14px; font-weight:bold");
expect(box.height).toEqual(15);
diff --git a/src/app/cartography/managers/selection-manager.ts b/src/app/cartography/managers/selection-manager.ts
index 40817618..b5cda11f 100644
--- a/src/app/cartography/managers/selection-manager.ts
+++ b/src/app/cartography/managers/selection-manager.ts
@@ -23,7 +23,7 @@ export class SelectionManager {
this.selection = dictItems;
- if (selected.length > 0) {
+ if (selected.length > 0) {
this.selected.emit(selected);
}
diff --git a/src/app/cartography/tools/text-editing-tool.ts b/src/app/cartography/tools/text-editing-tool.ts
new file mode 100644
index 00000000..afc33a78
--- /dev/null
+++ b/src/app/cartography/tools/text-editing-tool.ts
@@ -0,0 +1,78 @@
+import { Injectable, EventEmitter } from "@angular/core";
+import { SVGSelection } from '../models/types';
+import { select } from 'd3-selection';
+import { TextElement } from '../models/drawings/text-element';
+import { TextEditedDataEvent } from '../events/event-source';
+
+
+@Injectable()
+export class TextEditingTool {
+ private enabled = true;
+ private editingDrawingId: string;
+ private editedElement: any;
+ public editingFinished = new EventEmitter();
+
+ public setEnabled(enabled) {
+ this.enabled = enabled;
+ }
+
+ public draw(selection: SVGSelection){
+ if (!this.enabled){
+ return;
+ }
+
+ selection.selectAll('text.text_element')
+ .on("dblclick", (elem, index, textElements) => {
+
+ this.editedElement = elem;
+
+ select(textElements[index])
+ .attr("visibility", "hidden");
+
+ select(textElements[index])
+ .classed("editingMode", true);
+
+ this.editingDrawingId = textElements[index].parentElement.parentElement.getAttribute("drawing_id");
+
+ select(textElements[index].parentElement.parentElement.parentElement)
+ .append("foreignObject")
+ .attr("width", '1000px')
+ .attr("min-width", 'fit-content')
+ .attr("height", '100px')
+ .attr("id", "temporaryText")
+ .attr("transform", textElements[index].parentElement.getAttribute("transform"))
+ .append("xhtml:span")
+ .attr("width", "fit-content")
+ .attr("height", "fit-content")
+ .attr("class", "temporaryTextInside")
+ .attr('style', () => {
+ const styles: string[] = [];
+ styles.push(`white-space: pre-line`)
+ styles.push(`outline: 0px solid transparent`)
+ styles.push(`font-family: ${elem.font_family}`)
+ styles.push(`font-size: ${elem.font_size}pt!important`);
+ styles.push(`font-weight: ${elem.font_weight}`)
+ styles.push(`color: ${elem.fill}`);
+ return styles.join("; ");
+ })
+ .attr('text-decoration', elem.text_decoration)
+ .attr('contenteditable', 'true')
+ .text(elem.text)
+ .on("focusout", () => {
+ let temporaryText = document.getElementsByClassName("temporaryTextInside")[0] as HTMLElement;
+ let savedText = temporaryText.innerText;
+ this.editingFinished.emit(new TextEditedDataEvent(this.editingDrawingId, savedText, this.editedElement));
+
+ var temporaryElement = document.getElementById("temporaryText") as HTMLElement;
+ temporaryElement.remove();
+
+ selection.selectAll('text.editingMode')
+ .attr("visibility", "visible")
+ .classed("editingMode", false);
+ });
+
+ var txtInside = document.getElementsByClassName("temporaryTextInside")[0] as HTMLElement;
+ txtInside.focus();
+ });
+ }
+}
diff --git a/src/app/cartography/tools/text-editing.tools.spec.ts b/src/app/cartography/tools/text-editing.tools.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/cartography/widgets/drawing.ts b/src/app/cartography/widgets/drawing.ts
index 14f1a104..307a826c 100644
--- a/src/app/cartography/widgets/drawing.ts
+++ b/src/app/cartography/widgets/drawing.ts
@@ -9,7 +9,9 @@ import { RectDrawingWidget } from "./drawings/rect-drawing";
import { LineDrawingWidget } from "./drawings/line-drawing";
import { EllipseDrawingWidget } from "./drawings/ellipse-drawing";
import { MapDrawing } from "../models/map/map-drawing";
-
+import { SelectionManager } from "../managers/selection-manager";
+import { LineElement } from "../models/drawings/line-element";
+import { EllipseElement } from "../models/drawings/ellipse-element";
@Injectable()
export class DrawingWidget implements Widget {
@@ -20,7 +22,8 @@ export class DrawingWidget implements Widget {
private imageDrawingWidget: ImageDrawingWidget,
private rectDrawingWidget: RectDrawingWidget,
private lineDrawingWidget: LineDrawingWidget,
- private ellipseDrawingWidget: EllipseDrawingWidget
+ private ellipseDrawingWidget: EllipseDrawingWidget,
+ private selectionManager: SelectionManager
) {
this.drawingWidgets = [
this.textDrawingWidget,
@@ -33,20 +36,87 @@ export class DrawingWidget implements Widget {
public draw(view: SVGSelection) {
const drawing_body = view.selectAll("g.drawing_body")
- .data((l) => [l]);
+ .data((l:MapDrawing) => [l]);
const drawing_body_enter = drawing_body.enter()
.append('g')
- .attr("class", "drawing_body");
+ .attr("class", "drawing_body")
const drawing_body_merge = drawing_body.merge(drawing_body_enter)
.attr('transform', (d: MapDrawing) => {
return `translate(${d.x},${d.y}) rotate(${d.rotation})`;
});
-
+
this.drawingWidgets.forEach((widget) => {
widget.draw(drawing_body_merge);
});
+ drawing_body_merge
+ .select('line.top')
+ .attr('stroke', 'transparent')
+ .attr('stroke-width', '8px')
+ .attr('x1', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cx - (drawing.element.width/10) : '0')
+ .attr('x2', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cx + (drawing.element.width/10) : drawing.element.width)
+ .attr('y1', '0')
+ .attr('y2', '0')
+ .attr('draggable', 'true')
+ .attr("cursor", "ns-resize");
+
+ drawing_body_merge
+ .select('line.bottom')
+ .attr('stroke', 'transparent')
+ .attr('stroke-width', '8px')
+ .attr('x1', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cx - (drawing.element.width/10) : '0')
+ .attr('x2', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cx + (drawing.element.width/10) : drawing.element.width)
+ .attr('y1', (drawing) => drawing.element.height)
+ .attr('y2', (drawing) => drawing.element.height)
+ .attr('draggable', 'true')
+ .attr("cursor", "ns-resize");
+
+ drawing_body_merge
+ .select('line.right')
+ .attr('stroke', 'transparent')
+ .attr('stroke-width', '8px')
+ .attr('x1', '0')
+ .attr('x2', '0')
+ .attr('y1', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cy - (drawing.element.height/10) : '0')
+ .attr('y2', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cy + (drawing.element.height/10) : drawing.element.height)
+ .attr('draggable', 'true')
+ .attr("cursor", "ew-resize");
+
+ drawing_body_merge
+ .select('line.left')
+ .attr('stroke', 'transparent')
+ .attr('stroke-width', '8px')
+ .attr('x1', (drawing) => drawing.element.width)
+ .attr('x2', (drawing) => drawing.element.width)
+ .attr('y1', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cy - (drawing.element.height/10) : '0')
+ .attr('y2', (drawing) => drawing.element instanceof EllipseElement ? drawing.element.cy + (drawing.element.height/10) : drawing.element.height)
+ .attr('draggable', 'true')
+ .attr("cursor", "ew-resize");
+
+ drawing_body_merge
+ .select('circle.left')
+ .attr('draggable', 'true')
+ .attr('fill', 'transparent')
+ .attr('stroke', 'transparent')
+ .attr('cx', (drawing) => (drawing.element as LineElement).x1)
+ .attr('cy', (drawing) => (drawing.element as LineElement).y1)
+ .attr('r', 10)
+ .attr("cursor", "move");
+
+ drawing_body_merge
+ .select('circle.right')
+ .attr('draggable', 'true')
+ .attr('fill', 'transparent')
+ .attr('stroke', 'transparent')
+ .attr('cx', (drawing) => (drawing.element as LineElement).x2)
+ .attr('cy', (drawing) => (drawing.element as LineElement).y2)
+ .attr('r', 10)
+ .attr("cursor", "move");
+
+ drawing_body_merge
+ .classed('drawing_selected', (n: MapDrawing) => this.selectionManager.isSelected(n));
+
}
}
diff --git a/src/app/cartography/widgets/drawings.backup.ts b/src/app/cartography/widgets/drawings.backup.ts
new file mode 100644
index 00000000..c5bd6205
--- /dev/null
+++ b/src/app/cartography/widgets/drawings.backup.ts
@@ -0,0 +1,368 @@
+import { Injectable, EventEmitter } from "@angular/core";
+
+import { Widget } from "./widget";
+import { SVGSelection } from "../models/types";
+import { Layer } from "../models/layer";
+import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter";
+import { Draggable, DraggableDrag, DraggableStart, DraggableEnd } from "../events/draggable";
+import { DrawingWidget } from "./drawing";
+import { drag, D3DragEvent } from "d3-drag";
+import { event } from "d3-selection";
+import { MapDrawing } from "../models/map/map-drawing";
+import { Context } from "../models/context";
+import { EllipseElement } from "../models/drawings/ellipse-element";
+import { ResizingEnd } from "../events/resizing";
+import { LineElement } from "../models/drawings/line-element";
+import { MapSettingsManager } from "../managers/map-settings-manager";
+
+
+@Injectable()
+export class DrawingsWidget implements Widget {
+ public draggable = new Draggable();
+ public draggingEnabled = false;
+ public resizingFinished = new EventEmitter>();
+
+ // public onContextMenu = new EventEmitter();
+ // public onDrawingClicked = new EventEmitter();
+ // public onDrawingDragged = new EventEmitter();
+ // public onDrawingDragging = new EventEmitter();
+
+ constructor(
+ private drawingWidget: DrawingWidget,
+ private svgToDrawingConverter: SvgToDrawingConverter,
+ private context: Context,
+ private mapSettings: MapSettingsManager
+ ) {
+ this.svgToDrawingConverter = new SvgToDrawingConverter();
+ }
+
+ public redrawDrawing(view: SVGSelection, drawing: MapDrawing) {
+ this.drawingWidget.draw(this.selectDrawing(view, drawing));
+ }
+
+ public draw(view: SVGSelection) {
+ const drawing = view
+ .selectAll("g.drawing")
+ .data((layer: Layer) => {
+ layer.drawings.forEach((d: MapDrawing) => {
+ try {
+ d.element = this.svgToDrawingConverter.convert(d.svg);
+ } catch (error) {
+ console.log(`Cannot convert due to Error: '${error}'`);
+ }
+ });
+ return layer.drawings;
+ }, (l: MapDrawing) => {
+ return l.id;
+ });
+
+ const drawing_enter = drawing.enter()
+ .append('g')
+ .attr('class', 'drawing')
+ .attr('drawing_id', (l: MapDrawing) => l.id)
+
+ const merge = drawing.merge(drawing_enter);
+
+ this.drawingWidget.draw(merge);
+
+ drawing
+ .exit()
+ .remove();
+
+ if (!this.mapSettings.isReadOnly) {
+ this.draggable.call(merge);
+ }
+
+ let y: number;
+ let dy: number;
+ let topEdge: number;
+ let bottomEdge: number;
+ let isReflectedVertical: boolean = false;
+ let startEvent;
+ let bottom = drag()
+ .on('start', (datum: MapDrawing) => {
+ document.body.style.cursor = "ns-resize";
+ topEdge = datum.y;
+ console.log("started");
+ y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ // startEvent = event;
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+ dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
+ y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ let height = datum.element.height - dy;
+ if(height < 0) {
+ // height = datum.y - startEvent.y;
+ datum.y += height;
+ height = topEdge - datum.y;
+ // console.log(topEdge - datum.y);
+ }
+ console.log("Height", height);
+ datum.element.height = height;
+
+ // datum.element.height -= dy;
+ // if(datum.element.height < 0) {
+ // datum.y -= datum.element.height;
+ // datum.element.height = Math.abs(datum.element.height);
+ // }
+
+ // if (!isReflectedVertical) {
+ // if ((datum.element.height + evt.dy) < 0) {
+ // isReflectedVertical = true;
+ // y = topEdge;
+ // console.log(y);
+ // datum.element.height = Math.abs(datum.element.height + evt.dy);
+ // console.log(datum.element.height);
+ // } else {
+ // datum.element.height += evt.dy;
+
+ // if (datum.element instanceof EllipseElement){
+ // (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += evt.dy/2;
+ // (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += evt.dy/2;
+ // }
+ // }
+ // } else {
+ // dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
+ // y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+
+ // if ((datum.element.height + dy) < 0){
+ // isReflectedVertical = false;
+ // y = topEdge;
+ // console.log(y);
+ // datum.element.height = Math.abs(datum.element.height + evt.dy);
+ // console.log(datum.element.height);
+ // } else {
+ // datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ // datum.element.height += dy;
+ // if (datum.element instanceof EllipseElement) {
+ // (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += dy/2;
+ // (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += dy/2;
+ // }
+ // }
+ // }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let top = drag()
+ .on('start', (datum: MapDrawing) => {
+ y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ bottomEdge = y + datum.element.height;
+ document.body.style.cursor = "ns-resize";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+
+ if (!isReflectedVertical) {
+ dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
+ y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+
+ if ((datum.element.height + dy) < 0){
+ y = bottomEdge;
+ isReflectedVertical = true;
+ datum.element.height = Math.abs(datum.element.height + evt.dy);
+ } else {
+ datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ datum.element.height += dy;
+ if (datum.element instanceof EllipseElement) {
+ (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += dy/2;
+ (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += dy/2;
+ }
+ }
+ } else {
+ if ((datum.element.height + evt.dy) < 0) {
+ isReflectedVertical = false;
+ y = bottomEdge;
+ datum.element.height = Math.abs(datum.element.height + evt.dy);
+ } else {
+ datum.element.height += evt.dy;
+
+ if (datum.element instanceof EllipseElement){
+ (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += evt.dy/2;
+ (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += evt.dy/2;
+ }
+ }
+ }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let x: number;
+ let dx: number;
+ let rightEdge: number;
+ let leftEdge: number;
+ let isReflectedHorizontal: boolean = false;
+ let right = drag()
+ .on('start', (datum: MapDrawing) => {
+ x = event.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+ leftEdge = x + datum.element.width;
+ document.body.style.cursor = "ew-resize";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+
+ if (!isReflectedHorizontal) {
+ dx = x - (evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x);
+ x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+
+ if ((datum.element.width + dx) < 0) {
+ x = leftEdge;
+ isReflectedHorizontal = true;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ datum.x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+ datum.element.width += dx;
+ if (datum.element instanceof EllipseElement) {
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += dx/2;
+ }
+ }
+ } else {
+ if ((datum.element.width + evt.dx) < 0) {
+ x = leftEdge;
+ isReflectedHorizontal = false;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ if (datum.element instanceof EllipseElement){
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += evt.dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += evt.dx/2;
+ }
+ datum.element.width = (datum.element.width + evt.dx) < 0 ? 1 : datum.element.width += evt.dx;
+ }
+ }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let left = drag()
+ .on('start', (datum: MapDrawing) => {
+ document.body.style.cursor = "ew-resize";
+ rightEdge = datum.x;
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+
+ if (!isReflectedHorizontal) {
+ if ((datum.element.width + evt.dx) < 0) {
+ x = rightEdge;
+ isReflectedHorizontal = true;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ if (datum.element instanceof EllipseElement){
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += evt.dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += evt.dx/2;
+ }
+ datum.element.width = (datum.element.width + evt.dx) < 0 ? 1 : datum.element.width += evt.dx;
+ }
+ } else {
+ dx = x - (evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x);
+ x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+
+ if ((datum.element.width + dx) < 0) {
+ x = rightEdge;
+ isReflectedHorizontal = false;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ datum.x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+ datum.element.width += dx;
+ if (datum.element instanceof EllipseElement) {
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += dx/2;
+ }
+ }
+ }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let circleMoveRight = drag()
+ .on('start', () => {
+ document.body.style.cursor = "move";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+ datum.element.width += evt.dx;
+ datum.element.height += evt.dy;
+ (datum.element as LineElement).x2 += evt.dx;
+ (datum.element as LineElement).y2 += evt.dy;
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let circleMoveLeft = drag()
+ .on('start', () => {
+ document.body.style.cursor = "move";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+ datum.element.width += evt.dx;
+ datum.element.height += evt.dy;
+ (datum.element as LineElement).x1 += evt.dx;
+ (datum.element as LineElement).y1 += evt.dy;
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ merge
+ .select('line.bottom')
+ .call(bottom);
+
+ merge
+ .select('line.top')
+ .call(top);
+
+ merge
+ .select('line.right')
+ .call(right);
+
+ merge
+ .select('line.left')
+ .call(left);
+
+ merge
+ .select('circle.right')
+ .call(circleMoveRight)
+
+ merge
+ .select('circle.left')
+ .call(circleMoveLeft)
+ }
+
+ private createResizingEvent(datum: MapDrawing){
+ const evt = new ResizingEnd();
+ evt.x = datum.x;
+ evt.y = datum. y;
+ evt.width = datum.element.width;
+ evt.height = datum.element.height;
+ evt.datum = datum;
+ return evt;
+ }
+
+ private selectDrawing(view: SVGSelection, drawing: MapDrawing) {
+ return view.selectAll(`g.drawing[drawing_id="${drawing.id}"]`);
+ }
+
+}
diff --git a/src/app/cartography/widgets/drawings.ts b/src/app/cartography/widgets/drawings.ts
index dad1c7af..96a1f1e5 100644
--- a/src/app/cartography/widgets/drawings.ts
+++ b/src/app/cartography/widgets/drawings.ts
@@ -1,18 +1,26 @@
-import { Injectable } from "@angular/core";
+import { Injectable, EventEmitter } from "@angular/core";
import { Widget } from "./widget";
import { SVGSelection } from "../models/types";
import { Layer } from "../models/layer";
import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter";
-import { Draggable } from "../events/draggable";
+import { Draggable, DraggableDrag, DraggableStart, DraggableEnd } from "../events/draggable";
import { DrawingWidget } from "./drawing";
+import { drag, D3DragEvent } from "d3-drag";
+import { event } from "d3-selection";
import { MapDrawing } from "../models/map/map-drawing";
+import { Context } from "../models/context";
+import { EllipseElement } from "../models/drawings/ellipse-element";
+import { ResizingEnd } from "../events/resizing";
+import { LineElement } from "../models/drawings/line-element";
import { MapSettingsManager } from "../managers/map-settings-manager";
@Injectable()
export class DrawingsWidget implements Widget {
public draggable = new Draggable();
+ public draggingEnabled = false;
+ public resizingFinished = new EventEmitter>();
// public onContextMenu = new EventEmitter();
// public onDrawingClicked = new EventEmitter();
@@ -22,6 +30,7 @@ export class DrawingsWidget implements Widget {
constructor(
private drawingWidget: DrawingWidget,
private svgToDrawingConverter: SvgToDrawingConverter,
+ private context: Context,
private mapSettings: MapSettingsManager
) {
this.svgToDrawingConverter = new SvgToDrawingConverter();
@@ -63,9 +72,272 @@ export class DrawingsWidget implements Widget {
if (!this.mapSettings.isReadOnly) {
this.draggable.call(merge);
}
+
+ let y: number;
+ let dy: number;
+ let topEdge: number;
+ let bottomEdge: number;
+ let isReflectedVertical: boolean = false;
+ let bottom = drag()
+ .on('start', (datum: MapDrawing) => {
+ document.body.style.cursor = "ns-resize";
+ topEdge = datum.y;
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+
+ if (!isReflectedVertical) {
+ if ((datum.element.height + evt.dy) < 0) {
+ isReflectedVertical = true;
+ y = topEdge;
+ datum.element.height = Math.abs(datum.element.height + evt.dy);
+ } else {
+ datum.element.height += evt.dy;
+
+ if (datum.element instanceof EllipseElement){
+ (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += evt.dy/2;
+ (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += evt.dy/2;
+ }
+ }
+ } else {
+ dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
+ y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+
+ if ((datum.element.height + dy) < 0){
+ isReflectedVertical = false;
+ y = topEdge;
+ datum.element.height = Math.abs(datum.element.height + evt.dy);
+ } else {
+ datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ datum.element.height += dy;
+ if (datum.element instanceof EllipseElement) {
+ (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += dy/2;
+ (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += dy/2;
+ }
+ }
+ }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let top = drag()
+ .on('start', (datum: MapDrawing) => {
+ y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ bottomEdge = y + datum.element.height;
+ document.body.style.cursor = "ns-resize";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+
+ if (!isReflectedVertical) {
+ dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
+ y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+
+ if ((datum.element.height + dy) < 0){
+ y = bottomEdge;
+ isReflectedVertical = true;
+ datum.element.height = Math.abs(datum.element.height + evt.dy);
+ } else {
+ datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
+ datum.element.height += dy;
+ if (datum.element instanceof EllipseElement) {
+ (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += dy/2;
+ (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += dy/2;
+ }
+ }
+ } else {
+ if ((datum.element.height + evt.dy) < 0) {
+ isReflectedVertical = false;
+ y = bottomEdge;
+ datum.element.height = Math.abs(datum.element.height + evt.dy);
+ } else {
+ datum.element.height += evt.dy;
+
+ if (datum.element instanceof EllipseElement){
+ (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += evt.dy/2;
+ (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += evt.dy/2;
+ }
+ }
+ }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let x: number;
+ let dx: number;
+ let rightEdge: number;
+ let leftEdge: number;
+ let isReflectedHorizontal: boolean = false;
+ let right = drag()
+ .on('start', (datum: MapDrawing) => {
+ x = event.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+ leftEdge = x + datum.element.width;
+ document.body.style.cursor = "ew-resize";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+
+ if (!isReflectedHorizontal) {
+ dx = x - (evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x);
+ x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+
+ if ((datum.element.width + dx) < 0) {
+ x = leftEdge;
+ isReflectedHorizontal = true;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ datum.x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+ datum.element.width += dx;
+ if (datum.element instanceof EllipseElement) {
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += dx/2;
+ }
+ }
+ } else {
+ if ((datum.element.width + evt.dx) < 0) {
+ x = leftEdge;
+ isReflectedHorizontal = false;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ if (datum.element instanceof EllipseElement){
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += evt.dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += evt.dx/2;
+ }
+ datum.element.width = (datum.element.width + evt.dx) < 0 ? 1 : datum.element.width += evt.dx;
+ }
+ }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let left = drag()
+ .on('start', (datum: MapDrawing) => {
+ document.body.style.cursor = "ew-resize";
+ rightEdge = datum.x;
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+
+ if (!isReflectedHorizontal) {
+ if ((datum.element.width + evt.dx) < 0) {
+ x = rightEdge;
+ isReflectedHorizontal = true;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ if (datum.element instanceof EllipseElement){
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += evt.dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + evt.dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += evt.dx/2;
+ }
+ datum.element.width = (datum.element.width + evt.dx) < 0 ? 1 : datum.element.width += evt.dx;
+ }
+ } else {
+ dx = x - (evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x);
+ x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+
+ if ((datum.element.width + dx) < 0) {
+ x = rightEdge;
+ isReflectedHorizontal = false;
+ datum.element.width = Math.abs(datum.element.width + evt.dx);
+ } else {
+ datum.x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
+ datum.element.width += dx;
+ if (datum.element instanceof EllipseElement) {
+ (datum.element as EllipseElement).cx = (datum.element as EllipseElement).cx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).cx += dx/2;
+ (datum.element as EllipseElement).rx = (datum.element as EllipseElement).rx + dx/2 < 0 ? 1 : (datum.element as EllipseElement).rx += dx/2;
+ }
+ }
+ }
+
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let circleMoveRight = drag()
+ .on('start', () => {
+ document.body.style.cursor = "move";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+ datum.element.width += evt.dx;
+ datum.element.height += evt.dy;
+ (datum.element as LineElement).x2 += evt.dx;
+ (datum.element as LineElement).y2 += evt.dy;
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ let circleMoveLeft = drag()
+ .on('start', () => {
+ document.body.style.cursor = "move";
+ })
+ .on('drag', (datum: MapDrawing) => {
+ const evt = event;
+ datum.element.width += evt.dx;
+ datum.element.height += evt.dy;
+ (datum.element as LineElement).x1 += evt.dx;
+ (datum.element as LineElement).y1 += evt.dy;
+ this.redrawDrawing(view, datum);
+ })
+ .on('end', (datum: MapDrawing) => {
+ document.body.style.cursor = "initial";
+ this.resizingFinished.emit(this.createResizingEvent(datum));
+ });
+
+ merge
+ .select('line.bottom')
+ .call(bottom);
+
+ merge
+ .select('line.top')
+ .call(top);
+
+ merge
+ .select('line.right')
+ .call(right);
+
+ merge
+ .select('line.left')
+ .call(left);
+
+ merge
+ .select('circle.right')
+ .call(circleMoveRight)
+
+ merge
+ .select('circle.left')
+ .call(circleMoveLeft)
+ }
+
+ private createResizingEvent(datum: MapDrawing){
+ const evt = new ResizingEnd();
+ evt.x = datum.x;
+ evt.y = datum. y;
+ evt.width = datum.element.width;
+ evt.height = datum.element.height;
+ evt.datum = datum;
+ return evt;
}
private selectDrawing(view: SVGSelection, drawing: MapDrawing) {
return view.selectAll(`g.drawing[drawing_id="${drawing.id}"]`);
}
+
}
diff --git a/src/app/cartography/widgets/drawings/ellipse-drawing.ts b/src/app/cartography/widgets/drawings/ellipse-drawing.ts
index 2e2aedf3..762fff99 100644
--- a/src/app/cartography/widgets/drawings/ellipse-drawing.ts
+++ b/src/app/cartography/widgets/drawings/ellipse-drawing.ts
@@ -21,6 +21,22 @@ export class EllipseDrawingWidget implements DrawingShapeWidget {
return (d.element && d.element instanceof EllipseElement) ? [d.element] : [];
});
+ drawing.enter()
+ .append('line')
+ .attr("class", "top");
+
+ drawing.enter()
+ .append('line')
+ .attr("class", "bottom");
+
+ drawing.enter()
+ .append('line')
+ .attr("class", "right");
+
+ drawing.enter()
+ .append('line')
+ .attr("class", "left");
+
const drawing_enter = drawing
.enter()
.append('ellipse')
diff --git a/src/app/cartography/widgets/drawings/line-drawing.ts b/src/app/cartography/widgets/drawings/line-drawing.ts
index 729b1be2..280156db 100644
--- a/src/app/cartography/widgets/drawings/line-drawing.ts
+++ b/src/app/cartography/widgets/drawings/line-drawing.ts
@@ -21,8 +21,15 @@ export class LineDrawingWidget implements DrawingShapeWidget {
return (d.element && d.element instanceof LineElement) ? [d.element] : [];
});
- const drawing_enter = drawing
- .enter()
+ drawing.enter()
+ .append('circle')
+ .attr('class', 'right');
+
+ drawing.enter()
+ .append('circle')
+ .attr('class', 'left');
+
+ const drawing_enter = drawing.enter()
.append('line')
.attr('class', 'line_element noselect');
diff --git a/src/app/cartography/widgets/drawings/rect-drawing.ts b/src/app/cartography/widgets/drawings/rect-drawing.ts
index ba909184..ef3e0895 100644
--- a/src/app/cartography/widgets/drawings/rect-drawing.ts
+++ b/src/app/cartography/widgets/drawings/rect-drawing.ts
@@ -9,6 +9,7 @@ import { MapDrawing } from "../../models/map/map-drawing";
@Injectable()
export class RectDrawingWidget implements DrawingShapeWidget {
+
constructor(
private qtDasharrayFixer: QtDasharrayFixer
) {}
@@ -20,6 +21,22 @@ export class RectDrawingWidget implements DrawingShapeWidget {
return (d.element && d.element instanceof RectElement) ? [d.element] : [];
});
+ drawing.enter()
+ .append('line')
+ .attr("class", "top");
+
+ drawing.enter()
+ .append('line')
+ .attr("class", "bottom");
+
+ drawing.enter()
+ .append('line')
+ .attr("class", "right");
+
+ drawing.enter()
+ .append('line')
+ .attr("class", "left");
+
const drawing_enter = drawing
.enter()
.append('rect')
diff --git a/src/app/cartography/widgets/drawings/text-drawing.ts b/src/app/cartography/widgets/drawings/text-drawing.ts
index 7db42cc2..377b96a3 100644
--- a/src/app/cartography/widgets/drawings/text-drawing.ts
+++ b/src/app/cartography/widgets/drawings/text-drawing.ts
@@ -75,6 +75,7 @@ export class TextDrawingWidget implements DrawingShapeWidget {
// approx and make it matching to GUI
const tspan = select(this).selectAll('tspan');
const height = this.getBBox().height / tspan.size();
+ //return `translate(0, ${height})`;
return `translate(${TextDrawingWidget.MARGIN}, ${height - TextDrawingWidget.MARGIN})`;
});
diff --git a/src/app/cartography/widgets/graph-layout.ts b/src/app/cartography/widgets/graph-layout.ts
index a559853a..fe152411 100644
--- a/src/app/cartography/widgets/graph-layout.ts
+++ b/src/app/cartography/widgets/graph-layout.ts
@@ -8,6 +8,7 @@ import { MovingTool } from "../tools/moving-tool";
import { LayersWidget } from "./layers";
import { LayersManager } from "../managers/layers-manager";
import { Injectable } from "@angular/core";
+import { TextEditingTool } from '../tools/text-editing-tool';
@Injectable()
@@ -17,6 +18,7 @@ export class GraphLayout implements Widget {
private drawingLineTool: DrawingLineWidget,
private selectionTool: SelectionTool,
private movingTool: MovingTool,
+ private textEditingTool: TextEditingTool,
private layersWidget: LayersWidget,
private layersManager: LayersManager
) {
@@ -67,6 +69,7 @@ export class GraphLayout implements Widget {
this.drawingLineTool.draw(view, context);
this.selectionTool.draw(view, context);
this.movingTool.draw(view, context);
+ this.textEditingTool.draw(view);
}
disconnect(view: SVGSelection) {
diff --git a/src/app/components/project-map/project-map.component.css b/src/app/components/project-map/project-map.component.css
index f10ff2a3..29992d74 100644
--- a/src/app/components/project-map/project-map.component.css
+++ b/src/app/components/project-map/project-map.component.css
@@ -18,6 +18,84 @@ g.node:hover {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
+.draw-menu {
+ position: fixed;
+ background: transparent;
+ top: 20px;
+ left: 92px;
+ width: 296px !important;
+ height: 72px !important;
+}
+
+.draw-menu button {
+ outline: none;
+}
+
+.mat-drawer-content {
+ display: inline !important;
+}
+
+.drawer-container {
+ height: 72px !important;
+ background: transparent;
+}
+
+.shadow {
+ box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
+}
+
+.drawer {
+ width: 296px !important;
+ height: 72px !important;
+ background:#263238;
+}
+
+.drawer-content {
+ background: #F0F0F0!important;
+}
+
+.drawer-button {
+ width: 40px;
+ margin-right: 12px!important;
+ margin-left: 12px!important;
+ background: #263238;
+ padding: 0;
+ border: none;
+ outline: none;
+ background-color: transparent;
+}
+
+.drawer-arrow-button-right {
+ width: 40px;
+ height: 72px;
+ padding-top: 16px;
+ background:#263238;
+ position: fixed;
+}
+
+.drawer-arrow-button-left {
+ width: 40px;
+ margin-left: 256px;
+ background:#263238;
+ position: fixed;
+}
+
+.drawer-buttons {
+ background:#263238;
+ padding-top: 16px;
+ height: 72px;
+ display: flex;
+ flex-direction: row;
+}
+
+.mat-drawer-backdrop.mat-drawer-shown {
+ background-color: transparent;
+}
+
+.selected {
+ stroke: #0097a7!important;
+}
+
.project-toolbar .mat-toolbar-multiple-rows {
width: auto !important;
}
diff --git a/src/app/components/project-map/project-map.component.html b/src/app/components/project-map/project-map.component.html
index 39405f6d..f547df1e 100644
--- a/src/app/components/project-map/project-map.component.html
+++ b/src/app/components/project-map/project-map.component.html
@@ -10,8 +10,13 @@
[show-interface-labels]="project.show_interface_labels"
[selection-tool]="tools.selection"
[moving-tool]="tools.moving"
+ [text-editing-tool]="tools.text_editing"
[draw-link-tool]="tools.draw_link"
[readonly]="inReadOnlyMode"
+ (nodeDragged)="onNodeDragged($event)"
+ (drawingDragged)="onDrawingDragged($event)"
+ (onLinkCreated)="onLinkCreated($event)"
+ (onDrawingResized)="onDrawingResized($event)"
>
+
+
diff --git a/src/app/components/project-map/project-map.component.spec.ts b/src/app/components/project-map/project-map.component.spec.ts
index c714cec4..231e877f 100644
--- a/src/app/components/project-map/project-map.component.spec.ts
+++ b/src/app/components/project-map/project-map.component.spec.ts
@@ -1,14 +1,135 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectMapComponent } from './project-map.component';
+import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule } from '@angular/material';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { ServerService } from '../../services/server.service';
+import { ProjectService } from '../../services/project.service';
+import { SettingsService } from '../../services/settings.service';
+import { NodeService } from '../../services/node.service';
+import { LinkService } from '../../services/link.service';
+import { DrawingService } from '../../services/drawing.service';
+import { ProgressService } from '../../common/progress/progress.service';
+import { ProjectWebServiceHandler } from '../../handlers/project-web-service-handler';
+import { MapChangeDetectorRef } from '../../cartography/services/map-change-detector-ref';
+import { NodeWidget } from '../../cartography/widgets/node';
+import { MapNodeToNodeConverter } from '../../cartography/converters/map/map-node-to-node-converter';
+import { MapPortToPortConverter } from '../../cartography/converters/map/map-port-to-port-converter';
+import { NodesDataSource } from '../../cartography/datasources/nodes-datasource';
+import { LinksDataSource } from '../../cartography/datasources/links-datasource';
+import { NodesEventSource } from '../../cartography/events/nodes-event-source';
+import { DrawingsEventSource } from '../../cartography/events/drawings-event-source';
+import { LinksEventSource } from '../../cartography/events/links-event-source';
+import { MapDrawingToSvgConverter } from '../../cartography/converters/map/map-drawing-to-svg-converter';
+import { DrawingsDataSource } from '../../cartography/datasources/drawings-datasource';
+import { CommonModule } from '@angular/common';
+import { ANGULAR_MAP_DECLARATIONS } from '../../cartography/angular-map.imports';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { SymbolService } from '../../services/symbol.service';
+import { MockedSettingsService } from '../../services/settings.service.spec';
+import { MockedServerService } from '../../services/server.service.spec';
+import { MockedProjectService } from '../../services/project.service.spec';
+import { Observable } from 'rxjs/Rx';
+import { Drawing } from '../../cartography/models/drawing';
+import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
+import { Project } from '../../models/project';
+import { of } from 'rxjs';
+import { DrawingElement } from '../../cartography/models/drawings/drawing-element';
+import { RectElement } from '../../cartography/models/drawings/rect-element';
+import { MapDrawing } from '../../cartography/models/map/map-drawing';
+import { HttpServer } from '../../services/http-server.service';
+import { Server } from '../../models/server';
+import { ResizedDataEvent } from '../../cartography/events/event-source';
+import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter';
+
+export class MockedProgressService {
+ public activate() {}
+}
+
+export class MockedDrawingService {
+ public drawing = {} as Drawing;
+ constructor() {}
+
+ add(_server: Server, _project_id:string, _x: number, _y:number, _svg: string) {
+ return of(this.drawing);
+ }
+
+ updatePosition(_server: Server, _drawing: Drawing, _x: number, _y: number){
+ return of(this.drawing);
+ }
+
+ updateSizeAndPosition(_server: Server, _drawing: Drawing, _x: number, _y: number, _svg: string){
+ return of(this.drawing);
+ }
+
+ update(_server: Server, _drawing: Drawing){
+ return of(this.drawing);
+ }
+
+ delete(_server: Server, _drawing: Drawing){
+ return of(this.drawing);
+ }
+}
+
+export class MockedDrawingsDataSource {
+ add() {}
+
+ get() { return of({})}
+
+ update() { return of({})}
+}
describe('ProjectMapComponent', () => {
let component: ProjectMapComponent;
let fixture: ComponentFixture;
+ let drawingService = new MockedDrawingService;
+ let drawingsDataSource = new MockedDrawingsDataSource;
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ ProjectMapComponent ],
+ imports: [
+ MatIconModule,
+ MatToolbarModule,
+ MatMenuModule,
+ MatCheckboxModule,
+ CommonModule,
+ NoopAnimationsModule
+ ],
+ providers: [
+ { provide: ActivatedRoute },
+ { provide: ServerService, useClass: MockedServerService },
+ { provide: ProjectService, useClass: MockedProjectService },
+ { provide: SymbolService },
+ { provide: NodeService },
+ { provide: LinkService },
+ { provide: DrawingService, useValue: drawingService},
+ { provide: ProgressService, useClass: MockedProgressService },
+ { provide: ProjectWebServiceHandler },
+ { provide: MapChangeDetectorRef },
+ { provide: NodeWidget },
+ { provide: MapNodeToNodeConverter },
+ { provide: MapPortToPortConverter },
+ { provide: NodesDataSource },
+ { provide: LinksDataSource },
+ { provide: DrawingsDataSource, useValue: drawingsDataSource},
+ { provide: NodesEventSource },
+ { provide: DrawingsEventSource },
+ { provide: LinksEventSource },
+ { provide: MapDrawingToSvgConverter, useValue: {
+ convert: () => { return ''}
+ } },
+ { provide: SettingsService, useClass: MockedSettingsService },
+ { provide: MapLabelToLabelConverter}
+ ],
+ declarations: [
+ ProjectMapComponent,
+ D3MapComponent,
+ ...ANGULAR_MAP_DECLARATIONS
+ ],
+ schemas: [
+ NO_ERRORS_SCHEMA
+ ]
})
.compileComponents();
}));
@@ -16,7 +137,66 @@ describe('ProjectMapComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ProjectMapComponent);
component = fixture.componentInstance;
- fixture.detectChanges();
});
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should update position on resizing event', () => {
+ let drawingElement: DrawingElement;
+ let rect = new RectElement();
+ rect.fill = "#ffffff";
+ rect.fill_opacity = 1.0;
+ rect.stroke = "#000000";
+ rect.stroke_width = 2;
+ rect.width = 200;
+ rect.height = 100;
+ drawingElement = rect;
+ let mapDrawing = new MapDrawing;
+ mapDrawing.id = '1';
+ mapDrawing.projectId = '1';
+ mapDrawing.rotation = 1;
+ mapDrawing.svg = '';
+ mapDrawing.x = 0;
+ mapDrawing.y = 0;
+ mapDrawing.z = 0;
+ mapDrawing.element = drawingElement;
+ let event = new ResizedDataEvent(mapDrawing,0,0,100,100);
+ spyOn(drawingService, 'updateSizeAndPosition').and.returnValue( Observable.of({}));
+
+ component.onDrawingResized(event);
+
+ expect(drawingService.updateSizeAndPosition).toHaveBeenCalled();
+ });
+
+ it('should add selected drawing', () => {
+ component.mapChild = { context: {} } as D3MapComponent;
+ component.project = { project_id: "1" } as Project;
+ component.mapChild.context.getZeroZeroTransformationPoint = jasmine.createSpy('HTML element').and.callFake(() => { return {x: 0, y: 0}});
+ var dummyElement = document.createElement('map');
+ document.getElementsByClassName = jasmine.createSpy('HTML element').and.callFake(() => { return ([dummyElement])});
+ spyOn(drawingsDataSource, 'add');
+
+ component.addDrawing("rectangle");
+ dummyElement.click();
+
+ expect(drawingsDataSource.add).toHaveBeenCalled();
+ });
+
+ it('should hide draw tools when hide menu is called', () => {
+ var dummyElement = document.createElement('map');
+ document.getElementsByClassName = jasmine.createSpy('HTML element').and.callFake(() => { return ([dummyElement])});
+ spyOn(component, 'resetDrawToolChoice');
+
+ component.hideMenu();
+
+ expect(component.resetDrawToolChoice).toHaveBeenCalled();
+ });
+
+ it('should return drawing mock of correct type'), () => {
+ let mock = component.getDrawingMock('rectangle');
+
+ expect(mock instanceof RectElement).toBe(true);
+ }
});
diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts
index ced0364d..8e7d5aad 100644
--- a/src/app/components/project-map/project-map.component.ts
+++ b/src/app/components/project-map/project-map.component.ts
@@ -26,7 +26,7 @@ import { MapChangeDetectorRef } from '../../cartography/services/map-change-dete
import { NodeContextMenu } from '../../cartography/events/nodes';
import { MapLinkCreated } from '../../cartography/events/links';
import { NodeWidget } from '../../cartography/widgets/node';
-import { DraggedDataEvent } from '../../cartography/events/event-source';
+import { DraggedDataEvent, ResizedDataEvent, TextEditedDataEvent } from '../../cartography/events/event-source';
import { DrawingService } from '../../services/drawing.service';
import { MapNodeToNodeConverter } from '../../cartography/converters/map/map-node-to-node-converter';
import { NodesEventSource } from '../../cartography/events/nodes-event-source';
@@ -35,10 +35,18 @@ import { MapNode } from '../../cartography/models/map/map-node';
import { LinksEventSource } from '../../cartography/events/links-event-source';
import { MapDrawing } from '../../cartography/models/map/map-drawing';
import { MapPortToPortConverter } from '../../cartography/converters/map/map-port-to-port-converter';
+import { MapDrawingToSvgConverter } from '../../cartography/converters/map/map-drawing-to-svg-converter';
+import { DrawingElement } from '../../cartography/models/drawings/drawing-element';
+import { RectElement } from '../../cartography/models/drawings/rect-element';
+import { EllipseElement } from '../../cartography/models/drawings/ellipse-element';
+import { LineElement } from '../../cartography/models/drawings/line-element';
import { SettingsService, Settings } from '../../services/settings.service';
import { MapLabel } from '../../cartography/models/map/map-label';
+import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
import { MapLinkNode } from '../../cartography/models/map/map-link-node';
+import { TextElement } from '../../cartography/models/drawings/text-element';
import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter';
+import { select } from 'd3-selection';
@Component({
@@ -54,20 +62,32 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
public symbols: Symbol[] = [];
public project: Project;
public server: Server;
-
+ private drawListener: Function;
private ws: Subject;
tools = {
'selection': true,
'moving': false,
- 'draw_link': false
+ 'draw_link': false,
+ 'text_editing': true
};
protected settings: Settings;
+ protected drawTools = {
+ 'isRectangleChosen': false,
+ 'isEllipseChosen': false,
+ 'isLineChosen': false,
+ 'visibility': false,
+ 'isAddingTextChosen': false
+ };
+
+ protected selectedDrawing: string;
+
private inReadOnlyMode = false;
@ViewChild(NodeContextMenuComponent) nodeContextMenu: NodeContextMenuComponent;
+ @ViewChild(D3MapComponent) mapChild: D3MapComponent;
private subscriptions: Subscription[] = [];
@@ -77,7 +97,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private projectService: ProjectService,
private nodeService: NodeService,
private linkService: LinkService,
- private drawingService: DrawingService,
+ public drawingService: DrawingService,
private progressService: ProgressService,
private projectWebServiceHandler: ProjectWebServiceHandler,
private mapChangeDetectorRef: MapChangeDetectorRef,
@@ -90,6 +110,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private nodesEventSource: NodesEventSource,
private drawingsEventSource: DrawingsEventSource,
private linksEventSource: LinksEventSource,
+ private mapDrawingToSvgConverter: MapDrawingToSvgConverter,
private settingsService: SettingsService,
private mapLabelToLabel: MapLabelToLabelConverter
) {}
@@ -169,6 +190,14 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.drawingsEventSource.dragged.subscribe((evt) => this.onDrawingDragged(evt))
);
+ this.subscriptions.push(
+ this.drawingsEventSource.resized.subscribe((evt) => this.onDrawingResized(evt))
+ );
+
+ this.subscriptions.push(
+ this.drawingsEventSource.textEdited.subscribe((evt) => this.onTextEdited(evt))
+ );
+
this.subscriptions.push(
this.linksEventSource.created.subscribe((evt) => this.onLinkCreated(evt))
);
@@ -312,6 +341,32 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
});
}
+ public onDrawingResized(resizedEvent: ResizedDataEvent) {
+ const drawing = this.drawingsDataSource.get(resizedEvent.datum.id);
+ let svgString = this.mapDrawingToSvgConverter.convert(resizedEvent.datum);
+
+ this.drawingService
+ .updateSizeAndPosition(this.server, drawing, resizedEvent.x, resizedEvent.y, svgString)
+ .subscribe((serverDrawing: Drawing) => {
+ this.drawingsDataSource.update(serverDrawing);
+ });
+ }
+
+ public onTextEdited(evt: TextEditedDataEvent){
+ let mapDrawing: MapDrawing = new MapDrawing();
+ mapDrawing.element = evt.textElement;
+ (mapDrawing.element as TextElement).text = evt.editedText;
+ let svgString = this.mapDrawingToSvgConverter.convert(mapDrawing);
+
+ let drawing = this.drawingsDataSource.get(evt.textDrawingId);
+
+ this.drawingService
+ .updateText(this.server, drawing, svgString)
+ .subscribe((serverDrawing: Drawing) => {
+ this.drawingsDataSource.update(serverDrawing);
+ });
+ }
+
public set readonly(value) {
this.inReadOnlyMode = value;
if (value) {
@@ -340,6 +395,194 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.project.show_interface_labels = enabled;
}
+ public addDrawing(selectedObject: string) {
+ if (selectedObject === this.selectedDrawing){
+ var map = document.getElementsByClassName('map')[0];
+ map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject);
+ this.resetDrawToolChoice();
+ return;
+ }
+
+ switch (selectedObject) {
+ case "rectangle":
+ this.drawTools.isAddingTextChosen = false;
+ this.drawTools.isEllipseChosen = false;
+ this.drawTools.isRectangleChosen = !this.drawTools.isRectangleChosen;
+ this.drawTools.isLineChosen = false;
+ break;
+ case "ellipse":
+ this.drawTools.isAddingTextChosen = false;
+ this.drawTools.isEllipseChosen = !this.drawTools.isEllipseChosen;
+ this.drawTools.isRectangleChosen = false;
+ this.drawTools.isLineChosen = false;
+ break;
+ case "line":
+ this.drawTools.isAddingTextChosen = false;
+ this.drawTools.isEllipseChosen = false;
+ this.drawTools.isRectangleChosen = false;
+ this.drawTools.isLineChosen = !this.drawTools.isLineChosen;
+ break;
+ }
+
+ this.selectedDrawing = selectedObject;
+ var map = document.getElementsByClassName('map')[0];
+ let mapDrawing: MapDrawing = this.getDrawingMock(selectedObject);
+
+ let listener = (event: MouseEvent) => {
+ let x = event.clientX - this.mapChild.context.getZeroZeroTransformationPoint().x;
+ let y = event.clientY - this.mapChild.context.getZeroZeroTransformationPoint().y;
+ let svg = this.mapDrawingToSvgConverter.convert(mapDrawing);
+
+ this.drawingService
+ .add(this.server, this.project.project_id, x, y, svg)
+ .subscribe((serverDrawing: Drawing) => {
+ this.drawingsDataSource.add(serverDrawing);
+ });
+ this.resetDrawToolChoice();
+ }
+
+ map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject);
+ this.drawListener = listener;
+ map.addEventListener('click', this.drawListener as EventListenerOrEventListenerObject, {once : true});
+ }
+
+ public resetDrawToolChoice(){
+ this.drawTools.isRectangleChosen = false;
+ this.drawTools.isEllipseChosen = false;
+ this.drawTools.isLineChosen = false;
+ this.drawTools.isAddingTextChosen = false;
+ this.selectedDrawing = "";
+ }
+
+ public hideMenu(){
+ var map = document.getElementsByClassName('map')[0];
+ map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject);
+ this.resetDrawToolChoice();
+ this.drawTools.visibility = false;
+ }
+
+ public showMenu(){
+ setTimeout(() => {
+ this.drawTools.visibility = true;
+ },
+ 200);
+ }
+
+ public getDrawingMock(objectType: string, text?: string): MapDrawing {
+ let drawingElement: DrawingElement;
+
+ switch (objectType) {
+ case "rectangle":
+ let rectElement = new RectElement();
+ rectElement.fill = "#ffffff";
+ rectElement.fill_opacity = 1.0;
+ rectElement.stroke = "#000000";
+ rectElement.stroke_width = 2;
+ rectElement.width = 200;
+ rectElement.height = 100;
+ drawingElement = rectElement;
+ break;
+ case "ellipse":
+ let ellipseElement = new EllipseElement();
+ ellipseElement.fill = "#ffffff";
+ ellipseElement.fill_opacity = 1.0;
+ ellipseElement.stroke = "#000000";
+ ellipseElement.stroke_width = 2;
+ ellipseElement.cx = 100;
+ ellipseElement.cy = 100;
+ ellipseElement.rx = 100;
+ ellipseElement.ry = 100;
+ ellipseElement.width = 200;
+ ellipseElement.height = 200;
+ drawingElement = ellipseElement;
+ break;
+ case "line":
+ let lineElement = new LineElement();
+ lineElement.stroke = "#000000";
+ lineElement.stroke_width = 2;
+ lineElement.x1 = 0;
+ lineElement.x2 = 200;
+ lineElement.y1 = 0;
+ lineElement.y2 = 0;
+ lineElement.width = 100;
+ lineElement.height = 0;
+ drawingElement = lineElement;
+ break;
+ case "text":
+ let textElement = new TextElement();
+ textElement.height = 100; //should be calculated
+ textElement.width = 100;
+ textElement.text = text;
+ textElement.fill = "#000000";
+ textElement.fill_opacity = 0;
+ textElement.font_family = "Noto Sans";
+ textElement.font_size = 11;
+ textElement.font_weight = "bold";
+ drawingElement = textElement;
+ break;
+ }
+
+ let mapDrawing = new MapDrawing();
+ mapDrawing.element = drawingElement;
+ return mapDrawing;
+ }
+
+ public addText(){
+ if (!this.drawTools.isAddingTextChosen){
+ this.resetDrawToolChoice();
+ this.drawTools.isAddingTextChosen = true;
+ var map = document.getElementsByClassName('map')[0];
+
+ let addTextListener = (event: MouseEvent) => {
+
+ var div = document.createElement('div');
+ div.style.width = "fit-content";
+ div.style.left = event.clientX.toString() + 'px';
+ div.style.top = (event.clientY - 10).toString() + 'px';
+ div.style.position = "absolute";
+ div.style.zIndex = "99";
+
+ div.style.fontFamily = "Noto Sans";
+ div.style.fontSize = "11pt";
+ div.style.fontWeight = "bold";
+ div.style.color = "#000000";
+
+ div.setAttribute("contenteditable", "true");
+
+ document.body.appendChild(div);
+ div.innerText = "";
+ div.focus();
+ document.body.style.cursor = "text";
+
+ div.addEventListener("focusout", () => {
+ let savedText = div.innerText;
+
+ let drawing = this.getDrawingMock("text", savedText);
+ (drawing.element as TextElement).text = savedText;
+ let svgText = this.mapDrawingToSvgConverter.convert(drawing);
+
+ this.drawingService
+ .add(this.server, this.project.project_id, event.clientX - this.mapChild.context.getZeroZeroTransformationPoint().x, event.clientY - this.mapChild.context.getZeroZeroTransformationPoint().y, svgText)
+ .subscribe((serverDrawing: Drawing) => {
+ document.body.style.cursor = "default";
+ div.remove();
+ this.drawingsDataSource.add(serverDrawing);
+ });
+ });
+
+ this.resetDrawToolChoice();
+ }
+
+ map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject);
+ this.drawListener = addTextListener;
+ map.addEventListener('click', this.drawListener as EventListenerOrEventListenerObject, {once : true});
+ } else {
+ this.resetDrawToolChoice();
+ var map = document.getElementsByClassName('map')[0];
+ map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject);
+ }
+ }
+
public ngOnDestroy() {
this.drawingsDataSource.clear();
this.nodesDataSource.clear();
diff --git a/src/app/services/drawing.service.spec.ts b/src/app/services/drawing.service.spec.ts
index 363e4bb5..1017acad 100644
--- a/src/app/services/drawing.service.spec.ts
+++ b/src/app/services/drawing.service.spec.ts
@@ -58,6 +58,24 @@ describe('DrawingService', () => {
});
}));
+ it('should update size and position of drawing', inject([DrawingService], (service: DrawingService) => {
+ const drawing = new Drawing();
+ drawing.project_id = "myproject";
+ drawing.drawing_id = "id";
+ let svgSample = ""
+
+ service.updateSizeAndPosition(server, drawing, 100, 100, svgSample).subscribe();
+
+ const req = httpTestingController.expectOne(
+ 'http://127.0.0.1:3080/v2/projects/myproject/drawings/id');
+ expect(req.request.method).toEqual("PUT");
+ expect(req.request.body).toEqual({
+ 'x': 100,
+ 'y': 100,
+ 'svg': svgSample
+ });
+ }));
+
it('should update drawing', inject([DrawingService], (service: DrawingService) => {
const drawing = new Drawing();
drawing.project_id = "myproject";
diff --git a/src/app/services/drawing.service.ts b/src/app/services/drawing.service.ts
index 31ef46a7..851711cc 100644
--- a/src/app/services/drawing.service.ts
+++ b/src/app/services/drawing.service.ts
@@ -12,6 +12,15 @@ export class DrawingService {
constructor(private httpServer: HttpServer) { }
+ add(server: Server, project_id:string, x: number, y:number, svg: string) {
+ return this.httpServer
+ .post(server, `/projects/${project_id}/drawings`, {
+ 'svg': svg,
+ 'x': x,
+ 'y': y
+ });
+ }
+
updatePosition(server: Server, drawing: Drawing, x: number, y: number): Observable {
return this.httpServer
.put(server, `/projects/${drawing.project_id}/drawings/${drawing.drawing_id}`, {
@@ -20,6 +29,25 @@ export class DrawingService {
});
}
+ updateSizeAndPosition(server: Server, drawing: Drawing, x: number, y: number, svg: string): Observable {
+ return this.httpServer
+ .put(server, `/projects/${drawing.project_id}/drawings/${drawing.drawing_id}`, {
+ 'svg': svg,
+ 'x': x,
+ 'y': y
+ })
+ }
+
+ updateText(server: Server, drawing: Drawing, svg: string): Observable {
+ return this.httpServer
+ .put(server, `/projects/${drawing.project_id}/drawings/${drawing.drawing_id}`, {
+ 'svg': svg,
+ 'x': drawing.x,
+ 'y': drawing.y,
+ 'z': drawing.z
+ });
+ }
+
update(server: Server, drawing: Drawing): Observable {
return this.httpServer
.put(server, `/projects/${drawing.project_id}/drawings/${drawing.drawing_id}`, {