<commit_before>(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(["exports", "prop-types", "react", "lodash/clone", "lodash/has", "lodash/map", "lodash/get", "lodash/filter", "react-bootstrap/lib/Button", "react-bootstrap/lib/Col", "react-bootstrap/lib/Table", "react-bootstrap/lib/FormControl", "react-bootstrap/lib/FormGroup", "react-bootstrap/lib/ControlLabel", "react-bootstrap/lib/HelpBlock", "react-plupload", "lodash/isFunction"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports, require("prop-types"), require("react"), require("lodash/clone"), require("lodash/has"), require("lodash/map"), require("lodash/get"), require("lodash/filter"), require("react-bootstrap/lib/Button"), require("react-bootstrap/lib/Col"), require("react-bootstrap/lib/Table"), require("react-bootstrap/lib/FormControl"), require("react-bootstrap/lib/FormGroup"), require("react-bootstrap/lib/ControlLabel"), require("react-bootstrap/lib/HelpBlock"), require("react-plupload"), require("lodash/isFunction"));
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports, global.propTypes, global.react, global.clone, global.has, global.map, global.get, global.filter, global.Button, global.Col, global.Table, global.FormControl, global.FormGroup, global.ControlLabel, global.HelpBlock, global.reactPlupload, global.isFunction);
    global.WrapPlupload = mod.exports;
  }
})(this, function (_exports, _propTypes, _react, _clone2, _has2, _map2, _get2, _filter2, _Button, _Col, _Table, _FormControl, _FormGroup, _ControlLabel, _HelpBlock, _reactPlupload, _isFunction2) {
  "use strict";

  _exports.__esModule = true;
  _exports.default = void 0;
  _propTypes = _interopRequireDefault(_propTypes);
  _react = _interopRequireDefault(_react);
  _clone2 = _interopRequireDefault(_clone2);
  _has2 = _interopRequireDefault(_has2);
  _map2 = _interopRequireDefault(_map2);
  _get2 = _interopRequireDefault(_get2);
  _filter2 = _interopRequireDefault(_filter2);
  _Button = _interopRequireDefault(_Button);
  _Col = _interopRequireDefault(_Col);
  _Table = _interopRequireDefault(_Table);
  _FormControl = _interopRequireDefault(_FormControl);
  _FormGroup = _interopRequireDefault(_FormGroup);
  _ControlLabel = _interopRequireDefault(_ControlLabel);
  _HelpBlock = _interopRequireDefault(_HelpBlock);
  _reactPlupload = _interopRequireDefault(_reactPlupload);
  _isFunction2 = _interopRequireDefault(_isFunction2);

  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }

  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }

  function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

  var WrapPlupload =
  /*#__PURE__*/
  function (_React$Component) {
    _inheritsLoose(WrapPlupload, _React$Component);

    function WrapPlupload() {
      var _this;

      _this = _React$Component.call(this) || this;
      _this.state = {
        allFiles: [],
        changed: null
      };
      _this.input = {};
      _this.custom = {};
      _this.renderField = _this.renderField.bind(_assertThisInitialized(_this));
      return _this;
    }

    var _proto = WrapPlupload.prototype;

    _proto.renderField = function renderField(props) {
      var _this2 = this;

      if (this.props.field && this.props.field.hidden && (0, _isFunction2.default)(this.props.field.hidden)) {
        if (this.props.checkHidden(this.props.field.hidden, (0, _get2.default)(props, 'parent')) === true) {
          return null;
        }
      } else if (this.props.field && this.props.field.show && (0, _isFunction2.default)(this.props.field.show)) {
        if (this.props.checkShow(this.props.field.show, (0, _get2.default)(props, 'parent')) !== true) {
          return null;
        }
      }

      var input = props.input,
          label = props.label,
          help = props.help,
          _props$meta = props.meta,
          touched = _props$meta.touched,
          error = _props$meta.error,
          valid = _props$meta.valid,
          custom = _objectWithoutProperties(props, ["input", "label", "help", "meta"]);

      var allFiles = (0, _get2.default)(props, 'input.value', []);
      this.input = input;
      this.custom = custom;
      var size = (0, _get2.default)(this.props.field, 'bsSize', this.props.size);

      var stateChange = function stateChange(plupload) {
        if (plupload.state === 2) {
          // Starting with uploading
          _this2.setState({
            pending: true
          });

          return true;
        }

        _this2.setState({
          pending: false
        });
      };

      var addedFiles = function addedFiles(plupload, files) {
        var fileList = [];
        (0, _map2.default)(files, function (file) {
          fileList.push(file.name);
        });
      };

      var fileUploaded = function fileUploaded(plupload, file, response) {
        var uploadResponse = JSON.parse(response.response);

        if ((0, _get2.default)(custom, 'multi_selection', true) === false) {
          allFiles = [uploadResponse.result];

          _this2.setState({
            changed: Date.now()
          }, function () {
            _this2.input.onChange(allFiles);
          });
        } else {
          var files = (0, _clone2.default)(allFiles);
          files.push(uploadResponse.result);
          allFiles = files;

          _this2.setState({
            changed: Date.now()
          }, function () {
            _this2.input.onBlur();

            _this2.input.onChange(allFiles);
          });
        }
      };

      var fileDelete = function fileDelete(index) {
        allFiles[index].deleted = 1;

        _this2.setState({
          changed: Date.now()
        }, function () {
          _this2.input.onBlur();

          _this2.input.onChange(allFiles);

          _this2.forceUpdate();
        });
      };

      var staticForm = (0, _get2.default)(this.props, 'static', false);

      var editRender = function editRender(files) {
        if (files.length > 0) {
          return _react.default.createElement(_Table.default, {
            striped: true,
            bordered: true,
            condensed: true,
            hover: true
          }, _react.default.createElement("thead", null, _react.default.createElement("tr", null, _react.default.createElement("th", null, "Bestand"), _react.default.createElement("th", null))), _react.default.createElement("tbody", null, (0, _map2.default)(input.value, function (file, key) {
            return !file.deleted && _react.default.createElement("tr", {
              key: key
            }, _react.default.createElement("td", null, file.file_original_name, " ", file.deleted), _react.default.createElement("td", null, _react.default.createElement(_Button.default, {
              onClick: function onClick() {
                fileDelete(key);
              }
            }, _react.default.createElement("i", {
              className: "fa fa-trash-o"
            }))));
          })));
        }
      };

      var staticRender = function staticRender(files) {
        if (files.length > 0) {
          return _react.default.createElement(_Table.default, {
            striped: true,
            bordered: true,
            condensed: true,
            hover: true
          }, _react.default.createElement("thead", null, _react.default.createElement("tr", null, _react.default.createElement("th", null, "Bestand"))), _react.default.createElement("tbody", null, (0, _map2.default)(files, function (file, key) {
            return !file.deleted && _react.default.createElement("tr", {
              key: key
            }, _react.default.createElement("td", null, file.file_original_name, " ", file.deleted));
          })));
        }
      };

      var renderTable = function renderTable() {
        var files = (0, _filter2.default)(allFiles, function (file) {
          return !file.deleted;
        });

        if (files.length > 0) {
          return staticForm ? staticRender(files) : editRender(files);
        }
      };

      var thisSize = function thisSize() {
        if (size !== 'medium') {
          return {
            bsSize: size
          };
        }
      };

      var labelSize = function labelSize() {
        if ((0, _has2.default)(_this2.props.field, 'labelSize')) {
          return _this2.props.field.labelSize;
        }

        if (_this2.props.horizontal) {
          return {
            sm: 2
          };
        }
      };

      var fieldSize = function fieldSize() {
        if ((0, _has2.default)(_this2.props.field, 'fieldSize')) {
          return _this2.props.field.fieldSize;
        }

        if (_this2.props.horizontal) {
          return {
            sm: 10
          };
        }
      };

      var component = function component() {
        var disabled = false;

        if (_this2.props.field && _this2.props.field.disabled && (0, _isFunction2.default)(_this2.props.field.disabled)) {
          disabled = _this2.props.checkDisabled(_this2.props.field.disabled());
        }

        var staticField = _this2.props.static || (0, _get2.default)(_this2.props.field, 'static', false) || disabled;

        if (staticField === false) {
          return _react.default.createElement("div", null, _react.default.createElement(_reactPlupload.default, _extends({
            key: input.name
          }, custom.conf, {
            id: "plupload_" + input.name,
            onFilesAdded: addedFiles,
            onStateChanged: stateChange,
            onFileUploaded: fileUploaded
          })));
        }
      };

      var validationState = function validationState() {
        if (touched && error) {
          return 'error';
        }

        if (touched && valid) {
          return 'success';
        }
      };

      var getLabel = function getLabel() {
        if (label) {
          return _react.default.createElement(_Col.default, _extends({
            componentClass: _ControlLabel.default
          }, labelSize()), label);
        }
      };

      return _react.default.createElement(_FormGroup.default, _extends({}, thisSize(), {
        validationState: validationState()
      }), getLabel(), _react.default.createElement(_Col.default, _extends({}, fieldSize(), {
        className: (0, _get2.default)(this.props.field, 'fieldClassName', '')
      }), component(), renderTable(), touched && error && _react.default.createElement(_FormControl.default.Feedback, null), help && (!touched || !error) && _react.default.createElement(_HelpBlock.default, null, help), touched && error && _react.default.createElement(_HelpBlock.default, null, error)));
    };

    _proto.render = function render() {
      return null;
    };

    return WrapPlupload;
  }(_react.default.Component);

  WrapPlupload.propTypes = {
    'checkDisabled': _propTypes.default.func,
    'checkHidden': _propTypes.default.func,
    'checkShow': _propTypes.default.func,
    'field': _propTypes.default.object,
    'size': _propTypes.default.string,
    'static': _propTypes.default.bool,
    'horizontal': _propTypes.default.bool.isRequired
  };
  WrapPlupload.defaultProps = {};
  var _default = WrapPlupload;
  _exports.default = _default;
});<commit_msg>check if initial value is array<commit_after>(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(["exports", "prop-types", "react", "lodash/clone", "lodash/has", "lodash/map", "lodash/get", "lodash/filter", "react-bootstrap/lib/Button", "react-bootstrap/lib/Col", "react-bootstrap/lib/Table", "react-bootstrap/lib/FormControl", "react-bootstrap/lib/FormGroup", "react-bootstrap/lib/ControlLabel", "react-bootstrap/lib/HelpBlock", "react-plupload", "lodash/isFunction"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports, require("prop-types"), require("react"), require("lodash/clone"), require("lodash/has"), require("lodash/map"), require("lodash/get"), require("lodash/filter"), require("react-bootstrap/lib/Button"), require("react-bootstrap/lib/Col"), require("react-bootstrap/lib/Table"), require("react-bootstrap/lib/FormControl"), require("react-bootstrap/lib/FormGroup"), require("react-bootstrap/lib/ControlLabel"), require("react-bootstrap/lib/HelpBlock"), require("react-plupload"), require("lodash/isFunction"));
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports, global.propTypes, global.react, global.clone, global.has, global.map, global.get, global.filter, global.Button, global.Col, global.Table, global.FormControl, global.FormGroup, global.ControlLabel, global.HelpBlock, global.reactPlupload, global.isFunction);
    global.WrapPlupload = mod.exports;
  }
})(this, function (_exports, _propTypes, _react, _clone2, _has2, _map2, _get2, _filter2, _Button, _Col, _Table, _FormControl, _FormGroup, _ControlLabel, _HelpBlock, _reactPlupload, _isFunction2) {
  "use strict";

  _exports.__esModule = true;
  _exports.default = void 0;
  _propTypes = _interopRequireDefault(_propTypes);
  _react = _interopRequireDefault(_react);
  _clone2 = _interopRequireDefault(_clone2);
  _has2 = _interopRequireDefault(_has2);
  _map2 = _interopRequireDefault(_map2);
  _get2 = _interopRequireDefault(_get2);
  _filter2 = _interopRequireDefault(_filter2);
  _Button = _interopRequireDefault(_Button);
  _Col = _interopRequireDefault(_Col);
  _Table = _interopRequireDefault(_Table);
  _FormControl = _interopRequireDefault(_FormControl);
  _FormGroup = _interopRequireDefault(_FormGroup);
  _ControlLabel = _interopRequireDefault(_ControlLabel);
  _HelpBlock = _interopRequireDefault(_HelpBlock);
  _reactPlupload = _interopRequireDefault(_reactPlupload);
  _isFunction2 = _interopRequireDefault(_isFunction2);

  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }

  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }

  function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

  var WrapPlupload =
  /*#__PURE__*/
  function (_React$Component) {
    _inheritsLoose(WrapPlupload, _React$Component);

    function WrapPlupload() {
      var _this;

      _this = _React$Component.call(this) || this;
      _this.state = {
        allFiles: [],
        changed: null
      };
      _this.input = {};
      _this.custom = {};
      _this.renderField = _this.renderField.bind(_assertThisInitialized(_this));
      return _this;
    }

    var _proto = WrapPlupload.prototype;

    _proto.renderField = function renderField(props) {
      var _this2 = this;

      if (this.props.field && this.props.field.hidden && (0, _isFunction2.default)(this.props.field.hidden)) {
        if (this.props.checkHidden(this.props.field.hidden, (0, _get2.default)(props, 'parent')) === true) {
          return null;
        }
      } else if (this.props.field && this.props.field.show && (0, _isFunction2.default)(this.props.field.show)) {
        if (this.props.checkShow(this.props.field.show, (0, _get2.default)(props, 'parent')) !== true) {
          return null;
        }
      }

      var input = props.input,
          label = props.label,
          help = props.help,
          _props$meta = props.meta,
          touched = _props$meta.touched,
          error = _props$meta.error,
          valid = _props$meta.valid,
          custom = _objectWithoutProperties(props, ["input", "label", "help", "meta"]);

      var allFiles = (0, _get2.default)(props, 'input.value', []);

      if (!(allFiles instanceof Array)) {
        allFiles = [];
      }

      this.input = input;
      this.custom = custom;
      var size = (0, _get2.default)(this.props.field, 'bsSize', this.props.size);

      var stateChange = function stateChange(plupload) {
        if (plupload.state === 2) {
          // Starting with uploading
          _this2.setState({
            pending: true
          });

          return true;
        }

        _this2.setState({
          pending: false
        });
      };

      var addedFiles = function addedFiles(plupload, files) {
        var fileList = [];
        (0, _map2.default)(files, function (file) {
          fileList.push(file.name);
        });
      };

      var fileUploaded = function fileUploaded(plupload, file, response) {
        var uploadResponse = JSON.parse(response.response);

        if ((0, _get2.default)(custom, 'multi_selection', true) === false) {
          allFiles = [uploadResponse.result];

          _this2.setState({
            changed: Date.now()
          }, function () {
            _this2.input.onChange(allFiles);
          });
        } else {
          var files = (0, _clone2.default)(allFiles);
          files.push(uploadResponse.result);
          allFiles = files;

          _this2.setState({
            changed: Date.now()
          }, function () {
            _this2.input.onBlur();

            _this2.input.onChange(allFiles);
          });
        }
      };

      var fileDelete = function fileDelete(index) {
        allFiles[index].deleted = 1;

        _this2.setState({
          changed: Date.now()
        }, function () {
          _this2.input.onBlur();

          _this2.input.onChange(allFiles);

          _this2.forceUpdate();
        });
      };

      var staticForm = (0, _get2.default)(this.props, 'static', false);

      var editRender = function editRender(files) {
        if (files.length > 0) {
          return _react.default.createElement(_Table.default, {
            striped: true,
            bordered: true,
            condensed: true,
            hover: true
          }, _react.default.createElement("thead", null, _react.default.createElement("tr", null, _react.default.createElement("th", null, "Bestand"), _react.default.createElement("th", null))), _react.default.createElement("tbody", null, (0, _map2.default)(input.value, function (file, key) {
            return !file.deleted && _react.default.createElement("tr", {
              key: key
            }, _react.default.createElement("td", null, file.file_original_name, " ", file.deleted), _react.default.createElement("td", null, _react.default.createElement(_Button.default, {
              onClick: function onClick() {
                fileDelete(key);
              }
            }, _react.default.createElement("i", {
              className: "fa fa-trash-o"
            }))));
          })));
        }
      };

      var staticRender = function staticRender(files) {
        if (files.length > 0) {
          return _react.default.createElement(_Table.default, {
            striped: true,
            bordered: true,
            condensed: true,
            hover: true
          }, _react.default.createElement("thead", null, _react.default.createElement("tr", null, _react.default.createElement("th", null, "Bestand"))), _react.default.createElement("tbody", null, (0, _map2.default)(files, function (file, key) {
            return !file.deleted && _react.default.createElement("tr", {
              key: key
            }, _react.default.createElement("td", null, file.file_original_name, " ", file.deleted));
          })));
        }
      };

      var renderTable = function renderTable() {
        var files = (0, _filter2.default)(allFiles, function (file) {
          return !file.deleted;
        });

        if (files.length > 0) {
          return staticForm ? staticRender(files) : editRender(files);
        }
      };

      var thisSize = function thisSize() {
        if (size !== 'medium') {
          return {
            bsSize: size
          };
        }
      };

      var labelSize = function labelSize() {
        if ((0, _has2.default)(_this2.props.field, 'labelSize')) {
          return _this2.props.field.labelSize;
        }

        if (_this2.props.horizontal) {
          return {
            sm: 2
          };
        }
      };

      var fieldSize = function fieldSize() {
        if ((0, _has2.default)(_this2.props.field, 'fieldSize')) {
          return _this2.props.field.fieldSize;
        }

        if (_this2.props.horizontal) {
          return {
            sm: 10
          };
        }
      };

      var component = function component() {
        var disabled = false;

        if (_this2.props.field && _this2.props.field.disabled && (0, _isFunction2.default)(_this2.props.field.disabled)) {
          disabled = _this2.props.checkDisabled(_this2.props.field.disabled());
        }

        var staticField = _this2.props.static || (0, _get2.default)(_this2.props.field, 'static', false) || disabled;

        if (staticField === false) {
          return _react.default.createElement("div", null, _react.default.createElement(_reactPlupload.default, _extends({
            key: input.name
          }, custom.conf, {
            id: "plupload_" + input.name,
            onFilesAdded: addedFiles,
            onStateChanged: stateChange,
            onFileUploaded: fileUploaded
          })));
        }
      };

      var validationState = function validationState() {
        if (touched && error) {
          return 'error';
        }

        if (touched && valid) {
          return 'success';
        }
      };

      var getLabel = function getLabel() {
        if (label) {
          return _react.default.createElement(_Col.default, _extends({
            componentClass: _ControlLabel.default
          }, labelSize()), label);
        }
      };

      return _react.default.createElement(_FormGroup.default, _extends({}, thisSize(), {
        validationState: validationState()
      }), getLabel(), _react.default.createElement(_Col.default, _extends({}, fieldSize(), {
        className: (0, _get2.default)(this.props.field, 'fieldClassName', '')
      }), component(), renderTable(), touched && error && _react.default.createElement(_FormControl.default.Feedback, null), help && (!touched || !error) && _react.default.createElement(_HelpBlock.default, null, help), touched && error && _react.default.createElement(_HelpBlock.default, null, error)));
    };

    _proto.render = function render() {
      return null;
    };

    return WrapPlupload;
  }(_react.default.Component);

  WrapPlupload.propTypes = {
    'checkDisabled': _propTypes.default.func,
    'checkHidden': _propTypes.default.func,
    'checkShow': _propTypes.default.func,
    'field': _propTypes.default.object,
    'size': _propTypes.default.string,
    'static': _propTypes.default.bool,
    'horizontal': _propTypes.default.bool.isRequired
  };
  WrapPlupload.defaultProps = {};
  var _default = WrapPlupload;
  _exports.default = _default;
});<|endoftext|>


