tracking-code-which-will-go-to-the-HEAD draw/action/DragComponentAction.js

Source

draw/action/DragComponentAction.js

import Action from './Action';

/**
 * Action to drag component.
 */
class DragComponentAction extends Action {
  /**
   * Default constructor.
   * @param {DefaultData} pluginData - Plugin data storage.
   * @param {object} viewport - D3 selection of the view port.
   * @param {DefaultLayout} layout - Layout to organise components.
   */
  constructor(pluginData, viewport, layout) {
    super(pluginData, viewport, layout);

    /**
     * Whether the action is initialized or not.
     * @type {boolean}
     */
    this.isInit = false;
  }

  /**
   * Update position of sources element.
   * @param {object} event - The event containing the target information.
   * @returns {boolean} No need to redraw because, element is directly updated.
   */
  execute(event) {
    const sources = this.getSourcesId(event);

    if (!this.isInit) {
      this.isInit = true;

      const parentTypes = this.getParentTypes(sources);

      this.pluginData.components
        .filter(({ id, definition }) => !parentTypes.includes(definition.type)
          && !sources.includes(id))
        .forEach((component) => {
          this.viewport.select(`.${component.id}.component .canBeHidden`)
            .classed('hide', true);
        });
    }

    sources.forEach((id) => {
      const model = this.viewport.select(`.${id} .model`);

      model
        .attr('x', (parseFloat(model.attr('x')) || 0) + (event.dx / this.pluginData.scene.zoom))
        .attr('y', (parseFloat(model.attr('y')) || 0) + (event.dy / this.pluginData.scene.zoom));
    });

    return false;
  }

  /**
   * Update component position and update parent of component if needed.
   * @param {object} event - The event containing the target information.
   * @returns {boolean} Need to call draw at the end.
   */
  finalize(event) {
    this.isInit = false;

    const sourcesId = this.getSourcesId(event);
    const targetId = this.getTargetsId(event)[0] || null;
    const sources = sourcesId.map((id) => this.pluginData.getComponentById(id));

    if (targetId === null) {
      this.dropOnScene(event, sources);

      return true;
    }

    return this.dropOnTarget(event, sources, this.pluginData.getComponentById(targetId));
  }

  /**
   * Drop components on scene, remove referenced parent attribute.
   * @param {object} event - The event containing the target information.
   * @param {Component[]} sources - Sources to drop.
   * @returns {boolean} Need to redraw only if sources have container.
   */
  dropOnScene(event, sources) {
    const containerId = sources[0].getContainerId();
    const hasContainer = containerId !== null;
    const container = this.pluginData.getComponentById(containerId);
    let action;

    sources.forEach((source) => {
      const {
        x: sourceX,
        y: sourceY,
      } = this.getSourcePosition(source.id);

      if (hasContainer) {
        source.removeAllReferenceAttributes(container);

        const { dx, dy } = this.getTransform(
          `.${containerId} .components-background`,
          '.scene',
        );

        source.drawOption.x = sourceX + (dx - this.pluginData.scene.x) / this.pluginData.scene.zoom;
        source.drawOption.y = sourceY + (dy - this.pluginData.scene.y) / this.pluginData.scene.zoom;

        action = 'update';
      } else {
        source.drawOption.x = sourceX;
        source.drawOption.y = sourceY;
        action = 'move';
      }
    });

    if (container && container.definition.displayType === 'workflow') {
      this.layout.generateComponentsLayout(containerId);
    }

    this.pluginData.emitEvent({
      components: sources.map(({ id }) => id),
      type: 'Drawer',
      action,
      status: 'success',
    });

    return hasContainer;
  }

