You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2020/06/07 21:05:56 UTC

[incubator-streampipes] 02/02: [STREAMPIPES-145] Add dialog template

This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-145
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 6e85d52a5b714b71814219fd07858e6f000ddc5d
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Sun Jun 7 23:05:40 2020 +0200

    [STREAMPIPES-145] Add dialog template
---
 .../pipeline-assembly.component.html               |  3 -
 .../components/pipeline/pipeline.component.ts      | 51 ++++++++----
 .../dialog/customize/customize.component.css       |  0
 .../dialog/customize/customize.component.html      | 18 ++++
 .../customize/customize.component.ts}              | 27 +++---
 .../panel/dialog-ref.ts}                           | 33 +++++---
 .../dialog/panel/panel-dialog.component.html       | 23 +++++
 .../panel/panel-dialog.component.scss}             | 26 +++---
 .../dialog/panel/panel-dialog.component.ts         | 67 +++++++++++++++
 .../editor-v2/dialog/panel/panel-dialog.service.ts | 97 ++++++++++++++++++++++
 ui/src/app/editor-v2/editor.module.ts              | 13 ++-
 ui/src/app/editor-v2/model/editor.model.ts         | 12 ++-
 ui/src/app/editor-v2/services/editor.service.ts    | 28 ++++++-
 ui/src/app/editor-v2/services/jsplumb.service.ts   | 19 ++++-
 .../editor-v2/services/object-provider.service.ts  |  6 +-
 ui/src/app/services/rest-api.service.ts            |  8 --
 16 files changed, 364 insertions(+), 67 deletions(-)

diff --git a/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html b/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html
index 67dd075..5680b95 100644
--- a/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html
+++ b/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html
@@ -90,9 +90,6 @@
             <button mat-button matTooltip="Clear Assembly Area" [matTooltipPosition]="'above'"
                     class="md-icon-button" (click)="showClearAssemblyConfirmDialog($event)">
                 <i class="material-icons">clear</i>
-                <!--                <md-tooltip md-direction="top">-->
-                <!--                    Clear Assembly Area-->
-                <!--                </md-tooltip>-->
             </button>
         </div>
     </div>
diff --git a/ui/src/app/editor-v2/components/pipeline/pipeline.component.ts b/ui/src/app/editor-v2/components/pipeline/pipeline.component.ts
index 2b56f80..be53fe5 100644
--- a/ui/src/app/editor-v2/components/pipeline/pipeline.component.ts
+++ b/ui/src/app/editor-v2/components/pipeline/pipeline.component.ts
@@ -24,18 +24,22 @@ import {JsplumbService} from "../../services/jsplumb.service";
 import {PipelineEditorService} from "../../services/pipeline-editor.service";
 import {JsplumbBridge} from "../../services/jsplumb-bridge.service";
 import {ShepherdService} from "../../../services/tour/shepherd.service";
-import {Component, Input, OnInit, Pipe} from "@angular/core";
+import {Component, InjectionToken, Input, OnInit, Pipe} from "@angular/core";
 import {
-  InvocablePipelineElementUnion,
+  InvocablePipelineElementUnion, PIPELINE_ELEMENT_TOKEN,
   PipelineElementConfig,
   PipelineElementUnion
 } from "../../model/editor.model";
 import {
+  CustomOutputStrategy,
   DataProcessorInvocation,
   Pipeline,
   SpDataStream
 } from "../../../core-model/gen/streampipes-model";
 import {ObjectProvider} from "../../services/object-provider.service";
