From dbf141d866af5b8644d5a1f19c490421e0cd159e Mon Sep 17 00:00:00 2001
From: Alejandro Gallardo Escobar <alejandro.gallardo@artica.es>
Date: Wed, 14 Aug 2019 14:24:06 +0200
Subject: [PATCH] Improved the movement of the visual console line element

---
 .../javascript/pandora_visual_console.js      |  44 +++
 .../models/VisualConsole/Container.php        |   4 +-
 .../models/VisualConsole/Items/Line.php       |  74 ++--
 visual_console_client/src/VisualConsole.ts    |  46 ++-
 visual_console_client/src/items/Line.ts       | 359 ++++++++++++++----
 5 files changed, 405 insertions(+), 122 deletions(-)

diff --git a/pandora_console/include/javascript/pandora_visual_console.js b/pandora_console/include/javascript/pandora_visual_console.js
index d49f26df28..75697d1225 100755
--- a/pandora_console/include/javascript/pandora_visual_console.js
+++ b/pandora_console/include/javascript/pandora_visual_console.js
@@ -469,6 +469,50 @@ function createVisualConsole(
         })
         .init();
     });
+    // VC Line Item moved.
+    visualConsole.onLineMoved(function(e) {
+      var id = e.item.props.id;
+      var data = {
+        startX: e.startPosition.x,
+        startY: e.startPosition.y,
+        endX: e.endPosition.x,
+        endY: e.endPosition.y
+      };
+      var taskId = "visual-console-item-update-" + id;
+
+      // Persist the new position.
+      asyncTaskManager
+        .add(taskId, function(done) {
+          var abortable = updateVisualConsoleItem(
+            baseUrl,
+            visualConsole.props.id,
+            id,
+            data,
+            function(error, data) {
+              // if (!error && !data) return;
+              if (error || !data) {
+                console.log(
+                  "[ERROR]",
+                  "[VISUAL-CONSOLE-CLIENT]",
+                  "[API]",
+                  error ? error.message : "Invalid response"
+                );
+
+                // TODO: Move the element to its initial position.
+              }
+
+              done();
+            }
+          );
+
+          return {
+            cancel: function() {
+              abortable.abort();
+            }
+          };
+        })
+        .init();
+    });
 
     // VC Item resized.
     visualConsole.onItemResized(function(e) {
diff --git a/pandora_console/include/rest-api/models/VisualConsole/Container.php b/pandora_console/include/rest-api/models/VisualConsole/Container.php
index 6ae852d9e8..f45fee7651 100644
--- a/pandora_console/include/rest-api/models/VisualConsole/Container.php
+++ b/pandora_console/include/rest-api/models/VisualConsole/Container.php
@@ -426,10 +426,10 @@ final class Container extends Model
      *
      * @param integer $itemId Identifier of the Item.
      *
-     * @return Item Item.
+     * @return Model Item or Line.
      * @throws \Exception When the data cannot be retrieved from the DB.
      */
-    public static function getItemFromDB(int $itemId): Item
+    public static function getItemFromDB(int $itemId): Model
     {
         // Default filter.
         $filter = ['id' => $itemId];
diff --git a/pandora_console/include/rest-api/models/VisualConsole/Items/Line.php b/pandora_console/include/rest-api/models/VisualConsole/Items/Line.php
index e7f4e7dc67..0342a43aec 100644
--- a/pandora_console/include/rest-api/models/VisualConsole/Items/Line.php
+++ b/pandora_console/include/rest-api/models/VisualConsole/Items/Line.php
@@ -218,64 +218,70 @@ final class Line extends Model
     protected function encode(array $data): array
     {
         $result = [];
+        $result['type'] = LINE_ITEM;
 
         $id = static::getId($data);
         if ($id) {
             $result['id'] = $id;
         }
 
-        $id_layout = static::getIdLayout($data);
-        if ($id_layout) {
-            $result['id_layout'] = $id_layout;
+        $layoutId = static::getIdLayout($data);
+        if ($layoutId > 0) {
+            $result['id_layout'] = $layoutId;
         }
 
-        $pos_x = static::parseIntOr(
-            static::issetInArray($data, ['x', 'pos_x', 'posX']),
+        $startX = static::parseIntOr(
+            static::issetInArray($data, ['pos_x', 'startX']),
             null
         );
-        if ($pos_x !== null) {
-            $result['pos_x'] = $pos_x;
+        if ($startX !== null) {
+            $result['pos_x'] = $startX;
         }
 
-        $pos_y = static::parseIntOr(
-            static::issetInArray($data, ['y', 'pos_y', 'posY']),
+        $startY = static::parseIntOr(
+            static::issetInArray($data, ['pos_y', 'startY']),
             null
         );
-        if ($pos_y !== null) {
-            $result['pos_y'] = $pos_y;
+        if ($startY !== null) {
+            $result['pos_y'] = $startY;
         }
 
-        $height = static::getHeight($data);
-        if ($height !== null) {
-            $result['height'] = $height;
-        }
-
-        $width = static::getWidth($data);
-        if ($width !== null) {
-            $result['width'] = $width;
-        }
-
-        $type = static::parseIntOr(
-            static::issetInArray($data, ['type']),
+        $endX = static::parseIntOr(
+            static::issetInArray($data, ['width', 'endX']),
             null
         );
-        if ($type !== null) {
-            $result['type'] = $type;
+        if ($endX !== null) {
+            $result['width'] = $endX;
         }
 
-        $border_width = static::getBorderWidth($data);
-        if ($border_width !== null) {
-            $result['border_width'] = $border_width;
+        $endY = static::parseIntOr(
+            static::issetInArray($data, ['height', 'endY']),
+            null
+        );
+        if ($endY !== null) {
+            $result['height'] = $endY;
         }
 
-        $border_color = static::extractBorderColor($data);
-        if ($border_color !== null) {
-            $result['border_color'] = $border_color;
+        $borderWidth = static::getBorderWidth($data);
+        if ($borderWidth !== null) {
+            $result['border_width'] = $borderWidth;
         }
 
-        $show_on_top = static::issetInArray($data, ['isOnTop', 'show_on_top', 'showOnTop']);
-        if ($show_on_top !== null) {
-            $result['show_on_top'] = static::parseBool($show_on_top);
+        $borderColor = static::extractBorderColor($data);
+        if ($borderColor !== null) {
+            $result['border_color'] = $borderColor;
+        }
+
+        $showOnTop = static::issetInArray(
+            $data,
+            [
+                'isOnTop',
+                'show_on_top',
+                'showOnTop',
+            ]
+        );
+        if ($showOnTop !== null) {
+            $result['show_on_top'] = static::parseBool($showOnTop);
         }
 
         return $result;
diff --git a/visual_console_client/src/VisualConsole.ts b/visual_console_client/src/VisualConsole.ts
index e1e2235565..4adecdd5f5 100644
--- a/visual_console_client/src/VisualConsole.ts
+++ b/visual_console_client/src/VisualConsole.ts
@@ -23,7 +23,7 @@ import ColorCloud, { colorCloudPropsDecoder } from "./items/ColorCloud";
 import Group, { groupPropsDecoder } from "./items/Group";
 import Clock, { clockPropsDecoder } from "./items/Clock";
 import Box, { boxPropsDecoder } from "./items/Box";
-import Line, { linePropsDecoder } from "./items/Line";
+import Line, { linePropsDecoder, LineMovedEvent } from "./items/Line";
 import Label, { labelPropsDecoder } from "./items/Label";
 import SimpleValue, { simpleValuePropsDecoder } from "./items/SimpleValue";
 import EventsHistory, {
@@ -211,6 +211,8 @@ export default class VisualConsole {
   private readonly dblClickEventManager = new TypedEvent<ItemClickEvent>();
   // Event manager for move events.
   private readonly movedEventManager = new TypedEvent<ItemMovedEvent>();
+  // Event manager for line move events.
+  private readonly lineMovedEventManager = new TypedEvent<LineMovedEvent>();
   // Event manager for resize events.
   private readonly resizedEventManager = new TypedEvent<ItemResizedEvent>();
   // Event manager for remove events.
@@ -275,6 +277,17 @@ export default class VisualConsole {
     // console.log(`Movement finished for element #${e.item.props.id}`, e);
   };
 
+  /**
+   * React to a line movement.
+   * @param e Event object.
+   */
+  private handleLineElementMovementFinished: (
+    e: LineMovedEvent
+  ) => void = e => {
+    this.lineMovedEventManager.emit(e);
+    // console.log(`Movement finished for element #${e.item.props.id}`, e);
+  };
+
   /**
    * React to a resizement on an element.
    * @param e Event object.
@@ -403,13 +416,20 @@ export default class VisualConsole {
       // Item event handlers.
       itemInstance.onClick(context.handleElementClick);
       itemInstance.onDblClick(context.handleElementDblClick);
-      itemInstance.onMoved(context.handleElementMovement);
-      itemInstance.onMovementFinished(context.handleElementMovementFinished);
-      itemInstance.onResized(context.handleElementResizement);
-      itemInstance.onResizeFinished(context.handleElementResizementFinished);
       itemInstance.onRemove(context.handleElementRemove);
       itemInstance.onSelectionChanged(context.handleElementSelectionChanged);
 
+      if (itemInstance instanceof Line) {
+        itemInstance.onLineMovementFinished(
+          context.handleLineElementMovementFinished
+        );
+      } else {
+        itemInstance.onMoved(context.handleElementMovement);
+        itemInstance.onMovementFinished(context.handleElementMovementFinished);
+        itemInstance.onResized(context.handleElementResizement);
+        itemInstance.onResizeFinished(context.handleElementResizementFinished);
+      }
+
       // Add the item to the DOM.
       context.containerRef.append(itemInstance.elementRef);
       return itemInstance;
@@ -806,6 +826,22 @@ export default class VisualConsole {
     return disposable;
   }
 
+  /**
+   * Add an event handler to the movement of the visual console line elements.
+   * @param listener Function which is going to be executed when a linked console is moved.
+   */
+  public onLineMoved(listener: Listener<LineMovedEvent>): Disposable {
+    /*
+     * The '.on' function returns a function which will clean the event
+     * listener when executed. We store all the 'dispose' functions to
+     * call them when the item should be cleared.
+     */
+    const disposable = this.lineMovedEventManager.on(listener);
+    this.disposables.push(disposable);
+
+    return disposable;
+  }
+
   /**
    * Add an event handler to the resizement of the visual console elements.
    * @param listener Function which is going to be executed when a linked console is moved.
diff --git a/visual_console_client/src/items/Line.ts b/visual_console_client/src/items/Line.ts
index 2861dc5421..68339a4935 100644
--- a/visual_console_client/src/items/Line.ts
+++ b/visual_console_client/src/items/Line.ts
@@ -6,6 +6,7 @@ import {
   addMovementListener
 } from "../lib";
 import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
+import TypedEvent, { Listener, Disposable } from "../lib/TypedEvent";
 
 interface LineProps extends ItemProps {
   // Overrided properties.
@@ -68,68 +69,143 @@ export function linePropsDecoder(data: AnyObject): LineProps | never {
     ...props,
     // Enhance the props extracting the box size and position.
     // eslint-disable-next-line @typescript-eslint/no-use-before-define
-    ...Line.extractBoxSizeAndPosition(props)
+    ...Line.extractBoxSizeAndPosition(props.startPosition, props.endPosition)
   };
 }
 
+const svgNS = "http://www.w3.org/2000/svg";
+
+export interface LineMovedEvent {
+  item: Line;
+  startPosition: LineProps["startPosition"];
+  endPosition: LineProps["endPosition"];
+}
+
 export default class Line extends Item<LineProps> {
+  private circleRadius = 8;
   // To control if the line movement is enabled.
   private moveMode: boolean = false;
+  // To control if the line is moving.
+  private isMoving: boolean = false;
 
-  // // This function will only run the 2nd arg function after the time
-  // // of the first arg have passed after its last execution.
-  // private debouncedMovementSave = debounce(
-  //   500, // ms.
-  //   (x: Position["x"], y: Position["y"]) => {
-  //     const prevPosition = {
-  //       x: this.props.x,
-  //       y: this.props.y
-  //     };
-  //     const newPosition = {
-  //       x: x,
-  //       y: y
-  //     };
+  // Event manager for moved events.
+  private readonly lineMovedEventManager = new TypedEvent<LineMovedEvent>();
+  // List of references to clean the event listeners.
+  private readonly lineMovedEventDisposables: Disposable[] = [];
 
-  //     if (!this.positionChanged(prevPosition, newPosition)) return;
+  // This function will only run the 2nd arg function after the time
+  // of the first arg have passed after its last execution.
+  private debouncedStartPositionMovementSave = debounce(
+    500, // ms.
+    (x: Position["x"], y: Position["y"]) => {
+      this.isMoving = false;
+      const startPosition = { x, y };
+      // Emit the movement event.
+      this.lineMovedEventManager.emit({
+        item: this,
+        startPosition,
+        endPosition: this.props.endPosition
+      });
+    }
+  );
+  // This property will store the function
+  // to clean the movement listener.
+  private removeStartPositionMovement: Function | null = null;
 
-  //     // Save the new position to the props.
-  //     this.move(x, y);
-  //     // Emit the movement event.
-  //     this.movedEventManager.emit({
-  //       item: this,
-  //       prevPosition: prevPosition,
-  //       newPosition: newPosition
-  //     });
-  //   }
-  // );
-  // // This property will store the function
-  // // to clean the movement listener.
-  // private removeMovement: Function | null = null;
+  /**
+   * Start the movement funtionality for the start position.
+   * @param element Element to move inside its container.
+   */
+  private initStartPositionMovementListener(
+    element: HTMLElement,
+    container: HTMLElement
+  ): void {
+    this.removeStartPositionMovement = addMovementListener(
+      element,
+      (x: Position["x"], y: Position["y"]) => {
+        // Calculate the center of the circle.
+        x += this.circleRadius;
+        y += this.circleRadius;
 
-  // /**
-  //  * Start the movement funtionality.
-  //  * @param element Element to move inside its container.
-  //  */
-  // private initMovementListener(element: HTMLElement): void {
-  //   this.removeMovement = addMovementListener(
-  //     element,
-  //     (x: Position["x"], y: Position["y"]) => {
-  //       // Move the DOM element.
-  //       this.moveElement(x, y);
-  //       // Run the save function.
-  //       this.debouncedMovementSave(x, y);
-  //     }
-  //   );
-  // }
-  // /**
-  //  * Stop the movement fun
-  //  */
-  // private stopMovementListener(): void {
-  //   if (this.removeMovement) {
-  //     this.removeMovement();
-  //     this.removeMovement = null;
-  //   }
-  // }
+        const startPosition = { x, y };
+
+        this.isMoving = true;
+        this.props = {
+          ...this.props,
+          startPosition
+        };
+
+        // Run the end function.
+        this.debouncedStartPositionMovementSave(x, y);
+      },
+      container
+    );
+  }
+  /**
+   * Stop the movement fun
+   */
+  private stopStartPositionMovementListener(): void {
+    if (this.removeStartPositionMovement) {
+      this.removeStartPositionMovement();
+      this.removeStartPositionMovement = null;
+    }
+  }
+
+  // This function will only run the 2nd arg function after the time
+  // of the first arg have passed after its last execution.
+  private debouncedEndPositionMovementSave = debounce(
+    500, // ms.
+    (x: Position["x"], y: Position["y"]) => {
+      this.isMoving = false;
+      const endPosition = { x, y };
+      // Emit the movement event.
+      this.lineMovedEventManager.emit({
+        item: this,
+        endPosition,
+        startPosition: this.props.startPosition
+      });
+    }
+  );
+  // This property will store the function
+  // to clean the movement listener.
+  private removeEndPositionMovement: Function | null = null;
+
+  /**
+   * End the movement funtionality for the end position.
+   * @param element Element to move inside its container.
+   */
+  private initEndPositionMovementListener(
+    element: HTMLElement,
+    container: HTMLElement
+  ): void {
+    this.removeEndPositionMovement = addMovementListener(
+      element,
+      (x: Position["x"], y: Position["y"]) => {
+        // Calculate the center of the circle.
+        x += this.circleRadius;
+        y += this.circleRadius;
+
+        this.isMoving = true;
+        this.props = {
+          ...this.props,
+          endPosition: { x, y }
+        };
+
+        // Run the end function.
+        this.debouncedEndPositionMovementSave(x, y);
+      },
+      container
+    );
+  }
+  /**
+   * Stop the movement function.
+   */
+  private stopEndPositionMovementListener(): void {
+    if (this.removeEndPositionMovement) {
+      this.removeEndPositionMovement();
+      this.removeEndPositionMovement = null;
+    }
+  }
 
   /**
    * @override
@@ -142,7 +218,10 @@ export default class Line extends Item<LineProps> {
     super(
       {
         ...props,
-        ...Line.extractBoxSizeAndPosition(props)
+        ...Line.extractBoxSizeAndPosition(
+          props.startPosition,
+          props.endPosition
+        )
       },
       {
         ...meta,
@@ -164,7 +243,10 @@ export default class Line extends Item<LineProps> {
   public setProps(newProps: LineProps) {
     super.setProps({
       ...newProps,
-      ...Line.extractBoxSizeAndPosition(newProps)
+      ...Line.extractBoxSizeAndPosition(
+        newProps.startPosition,
+        newProps.endPosition
+      )
     });
   }
 
@@ -202,15 +284,11 @@ export default class Line extends Item<LineProps> {
       color // Line color
     } = this.props;
 
-    const startIsLeft = startPosition.x - endPosition.x <= 0;
-    const startIsTop = startPosition.y - endPosition.y <= 0;
-
     const x1 = startPosition.x - x + lineWidth / 2;
     const y1 = startPosition.y - y + lineWidth / 2;
     const x2 = endPosition.x - x + lineWidth / 2;
     const y2 = endPosition.y - y + lineWidth / 2;
 
-    const svgNS = "http://www.w3.org/2000/svg";
     // SVG container.
     const svg = document.createElementNS(svgNS, "svg");
     // Set SVG size.
@@ -227,36 +305,130 @@ export default class Line extends Item<LineProps> {
     svg.append(line);
     element.append(svg);
 
+    return element;
+  }
+
+  protected updateDomElement(element: HTMLElement): void {
+    const {
+      x, // Box x
+      y, // Box y
+      width, // Box width
+      height, // Box height
+      lineWidth, // Line thickness
+      startPosition, // Line start position
+      endPosition, // Line end position
+      color // Line color
+    } = this.props;
+
+    const x1 = startPosition.x - x + lineWidth / 2;
+    const y1 = startPosition.y - y + lineWidth / 2;
+    const x2 = endPosition.x - x + lineWidth / 2;
+    const y2 = endPosition.y - y + lineWidth / 2;
+
+    const svgs = element.getElementsByTagName("svg");
+
+    if (svgs.length > 0) {
+      const svg = svgs.item(0);
+
+      if (svg != null) {
+        // Set SVG size.
+        svg.setAttribute("width", `${width + lineWidth}`);
+        svg.setAttribute("height", `${height + lineWidth}`);
+
+        const lines = svg.getElementsByTagNameNS(svgNS, "line");
+
+        if (lines.length > 0) {
+          const line = lines.item(0);
+
+          if (line != null) {
+            line.setAttribute("x1", `${x1}`);
+            line.setAttribute("y1", `${y1}`);
+            line.setAttribute("x2", `${x2}`);
+            line.setAttribute("y2", `${y2}`);
+            line.setAttribute("stroke", color || "black");
+            line.setAttribute("stroke-width", `${lineWidth}`);
+          }
+        }
+      }
+    }
+
     if (this.moveMode) {
-      const startCircle = document.createElement("div");
-      startCircle.style.width = "16px";
-      startCircle.style.height = "16px";
+      const startIsLeft = startPosition.x - endPosition.x <= 0;
+      const startIsTop = startPosition.y - endPosition.y <= 0;
+
+      let startCircle: HTMLElement = document.createElement("div");
+      let endCircle: HTMLElement = document.createElement("div");
+
+      if (this.isMoving) {
+        const circlesStart = element.getElementsByClassName(
+          "visual-console-item-line-circle-start"
+        );
+        if (circlesStart.length > 0) {
+          const circle = circlesStart.item(0) as HTMLElement;
+          if (circle) startCircle = circle;
+        }
+        const circlesEnd = element.getElementsByClassName(
+          "visual-console-item-line-circle-end"
+        );
+        if (circlesEnd.length > 0) {
+          const circle = circlesEnd.item(0) as HTMLElement;
+          if (circle) endCircle = circle;
+        }
+      }
+
+      startCircle.classList.add(
+        "visual-console-item-line-circle",
+        "visual-console-item-line-circle-start"
+      );
+      startCircle.style.width = `${this.circleRadius * 2}px`;
+      startCircle.style.height = `${this.circleRadius * 2}px`;
       startCircle.style.borderRadius = "50%";
       startCircle.style.backgroundColor = "white";
       startCircle.style.position = "absolute";
       startCircle.style.left = startIsLeft
-        ? "-8px"
-        : `${width + lineWidth - 8}px`;
+        ? `-${this.circleRadius}px`
+        : `${width + lineWidth - this.circleRadius}px`;
       startCircle.style.top = startIsTop
-        ? "-8px"
-        : `${height + lineWidth - 8}px`;
+        ? `-${this.circleRadius}px`
+        : `${height + lineWidth - this.circleRadius}px`;
 
-      const endCircle = document.createElement("div");
-      endCircle.style.width = "16px";
-      endCircle.style.height = "16px";
+      endCircle.classList.add(
+        "visual-console-item-line-circle",
+        "visual-console-item-line-circle-end"
+      );
+      endCircle.style.width = `${this.circleRadius * 2}px`;
+      endCircle.style.height = `${this.circleRadius * 2}px`;
       endCircle.style.borderRadius = "50%";
-      endCircle.style.backgroundColor = "white";
+      endCircle.style.backgroundColor = "black";
       endCircle.style.position = "absolute";
       endCircle.style.left = startIsLeft
         ? `${width + lineWidth - 8}px`
-        : "-8px";
-      endCircle.style.top = startIsTop ? `${height + lineWidth - 8}px` : "-8px";
+        : `-${this.circleRadius}px`;
+      endCircle.style.top = startIsTop
+        ? `${height + lineWidth - this.circleRadius}px`
+        : `-${this.circleRadius}px`;
 
-      element.append(startCircle);
-      element.append(endCircle);
+      if (!this.isMoving) {
+        element.appendChild(startCircle);
+        element.appendChild(endCircle);
+
+        // Init the movement listeners.
+        this.initStartPositionMovementListener(startCircle, this.elementRef
+          .parentElement as HTMLElement);
+        this.initEndPositionMovementListener(endCircle, this.elementRef
+          .parentElement as HTMLElement);
+      }
+    } else if (!this.isMoving) {
+      this.stopStartPositionMovementListener();
+      // Remove circles.
+      const circles = element.getElementsByClassName(
+        "visual-console-item-line-circle"
+      );
+      while (circles.length > 0) {
+        const circle = circles.item(0);
+        if (circle) circle.remove();
+      }
     }
-
-    return element;
   }
 
   /**
@@ -264,12 +436,15 @@ export default class Line extends Item<LineProps> {
    * the start and the finish of the line.
    * @param props Item properties.
    */
-  public static extractBoxSizeAndPosition(props: LineProps): Size & Position {
+  public static extractBoxSizeAndPosition(
+    startPosition: Position,
+    endPosition: Position
+  ): Size & Position {
     return {
-      width: Math.abs(props.startPosition.x - props.endPosition.x),
-      height: Math.abs(props.startPosition.y - props.endPosition.y),
-      x: Math.min(props.startPosition.x, props.endPosition.x),
-      y: Math.min(props.startPosition.y, props.endPosition.y)
+      width: Math.abs(startPosition.x - endPosition.x),
+      height: Math.abs(startPosition.y - endPosition.y),
+      x: Math.min(startPosition.x, endPosition.x),
+      y: Math.min(startPosition.y, endPosition.y)
     };
   }
 
@@ -278,7 +453,29 @@ export default class Line extends Item<LineProps> {
    * @override Item.remove
    */
   public remove(): void {
-    // TODO: clear the item's event listeners.
+    // Clear the item's event listeners.
+    this.stopStartPositionMovementListener();
+    // Call the parent's .remove()
     super.remove();
   }
+
+  /**
+   * To add an event handler to the movement of visual console elements.
+   * @param listener Function which is going to be executed when a linked console is moved.
+   *
+   * @override Item.onMoved
+   */
+  public onLineMovementFinished(
+    listener: Listener<LineMovedEvent>
+  ): Disposable {
+    /*
+     * The '.on' function returns a function which will clean the event
+     * listener when executed. We store all the 'dispose' functions to
+     * call them when the item should be cleared.
+     */
+    const disposable = this.lineMovedEventManager.on(listener);
+    this.lineMovedEventDisposables.push(disposable);
+
+    return disposable;
+  }
 }