mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-01-25 21:59:17 +00:00
Merge pull request #471 from GNS3/Editing-node-labels-on-double-click
Editing interface labels on double click
This commit is contained in:
commit
649bd0f245
@ -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: 499 B After Width: | Height: | Size: 517 B |
@ -185,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'])
|
||||||
|
@ -6,10 +6,17 @@ 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 { 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({
|
||||||
@ -19,7 +26,12 @@ describe('TemporaryTextElementComponent', () => {
|
|||||||
{ 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: 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();
|
||||||
|
@ -7,6 +7,19 @@ 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 { 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',
|
||||||
@ -16,6 +29,7 @@ import { MapScaleService } from '../../../services/mapScale.service';
|
|||||||
export class TextEditorComponent implements OnInit, OnDestroy {
|
export class TextEditorComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild('temporaryTextElement', {static: false}) 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';
|
||||||
@ -23,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;
|
||||||
@ -34,7 +50,12 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
private toolsService: ToolsService,
|
private toolsService: ToolsService,
|
||||||
private context: Context,
|
private context: Context,
|
||||||
private renderer: Renderer2,
|
private renderer: Renderer2,
|
||||||
private mapScaleService: MapScaleService
|
private mapScaleService: MapScaleService,
|
||||||
|
private linkService: LinkService,
|
||||||
|
private linksDataSource: LinksDataSource,
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private selectionManager: SelectionManager,
|
||||||
|
private fontFixer: FontFixer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -42,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() {
|
||||||
@ -80,7 +102,74 @@ 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
|
||||||
|
@ -11,6 +11,7 @@ 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 { LabelContextMenu } from '../events/event-source';
|
||||||
|
import { TextElement } from '../models/drawings/text-element';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LabelWidget implements Widget {
|
export class LabelWidget implements Widget {
|
||||||
@ -91,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')
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user