<commit_before>/**
 * Package: svedit.math
 *
 * Licensed under the MIT License
 *
 * Copyright(c) 2010 Alexis Deveria
 * Copyright(c) 2010 Jeff Schiller
 */

// Dependencies:
// None.

var svgedit = svgedit || {};

(function() {

if (!svgedit.math) {
	svgedit.math = {};
}

// Constants
var NEAR_ZERO = 1e-14;

// Throw away SVGSVGElement used for creating matrices/transforms.
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

// Function: svgedit.math.transformPoint
// A (hopefully) quicker function to transform a point by a matrix
// (this function avoids any DOM calls and just does the math)
//
// Parameters:
// x - Float representing the x coordinate
// y - Float representing the y coordinate
// m - Matrix object to transform the point with
// Returns a x,y object representing the transformed point
svgedit.math.transformPoint = function(x, y, m) {
	return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f};
};

// Function: svgedit.math.isIdentity
// Helper function to check if the matrix performs no actual transform
// (i.e. exists for identity purposes)
//
// Parameters:
// m - The matrix object to check
//
// Returns:
// Boolean indicating whether or not the matrix is 1,0,0,1,0,0
svgedit.math.isIdentity = function(m) {
	return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0);
};

// Function: svgedit.math.matrixMultiply
// This function tries to return a SVGMatrix that is the multiplication m1*m2.
// We also round to zero when it's near zero
//
// Parameters:
// >= 2 Matrix objects to multiply
//
// Returns:
// The matrix object resulting from the calculation
svgedit.math.matrixMultiply = function() {
	var args = arguments, i = args.length, m = args[i-1];

	while(i-- > 1) {
		var m1 = args[i-1];
		m = m1.multiply(m);
	}
	if (Math.abs(m.a) < NEAR_ZERO) m.a = 0;
	if (Math.abs(m.b) < NEAR_ZERO) m.b = 0;
	if (Math.abs(m.c) < NEAR_ZERO) m.c = 0;
	if (Math.abs(m.d) < NEAR_ZERO) m.d = 0;
	if (Math.abs(m.e) < NEAR_ZERO) m.e = 0;
	if (Math.abs(m.f) < NEAR_ZERO) m.f = 0;

	return m;
};