  /**
   * Drop components into container.
   * @param {object} event - The event containing the target information.
   * @param {Component[]} sources - Sources to drop.
   * @param {Component} target - Target container.
   * @returns {boolean} Redraw only if source can be dropped on target.
   */
  dropOnTarget(event, sources, target) {
    if (!sources.every((source) => target.canContain(source.definition.type))) {
      sources.forEach((source) => {
        const model = this.viewport.select(`.${source.id} .model`);

        model
          .attr('x', parseFloat(model.attr('x')) + (event.dx / this.pluginData.scene.zoom))
          .attr('y', parseFloat(model.attr('y')) + (event.dy / this.pluginData.scene.zoom));
      });

      return false;
    }

    if (target.definition.displayType === 'workflow') {
      return this.dropInWorkflow(event, sources, target);
    }

    const isChangingContainer = sources[0].getContainerId() !== target.id;

    sources.forEach((source) => {
      if (isChangingContainer) {
        source.setReferenceAttribute(target);
      }

      const { dx, dy } = this.getTransform(
        `.${source.id} .background`,
        `.${target.id} .components-background`,
      );

      source.drawOption.x = dx < 1 ? 1 : dx / this.pluginData.scene.zoom;
      source.drawOption.y = dy < 1 ? 1 : dy / this.pluginData.scene.zoom;
    });

    this.pluginData.emitEvent({
      components: sources.map(({ id }) => id),
      type: 'Drawer',
      action: isChangingContainer ? 'update' : 'move',
      status: 'success',
    });

    return true;
  }

  /**
   * Drop component into workflow container.
   * @param {object} event - The event containing the target information.
   * @param {Component[]} sources - Sources to drop.
   * @param {Component} target - Target container.
   * @returns {boolean} Need to call draw at the end.
   */
  dropInWorkflow(event, sources, target) {
    const oldContainerId = sources[0].getContainerId();
    const container = this.pluginData.getComponentById(target.id);
    const children = this.pluginData.getChildren(target.id);
    const isVertical = target.definition.workflowDirection === 'vertical';
    const sourceIds = this.pluginData.components
      .filter(({ id }) => sources.some(({ id: sourceId }) => id === sourceId))
      .map(({ id }) => id);
    let nextId;

    children
      .filter(({ id }) => !sourceIds.includes(id))
      .forEach((child) => {
        const {
          x: childX,
          y: childY,
          width,
          height,
        } = this.viewport.select(`.${child.id} .model`).node().getBoundingClientRect();

        if (isVertical && childY + (height / 2) < event.sourceEvent.clientY) {
          nextId = child.id;
        }

        if (!isVertical && childX + (width / 2) < event.sourceEvent.clientX) {
          nextId = child.id;
        }
      });

    sourceIds.forEach((sourceId) => {
      const source = this.pluginData.getComponentById(sourceId);

      source.setReferenceAttribute(container);

      if (!nextId) {
        nextId = this.pluginData.components
          .find((component) => component.getContainerId() === target.id).id;

        if (sourceId !== nextId) {
          this.pluginData.insertComponentBefore(sourceId, nextId);
        }
      } else {
        this.pluginData.insertComponentAfter(sourceId, nextId);
      }
      nextId = sourceId;
    });

    if (oldContainerId !== target.id && oldContainerId) {
      this.layout.generateComponentsLayout(oldContainerId, false);
    }

    this.layout.generateComponentsLayout(target.id, false);

    this.pluginData.emitEvent({
      components: sourceIds,
      type: 'Drawer',
      action: 'update',
      status: 'success',
    });

    return true;
  }

  /**
   * Get all parent types that the sources have in common.
   * @param {string[]} sources - Component ids list.
   * @returns {string[]} Parent types list.
   */
  getParentTypes(sources) {
    const map = new Map();

    sources.map((id) => this.pluginData.getComponentById(id))
      .forEach(({ id, definition }) => {
        definition.parentTypes.forEach((type) => {
          if (!map.has(type)) {
            map.set(type, []);
          }

          map.get(type).push(id);
        });
      });

    const types = [];

    map.forEach((ids, type) => {
      if (ids.length === sources.length) {
        types.push(type);
      }
    });

    return types;
  }
}

export default DragComponentAction;