text: /**
   * @license
   * Copyright 2018 Google LLC
   * SPDX-License-Identifier: Apache-2.0
   */
  
  /**
   * @fileoverview Methods animating a block on connection and disconnection.
   * @author fenichel@google.com (Rachel Fenichel)
   */
  'use strict';
  
  goog.provide('Blockly.blockAnimations');
  
  goog.require('Blockly.utils.dom');
  goog.require('Blockly.utils.Svg');
  
  
  /**
   * PID of disconnect UI animation.  There can only be one at a time.
   * @type {number}
   * @private
   */
  Blockly.blockAnimations.disconnectPid_ = 0;
  
  /**
   * SVG group of wobbling block.  There can only be one at a time.
   * @type {Element}
   * @private
   */
  Blockly.blockAnimations.disconnectGroup_ = null;
  
  /**
   * Play some UI effects (sound, animation) when disposing of a block.
   * @param {!Blockly.BlockSvg} block The block being disposed of.
   * @package
   */
  Blockly.blockAnimations.disposeUiEffect = function(block) {
    var workspace = block.workspace;
    var svgGroup = block.getSvgRoot();
    workspace.getAudioManager().play('delete');
  
    var xy = workspace.getSvgXY(svgGroup);
    // Deeply clone the current block.
    var clone = svgGroup.cloneNode(true);
    clone.translateX_ = xy.x;
    clone.translateY_ = xy.y;
    clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
    workspace.getParentSvg().appendChild(clone);
    clone.bBox_ = clone.getBBox();
    // Start the animation.
    Blockly.blockAnimations.disposeUiStep_(clone, workspace.RTL, new Date,
        workspace.scale);
  };
  
  /**
   * Animate a cloned block and eventually dispose of it.
   * This is a class method, not an instance method since the original block has
   * been destroyed and is no longer accessible.
   * @param {!Element} clone SVG element to animate and dispose of.
   * @param {boolean} rtl True if RTL, false if LTR.
   * @param {!Date} start Date of animation's start.
   * @param {number} workspaceScale Scale of workspace.
   * @private
   */
  Blockly.blockAnimations.disposeUiStep_ = function(clone, rtl, start,
      workspaceScale) {
    var ms = new Date - start;
    var percent = ms / 150;
    if (percent > 1) {
      Blockly.utils.dom.removeNode(clone);
    } else {
      var x = clone.translateX_ +
          (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
      var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
      var scale = (1 - percent) * workspaceScale;
      clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
          ' scale(' + scale + ')');
      setTimeout(Blockly.blockAnimations.disposeUiStep_, 10, clone, rtl, start,
          workspaceScale);
    }
  };
  
  /**
   * Play some UI effects (sound, ripple) after a connection has been established.
   * @param {!Blockly.BlockSvg} block The block being connected.
   * @package
   */
  Blockly.blockAnimations.connectionUiEffect = function(block) {
    var workspace = block.workspace;
    var scale = workspace.scale;
    workspace.getAudioManager().play('click');
    if (scale < 1) {
      return;  // Too small to care about visual effects.
    }
    // Determine the absolute coordinates of the inferior block.
    var xy = workspace.getSvgXY(block.getSvgRoot());
    // Offset the coordinates based on the two connection types, fix scale.
    if (block.outputConnection) {
      xy.x += (block.RTL ? 3 : -3) * scale;
      xy.y += 13 * scale;
    } else if (block.previousConnection) {
      xy.x += (block.RTL ? -23 : 23) * scale;
      xy.y += 3 * scale;
    }
    var ripple = Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.CIRCLE,
        {
          'cx': xy.x,
          'cy': xy.y,
          'r': 0,
          'fill': 'none',
          'stroke': '#888',
          'stroke-width': 10
        },
        workspace.getParentSvg());
    // Start the animation.
    Blockly.blockAnimations.connectionUiStep_(ripple, new Date, scale);
  };
  
  /**
   * Expand a ripple around a connection.
   * @param {!SVGElement} ripple Element to animate.
   * @param {!Date} start Date of animation's start.
   * @param {number} scale Scale of workspace.
   * @private
   */
  Blockly.blockAnimations.connectionUiStep_ = function(ripple, start, scale) {
    var ms = new Date - start;
    var percent = ms / 150;
    if (percent > 1) {
      Blockly.utils.dom.removeNode(ripple);
    } else {
      ripple.setAttribute('r', percent * 25 * scale);
      ripple.style.opacity = 1 - percent;
      Blockly.blockAnimations.disconnectPid_ = setTimeout(
          Blockly.blockAnimations.connectionUiStep_, 10, ripple, start, scale);
    }
  };
  
  /**
   * Play some UI effects (sound, animation) when disconnecting a block.
   * @param {!Blockly.BlockSvg} block The block being disconnected.
   * @package
   */
  Blockly.blockAnimations.disconnectUiEffect = function(block) {
    block.workspace.getAudioManager().play('disconnect');
    if (block.workspace.scale < 1) {
      return;  // Too small to care about visual effects.
    }
    // Horizontal distance for bottom of block to wiggle.
    var DISPLACEMENT = 10;
    // Scale magnitude of skew to height of block.
    var height = block.getHeightWidth().height;
    var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
    if (!block.RTL) {
      magnitude *= -1;
    }
    // Start the animation.
    Blockly.blockAnimations.disconnectUiStep_(
        block.getSvgRoot(), magnitude, new Date);
  };
  /**
   * Animate a brief wiggle of a disconnected block.
   * @param {!SVGElement} group SVG element to animate.
   * @param {number} magnitude Maximum degrees skew (reversed for RTL).
   * @param {!Date} start Date of animation's start.
   * @private
   */
  Blockly.blockAnimations.disconnectUiStep_ = function(group, magnitude, start) {
    var DURATION = 200;  // Milliseconds.
    var WIGGLES = 3;  // Half oscillations.
  
    var ms = new Date - start;
    var percent = ms / DURATION;
  
    if (percent > 1) {
      group.skew_ = '';
    } else {
      var skew = Math.round(
          Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude);
      group.skew_ = 'skewX(' + skew + ')';
      Blockly.blockAnimations.disconnectGroup_ = group;
      Blockly.blockAnimations.disconnectPid_ =
          setTimeout(Blockly.blockAnimations.disconnectUiStep_, 10, group,
              magnitude, start);
    }
    group.setAttribute('transform', group.translate_ + group.skew_);
  };
  
  /**
   * Stop the disconnect UI animation immediately.
   * @package
   */
  Blockly.blockAnimations.disconnectUiStop = function() {
    if (Blockly.blockAnimations.disconnectGroup_) {
      clearTimeout(Blockly.blockAnimations.disconnectPid_);
      var group = Blockly.blockAnimations.disconnectGroup_;
      group.skew_ = '';
      group.setAttribute('transform', group.translate_);
      Blockly.blockAnimations.disconnectGroup_ = null;
    }
  };
  