// Function: svgedit.math.hasMatrixTransform
// See if the given transformlist includes a non-indentity matrix transform
//
// Parameters:
// tlist - The transformlist to check
//
// Returns:
// Boolean on whether or not a matrix transform was found
svgedit.math.hasMatrixTransform = function(tlist) {
	if(!tlist) return false;
	var num = tlist.numberOfItems;
	while (num--) {
		var xform = tlist.getItem(num);
		if (xform.type == 1 && !svgedit.math.isIdentity(xform.matrix)) return true;
	}
	return false;
};

// Function: svgedit.math.transformBox
// Transforms a rectangle based on the given matrix
//
// Parameters:
// l - Float with the box's left coordinate
// t - Float with the box's top coordinate
// w - Float with the box width
// h - Float with the box height
// m - Matrix object to transform the box by
//
// Returns:
// An object with the following values:
// * tl - The top left coordinate (x,y object)
// * tr - The top right coordinate (x,y object)
// * bl - The bottom left coordinate (x,y object)
// * br - The bottom right coordinate (x,y object)
// * aabox - Object with the following values:
// * Float with the axis-aligned x coordinate
// * Float with the axis-aligned y coordinate
// * Float with the axis-aligned width coordinate
// * Float with the axis-aligned height coordinate
svgedit.math.transformBox = function(l, t, w, h, m) {
	var topleft = {x:l,y:t},
		topright = {x:(l+w),y:t},
		botright = {x:(l+w),y:(t+h)},
		botleft = {x:l,y:(t+h)};
	var transformPoint = svgedit.math.transformPoint;
	topleft = transformPoint( topleft.x, topleft.y, m );
	var minx = topleft.x,
		maxx = topleft.x,
		miny = topleft.y,
		maxy = topleft.y;
	topright = transformPoint( topright.x, topright.y, m );
	minx = Math.min(minx, topright.x);
	maxx = Math.max(maxx, topright.x);
	miny = Math.min(miny, topright.y);
	maxy = Math.max(maxy, topright.y);
	botleft = transformPoint( botleft.x, botleft.y, m);
	minx = Math.min(minx, botleft.x);
	maxx = Math.max(maxx, botleft.x);
	miny = Math.min(miny, botleft.y);
	maxy = Math.max(maxy, botleft.y);
	botright = transformPoint( botright.x, botright.y, m );
	minx = Math.min(minx, botright.x);
	maxx = Math.max(maxx, botright.x);
	miny = Math.min(miny, botright.y);
	maxy = Math.max(maxy, botright.y);

	return {tl:topleft, tr:topright, bl:botleft, br:botright,
			aabox: {x:minx, y:miny, width:(maxx-minx), height:(maxy-miny)} };
};

// Function: svgedit.math.transformListToTransform
// This returns a single matrix Transform for a given Transform List
// (this is the equivalent of SVGTransformList.consolidate() but unlike
//  that method, this one does not modify the actual SVGTransformList)
// This function is very liberal with its min,max arguments
//
// Parameters:
// tlist - The transformlist object
// min - Optional integer indicating start transform position
// max - Optional integer indicating end transform position
//
// Returns:
// A single matrix transform object
svgedit.math.transformListToTransform = function(tlist, min, max) {
	if(tlist == null) {
		// Or should tlist = null have been prevented before this?
		return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
	}
	var min = min == undefined ? 0 : min;
	var max = max == undefined ? (tlist.numberOfItems-1) : max;
	min = parseInt(min);
	max = parseInt(max);
	if (min > max) { var temp = max; max = min; min = temp; }
	var m = svg.createSVGMatrix();
	for (var i = min; i <= max; ++i) {
		// if our indices are out of range, just use a harmless identity matrix
		var mtom = (i >= 0 && i < tlist.numberOfItems ?
						tlist.getItem(i).matrix :
						svg.createSVGMatrix());
		m = svgedit.math.matrixMultiply(m, mtom);
	}
	return svg.createSVGTransformFromMatrix(m);
};

// Function: svgedit.math.getMatrix
// Get the matrix object for a given element
//
// Parameters:
// elem - The DOM element to check
//
// Returns:
// The matrix object associated with the element's transformlist
svgedit.math.getMatrix = function(elem) {
	var tlist = svgedit.transformlist.getTransformList(elem);
	return svgedit.math.transformListToTransform(tlist).matrix;
};

// Function: svgedit.math.snapToAngle
// Returns a 45 degree angle coordinate associated with the two given
// coordinates
//
// Parameters:
// x1 - First coordinate's x value
// x2 - Second coordinate's x value
// y1 - First coordinate's y value
// y2 - Second coordinate's y value
//
// Returns:
// Object with the following values:
// x - The angle-snapped x value
// y - The angle-snapped y value
// snapangle - The angle at which to snap
svgedit.math.snapToAngle = function(x1,y1,x2,y2) {
	var snap = Math.PI/4; // 45 degrees
	var dx = x2 - x1;
	var dy = y2 - y1;
	var angle = Math.atan2(dy,dx);
	var dist = Math.sqrt(dx * dx + dy * dy);
	var snapangle= Math.round(angle/snap)*snap;
	var x = x1 + dist*Math.cos(snapangle);
	var y = y1 + dist*Math.sin(snapangle);
	//console.log(x1,y1,x2,y2,x,y,angle)
	return {x:x, y:y, a:snapangle};
};

// Function: rectsIntersect
// Check if two rectangles (BBoxes objects) intersect each other
//
// Paramaters:
// r1 - The first BBox-like object
// r2 - The second BBox-like object
//
// Returns:
// Boolean that's true if rectangles intersect
svgedit.math.rectsIntersect = function(r1, r2) {
	return r2.x < (r1.x+r1.width) &&
		(r2.x+r2.width) > r1.x &&
		r2.y < (r1.y+r1.height) &&
		(r2.y+r2.height) > r1.y;
};

})();<commit_msg>added missing base 10 radix<commit_after>/**
 * Package: svedit.math
 *
 * Licensed under the MIT License
 *
 * Copyright(c) 2010 Alexis Deveria
 * Copyright(c) 2010 Jeff Schiller
 */

// Dependencies:
// None.

var svgedit = svgedit || {};

(function() {

if (!svgedit.math) {
	svgedit.math = {};
}

// Constants
var NEAR_ZERO = 1e-14;

// Throw away SVGSVGElement used for creating matrices/transforms.
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

// Function: svgedit.math.transformPoint
// A (hopefully) quicker function to transform a point by a matrix
// (this function avoids any DOM calls and just does the math)
//
// Parameters:
// x - Float representing the x coordinate
// y - Float representing the y coordinate
// m - Matrix object to transform the point with
// Returns a x,y object representing the transformed point
svgedit.math.transformPoint = function(x, y, m) {
	return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f};
};

// Function: svgedit.math.isIdentity
// Helper function to check if the matrix performs no actual transform
// (i.e. exists for identity purposes)
//
// Parameters:
// m - The matrix object to check
//
// Returns:
// Boolean indicating whether or not the matrix is 1,0,0,1,0,0
svgedit.math.isIdentity = function(m) {
	return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0);
};

// Function: svgedit.math.matrixMultiply
// This function tries to return a SVGMatrix that is the multiplication m1*m2.
// We also round to zero when it's near zero
//
// Parameters:
// >= 2 Matrix objects to multiply
//
// Returns:
// The matrix object resulting from the calculation
svgedit.math.matrixMultiply = function() {
	var args = arguments, i = args.length, m = args[i-1];

	while(i-- > 1) {
		var m1 = args[i-1];
		m = m1.multiply(m);
	}
	if (Math.abs(m.a) < NEAR_ZERO) m.a = 0;
	if (Math.abs(m.b) < NEAR_ZERO) m.b = 0;
	if (Math.abs(m.c) < NEAR_ZERO) m.c = 0;
	if (Math.abs(m.d) < NEAR_ZERO) m.d = 0;
	if (Math.abs(m.e) < NEAR_ZERO) m.e = 0;
	if (Math.abs(m.f) < NEAR_ZERO) m.f = 0;

	return m;
};

// Function: svgedit.math.hasMatrixTransform
// See if the given transformlist includes a non-indentity matrix transform
//
// Parameters:
// tlist - The transformlist to check
//
// Returns:
// Boolean on whether or not a matrix transform was found
svgedit.math.hasMatrixTransform = function(tlist) {
	if(!tlist) return false;
	var num = tlist.numberOfItems;
	while (num--) {
		var xform = tlist.getItem(num);
		if (xform.type == 1 && !svgedit.math.isIdentity(xform.matrix)) return true;
	}
	return false;
};