+import {PanelDialogService} from "../../dialog/panel/panel-dialog.service";
+import {CustomizeComponent} from "../../dialog/customize/customize.component";
+import {DialogRef} from "../../dialog/panel/dialog-ref";
 
 @Component({
   selector: 'pipeline',
@@ -87,7 +91,8 @@ export class PipelineComponent implements OnInit {
               // TransitionService,
               private ShepherdService: ShepherdService,
               private PipelineValidationService: PipelineValidationService,
-              private RestApi: RestApi) {
+              private RestApi: RestApi,
+              private PanelDialogService: PanelDialogService) {
     this.plumbReady = false;
     this.currentMouseOverElement = "";
     this.currentPipelineModel = new Pipeline();
@@ -281,26 +286,19 @@ export class PipelineComponent implements OnInit {
       var pe = this.ObjectProvider.findElement(info.target.id, this.rawPipelineModel);
       if (pe.settings.openCustomize) {
         this.currentPipelineModel = this.ObjectProvider.makePipeline(this.rawPipelineModel);
-        console.log(this.currentPipelineModel);
         pe.settings.loadingStatus = true;
         this.ObjectProvider.updatePipeline(this.currentPipelineModel)
-            .then(msg => {
-              let data = msg.data;
+            .subscribe(pipelineModificationMessage => {
               pe.settings.loadingStatus = false;
-              if (data.success) {
+              if (pipelineModificationMessage.success) {
                 info.targetEndpoint.setType("token");
                 this.validatePipeline();
-                this.modifyPipeline(data.pipelineModifications);
+                this.modifyPipeline(pipelineModificationMessage.pipelineModifications);
                 var sourceEndpoint = this.JsplumbBridge.selectEndpoints({element: info.targetEndpoint.elementId});
                 if (this.PipelineEditorService.isFullyConnected(pe)) {
                   let payload = pe.payload as InvocablePipelineElementUnion;
                   if ((payload.staticProperties && payload.staticProperties.length > 0) || this.isCustomOutput(pe)) {
-                    this.EditorDialogManager.showCustomizeDialog($("#" +pe.payload.dom), sourceEndpoint, pe.payload, false)
-                        .then(() => {
-                          this.JsplumbService.activateEndpoint(pe.payload.dom, !payload.uncompleted);
-                        }, () => {
-                          this.JsplumbService.activateEndpoint(pe.payload.dom, !payload.uncompleted);
-                        });
+                    this.showCustomizeDialog(pe);
                   } else {
                     //this.$rootScope.$broadcast("SepaElementConfigured", pe.payload.DOM);
                     (pe.payload as InvocablePipelineElementUnion).configured = true;
@@ -308,7 +306,7 @@ export class PipelineComponent implements OnInit {
                 }
               } else {
                 this.JsplumbBridge.detach(info.connection);
-                this.EditorDialogManager.showMatchingErrorDialog(data);
+                this.EditorDialogManager.showMatchingErrorDialog(pipelineModificationMessage);
               }
             });
       }
@@ -338,7 +336,7 @@ export class PipelineComponent implements OnInit {
   isCustomOutput(pe) {
     var custom = false;
     angular.forEach(pe.payload.outputStrategies, strategy => {
-      if (strategy.type == 'org.apache.streampipes.model.output.CustomOutputStrategy') {
+      if (strategy instanceof CustomOutputStrategy) {
         custom = true;
       }
     });
@@ -353,5 +351,26 @@ export class PipelineComponent implements OnInit {
     });
   }
 
+  showCustomizeDialog(pipelineElement: PipelineElementConfig) {
+    const inputMap = {};
+    inputMap["pipelineElement"] = pipelineElement;
+
+    const dialogRef = this.PanelDialogService.open(CustomizeComponent, {
+      width: "400px",
+      title: "Customize " + pipelineElement.payload.name
+    }, inputMap);
+
+    dialogRef.afterClosed().subscribe(c => {
+
+    });
+
+    // this.EditorDialogManager.showCustomizeDialog($("#" +pe.payload.dom), sourceEndpoint, pe.payload, false)
+    //     .then(() => {
+    //       this.JsplumbService.activateEndpoint(pe.payload.dom, !payload.uncompleted);
+    //     }, () => {
+    //       this.JsplumbService.activateEndpoint(pe.payload.dom, !payload.uncompleted);
+    //     });
+  }
+
 
 }
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/dialog/customize/customize.component.css b/ui/src/app/editor-v2/dialog/customize/customize.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/ui/src/app/editor-v2/dialog/customize/customize.component.html b/ui/src/app/editor-v2/dialog/customize/customize.component.html
new file mode 100644
index 0000000..e40d375
--- /dev/null
+++ b/ui/src/app/editor-v2/dialog/customize/customize.component.html
@@ -0,0 +1,18 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
diff --git a/ui/src/app/editor-v2/services/editor.service.ts b/ui/src/app/editor-v2/dialog/customize/customize.component.ts
similarity index 61%
copy from ui/src/app/editor-v2/services/editor.service.ts
copy to ui/src/app/editor-v2/dialog/customize/customize.component.ts
index 42f8cc2..9e9811b 100644
--- a/ui/src/app/editor-v2/services/editor.service.ts
+++ b/ui/src/app/editor-v2/dialog/customize/customize.component.ts
@@ -16,19 +16,26 @@
  *
  */
 
-import {Injectable} from "@angular/core";
-import {HttpClient} from "@angular/common/http";
-import {AuthStatusService} from "../../services/auth-status.service";
-import {TsonLdSerializerService} from "../../platform-services/tsonld-serializer.service";
+import {Component, Input, OnInit} from "@angular/core";
+import {PipelineElementConfig} from "../../model/editor.model";
+import {DialogRef} from "../panel/dialog-ref";
 
-@Injectable()
-export class EditorService {
+@Component({
+  selector: 'customize-pipeline-element',
+  templateUrl: './customize.component.html',
+  styleUrls: ['./customize.component.css']
+})
+export class CustomizeComponent implements OnInit {
 
-    constructor(private http: HttpClient,
-                private authStatusService: AuthStatusService,
-                private tsonLdSerializerService: TsonLdSerializerService) {
-    }
+  @Input()
+  pipelineElement: PipelineElementConfig;
 
+  constructor(private dialogRef: DialogRef<CustomizeComponent>) {
 
+  }
+
+  ngOnInit(): void {
+
+  }
 
 }
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/services/editor.service.ts b/ui/src/app/editor-v2/dialog/panel/dialog-ref.ts
similarity index 56%
copy from ui/src/app/editor-v2/services/editor.service.ts
copy to ui/src/app/editor-v2/dialog/panel/dialog-ref.ts
index 42f8cc2..9cf9e6a 100644
--- a/ui/src/app/editor-v2/services/editor.service.ts
+++ b/ui/src/app/editor-v2/dialog/panel/dialog-ref.ts
@@ -16,19 +16,32 @@
  *
  */
 
-import {Injectable} from "@angular/core";
-import {HttpClient} from "@angular/common/http";
-import {AuthStatusService} from "../../services/auth-status.service";
-import {TsonLdSerializerService} from "../../platform-services/tsonld-serializer.service";
+import {ComponentRef} from "@angular/core";
+import {OverlayRef} from "@angular/cdk/overlay";
+import {Observable, Subject} from "rxjs";
 
-@Injectable()
-export class EditorService {
+export class DialogRef<T> {
+  private _componentInstance: ComponentRef<T>;
+  private subject: Subject<any> = new Subject<any>();
 
-    constructor(private http: HttpClient,
-                private authStatusService: AuthStatusService,
-                private tsonLdSerializerService: TsonLdSerializerService) {
-    }
+  constructor(private overlayRef: OverlayRef) {
+  }
 
+  get componentInstance() {
+    return this._componentInstance;
+  }
 
+  set componentInstance(c: ComponentRef<T>) {
+    this._componentInstance = c;
+  }
+
+  public close(data?: any) {
+    this.overlayRef.dispose();
+    this.subject.next(data);
+  }
+
+  public afterClosed(): Observable<any> {
+    return this.subject;
+  }
 
 }
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.html b/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.html
new file mode 100644
index 0000000..23c2f5f
--- /dev/null
+++ b/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.html
@@ -0,0 +1,23 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div style="display:flex;justify-content:space-between;padding:0.5rem;width:100%">
+    <span class="dialog-title">{{ dialogTitle }}</span>
+    <button mat-button mat-icon-button (click)="closeDialog()"><i class="material-icons">clear</i></button>
+</div>
+<ng-template cdkPortalOutlet #portal></ng-template>
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/services/editor.service.ts b/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.scss
similarity index 62%
copy from ui/src/app/editor-v2/services/editor.service.ts
copy to ui/src/app/editor-v2/dialog/panel/panel-dialog.component.scss
index 42f8cc2..cdeec74 100644
--- a/ui/src/app/editor-v2/services/editor.service.ts
+++ b/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.scss
@@ -16,19 +16,19 @@
  *
  */
 
-import {Injectable} from "@angular/core";
-import {HttpClient} from "@angular/common/http";
-import {AuthStatusService} from "../../services/auth-status.service";
-import {TsonLdSerializerService} from "../../platform-services/tsonld-serializer.service";
-
-@Injectable()
-export class EditorService {
-
-    constructor(private http: HttpClient,
-                private authStatusService: AuthStatusService,
-                private tsonLdSerializerService: TsonLdSerializerService) {
-    }
-
+@import '../../../../scss/sp/colors';
 
+app-dialog-container {
+    width: 100%;
+    display: grid;
+    grid-template-rows: 40px 1fr;
+    padding: 0 10px;
+    background-color: #fff
+}
 
+.dialog-title {
+    font-weight: bold;
+    background: $sp-color-primary;
+    padding:20px;
+    color:white;
 }
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.ts b/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.ts
new file mode 100644
index 0000000..2ba2715
--- /dev/null
+++ b/ui/src/app/editor-v2/dialog/panel/panel-dialog.component.ts
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import {CdkPortalOutlet, ComponentPortal, Portal} from "@angular/cdk/portal";
+import {
+  Component,
+  EventEmitter,
+  Input,
+  OnInit,
+  Output,
+  ViewChild,
+  ViewEncapsulation
+} from "@angular/core";
+
+@Component({
+  selector: "app-dialog-container",
+  templateUrl: './panel-dialog.component.html',
+  encapsulation: ViewEncapsulation.None,
+  styleUrls: ['./panel-dialog.component.scss']
+})
+export class PanelDialogComponent<T> implements OnInit {
+
+  @Input()
+  dialogTitle = "";
+
+  @Input()
+  comp: ComponentPortal<T>;
+
+  @Output()
+  containerEvent = new EventEmitter<{ key: "CLOSE" }>();
+
+  @ViewChild("portal", {read: CdkPortalOutlet, static: true})
+  portal: CdkPortalOutlet;
+
+  @Input()
+  selectedPortal: Portal<T>;
+
+  constructor() {
+  }
+
+  ngOnInit() {
+  }
+
+  attach() {
+    const c = this.portal.attach(this.selectedPortal);
+    return c.instance;
+  }
+
+  closeDialog() {
+    this.containerEvent.emit({key: "CLOSE"});
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/dialog/panel/panel-dialog.service.ts b/ui/src/app/editor-v2/dialog/panel/panel-dialog.service.ts
new file mode 100644
index 0000000..69947ac
--- /dev/null
+++ b/ui/src/app/editor-v2/dialog/panel/panel-dialog.service.ts
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import {ComponentType, Overlay, OverlayRef,} from "@angular/cdk/overlay";
+import {ComponentPortal, PortalInjector,} from "@angular/cdk/portal";
+import {PanelDialogComponent} from "./panel-dialog.component";
+import {DialogConfig} from "../../model/editor.model";
+import {ComponentRef, Injectable, Injector} from "@angular/core";
+import {DialogRef} from "./dialog-ref";
+
+@Injectable({
+  providedIn: "root"
+})
+export class PanelDialogService {
+
+  constructor(private overlay: Overlay, private injector: Injector) {
+
+  }
+
+  public open<T>(component: ComponentType<T>,
+                 config?: DialogConfig,
+                 inputMap?: Object): DialogRef<T> {
+    config = config || {width: "auto", title: ""};
+
+    const positionStrategy = this.overlay
+        .position()
+        .global()
+        .top("0")
+        .right("0");
+
+    const overlay = this.overlay.create({
+      hasBackdrop: true,
+      positionStrategy,
+      panelClass: "dialog-container",
+      width: config.width,
+      maxWidth: "90vw",
+      height: "100vh"
+    });
+
+    const dialogPreview = new ComponentPortal(PanelDialogComponent);
+    const dialogContainerRef = overlay.attach(dialogPreview);
+    dialogContainerRef.instance.dialogTitle = config.title;
+    const dialogRef = new DialogRef<T>(overlay);
+
+    const injector = this.createInjector(dialogRef);
+    dialogContainerRef.instance.selectedPortal = new ComponentPortal(component,
+        null, injector);
+    dialogRef.componentInstance = dialogContainerRef.instance.attach();
+
+    Object.keys(inputMap).forEach(key => {
+      dialogRef.componentInstance[key] = inputMap[key];
+    })
+
+    this.applyDialogProperties(dialogContainerRef, overlay, config);
+
+    return dialogRef;
+  }
+
+  private applyDialogProperties(componentRef: ComponentRef<any>,
+                                overlayRef: OverlayRef,
+                                config: DialogConfig
+  ) {
+    componentRef.instance.containerEvent.subscribe(e => {
+      if (e.key === "CLOSE") {
+        overlayRef.dispose();
+      }
+    });
+    if (!config.disableClose) {
+      overlayRef.backdropClick().subscribe(() => overlayRef.dispose());
+    }
+  }
+
+  private createInjector<T>(dialogRef: DialogRef<T>) {
+    const injectorMap = new WeakMap();
+    injectorMap.set(DialogRef, dialogRef);
+    return new PortalInjector(this.injector, injectorMap);
+  }
+}
+
+
+
+
diff --git a/ui/src/app/editor-v2/editor.module.ts b/ui/src/app/editor-v2/editor.module.ts
index 25bfd62..936e1f2 100644
--- a/ui/src/app/editor-v2/editor.module.ts
+++ b/ui/src/app/editor-v2/editor.module.ts
@@ -43,6 +43,11 @@ import {PipelineComponent} from "./components/pipeline/pipeline.component";
 import {ObjectProvider} from "./services/object-provider.service";
 import {PipelineElementOptionsComponent} from "./components/pipeline-element-options/pipeline-element-options.component";
 import {PipelineElementRecommendationService} from "./services/pipeline-element-recommendation.service";
+import {PortalModule} from "@angular/cdk/portal";
+import {OverlayModule} from "@angular/cdk/overlay";
+import {PanelDialogComponent} from "./dialog/panel/panel-dialog.component";
+import {PanelDialogService} from "./dialog/panel/panel-dialog.service";
+import {CustomizeComponent} from "./dialog/customize/customize.component";
 
 @NgModule({
     imports: [
@@ -55,14 +60,18 @@ import {PipelineElementRecommendationService} from "./services/pipeline-element-
         CustomMaterialModule,
         FormsModule,
         ConnectModule,
+        PortalModule,
+        OverlayModule
     ],
     declarations: [
+        CustomizeComponent,
         EditorComponent,
         PipelineAssemblyComponent,
         PipelineElementIconStandComponent,
         PipelineElementComponent,
         PipelineElementOptionsComponent,
-        PipelineComponent
+        PipelineComponent,
+        PanelDialogComponent
     ],
     providers: [
         EditorService,
@@ -76,6 +85,7 @@ import {PipelineElementRecommendationService} from "./services/pipeline-element-
         JsplumbService,
         JsplumbConfigService,
         ObjectProvider,
+        PanelDialogService,
         PipelineEditorService,
         PipelinePositioningService,
         PipelineValidationService,
@@ -98,6 +108,7 @@ import {PipelineElementRecommendationService} from "./services/pipeline-element-
     ],
     entryComponents: [
         EditorComponent,
+        PanelDialogComponent
     ]
 })
 export class EditorModule {
diff --git a/ui/src/app/editor-v2/model/editor.model.ts b/ui/src/app/editor-v2/model/editor.model.ts
index 025fbdc..9a98ce9 100644
--- a/ui/src/app/editor-v2/model/editor.model.ts
+++ b/ui/src/app/editor-v2/model/editor.model.ts
@@ -22,6 +22,7 @@ import {
   SpDataStream
 } from "../../core-model/gen/streampipes-model";
 import {EditorConstants} from "../constants/editor.constants";
+import {InjectionToken} from "@angular/core";
 
 export type PipelineElementHolder = {
   [key: string]: Array<PipelineElementUnion>;
@@ -51,6 +52,15 @@ export enum PipelineElementType {
   DataSink
 }
 
+export interface DialogConfig {
+  width?: string;
+  disableClose?: boolean;
+  autoFocus?: boolean;
+  title: string;
+}
+
 export type PipelineElementUnion = SpDataSet | SpDataStream | DataProcessorInvocation | DataSinkInvocation;
 
-export type InvocablePipelineElementUnion = DataProcessorInvocation | DataSinkInvocation;
\ No newline at end of file
+export type InvocablePipelineElementUnion = DataProcessorInvocation | DataSinkInvocation;
+
+export const PIPELINE_ELEMENT_TOKEN = new InjectionToken<{}>('pipelineElement');
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/services/editor.service.ts b/ui/src/app/editor-v2/services/editor.service.ts
index 42f8cc2..309c386 100644
--- a/ui/src/app/editor-v2/services/editor.service.ts
+++ b/ui/src/app/editor-v2/services/editor.service.ts
@@ -20,13 +20,37 @@ import {Injectable} from "@angular/core";
 import {HttpClient} from "@angular/common/http";
 import {AuthStatusService} from "../../services/auth-status.service";
 import {TsonLdSerializerService} from "../../platform-services/tsonld-serializer.service";
+import {
+    DataProcessorInvocation, PipelineElementRecommendationMessage,
+    PipelineModificationMessage
+} from "../../core-model/gen/streampipes-model";
+import {Observable} from "rxjs";
 
 @Injectable()
 export class EditorService {
 
     constructor(private http: HttpClient,
-                private authStatusService: AuthStatusService,
-                private tsonLdSerializerService: TsonLdSerializerService) {
+                private authStatusService: AuthStatusService) {
+    }
+
+    recommendPipelineElement(pipeline): Observable<PipelineElementRecommendationMessage> {
+        return this.http.post(this.pipelinesResourceUrl +"/recommend", pipeline)
+            .map(data => PipelineElementRecommendationMessage.fromData(data as any));
+    }
+
+    updatePartialPipeline(pipeline): Observable<PipelineModificationMessage> {
+        return this.http.post(this.pipelinesResourceUrl +"/update", pipeline)
+            .map(data => {
+                return PipelineModificationMessage.fromData(data as any);
+            });
+    }
+
+    private get baseUrl() {
+        return '/streampipes-backend';
+    }
+
+    private get pipelinesResourceUrl() {
+        return this.baseUrl + '/api/v2/users/' + this.authStatusService.email + '/pipelines'
     }
 
 
diff --git a/ui/src/app/editor-v2/services/jsplumb.service.ts b/ui/src/app/editor-v2/services/jsplumb.service.ts
index e91e841..2c773e5 100644
--- a/ui/src/app/editor-v2/services/jsplumb.service.ts
+++ b/ui/src/app/editor-v2/services/jsplumb.service.ts
@@ -22,6 +22,11 @@ import {Inject, Injectable} from "@angular/core";
 import {PipelineElementConfig, PipelineElementUnion} from "../model/editor.model";
 import {PipelineElementTypeUtils} from "../utils/editor.utils";
 import * as angular from "angular";
+import {
+    DataProcessorInvocation, DataSinkInvocation,
+    SpDataSet,
+    SpDataStream
+} from "../../core-model/gen/streampipes-model";
 
 @Injectable()
 export class JsplumbService {
@@ -147,7 +152,7 @@ export class JsplumbService {
         let pipelineElementConfig = {} as PipelineElementConfig;
         pipelineElementConfig.type = PipelineElementTypeUtils
             .toCssShortHand(PipelineElementTypeUtils.fromType(pipelineElement))
-        pipelineElementConfig.payload = angular.copy(pipelineElement)
+        pipelineElementConfig.payload = this.clone(pipelineElement);
         pipelineElementConfig.settings = {connectable: connectable,
             openCustomize: !(pipelineElement as any).configured,
             preview: isPreview,
@@ -166,6 +171,18 @@ export class JsplumbService {
         return pipelineElementConfig;
     }
 
+    clone(pipelineElement: PipelineElementUnion) {
+        if (pipelineElement instanceof SpDataSet) {
+            return SpDataSet.fromData(pipelineElement, new SpDataSet());
+        } else if (pipelineElement instanceof SpDataStream) {
+            return SpDataStream.fromData(pipelineElement, new SpDataStream());
+        } else if (pipelineElement instanceof DataProcessorInvocation) {
+            return DataProcessorInvocation.fromData(pipelineElement, new DataProcessorInvocation());
+        } else {
+            return DataSinkInvocation.fromData(pipelineElement, new DataSinkInvocation());
+        }
+    }
+
     makeId(count) {
         var text = "";
         var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
diff --git a/ui/src/app/editor-v2/services/object-provider.service.ts b/ui/src/app/editor-v2/services/object-provider.service.ts
index bd07a51..bbf1cff 100644
--- a/ui/src/app/editor-v2/services/object-provider.service.ts
+++ b/ui/src/app/editor-v2/services/object-provider.service.ts
@@ -22,12 +22,14 @@ import {RestApi} from "../../services/rest-api.service";
 import {JsplumbBridge} from "./jsplumb-bridge.service";
 import {InvocablePipelineElementUnion, PipelineElementConfig} from "../model/editor.model";
 import {InvocableStreamPipesEntity, Pipeline} from "../../core-model/gen/streampipes-model";
+import {EditorService} from "./editor.service";
 
 @Injectable()
 export class ObjectProvider {
 
     constructor(private RestApi: RestApi,
-                private JsplumbBridge: JsplumbBridge) {
+                private JsplumbBridge: JsplumbBridge,
+                private EditorService: EditorService) {
     }
 
     prepareElement(pipelineElement: InvocablePipelineElementUnion) {
@@ -91,7 +93,7 @@ export class ObjectProvider {
     }
 
     updatePipeline(pipeline: Pipeline) {
-        return this.RestApi.updatePartialPipeline(pipeline);
+        return this.EditorService.updatePartialPipeline(pipeline);
     };
 
     storePipeline(pipeline) {
diff --git a/ui/src/app/services/rest-api.service.ts b/ui/src/app/services/rest-api.service.ts
index bceb0bd..ad9491e 100644
--- a/ui/src/app/services/rest-api.service.ts
+++ b/ui/src/app/services/rest-api.service.ts
@@ -268,15 +268,7 @@ export class RestApi {
         return this.$http.post(this.urlBase() +"/pe/options", resolvableOptionsParameterRequest);
     }
 
-    recommendPipelineElement(pipeline) {
-        return this.$http.post(this.urlBase() +"/pipelines/recommend", pipeline);
-    }
 
-    updatePartialPipeline(pipeline) {
-        return this.$http.post(this.urlBase() +"/pipelines/update", pipeline, {
-            ignoreLoadingBar: true
-        });
-    }
 
     updateDataSet(dataSet) {
         return this.$http.post(this.urlBase() +"/pipelines/update/dataset", dataSet);