mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-01 15:00:49 +00:00
Merge pull request #832 from GNS3/Drag-&-drop-to-add-new-nodes
Initial implementation of drag & drop to add new node
This commit is contained in:
commit
9595787e59
@ -181,6 +181,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
mergeMap((project: Project) => {
|
mergeMap((project: Project) => {
|
||||||
this.project = project;
|
this.project = project;
|
||||||
|
this.projectService.open(this.server, this.project.project_id);
|
||||||
this.title.setTitle(this.project.name);
|
this.title.setTitle(this.project.name);
|
||||||
|
|
||||||
if (this.mapSettingsService.interfaceLabels.has(project.project_id)) {
|
if (this.mapSettingsService.interfaceLabels.has(project.project_id)) {
|
||||||
|
@ -1 +1,63 @@
|
|||||||
<button class="addNode" matTooltip="Add a node" mat-icon-button (click)="listTemplatesModal()"><mat-icon>add_to_queue</mat-icon></button>
|
<button class="addNode" matTooltip="Add a node" mat-icon-button [matMenuTriggerFor]="mainMenu">
|
||||||
|
<mat-icon>add_to_queue</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-menu #mainMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="openDialog()">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
<span>Open dialog to configure</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-form-field (click)="$event.stopPropagation()" class="form-field" floatPlaceholder="never">
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
placeholder="Search by name"
|
||||||
|
(keyup)="filterTemplates($event)"
|
||||||
|
[(ngModel)]="searchText"
|
||||||
|
[ngModelOptions]="{standalone: true}">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field (click)="$event.stopPropagation()" class="form-field">
|
||||||
|
<mat-select
|
||||||
|
[ngModelOptions]="{standalone: true}"
|
||||||
|
placeholder="Filter templates by type"
|
||||||
|
(selectionChange)="filterTemplates($event)"
|
||||||
|
[(ngModel)]="selectedType">
|
||||||
|
<mat-option *ngFor="let type of templateTypes" [value]="type">
|
||||||
|
{{type}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="menu">
|
||||||
|
<div class="templateList">
|
||||||
|
<mat-list-item *ngFor="let template of filteredTemplates;let i = index">
|
||||||
|
<span *ngIf="i%4===0" class="templateRow">
|
||||||
|
<span class="templateIcon">
|
||||||
|
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, templates[i])">
|
||||||
|
<img class="image" [src]="getImageSourceForTemplate(templates[i])"/>
|
||||||
|
</div>
|
||||||
|
<div class="templateText">{{templates[i].name}}</div>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="templates[i+1]" class="templateIcon">
|
||||||
|
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, templates[i+1])">
|
||||||
|
<img class="image" [src]="getImageSourceForTemplate(templates[i+1])"/>
|
||||||
|
</div>
|
||||||
|
<div class="templateText">{{templates[i+1].name}}</div>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="templates[i+2]" class="templateIcon">
|
||||||
|
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, templates[i+2])">
|
||||||
|
<img class="image" [src]="getImageSourceForTemplate(templates[i+2])"/>
|
||||||
|
</div>
|
||||||
|
<div class="templateText">{{templates[i+2].name}}</div>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="templates[i+3]" class="templateIcon">
|
||||||
|
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, templates[i+3])">
|
||||||
|
<img class="image" [src]="getImageSourceForTemplate(templates[i+3])"/>
|
||||||
|
</div>
|
||||||
|
<div class="templateText">{{templates[i+3].name}}</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-menu>
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
::ng-deep .mat-menu-panel {
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-color: darkgrey #263238;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: darkgrey;
|
||||||
|
outline: 1px solid #263238;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
width: 90%;
|
||||||
|
margin-left: 5%;
|
||||||
|
margin-right: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
filter: invert(0);
|
||||||
|
--webkit-filter: invert(0)!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.templateList {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.templateRow {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.templateText {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.templateIcon {
|
||||||
|
width: 80px!important;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
@ -5,6 +5,9 @@ import { TemplateListDialogComponent, NodeAddedEvent } from './template-list-dia
|
|||||||
import { Server } from '../../models/server';
|
import { Server } from '../../models/server';
|
||||||
import { Template } from '../../models/template';
|
import { Template } from '../../models/template';
|
||||||
import { Project } from '../../models/project';
|
import { Project } from '../../models/project';
|
||||||
|
import { TemplateService } from '../../services/template.service';
|
||||||
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
|
import { SymbolService } from '../../services/symbol.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-template',
|
selector: 'app-template',
|
||||||
@ -16,11 +19,72 @@ export class TemplateComponent implements OnInit {
|
|||||||
@Input() project: Project;
|
@Input() project: Project;
|
||||||
@Output() onNodeCreation = new EventEmitter<any>();
|
@Output() onNodeCreation = new EventEmitter<any>();
|
||||||
|
|
||||||
constructor(private dialog: MatDialog) {}
|
templates: Template[] = [];
|
||||||
|
filteredTemplates: Template[] = [];
|
||||||
|
searchText: string = '';
|
||||||
|
templateTypes: string[] = ['all', 'cloud', 'ethernet_hub', 'ethernet_switch', 'docker', 'dynamips', 'vpcs', 'traceng', 'virtualbox', 'vmware', 'iou', 'qemu'];
|
||||||
|
selectedType: string;
|
||||||
|
|
||||||
ngOnInit() {}
|
movementX: number;
|
||||||
|
movementY: number;
|
||||||
|
|
||||||
listTemplatesModal() {
|
startX: number;
|
||||||
|
startY: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private templateService: TemplateService,
|
||||||
|
private scaleService: MapScaleService,
|
||||||
|
private symbolService: SymbolService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.templateService.list(this.server).subscribe((listOfTemplates: Template[]) => {
|
||||||
|
this.filteredTemplates = listOfTemplates;
|
||||||
|
this.templates = listOfTemplates;
|
||||||
|
});
|
||||||
|
this.symbolService.list(this.server);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterTemplates(event) {
|
||||||
|
let temporaryTemplates = this.templates.filter(item => {
|
||||||
|
return item.name.toLowerCase().includes(this.searchText.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.selectedType === 'all') {
|
||||||
|
this.filteredTemplates = temporaryTemplates;
|
||||||
|
} else {
|
||||||
|
this.filteredTemplates = temporaryTemplates.filter(t => t.template_type === this.selectedType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dragStart(ev) {
|
||||||
|
let elemRect = (event.target as HTMLElement).getBoundingClientRect();
|
||||||
|
|
||||||
|
this.startX = (event as MouseEvent).clientX;
|
||||||
|
this.startY = (event as MouseEvent).clientY;
|
||||||
|
|
||||||
|
this.movementY = elemRect.top - (event as MouseEvent).clientY;
|
||||||
|
this.movementX = elemRect.left - (event as MouseEvent).clientX;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragEnd(ev, template: Template) {
|
||||||
|
this.symbolService.raw(this.server, template.symbol.substring(1)).subscribe((symbolSvg: string) => {
|
||||||
|
let width = +symbolSvg.split("width=\"")[1].split("\"")[0] ? +symbolSvg.split("width=\"")[1].split("\"")[0] : 0;
|
||||||
|
let scale = this.scaleService.getScale();
|
||||||
|
|
||||||
|
let nodeAddedEvent: NodeAddedEvent = {
|
||||||
|
template: template,
|
||||||
|
server: 'local',
|
||||||
|
numberOfNodes: 1,
|
||||||
|
x: (this.startX + ev.x - this.project.scene_width/2 - (width/2)) * scale + window.scrollX ,
|
||||||
|
y: (this.startY + ev.y - this.project.scene_height/2) * scale + window.scrollY
|
||||||
|
};
|
||||||
|
this.onNodeCreation.emit(nodeAddedEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openDialog() {
|
||||||
const dialogRef = this.dialog.open(TemplateListDialogComponent, {
|
const dialogRef = this.dialog.open(TemplateListDialogComponent, {
|
||||||
width: '600px',
|
width: '600px',
|
||||||
data: {
|
data: {
|
||||||
@ -37,4 +101,8 @@ export class TemplateComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getImageSourceForTemplate(template: Template) {
|
||||||
|
return `http://${this.server.host}:${this.server.port}/v2${template.symbol.substring(1)}/raw`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,10 @@ export class SymbolService {
|
|||||||
return this.symbols.getValue().find((symbol: Symbol) => symbol.symbol_id === symbol_id);
|
return this.symbols.getValue().find((symbol: Symbol) => symbol.symbol_id === symbol_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getByFilename(symbol_filename: string) {
|
||||||
|
return this.symbols.getValue().find((symbol: Symbol) => symbol.filename === symbol_filename);
|
||||||
|
}
|
||||||
|
|
||||||
add(server: Server, symbolName: string, symbol: string) {
|
add(server: Server, symbolName: string, symbol: string) {
|
||||||
this.cache = null;
|
this.cache = null;
|
||||||
return this.httpServer.post(server, `/symbols/${symbolName}/raw`, symbol)
|
return this.httpServer.post(server, `/symbols/${symbolName}/raw`, symbol)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user