// Function: svgedit.math.transformBox
// Transforms a rectangle based on the given matrix
//
// Parameters:
// l - Float with the box's left coordinate
// t - Float with the box's top coordinate
// w - Float with the box width
// h - Float with the box height
// m - Matrix object to transform the box by
//
// Returns:
// An object with the following values:
// * tl - The top left coordinate (x,y object)
// * tr - The top right coordinate (x,y object)
// * bl - The bottom left coordinate (x,y object)
// * br - The bottom right coordinate (x,y object)
// * aabox - Object with the following values:
// * Float with the axis-aligned x coordinate
// * Float with the axis-aligned y coordinate
// * Float with the axis-aligned width coordinate
// * Float with the axis-aligned height coordinate
svgedit.math.transformBox = function(l, t, w, h, m) {
	var topleft = {x:l,y:t},
		topright = {x:(l+w),y:t},
		botright = {x:(l+w),y:(t+h)},
		botleft = {x:l,y:(t+h)};
	var transformPoint = svgedit.math.transformPoint;
	topleft = transformPoint( topleft.x, topleft.y, m );
	var minx = topleft.x,
		maxx = topleft.x,
		miny = topleft.y,
		maxy = topleft.y;
	topright = transformPoint( topright.x, topright.y, m );
	minx = Math.min(minx, topright.x);
	maxx = Math.max(maxx, topright.x);
	miny = Math.min(miny, topright.y);
	maxy = Math.max(maxy, topright.y);
	botleft = transformPoint( botleft.x, botleft.y, m);
	minx = Math.min(minx, botleft.x);
	maxx = Math.max(maxx, botleft.x);
	miny = Math.min(miny, botleft.y);
	maxy = Math.max(maxy, botleft.y);
	botright = transformPoint( botright.x, botright.y, m );
	minx = Math.min(minx, botright.x);
	maxx = Math.max(maxx, botright.x);
	miny = Math.min(miny, botright.y);
	maxy = Math.max(maxy, botright.y);

	return {tl:topleft, tr:topright, bl:botleft, br:botright,
			aabox: {x:minx, y:miny, width:(maxx-minx), height:(maxy-miny)} };
};

// Function: svgedit.math.transformListToTransform
// This returns a single matrix Transform for a given Transform List
// (this is the equivalent of SVGTransformList.consolidate() but unlike
//  that method, this one does not modify the actual SVGTransformList)
// This function is very liberal with its min,max arguments
//
// Parameters:
// tlist - The transformlist object
// min - Optional integer indicating start transform position
// max - Optional integer indicating end transform position
//
// Returns:
// A single matrix transform object
svgedit.math.transformListToTransform = function(tlist, min, max) {
	if(tlist == null) {
		// Or should tlist = null have been prevented before this?
		return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
	}
	min = min || 0;
	max = max || (tlist.numberOfItems - 1);
	min = parseInt(min, 10);
	max = parseInt(max, 10);
	if (min > max) { var temp = max; max = min; min = temp; }
	var m = svg.createSVGMatrix();
	for (var i = min; i <= max; ++i) {
		// if our indices are out of range, just use a harmless identity matrix
		var mtom = (i >= 0 && i < tlist.numberOfItems ?
						tlist.getItem(i).matrix :
						svg.createSVGMatrix());
		m = svgedit.math.matrixMultiply(m, mtom);
	}
	return svg.createSVGTransformFromMatrix(m);
};

// Function: svgedit.math.getMatrix
// Get the matrix object for a given element
//
// Parameters:
// elem - The DOM element to check
//
// Returns:
// The matrix object associated with the element's transformlist
svgedit.math.getMatrix = function(elem) {
	var tlist = svgedit.transformlist.getTransformList(elem);
	return svgedit.math.transformListToTransform(tlist).matrix;
};

// Function: svgedit.math.snapToAngle
// Returns a 45 degree angle coordinate associated with the two given
// coordinates
//
// Parameters:
// x1 - First coordinate's x value
// x2 - Second coordinate's x value
// y1 - First coordinate's y value
// y2 - Second coordinate's y value
//
// Returns:
// Object with the following values:
// x - The angle-snapped x value
// y - The angle-snapped y value
// snapangle - The angle at which to snap
svgedit.math.snapToAngle = function(x1,y1,x2,y2) {
	var snap = Math.PI/4; // 45 degrees
	var dx = x2 - x1;
	var dy = y2 - y1;
	var angle = Math.atan2(dy,dx);
	var dist = Math.sqrt(dx * dx + dy * dy);
	var snapangle= Math.round(angle/snap)*snap;
	var x = x1 + dist*Math.cos(snapangle);
	var y = y1 + dist*Math.sin(snapangle);
	//console.log(x1,y1,x2,y2,x,y,angle)
	return {x:x, y:y, a:snapangle};
};

// Function: rectsIntersect
// Check if two rectangles (BBoxes objects) intersect each other
//
// Paramaters:
// r1 - The first BBox-like object
// r2 - The second BBox-like object
//
// Returns:
// Boolean that's true if rectangles intersect
svgedit.math.rectsIntersect = function(r1, r2) {
	return r2.x < (r1.x+r1.width) &&
		(r2.x+r2.width) > r1.x &&
		r2.y < (r1.y+r1.height) &&
		(r2.y+r2.height) > r1.y;
};

})();<|endoftext|>


<commit_before>module.exports = function(config){
  config.set({
    basePath: '../../',

    files: [
//      'app/lib/angular/angular-*.js',
      'dev/lib/angular.js',
      'test/lib/angular-mocks.js',
      'dev/lib/**/*.js',
      'dev/js/faber/faber.js',
      'dev/js/faber/**/*.js',
      'test/helpers/**/*',
      'test/unit/**/*Spec.coffee'
    ],

    exclude: [
//      'app/lib/angular/angular-loader.js',
//      'app/lib/angular/*.min.js',
//      'app/lib/angular/angular-scenario.js'
    ],

    preprocessors: {
      '**/*.coffee': ['coffee']
    },

    autoWatch: false,

    port: 9018,
    runnerPort: 9101,

    frameworks: ['jasmine'],

    browsers: ['PhantomJS'],

//    logLevel: config.LOG_ERROR,
    reporters: ['dots'],

    plugins: [
      'karma-phantomjs-launcher',
      'karma-coffee-preprocessor',
      'karma-chrome-launcher',
      'karma-firefox-launcher',
      'karma-jasmine',
      'karma-coverage'
    ]

//    junitReporter : {
//      outputFile: 'test_out/unit.xml',
//      suite: 'unit'
//    }

  })}<commit_msg>Clean up Karma config file<commit_after>module.exports = function(config){
  config.set({
    basePath: '../../',

    preprocessors: {
      '**/*.coffee': ['coffee']
    },

    port: 9018,
    runnerPort: 9101,

    frameworks: ['jasmine'],

    browsers: ['PhantomJS'],

    reporters: ['dots'],

    plugins: [
      'karma-phantomjs-launcher',
      'karma-coffee-preprocessor',
      'karma-chrome-launcher',
      'karma-firefox-launcher',
      'karma-jasmine',
      'karma-coverage'
    ]

  })}<|endoftext|>


<commit_before>var debug = require('debug')('SebastiaanLuca:Volume:Volume');

var Loudness = require('loudness');

//

var Volume = function () {

    this.getVolume = function () {
        return Loudness.getVolume(function (err, vol) {
            if (err) {
                debug('Error getting volume', err);

                return;
            }

            return vol;
        })
    };

    this.setVolume = function (vol) {
        Loudness.setVolume(vol, function (err) {
            if (err) {
                debug('Error setting volume', err);

                return;
            }

            debug('Speaker volume set to %s%', vol);
        });
    };

};

// Singleton
module.exports = new Volume();<commit_msg>Enhance volume module<commit_after>var debug = require('debug')('SebastiaanLuca:Volume:Volume');

var Loudness = require('loudness');
var deasync = require('deasync');

//

var Volume = function () {

    this.getVolume = function () {
        var volume = deasync(Loudness.getVolume);

        return volume();
    };

    this.setVolume = function (vol) {
        if (vol < 0) {
            vol = 0;
        } else if (vol > 100) {
            vol = 100;
        }

        Loudness.setVolume(vol, function (err) {
            if (err) {
                debug('Error setting volume', err);

                return;
            }

            debug('System volume set to %s%', vol);
        });
    };

    this.decreaseBy = function (num) {
        this.setVolume(this.getVolume() - num);
    };

    this.increaseBy = function (num) {
        this.setVolume(this.getVolume() + num);
    };

};

// Singleton
module.exports = new Volume();<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { genKey } from 'draft-js';
import { List } from 'immutable';
import Entry from './Entry';
import addMention from '../modifiers/addMention';
import decodeOffsetKey from '../utils/decodeOffsetKey';
import getSearchText from '../utils/getSearchText';
import defaultEntryComponent from './Entry/defaultEntryComponent';

export default class MentionSuggestions extends Component {

  static propTypes = {
    entityMutability: PropTypes.oneOf([
      'SEGMENTED',
      'IMMUTABLE',
      'MUTABLE',
    ]),
    entryComponent: PropTypes.func,
    onAddMention: PropTypes.func,
    suggestions: (props, propName, componentName) => {
      if (!List.isList(props[propName])) {
        return new Error(
          `Invalid prop \`${propName}\` supplied to \`${componentName}\`. should be an instance of immutable list.`
        );
      }
      return undefined;
    },
  };

  state = {
    isActive: false,
    focusedOptionIndex: 0,
  };

  componentWillMount() {
    this.key = genKey();
    this.props.callbacks.onChange = this.onEditorStateChange;
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.suggestions.size === 0 && this.state.isActive) {
      this.closeDropdown();
    }
  }

  componentDidUpdate = (prevProps, prevState) => {
    if (this.popover) {
      // In case the list shrinks there should be still an option focused.
      // Note: this might run multiple times and deduct 1 until the condition is
      // not fullfilled anymore.
      const size = this.props.suggestions.size;
      if (size > 0 && this.state.focusedOptionIndex >= size) {
        this.setState({
          focusedOptionIndex: size - 1,
        });
      }

      // Note: this is a simple protection for the error when componentDidUpdate
      // try to get new getPortalClientRect, but the key already was deleted by
      // previous action. (right now, it only can happened when set the mention
      // trigger to be multi-characters which not supported anyway!)
      if (!this.props.store.getAllSearches().has(this.activeOffsetKey)) {
        return;
      }

      const decoratorRect = this.props.store.getPortalClientRect(this.activeOffsetKey);
      const newStyles = this.props.positionSuggestions({
        decoratorRect,
        prevProps,
        prevState,
        props: this.props,
        state: this.state,
        popover: this.popover,
      });
      Object.keys(newStyles).forEach((key) => {
        this.popover.style[key] = newStyles[key];
      });
    }
  };

  componentWillUnmount = () => {
    this.props.callbacks.onChange = undefined;
  };

  onEditorStateChange = (editorState) => {
    const searches = this.props.store.getAllSearches();

    // if no search portal is active there is no need to show the popover
    if (searches.size === 0) {
      return editorState;
    }

    const removeList = () => {
      this.props.store.resetEscapedSearch();
      this.closeDropdown();
      return editorState;
    };

    // get the current selection
    const selection = editorState.getSelection();
    const anchorKey = selection.getAnchorKey();
    const anchorOffset = selection.getAnchorOffset();

    // the list should not be visible if a range is selected or the editor has no focus
    if (!selection.isCollapsed() || !selection.getHasFocus()) return removeList();

    // identify the start & end positon of each search-text
    const offsetDetails = searches.map((offsetKey) => decodeOffsetKey(offsetKey));

    // a leave can be empty when it is removed due e.g. using backspace
    const leaves = offsetDetails
      .filter(({ blockKey }) => blockKey === anchorKey)
      .map(({ blockKey, decoratorKey, leafKey }) => (
        editorState
          .getBlockTree(blockKey)
          .getIn([decoratorKey, 'leaves', leafKey])
      ));

    // if all leaves are undefined the popover should be removed
    if (leaves.every((leave) => leave === undefined)) {
      return removeList();
    }

    // Checks that the cursor is after the @ character but still somewhere in
    // the word (search term). Setting it to allow the cursor to be left of
    // the @ causes troubles due selection confusion.
    const selectionIsInsideWord = leaves
      .filter((leave) => leave !== undefined)
      .map(({ start, end }) => (
        (start === 0 && anchorOffset === 1 && anchorOffset <= end) || // @ is the first character
        (anchorOffset > start + 1 && anchorOffset <= end) // @ is in the text or at the end
      ));

    if (selectionIsInsideWord.every((isInside) => isInside === false)) return removeList();

    const lastActiveOffsetKey = this.activeOffsetKey;
    this.activeOffsetKey = selectionIsInsideWord
      .filter((value) => value === true)
      .keySeq()
      .first();

    this.onSearchChange(editorState, selection, this.activeOffsetKey, lastActiveOffsetKey);

    // make sure the escaped search is reseted in the cursor since the user
    // already switched to another mention search
    if (!this.props.store.isEscaped(this.activeOffsetKey)) {
      this.props.store.resetEscapedSearch();
    }

    // If none of the above triggered to close the window, it's safe to assume
    // the dropdown should be open. This is useful when a user focuses on another
    // input field and then comes back: the dropdown will again.
    if (!this.state.isActive && !this.props.store.isEscaped(this.activeOffsetKey)) {
      this.openDropdown();
    }

    // makes sure the focused index is reseted every time a new selection opens
    // or the selection was moved to another mention search
    if (this.lastSelectionIsInsideWord === undefined ||
        !selectionIsInsideWord.equals(this.lastSelectionIsInsideWord)) {
      this.setState({
        focusedOptionIndex: 0,
      });
    }

    this.lastSelectionIsInsideWord = selectionIsInsideWord;

    return editorState;
  };

  onSearchChange = (editorState, selection, activeOffsetKey, lastActiveOffsetKey) => {
    const { word } = getSearchText(editorState, selection);
    const searchValue = word.substring(1, word.length);
    if (this.lastSearchValue !== searchValue || activeOffsetKey !== lastActiveOffsetKey) {
      this.lastSearchValue = searchValue;
      this.props.onSearchChange({ value: searchValue });
    }
  };

  onDownArrow = (keyboardEvent) => {
    keyboardEvent.preventDefault();
    const newIndex = this.state.focusedOptionIndex + 1;
    this.onMentionFocus(newIndex >= this.props.suggestions.size ? 0 : newIndex);
  };

  onTab = (keyboardEvent) => {
    keyboardEvent.preventDefault();
    this.commitSelection();
  };

  onUpArrow = (keyboardEvent) => {
    keyboardEvent.preventDefault();
    if (this.props.suggestions.size > 0) {
      const newIndex = this.state.focusedOptionIndex - 1;
      this.onMentionFocus(newIndex < 0 ? this.props.suggestions.size - 1 : newIndex);
    }
  };

  onEscape = (keyboardEvent) => {
    keyboardEvent.preventDefault();

    const activeOffsetKey = this.lastSelectionIsInsideWord
      .filter((value) => value === true)
      .keySeq()
      .first();
    this.props.store.escapeSearch(activeOffsetKey);
    this.closeDropdown();

    // to force a re-render of the outer component to change the aria props
    this.props.store.setEditorState(this.props.store.getEditorState());
  };

  onMentionSelect = (mention) => {
    // Note: This can happen in case a user typed @xxx (invalid mention) and
    // then hit Enter. Then the mention will be undefined.
    if (!mention) {
      return;
    }

    if (this.props.onAddMention) {
      this.props.onAddMention(mention);
    }

    this.closeDropdown();
    const newEditorState = addMention(
      this.props.store.getEditorState(),
      mention,
      this.props.mentionPrefix,
      this.props.mentionTrigger,
      this.props.entityMutability,
    );
    this.props.store.setEditorState(newEditorState);
  };

  onMentionFocus = (index) => {
    const descendant = `mention-option-${this.key}-${index}`;
    this.props.ariaProps.ariaActiveDescendantID = descendant;
    this.state.focusedOptionIndex = index;

    // to force a re-render of the outer component to change the aria props
    this.props.store.setEditorState(this.props.store.getEditorState());
  };

  commitSelection = () => {
    this.onMentionSelect(this.props.suggestions.get(this.state.focusedOptionIndex));
    return 'handled';
  };

  openDropdown = () => {
    // This is a really nasty way of attaching & releasing the key related functions.
    // It assumes that the keyFunctions object will not loose its reference and
    // by this we can replace inner parameters spread over different modules.
    // This better be some registering & unregistering logic. PRs are welcome :)
    this.props.callbacks.onDownArrow = this.onDownArrow;
    this.props.callbacks.onUpArrow = this.onUpArrow;
    this.props.callbacks.onEscape = this.onEscape;
    this.props.callbacks.handleReturn = this.commitSelection;
    this.props.callbacks.onTab = this.onTab;

    const descendant = `mention-option-${this.key}-${this.state.focusedOptionIndex}`;
    this.props.ariaProps.ariaActiveDescendantID = descendant;
    this.props.ariaProps.ariaOwneeID = `mentions-list-${this.key}`;
    this.props.ariaProps.ariaHasPopup = 'true';
    this.props.ariaProps.ariaExpanded = 'true';
    this.setState({
      isActive: true,
    });

    if (this.props.onOpen) {
      this.props.onOpen();
    }
  };

  closeDropdown = () => {
    // make sure none of these callbacks are triggered
    this.props.callbacks.onDownArrow = undefined;
    this.props.callbacks.onUpArrow = undefined;
    this.props.callbacks.onTab = undefined;
    this.props.callbacks.onEscape = undefined;
    this.props.callbacks.handleReturn = undefined;
    this.props.ariaProps.ariaHasPopup = 'false';
    this.props.ariaProps.ariaExpanded = 'false';
    this.props.ariaProps.ariaActiveDescendantID = undefined;
    this.props.ariaProps.ariaOwneeID = undefined;
    this.setState({
      isActive: false,
    });

    if (this.props.onClose) {
      this.props.onClose();
    }
  };

  render() {
    if (!this.state.isActive) {
      return null;
    }

    const {
      entryComponent,
      onClose, // eslint-disable-line no-unused-vars
      onOpen, // eslint-disable-line no-unused-vars
      onAddMention, // eslint-disable-line no-unused-vars, no-shadow
      onSearchChange, // eslint-disable-line no-unused-vars, no-shadow
      suggestions, // eslint-disable-line no-unused-vars
      ariaProps, // eslint-disable-line no-unused-vars
      callbacks, // eslint-disable-line no-unused-vars
      theme = {},
      store, // eslint-disable-line no-unused-vars
      entityMutability, // eslint-disable-line no-unused-vars
      positionSuggestions, // eslint-disable-line no-unused-vars
      mentionTrigger, // eslint-disable-line no-unused-vars
      mentionPrefix, // eslint-disable-line no-unused-vars
      ...elementProps } = this.props;

    return (
      <div
        {...elementProps}
        className={theme.mentionSuggestions}
        role="listbox"
        id={`mentions-list-${this.key}`}
        ref={(element) => { this.popover = element; }}
      >
        {
          this.props.suggestions.map((mention, index) => (
            <Entry
              key={mention.has('id') ? mention.get('id') : mention.get('name')}
              onMentionSelect={this.onMentionSelect}
              onMentionFocus={this.onMentionFocus}
              isFocused={this.state.focusedOptionIndex === index}
              mention={mention}
              index={index}
              id={`mention-option-${this.key}-${index}`}
              theme={theme}
              searchValue={this.lastSearchValue}
              entryComponent={entryComponent || defaultEntryComponent}
            />
          )).toJS()
        }
      </div>
    );
  }
}
<commit_msg>Reopens dropdown if new suggestions are available.<commit_after>import React, { Component, PropTypes } from 'react';
import { genKey } from 'draft-js';
import { List } from 'immutable';
import Entry from './Entry';
import addMention from '../modifiers/addMention';
import decodeOffsetKey from '../utils/decodeOffsetKey';
import getSearchText from '../utils/getSearchText';
import defaultEntryComponent from './Entry/defaultEntryComponent';

export default class MentionSuggestions extends Component {

  static propTypes = {
    entityMutability: PropTypes.oneOf([
      'SEGMENTED',
      'IMMUTABLE',
      'MUTABLE',
    ]),
    entryComponent: PropTypes.func,
    onAddMention: PropTypes.func,
    suggestions: (props, propName, componentName) => {
      if (!List.isList(props[propName])) {
        return new Error(
          `Invalid prop \`${propName}\` supplied to \`${componentName}\`. should be an instance of immutable list.`
        );
      }
      return undefined;
    },
  };

  state = {
    isActive: false,
    focusedOptionIndex: 0,
  };

  componentWillMount() {
    this.key = genKey();
    this.props.callbacks.onChange = this.onEditorStateChange;
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.suggestions.size === 0 && this.state.isActive) {
      this.closeDropdown();
    }
    if (nextProps.suggestions.size >= 1 && !this.state.isActive){
      this.openDropdown();
    }
  }

  componentDidUpdate = (prevProps, prevState) => {
    if (this.popover) {
      // In case the list shrinks there should be still an option focused.
      // Note: this might run multiple times and deduct 1 until the condition is
      // not fullfilled anymore.
      const size = this.props.suggestions.size;
      if (size > 0 && this.state.focusedOptionIndex >= size) {
        this.setState({
          focusedOptionIndex: size - 1,
        });
      }

      // Note: this is a simple protection for the error when componentDidUpdate
      // try to get new getPortalClientRect, but the key already was deleted by
      // previous action. (right now, it only can happened when set the mention
      // trigger to be multi-characters which not supported anyway!)
      if (!this.props.store.getAllSearches().has(this.activeOffsetKey)) {
        return;
      }

      const decoratorRect = this.props.store.getPortalClientRect(this.activeOffsetKey);
      const newStyles = this.props.positionSuggestions({
        decoratorRect,
        prevProps,
        prevState,
        props: this.props,
        state: this.state,
        popover: this.popover,
      });
      Object.keys(newStyles).forEach((key) => {
        this.popover.style[key] = newStyles[key];
      });
    }
  };

  componentWillUnmount = () => {
    this.props.callbacks.onChange = undefined;
  };

  onEditorStateChange = (editorState) => {
    const searches = this.props.store.getAllSearches();

    // if no search portal is active there is no need to show the popover
    if (searches.size === 0) {
      return editorState;
    }

    const removeList = () => {
      this.props.store.resetEscapedSearch();
      this.closeDropdown();
      return editorState;
    };

    // get the current selection
    const selection = editorState.getSelection();
    const anchorKey = selection.getAnchorKey();
    const anchorOffset = selection.getAnchorOffset();

    // the list should not be visible if a range is selected or the editor has no focus
    if (!selection.isCollapsed() || !selection.getHasFocus()) return removeList();

    // identify the start & end positon of each search-text
    const offsetDetails = searches.map((offsetKey) => decodeOffsetKey(offsetKey));

    // a leave can be empty when it is removed due e.g. using backspace
    const leaves = offsetDetails
      .filter(({ blockKey }) => blockKey === anchorKey)
      .map(({ blockKey, decoratorKey, leafKey }) => (
        editorState
          .getBlockTree(blockKey)
          .getIn([decoratorKey, 'leaves', leafKey])
      ));

    // if all leaves are undefined the popover should be removed
    if (leaves.every((leave) => leave === undefined)) {
      return removeList();
    }

    // Checks that the cursor is after the @ character but still somewhere in
    // the word (search term). Setting it to allow the cursor to be left of
    // the @ causes troubles due selection confusion.
    const selectionIsInsideWord = leaves
      .filter((leave) => leave !== undefined)
      .map(({ start, end }) => (
        (start === 0 && anchorOffset === 1 && anchorOffset <= end) || // @ is the first character
        (anchorOffset > start + 1 && anchorOffset <= end) // @ is in the text or at the end
      ));

    if (selectionIsInsideWord.every((isInside) => isInside === false)) return removeList();

    const lastActiveOffsetKey = this.activeOffsetKey;
    this.activeOffsetKey = selectionIsInsideWord
      .filter((value) => value === true)
      .keySeq()
      .first();

    this.onSearchChange(editorState, selection, this.activeOffsetKey, lastActiveOffsetKey);

    // make sure the escaped search is reseted in the cursor since the user
    // already switched to another mention search
    if (!this.props.store.isEscaped(this.activeOffsetKey)) {
      this.props.store.resetEscapedSearch();
    }

    // If none of the above triggered to close the window, it's safe to assume
    // the dropdown should be open. This is useful when a user focuses on another
    // input field and then comes back: the dropdown will again.
    if (!this.state.isActive && !this.props.store.isEscaped(this.activeOffsetKey)) {
      this.openDropdown();
    }

    // makes sure the focused index is reseted every time a new selection opens
    // or the selection was moved to another mention search
    if (this.lastSelectionIsInsideWord === undefined ||
        !selectionIsInsideWord.equals(this.lastSelectionIsInsideWord)) {
      this.setState({
        focusedOptionIndex: 0,
      });
    }

    this.lastSelectionIsInsideWord = selectionIsInsideWord;

    return editorState;
  };

  onSearchChange = (editorState, selection, activeOffsetKey, lastActiveOffsetKey) => {
    const { word } = getSearchText(editorState, selection);
    const searchValue = word.substring(1, word.length);
    if (this.lastSearchValue !== searchValue || activeOffsetKey !== lastActiveOffsetKey) {
      this.lastSearchValue = searchValue;
      this.props.onSearchChange({ value: searchValue });
    }
  };

  onDownArrow = (keyboardEvent) => {
    keyboardEvent.preventDefault();
    const newIndex = this.state.focusedOptionIndex + 1;
    this.onMentionFocus(newIndex >= this.props.suggestions.size ? 0 : newIndex);
  };

  onTab = (keyboardEvent) => {
    keyboardEvent.preventDefault();
    this.commitSelection();
  };

  onUpArrow = (keyboardEvent) => {
    keyboardEvent.preventDefault();
    if (this.props.suggestions.size > 0) {
      const newIndex = this.state.focusedOptionIndex - 1;
      this.onMentionFocus(newIndex < 0 ? this.props.suggestions.size - 1 : newIndex);
    }
  };

  onEscape = (keyboardEvent) => {
    keyboardEvent.preventDefault();

    const activeOffsetKey = this.lastSelectionIsInsideWord
      .filter((value) => value === true)
      .keySeq()
      .first();
    this.props.store.escapeSearch(activeOffsetKey);
    this.closeDropdown();

    // to force a re-render of the outer component to change the aria props
    this.props.store.setEditorState(this.props.store.getEditorState());
  };

  onMentionSelect = (mention) => {
    // Note: This can happen in case a user typed @xxx (invalid mention) and
    // then hit Enter. Then the mention will be undefined.
    if (!mention) {
      return;
    }

    if (this.props.onAddMention) {
      this.props.onAddMention(mention);
    }

    this.closeDropdown();
    const newEditorState = addMention(
      this.props.store.getEditorState(),
      mention,
      this.props.mentionPrefix,
      this.props.mentionTrigger,
      this.props.entityMutability,
    );
    this.props.store.setEditorState(newEditorState);
  };

  onMentionFocus = (index) => {
    const descendant = `mention-option-${this.key}-${index}`;
    this.props.ariaProps.ariaActiveDescendantID = descendant;
    this.state.focusedOptionIndex = index;

    // to force a re-render of the outer component to change the aria props
    this.props.store.setEditorState(this.props.store.getEditorState());
  };

  commitSelection = () => {
    this.onMentionSelect(this.props.suggestions.get(this.state.focusedOptionIndex));
    return 'handled';
  };

  openDropdown = () => {
    // This is a really nasty way of attaching & releasing the key related functions.
    // It assumes that the keyFunctions object will not loose its reference and
    // by this we can replace inner parameters spread over different modules.
    // This better be some registering & unregistering logic. PRs are welcome :)
    this.props.callbacks.onDownArrow = this.onDownArrow;
    this.props.callbacks.onUpArrow = this.onUpArrow;
    this.props.callbacks.onEscape = this.onEscape;
    this.props.callbacks.handleReturn = this.commitSelection;
    this.props.callbacks.onTab = this.onTab;

    const descendant = `mention-option-${this.key}-${this.state.focusedOptionIndex}`;
    this.props.ariaProps.ariaActiveDescendantID = descendant;
    this.props.ariaProps.ariaOwneeID = `mentions-list-${this.key}`;
    this.props.ariaProps.ariaHasPopup = 'true';
    this.props.ariaProps.ariaExpanded = 'true';
    this.setState({
      isActive: true,
    });

    if (this.props.onOpen) {
      this.props.onOpen();
    }
  };

  closeDropdown = () => {
    // make sure none of these callbacks are triggered
    this.props.callbacks.onDownArrow = undefined;
    this.props.callbacks.onUpArrow = undefined;
    this.props.callbacks.onTab = undefined;
    this.props.callbacks.onEscape = undefined;
    this.props.callbacks.handleReturn = undefined;
    this.props.ariaProps.ariaHasPopup = 'false';
    this.props.ariaProps.ariaExpanded = 'false';
    this.props.ariaProps.ariaActiveDescendantID = undefined;
    this.props.ariaProps.ariaOwneeID = undefined;
    this.setState({
      isActive: false,
    });

    if (this.props.onClose) {
      this.props.onClose();
    }
  };

  render() {
    if (!this.state.isActive) {
      return null;
    }

    const {
      entryComponent,
      onClose, // eslint-disable-line no-unused-vars
      onOpen, // eslint-disable-line no-unused-vars
      onAddMention, // eslint-disable-line no-unused-vars, no-shadow
      onSearchChange, // eslint-disable-line no-unused-vars, no-shadow
      suggestions, // eslint-disable-line no-unused-vars
      ariaProps, // eslint-disable-line no-unused-vars
      callbacks, // eslint-disable-line no-unused-vars
      theme = {},
      store, // eslint-disable-line no-unused-vars
      entityMutability, // eslint-disable-line no-unused-vars
      positionSuggestions, // eslint-disable-line no-unused-vars
      mentionTrigger, // eslint-disable-line no-unused-vars
      mentionPrefix, // eslint-disable-line no-unused-vars
      ...elementProps } = this.props;

    return (
      <div
        {...elementProps}
        className={theme.mentionSuggestions}
        role="listbox"
        id={`mentions-list-${this.key}`}
        ref={(element) => { this.popover = element; }}
      >
        {
          this.props.suggestions.map((mention, index) => (
            <Entry
              key={mention.has('id') ? mention.get('id') : mention.get('name')}
              onMentionSelect={this.onMentionSelect}
              onMentionFocus={this.onMentionFocus}
              isFocused={this.state.focusedOptionIndex === index}
              mention={mention}
              index={index}
              id={`mention-option-${this.key}-${index}`}
              theme={theme}
              searchValue={this.lastSearchValue}
              entryComponent={entryComponent || defaultEntryComponent}
            />
          )).toJS()
        }
      </div>
    );
  }
}
<|endoftext|>


<commit_before>define([
    'jquery',
    'fancytree',
    'uicontextmenu',
    'underscore',
    'backbone',
    'app',
    'views/navigationtree/NavigationTreeView'
], function($, fancytree, uicontextmenu, _, Backbone, app, NavigationTreeView) {

    var NavigationTreeView = Backbone.View.extend({

        initialize: function(options) {
            _.bindAll(this, 'render', 'convertToTree', 'sortCmp', 'getNodeHref', 'getNodeIcon');

            this.projectId = 0;

            if (typeof options !== typeof undefined && typeof options.draggable !== typeof undefined) {
                this.draggable = options.draggable;
            } else {
                this.draggable = false;
            }

            if (typeof options !== typeof undefined && typeof options.checkboxes !== typeof undefined) {
                this.checkboxes = options.checkboxes;
            } else {
                this.checkboxes = false;
            }

            this.rootNodeUrl = options.rootNodeUrl;
            this.nodeUrlPrefix = options.nodeUrlPrefix;

            this.nodeUrlSuffix = '/view';
            if (typeof options !== typeof undefined && typeof options.nodeUrlSuffix !== typeof undefined) {
                this.nodeUrlSuffix = options.nodeUrlSuffix;
            }

            this.collection = options.collection;
        },

        events: {

        },

        getNodeHref: function(node, parent, nodeUrlPrefix) {
            var url = nodeUrlPrefix + '/testsuites/';
            if (parseInt(node.node_type_id) === 2) {
                url += node.node_id + this.nodeUrlSuffix;
            } else {
                url += parent.node_id + '/testcases/' + node.node_id + this.nodeUrlSuffix;
            }
            return url;
        },

        getNodeIcon: function(node) {
            if (node != null) {
                if (typeof node.attributes !== typeof undefined && node.attributes !== null) {
                    var attributes = JSON.parse(node.attributes);
                    if (typeof attributes.execution_type_id !== typeof undefined) {
                        if (parseInt(attributes.execution_type_id) === 2) {
                            return '/icons/robot-icon.png';
                        }
                    }
                }
            }
            return true;
        },

        convertToTree: function(parent, children, nodeUrlPrefix, selected) {
            for (idx in children) {
                var child = children[idx];
                var folder = parseInt(child.node_type_id) === 3 ? false : true;
                var node = {
                    title: child.display_name,
                    key: child.descendant,
                    folder: folder,
                    expanded: true,
                    children: [],
                    node_id: child.node_id,
                    node_type_id: child.node_type_id,
                    href: this.getNodeHref(child, parent, nodeUrlPrefix),
                    icon: this.getNodeIcon(child)
                };
                parent.children.push(node);
                this.convertToTree(node, child.children, nodeUrlPrefix,selected);
            }

            // Only test cases can be selected... in the database at least.
            if (parent.node_type_id == 3) {
                for (entry in selected) {
                    if (selected[entry]['test_cases_id'] == parent.node_id) {
                        parent.selected = true;
                        parent.preselected = true;
                    }
                }
            }
        },

        /**
         * Used by FancyTree, after a node is moved. This is the sort comparator to organise
         * the nodes within the tree.
         */
        sortCmp: function(a, b) {
            var x = a.data.node_type_id + a.title.toLowerCase(),
                y = b.data.node_type_id + b.title.toLowerCase();
            return x === y ? 0 : x > y ? 1 : -1;
        },

        render: function(options) {
            console.log('Rendering navigation tree for project ID [' + this.projectId + ']');
            var el = options.el;
            var selected = [];
            if (typeof options.selected !== typeof undefined) {
                selected = options.selected;
            }
            var self = this;
            var nodeObject = this.collection.get(this.projectId);
            var model = nodeObject.toJSON();

            tree = [];
            var node = {
                title: model.display_name,
                key: model.descendant,
                folder: true,
                expanded: true,
                children: [],
                node_id: model.node_id,
                node_type_id: model.node_type_id,
                href: self.rootNodeUrl,
                icon: true,
                selected: false,
                preselected: false
            };
            self.convertToTree(node, model.children, self.nodeUrlPrefix, selected);
            tree.push(node);

            // --- check boxes
            var checkbox = self.checkboxes;
            var selectMode = 1;
            if (checkbox) {
                selectMode = 3;
            }
            // --- end check boxes

            // --- drag and drop
            var extensions = [];
            var dnd = {};
            // enable drag and drop
            if (self.draggable) {
                extensions.push('dnd');
                dnd = {
                    preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
                    preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
                    autoExpandMS: 400,
                    dragStart: function(node, data) {
                        // Defines whether this node is draggable or not
                        // The top level project should have dnd disabled
                        if (node.data.node_type_id == 1) {
                            return false;
                        }
                        return true;
                    },
                    dragStop: function(node, data) {

                    },
                    dragOver: function(node, data) {
                    },
                    dragEnter: function(node, data) {
                        // Defines whether another node can be dragged on this node
                        // Return ['before', 'after'] to restrict available hitModes.
                        // Any other return value will calc the hitMode from the cursor position.
                        // Prevent dropping a parent below another parent (only sort
                        // nodes under the same parent)
                        // if(node.parent !== data.otherNode.parent){
                        //   return false;
                        // }
                        // Don't allow dropping *over* a node (would create a child)
                        // return ["before", "after"];

                        var selectedNode = data.otherNode; // the selected node
                        if (selectedNode.data.node_type_id == 3) {
                            // Test cases should not be dnd onto projects or test cases, only test suites
                            if (node.data.node_type_id != 2) {
                                return false;
                            }
                            // Test cases should not be dnd onto its own parent
                            if (node == selectedNode.parent) {
                                return false;
                            }
                        }
                        if (selectedNode.data.node_type_id == 2) {
                            // Test suites should not be dnd onto test cases
                            if (node.data.node_type_id == 3) {
                                return false;
                            }
                            // Test suites should not be dnd onto its own parent
                            if (node == selectedNode.parent) {
                                return false;
                            }
                        }
                        return "over";
                    },
                    dragDrop: function(node, data) {
                        var selectedNode = data.otherNode; // the selected node
                        var descendant = '' + selectedNode.data.node_type_id + '-' + selectedNode.data.node_id;
                        var ancestor = '' + node.data.node_type_id + '-' + node.data.node_id;
                        var url = "/api/navigationtree/move";
                        $.ajax({
                          type: "POST",
                          url: url,
                          data: {descendant: descendant, ancestor: ancestor},
                          success: function(jqXHR, textStatus, data) {
                            selectedNode.moveTo(node, jqXHR.hitMode);
                            var rootNode = node.tree.rootNode;
                            rootNode.sortChildren(self.sortCmp, true);
                            app.showAlert('Success!', 'Moved successfully!', 'success');
                          },
                          error: function(jqXHR, textStatus, data) {
                            var responseText = jQuery.parseJSON(jqXHR.responseText);
                            var message = responseText.error.message;
                            app.showAlert('Error moving!', message, 'error');
                          }
                        });
                    },
                    dragLeave: function(node, data) {
                    }
                }
            };
            // --- end of drag and drop

            el.fancytree('destroy');
            el.fancytree({
                source: tree,
                extensions: extensions,
                activeVisible: true, // Make sure, active nodes are visible (expanded).
                aria: false, // Enable WAI-ARIA support.
                autoActivate: true, // Automatically activate a node when it is focused (using keys).
                autoCollapse: false, // Automatically collapse all siblings, when a node is expanded.
                autoScroll: false, // Automatically scroll nodes into visible area.
                clickFolderMode: 1, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
                checkbox: checkbox, // Show checkboxes.
                debugLevel: 1, // 0:quiet, 1:normal, 2:debug
                disabled: false, // Disable control
                generateIds: true, // Generate id attributes like
                idPrefix: "ft_", // Used to generate node id´s like <span id='fancytree-id-<key>'>.
                icon: true, // Display node icons.
                keyboard: true, // Support keyboard navigation.
                keyPathSeparator: "/", // Used by node.getKeyPath() and tree.loadKeyPath().
                minExpandLevel: 2, // 1: root node is not collapsible
                selectMode: selectMode, // 1:single, 2:multi, 3:multi-hier
                tabindex: 0, // Whole tree behaves as one single control
                childcounter: {
                    deep: true,
                    hideZeros: true,
                    hideExpanded: true
                },
                focus: function(e, data) {
                    var node = data.node;
                    // Auto-activate focused node after 1 second
                    if(node.data.href){
                        node.scheduleAction("activate", 100000);
                    }
                },
                blur: function(e, data) {
                    data.node.scheduleAction("cancel");
                },
                activate: function(e, data) {
                    if (data.draggable)
                        return; // prevent false hits
                    var node = data.node;
                    if(node.data.href){
                        Backbone.history.navigate(node.data.href, { trigger: false });
                    }
                },
                click: function(e, data) { // allow re-loads
                    var node = data.node;
                    if(node.isActive() && node.data.href){
                        // TODO: data.tree.reactivate();
                    }
                },
                // https://github.com/mar10/fancytree/issues/581
                init: function (e, data) {
                    data.tree.getRootNode().visit(function (node) {
                        if (node.data.preselected) node.setSelected(true);
                    });
                },
                dnd: dnd
            });
            // end
            var rootNode = el.fancytree('getRootNode');
            if (typeof rootNode.sortChildren !== typeof undefined) {
                rootNode.sortChildren(self.sortCmp, true);
            }

            // context menu
            el.contextmenu({
                delegate: ".fancytree-node",
                menu: [
                    {
                        title: "Edit",
                        cmd: "edit",
                        uiIcon: "ui-icon-pencil"
                    },
                    {
                        title: "Delete",
                        cmd: "delete",
                        uiIcon: "ui-icon-trash"
                    }
                ],
                select: function(event, ui) {
                    var nodeId = ui.target[0].parentNode.parentNode.id;
                    var underscoreIndex = nodeId.indexOf('_');
                    if (underscoreIndex > 0) {
                        var length = nodeId.length;
                        var key = nodeId.substring(underscoreIndex+1, length);
                        var node = el.fancytree('getNodeByKey', key);
                        var href = node.data.href;
                        console.log(node);
                        if (ui.cmd == 'edit') {
                            var editHref = href.substring(0, href.length - 5);
                            Backbone.history.navigate(editHref, {
                                trigger: false
                            });
                        } else if (ui.cmd == 'delete') {
                            var editHref = href.substring(0, href.length - 5);
                            var deleteHref = editHref + '/confirmDelete';
                            Backbone.history.navigate(deleteHref, {
                                trigger: false
                            });
                        } else {
                            console.log('Command ' + ui.cmd + ' not recognized!');
                        }
                    }
                }
            });

            this.delegateEvents();
        }

    });

    return NavigationTreeView;

});<commit_msg>Remove debug log<commit_after>define([
    'jquery',
    'fancytree',
    'uicontextmenu',
    'underscore',
    'backbone',
    'app',
    'views/navigationtree/NavigationTreeView'
], function($, fancytree, uicontextmenu, _, Backbone, app, NavigationTreeView) {

    var NavigationTreeView = Backbone.View.extend({

        initialize: function(options) {
            _.bindAll(this, 'render', 'convertToTree', 'sortCmp', 'getNodeHref', 'getNodeIcon');

            this.projectId = 0;

            if (typeof options !== typeof undefined && typeof options.draggable !== typeof undefined) {
                this.draggable = options.draggable;
            } else {
                this.draggable = false;
            }

            if (typeof options !== typeof undefined && typeof options.checkboxes !== typeof undefined) {
                this.checkboxes = options.checkboxes;
            } else {
                this.checkboxes = false;
            }

            this.rootNodeUrl = options.rootNodeUrl;
            this.nodeUrlPrefix = options.nodeUrlPrefix;

            this.nodeUrlSuffix = '/view';
            if (typeof options !== typeof undefined && typeof options.nodeUrlSuffix !== typeof undefined) {
                this.nodeUrlSuffix = options.nodeUrlSuffix;
            }

            this.collection = options.collection;
        },

        events: {

        },

        getNodeHref: function(node, parent, nodeUrlPrefix) {
            var url = nodeUrlPrefix + '/testsuites/';
            if (parseInt(node.node_type_id) === 2) {
                url += node.node_id + this.nodeUrlSuffix;
            } else {
                url += parent.node_id + '/testcases/' + node.node_id + this.nodeUrlSuffix;
            }
            return url;
        },

        getNodeIcon: function(node) {
            if (node != null) {
                if (typeof node.attributes !== typeof undefined && node.attributes !== null) {
                    var attributes = JSON.parse(node.attributes);
                    if (typeof attributes.execution_type_id !== typeof undefined) {
                        if (parseInt(attributes.execution_type_id) === 2) {
                            return '/icons/robot-icon.png';
                        }
                    }
                }
            }
            return true;
        },

        convertToTree: function(parent, children, nodeUrlPrefix, selected) {
            for (idx in children) {
                var child = children[idx];
                var folder = parseInt(child.node_type_id) === 3 ? false : true;
                var node = {
                    title: child.display_name,
                    key: child.descendant,
                    folder: folder,
                    expanded: true,
                    children: [],
                    node_id: child.node_id,
                    node_type_id: child.node_type_id,
                    href: this.getNodeHref(child, parent, nodeUrlPrefix),
                    icon: this.getNodeIcon(child)
                };
                parent.children.push(node);
                this.convertToTree(node, child.children, nodeUrlPrefix,selected);
            }

            // Only test cases can be selected... in the database at least.
            if (parent.node_type_id == 3) {
                for (entry in selected) {
                    if (selected[entry]['test_cases_id'] == parent.node_id) {
                        parent.selected = true;
                        parent.preselected = true;
                    }
                }
            }
        },

        /**
         * Used by FancyTree, after a node is moved. This is the sort comparator to organise
         * the nodes within the tree.
         */
        sortCmp: function(a, b) {
            var x = a.data.node_type_id + a.title.toLowerCase(),
                y = b.data.node_type_id + b.title.toLowerCase();
            return x === y ? 0 : x > y ? 1 : -1;
        },

        render: function(options) {
            console.log('Rendering navigation tree for project ID [' + this.projectId + ']');
            var el = options.el;
            var selected = [];
            if (typeof options.selected !== typeof undefined) {
                selected = options.selected;
            }
            var self = this;
            var nodeObject = this.collection.get(this.projectId);
            var model = nodeObject.toJSON();

            tree = [];
            var node = {
                title: model.display_name,
                key: model.descendant,
                folder: true,
                expanded: true,
                children: [],
                node_id: model.node_id,
                node_type_id: model.node_type_id,
                href: self.rootNodeUrl,
                icon: true,
                selected: false,
                preselected: false
            };
            self.convertToTree(node, model.children, self.nodeUrlPrefix, selected);
            tree.push(node);

            // --- check boxes
            var checkbox = self.checkboxes;
            var selectMode = 1;
            if (checkbox) {
                selectMode = 3;
            }
            // --- end check boxes

            // --- drag and drop
            var extensions = [];
            var dnd = {};
            // enable drag and drop
            if (self.draggable) {
                extensions.push('dnd');
                dnd = {
                    preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
                    preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
                    autoExpandMS: 400,
                    dragStart: function(node, data) {
                        // Defines whether this node is draggable or not
                        // The top level project should have dnd disabled
                        if (node.data.node_type_id == 1) {
                            return false;
                        }
                        return true;
                    },
                    dragStop: function(node, data) {

                    },
                    dragOver: function(node, data) {
                    },
                    dragEnter: function(node, data) {
                        // Defines whether another node can be dragged on this node
                        // Return ['before', 'after'] to restrict available hitModes.
                        // Any other return value will calc the hitMode from the cursor position.
                        // Prevent dropping a parent below another parent (only sort
                        // nodes under the same parent)
                        // if(node.parent !== data.otherNode.parent){
                        //   return false;
                        // }
                        // Don't allow dropping *over* a node (would create a child)
                        // return ["before", "after"];

                        var selectedNode = data.otherNode; // the selected node
                        if (selectedNode.data.node_type_id == 3) {
                            // Test cases should not be dnd onto projects or test cases, only test suites
                            if (node.data.node_type_id != 2) {
                                return false;
                            }
                            // Test cases should not be dnd onto its own parent
                            if (node == selectedNode.parent) {
                                return false;
                            }
                        }
                        if (selectedNode.data.node_type_id == 2) {
                            // Test suites should not be dnd onto test cases
                            if (node.data.node_type_id == 3) {
                                return false;
                            }
                            // Test suites should not be dnd onto its own parent
                            if (node == selectedNode.parent) {
                                return false;
                            }
                        }
                        return "over";
                    },
                    dragDrop: function(node, data) {
                        var selectedNode = data.otherNode; // the selected node
                        var descendant = '' + selectedNode.data.node_type_id + '-' + selectedNode.data.node_id;
                        var ancestor = '' + node.data.node_type_id + '-' + node.data.node_id;
                        var url = "/api/navigationtree/move";
                        $.ajax({
                          type: "POST",
                          url: url,
                          data: {descendant: descendant, ancestor: ancestor},
                          success: function(jqXHR, textStatus, data) {
                            selectedNode.moveTo(node, jqXHR.hitMode);
                            var rootNode = node.tree.rootNode;
                            rootNode.sortChildren(self.sortCmp, true);
                            app.showAlert('Success!', 'Moved successfully!', 'success');
                          },
                          error: function(jqXHR, textStatus, data) {
                            var responseText = jQuery.parseJSON(jqXHR.responseText);
                            var message = responseText.error.message;
                            app.showAlert('Error moving!', message, 'error');
                          }
                        });
                    },
                    dragLeave: function(node, data) {
                    }
                }
            };
            // --- end of drag and drop

            el.fancytree('destroy');
            el.fancytree({
                source: tree,
                extensions: extensions,
                activeVisible: true, // Make sure, active nodes are visible (expanded).
                aria: false, // Enable WAI-ARIA support.
                autoActivate: true, // Automatically activate a node when it is focused (using keys).
                autoCollapse: false, // Automatically collapse all siblings, when a node is expanded.
                autoScroll: false, // Automatically scroll nodes into visible area.
                clickFolderMode: 1, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
                checkbox: checkbox, // Show checkboxes.
                debugLevel: 1, // 0:quiet, 1:normal, 2:debug
                disabled: false, // Disable control
                generateIds: true, // Generate id attributes like
                idPrefix: "ft_", // Used to generate node id´s like <span id='fancytree-id-<key>'>.
                icon: true, // Display node icons.
                keyboard: true, // Support keyboard navigation.
                keyPathSeparator: "/", // Used by node.getKeyPath() and tree.loadKeyPath().
                minExpandLevel: 2, // 1: root node is not collapsible
                selectMode: selectMode, // 1:single, 2:multi, 3:multi-hier
                tabindex: 0, // Whole tree behaves as one single control
                childcounter: {
                    deep: true,
                    hideZeros: true,
                    hideExpanded: true
                },
                focus: function(e, data) {
                    var node = data.node;
                    // Auto-activate focused node after 1 second
                    if(node.data.href){
                        node.scheduleAction("activate", 100000);
                    }
                },
                blur: function(e, data) {
                    data.node.scheduleAction("cancel");
                },
                activate: function(e, data) {
                    if (data.draggable)
                        return; // prevent false hits
                    var node = data.node;
                    if(node.data.href){
                        Backbone.history.navigate(node.data.href, { trigger: false });
                    }
                },
                click: function(e, data) { // allow re-loads
                    var node = data.node;
                    if(node.isActive() && node.data.href){
                        // TODO: data.tree.reactivate();
                    }
                },
                // https://github.com/mar10/fancytree/issues/581
                init: function (e, data) {
                    data.tree.getRootNode().visit(function (node) {
                        if (node.data.preselected) node.setSelected(true);
                    });
                },
                dnd: dnd
            });
            // end
            var rootNode = el.fancytree('getRootNode');
            if (typeof rootNode.sortChildren !== typeof undefined) {
                rootNode.sortChildren(self.sortCmp, true);
            }

            // context menu
            el.contextmenu({
                delegate: ".fancytree-node",
                menu: [
                    {
                        title: "Edit",
                        cmd: "edit",
                        uiIcon: "ui-icon-pencil"
                    },
                    {
                        title: "Delete",
                        cmd: "delete",
                        uiIcon: "ui-icon-trash"
                    }
                ],
                select: function(event, ui) {
                    var nodeId = ui.target[0].parentNode.parentNode.id;
                    var underscoreIndex = nodeId.indexOf('_');
                    if (underscoreIndex > 0) {
                        var length = nodeId.length;
                        var key = nodeId.substring(underscoreIndex+1, length);
                        var node = el.fancytree('getNodeByKey', key);
                        var href = node.data.href;
                        if (ui.cmd == 'edit') {
                            var editHref = href.substring(0, href.length - 5);
                            Backbone.history.navigate(editHref, {
                                trigger: false
                            });
                        } else if (ui.cmd == 'delete') {
                            var editHref = href.substring(0, href.length - 5);
                            var deleteHref = editHref + '/confirmDelete';
                            Backbone.history.navigate(deleteHref, {
                                trigger: false
                            });
                        } else {
                            console.log('Command ' + ui.cmd + ' not recognized!');
                        }
                    }
                }
            });

            this.delegateEvents();
        }

    });

    return NavigationTreeView;

});<|endoftext|>