<commit_before>import { createSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import maxBy from 'lodash/maxBy';
import remove from 'lodash/remove';
import groupBy from 'lodash/groupBy';
import sumBy from 'lodash/sumBy';
import { format } from 'd3-format';
import { sortByKey } from 'utils/data';
import endsWith from 'lodash/endsWith';

const getPlantations = state => (state.data && state.data.plantations) || null;
const getExtent = state => (state.data && state.data.extent) || null;
const getSettings = state => state.settings || null;
const getLocation = state => state.payload || null;
const getQuery = state => state.query || null;
const getLocationsMeta = state =>
  (state.payload.region ? state.regions : state.subRegions) || null;
const getLocationNames = state => state.locationNames || null;
const getColors = state => state.colors || null;
const getEmbed = state => state.embed || null;
const getSentences = state => state.config.sentences || null;

const getPlanationKeys = createSelector(
  [getPlantations],
  plantations =>
    (plantations ? Object.keys(groupBy(plantations, 'label')) : null)
);

export const parseData = createSelector(
  [
    getPlantations,
    getExtent,
    getPlanationKeys,
    getLocationsMeta,
    getLocation,
    getQuery,
    getEmbed
  ],
  (plantations, extent, plantationKeys, meta, location, query, embed) => {
    if (isEmpty(plantations) || isEmpty(meta) || isEmpty(extent)) return null;
    const groupedByRegion = groupBy(plantations, 'region');
    const regionData = Object.keys(groupedByRegion).map(r => {
      const yKeys = {};
      const regionId = parseInt(r, 10);
      const regionLabel =
        meta && meta.find(region => region.value === regionId);
      const totalRegionPlantations = sumBy(
        groupedByRegion[regionId],
        'plantation_extent'
      );
      const totalArea = extent.find(e => e.region === regionId).total;
      plantationKeys.forEach(key => {
        const labelFromKey = groupedByRegion[regionId].find(
          p => p.label === key
        );
        const pExtent = labelFromKey && labelFromKey.plantation_extent;
        const pPercentage = pExtent / totalRegionPlantations * 100;
        yKeys[key] = pPercentage || 0;
        yKeys[`${key} label`] = key;
      });

      return {
        region: regionLabel && regionLabel.label,
        ...yKeys,
        total: totalRegionPlantations / totalArea * 100,
        path: `${embed ? `http://${window.location.host}` : ''}/country/${
          location.country
        }/${location.region ? `${location.region}/` : ''}${regionId}${
          query ? `?${query}` : ''
        }`,
        extLink: embed
      };
    });
    const dataParsed = sortByKey(regionData, 'total', true);

    return dataParsed;
  }
);

export const parseConfig = createSelector(
  [getPlanationKeys, getColors, getSettings],
  (dataKeys, colors, settings) => {
    if (!dataKeys) return null;
    const colorsByType =
      settings.type === 'bound1' ? colors.types : colors.species;
    return {
      colors: colorsByType,
      unit: '%',
      xKey: 'region',
      yKeys: dataKeys,
      yAxisDotFill: '#d4d4d4',
      tooltip: dataKeys.map(item => ({
        key: item,
        label: `${item} label`,
        color: colorsByType[item],
        unit: '%',
        unitFormat: value => format('.1f')(value)
      }))
    };
  }
);

export const getSentence = createSelector(
  [parseData, getSettings, getLocation, getLocationNames, getSentences],
  (data, settings, location, locationNames, sentences) => {
    if (!data || !data.length) return null;
    const { initial } = sentences;
    const currentLocation =
      locationNames && locationNames.current && locationNames.current.label;
    const topRegion = data[0];
    const topPlantation = maxBy(
      remove(
        Object.keys(topRegion).map(k => ({
          label: k,
          value: topRegion[k] > 0 ? topRegion[k] : 0
        })),
        item => item.label !== 'total'
      ),
      'value'
    );
    const plantationLabel = topPlantation.label;
    const isPlural = endsWith(plantationLabel, 's');

    const params = {
      location: currentLocation,
      region: topRegion.region,
      topType: `${plantationLabel}${isPlural ? 's' : ''} plantations`,
      percentage: `${format('.1f')(data[0].total)}%`
    };

    return {
      sentence: initial,
      params
    };
  }
);
<commit_msg>fix plantations<commit_after>import { createSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import maxBy from 'lodash/maxBy';
import remove from 'lodash/remove';
import groupBy from 'lodash/groupBy';
import sumBy from 'lodash/sumBy';
import { format } from 'd3-format';
import { sortByKey } from 'utils/data';
import endsWith from 'lodash/endsWith';

const getPlantations = state => (state.data && state.data.plantations) || null;
const getExtent = state => (state.data && state.data.extent) || null;
const getSettings = state => state.settings || null;
const getLocation = state => state.payload || null;
const getQuery = state => state.search || null;
const getLocationsMeta = state =>
  (state.payload.region ? state.subRegions : state.regions) || null;
const getLocationNames = state => state.locationNames || null;
const getColors = state => state.colors || null;
const getEmbed = state => state.embed || null;
const getSentences = state => state.config.sentences || null;

const getPlanationKeys = createSelector(
  [getPlantations],
  plantations =>
    (plantations ? Object.keys(groupBy(plantations, 'label')) : null)
);

export const parseData = createSelector(
  [
    getPlantations,
    getExtent,
    getPlanationKeys,
    getLocationsMeta,
    getLocation,
    getQuery,
    getEmbed
  ],
  (plantations, extent, plantationKeys, meta, location, query, embed) => {
    if (isEmpty(plantations) || isEmpty(meta) || isEmpty(extent)) return null;
    const groupedByRegion = groupBy(plantations, 'region');
    const regionData = Object.keys(groupedByRegion).map(r => {
      const yKeys = {};
      const regionId = parseInt(r, 10);
      const regionLabel =
        meta && meta.find(region => region.value === regionId);
      const totalRegionPlantations = sumBy(
        groupedByRegion[regionId],
        'plantation_extent'
      );
      const totalArea = extent.find(e => e.region === regionId).total;
      plantationKeys.forEach(key => {
        const labelFromKey = groupedByRegion[regionId].find(
          p => p.label === key
        );
        const pExtent = labelFromKey && labelFromKey.plantation_extent;
        const pPercentage = pExtent / totalRegionPlantations * 100;
        yKeys[key] = pPercentage || 0;
        yKeys[`${key} label`] = key;
      });

      return {
        region: regionLabel && regionLabel.label,
        ...yKeys,
        total: totalRegionPlantations / totalArea * 100,
        path: `${embed ? `http://${window.location.host}` : ''}/country/${
          location.country
        }/${location.region ? `${location.region}/` : ''}${regionId}${
          query ? `?${query}` : ''
        }`,
        extLink: embed
      };
    });
    const dataParsed = sortByKey(regionData, 'total', true);

    return dataParsed;
  }
);

export const parseConfig = createSelector(
  [getPlanationKeys, getColors, getSettings],
  (dataKeys, colors, settings) => {
    if (!dataKeys) return null;
    const colorsByType =
      settings.type === 'bound1' ? colors.types : colors.species;
    return {
      colors: colorsByType,
      unit: '%',
      xKey: 'region',
      yKeys: dataKeys,
      yAxisDotFill: '#d4d4d4',
      tooltip: dataKeys.map(item => ({
        key: item,
        label: `${item} label`,
        color: colorsByType[item],
        unit: '%',
        unitFormat: value => format('.1f')(value)
      }))
    };
  }
);

export const getSentence = createSelector(
  [parseData, getSettings, getLocation, getLocationNames, getSentences],
  (data, settings, location, locationNames, sentences) => {
    if (!data || !data.length) return null;
    const { initial } = sentences;
    const currentLocation =
      locationNames && locationNames.current && locationNames.current.label;
    const topRegion = data[0];
    const topPlantation = maxBy(
      remove(
        Object.keys(topRegion).map(k => ({
          label: k,
          value: topRegion[k] > 0 ? topRegion[k] : 0
        })),
        item => item.label !== 'total'
      ),
      'value'
    );
    const plantationLabel = topPlantation.label;
    const isPlural = endsWith(plantationLabel, 's');

    const params = {
      location: currentLocation,
      region: topRegion.region,
      topType: `${plantationLabel}${isPlural ? 's' : ''} plantations`,
      percentage: `${format('.1f')(data[0].total)}%`
    };

    return {
      sentence: initial,
      params
    };
  }
);
<|endoftext|>


<commit_before>(function(global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = { }),
      extend = fabric.util.object.extend,
      min = fabric.util.array.min,
      max = fabric.util.array.max,
      invoke = fabric.util.array.invoke;

  if (fabric.Group) {
    return;
  }

  // lock-related properties, for use in fabric.Group#get
  // to enable locking behavior on group
  // when one of its objects has lock-related properties set
  var _lockProperties = {
    lockMovementX:  true,
    lockMovementY:  true,
    lockRotation:   true,
    lockScalingX:   true,
    lockScalingY:   true,
    lockUniScaling: true
  };

  /**
   * Group class
   * @class fabric.Group
   * @extends fabric.Object
   * @mixes fabric.Collection
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups}
   * @see {@link fabric.Group#initialize} for constructor definition
   */
  fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'group',

    /**
     * Width of stroke
     * @type Number
     * @default
     */
    strokeWidth: 0,

    /**
     * Indicates if click events should also check for subtargets
     * @type Boolean
     * @default
     */
    subTargetCheck: false,

    /**
     * Constructor
     * @param {Object} objects Group objects
     * @param {Object} [options] Options object
     * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already.
     * @return {Object} thisArg
     */
    initialize: function(objects, options, isAlreadyGrouped) {
      options = options || { };

      this._objects = [];
      // if objects enclosed in a group have been grouped already,
      // we cannot change properties of objects.
      // Thus we need to set options to group without objects,
      // because delegatedProperties propagate to objects.
      isAlreadyGrouped && this.callSuper('initialize', options);

      this._objects = objects || [];
      for (var i = this._objects.length; i--; ) {
        this._objects[i].group = this;
      }

      this.originalState = { };

      if (options.originX) {
        this.originX = options.originX;
      }
      if (options.originY) {
        this.originY = options.originY;
      }

      if (isAlreadyGrouped) {
        // do not change coordinate of objects enclosed in a group,
        // because objects coordinate system have been group coodinate system already.
        this._updateObjectsCoords(true);
      }
      else {
        this._calcBounds();
        this._updateObjectsCoords();
        this.callSuper('initialize', options);
      }

      this.setCoords();
      this.saveCoords();
    },

    /**
     * @private
     * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change
     */
    _updateObjectsCoords: function(skipCoordsChange) {
      var center = this.getCenterPoint();
      for (var i = this._objects.length; i--; ){
        this._updateObjectCoords(this._objects[i], center, skipCoordsChange);
      }
    },

    /**
     * @private
     * @param {Object} object
     * @param {fabric.Point} center, current center of group.
     * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change
     */
    _updateObjectCoords: function(object, center, skipCoordsChange) {
      // do not display corners of objects enclosed in a group
      object.__origHasControls = object.hasControls;
      object.hasControls = false;

      if (skipCoordsChange) {
        return;
      }

      var objectLeft = object.getLeft(),
          objectTop = object.getTop(),
          ignoreZoom = true;

      object.set({
        originalLeft: objectLeft,
        originalTop: objectTop,
        left: objectLeft - center.x,
        top: objectTop - center.y
      });
      object.setCoords(ignoreZoom);
    },

    /**
     * Returns string represenation of a group
     * @return {String}
     */
    toString: function() {
      return '#<fabric.Group: (' + this.complexity() + ')>';
    },

    /**
     * Adds an object to a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    addWithUpdate: function(object) {
      this._restoreObjectsState();
      fabric.util.resetObjectTransform(this);
      if (object) {
        this._objects.push(object);
        object.group = this;
        object._set('canvas', this.canvas);
      }
      // since _restoreObjectsState set objects inactive
      this.forEachObject(this._setObjectActive, this);
      this._calcBounds();
      this._updateObjectsCoords();
      this.dirty = true;
      return this;
    },

    /**
     * @private
     */
    _setObjectActive: function(object) {
      object.set('active', true);
      object.group = this;
    },

    /**
     * Removes an object from a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    removeWithUpdate: function(object) {
      this._restoreObjectsState();
      fabric.util.resetObjectTransform(this);
      // since _restoreObjectsState set objects inactive
      this.forEachObject(this._setObjectActive, this);

      this.remove(object);
      this._calcBounds();
      this._updateObjectsCoords();
      this.dirty = true;
      return this;
    },

    /**
     * @private
     */
    _onObjectAdded: function(object) {
      this.dirty = true;
      object.group = this;
      object._set('canvas', this.canvas);
    },

    /**
     * @private
     */
    _onObjectRemoved: function(object) {
      this.dirty = true;
      delete object.group;
      object.set('active', false);
    },

    /**
     * Properties that are delegated to group objects when reading/writing
     * @param {Object} delegatedProperties
     */
    delegatedProperties: {
      fill:             true,
      stroke:           true,
      strokeWidth:      true,
      fontFamily:       true,
      fontWeight:       true,
      fontSize:         true,
      fontStyle:        true,
      lineHeight:       true,
      textDecoration:   true,
      textAlign:        true,
      backgroundColor:  true
    },

    /**
     * @private
     */
    _set: function(key, value) {
      var i = this._objects.length;

      if (this.delegatedProperties[key] || key === 'canvas') {
        while (i--) {
          this._objects[i].set(key, value);
        }
      }
      else {
        while (i--) {
          this._objects[i].setOnGroup(key, value);
        }
      }

      this.callSuper('_set', key, value);
    },

    /**
     * Returns object representation of an instance
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
     * @return {Object} object representation of an instance
     */
    toObject: function(propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        objects: invoke(this._objects, 'toObject', propertiesToInclude)
      });
    },

    /**
     * Renders instance on a given context
     * @param {CanvasRenderingContext2D} ctx context to render instance on
     */
    render: function(ctx) {
      this._transformDone = true;
      this.callSuper('render', ctx);
      this._transformDone = false;
    },

    /**
     * Execute the drawing operation for an object on a specified context
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    drawObject: function(ctx) {
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._renderObject(this._objects[i], ctx);
      }
    },

    /**
     * Check if cache is dirty
     */
    isCacheDirty: function() {
      if (this.callSuper('isCacheDirty')) {
        return true
      }
      if (!this.statefullCache) {
        return false;
      }
      for (var i = 0, len = this._objects.length; i < len; i++) {
        if (this._objects[i].isCacheDirty(true)) {
          var dim = this._getNonTransformedDimensions();
          this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
          return true
        }
      }
      return false;
    },

    /**
     * Renders controls and borders for the object
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    _renderControls: function(ctx, noTransform) {
      ctx.save();
      ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
      this.callSuper('_renderControls', ctx, noTransform);
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._objects[i]._renderControls(ctx);
      }
      ctx.restore();
    },

    /**
     * @private
     */
    _renderObject: function(object, ctx) {
      // do not render if object is not visible
      if (!object.visible) {
        return;
      }

      var originalHasRotatingPoint = object.hasRotatingPoint;
      object.hasRotatingPoint = false;
      object.render(ctx);
      object.hasRotatingPoint = originalHasRotatingPoint;
    },

    /**
     * Retores original state of each of group objects (original state is that which was before group was created).
     * @private
     * @return {fabric.Group} thisArg
     * @chainable
     */
    _restoreObjectsState: function() {
      this._objects.forEach(this._restoreObjectState, this);
      return this;
    },

    /**
     * Realises the transform from this group onto the supplied object
     * i.e. it tells you what would happen if the supplied object was in
     * the group, and then the group was destroyed. It mutates the supplied
     * object.
     * @param {fabric.Object} object
     * @return {fabric.Object} transformedObject
     */
    realizeTransform: function(object) {
      var matrix = object.calcTransformMatrix(),
          options = fabric.util.qrDecompose(matrix),
          center = new fabric.Point(options.translateX, options.translateY);
      object.scaleX = options.scaleX;
      object.scaleY = options.scaleY;
      object.skewX = options.skewX;
      object.skewY = options.skewY;
      object.angle = options.angle;
      object.flipX = false;
      object.flipY = false;
      object.setPositionByOrigin(center, 'center', 'center');
      return object;
    },

    /**
     * Restores original state of a specified object in group
     * @private
     * @param {fabric.Object} object
     * @return {fabric.Group} thisArg
     */
    _restoreObjectState: function(object) {
      this.realizeTransform(object);
      object.setCoords();
      object.hasControls = object.__origHasControls;
      delete object.__origHasControls;
      object.set('active', false);
      delete object.group;

      return this;
    },

    /**
     * Destroys a group (restoring state of its objects)
     * @return {fabric.Group} thisArg
     * @chainable
     */
    destroy: function() {
      return this._restoreObjectsState();
    },

    /**
     * Saves coordinates of this instance (to be used together with `hasMoved`)
     * @saveCoords
     * @return {fabric.Group} thisArg
     * @chainable
     */
    saveCoords: function() {
      this._originalLeft = this.get('left');
      this._originalTop = this.get('top');
      return this;
    },

    /**
     * Checks whether this group was moved (since `saveCoords` was called last)
     * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
     */
    hasMoved: function() {
      return this._originalLeft !== this.get('left') ||
             this._originalTop !== this.get('top');
    },

    /**
     * Sets coordinates of all group objects
     * @return {fabric.Group} thisArg
     * @chainable
     */
    setObjectsCoords: function() {
      var ignoreZoom = true;
      this.forEachObject(function(object) {
        object.setCoords(ignoreZoom);
      });
      return this;
    },

    /**
     * @private
     */
    _calcBounds: function(onlyWidthHeight) {
      var aX = [],
          aY = [],
          o, prop,
          props = ['tr', 'br', 'bl', 'tl'],
          i = 0, iLen = this._objects.length,
          j, jLen = props.length,
          ignoreZoom = true;

      for ( ; i < iLen; ++i) {
        o = this._objects[i];
        o.setCoords(ignoreZoom);
        for (j = 0; j < jLen; j++) {
          prop = props[j];
          aX.push(o.oCoords[prop].x);
          aY.push(o.oCoords[prop].y);
        }
      }

      this.set(this._getBounds(aX, aY, onlyWidthHeight));
    },

    /**
     * @private
     */
    _getBounds: function(aX, aY, onlyWidthHeight) {
      var minXY = new fabric.Point(min(aX), min(aY)),
          maxXY = new fabric.Point(max(aX), max(aY)),
          obj = {
            width: (maxXY.x - minXY.x) || 0,
            height: (maxXY.y - minXY.y) || 0
          };

      if (!onlyWidthHeight) {
        obj.left = minXY.x || 0;
        obj.top = minXY.y || 0;
        if (this.originX === 'center') {
          obj.left += obj.width / 2;
        }
        if (this.originX === 'right') {
          obj.left += obj.width;
        }
        if (this.originY === 'center') {
          obj.top += obj.height / 2;
        }
        if (this.originY === 'bottom') {
          obj.top += obj.height;
        }
      }
      return obj;
    },

    /* _TO_SVG_START_ */
    /**
     * Returns svg representation of an instance
     * @param {Function} [reviver] Method for further parsing of svg representation.
     * @return {String} svg representation of an instance
     */
    toSVG: function(reviver) {
      var markup = this._createBaseSVGMarkup();
      markup.push(
        '<g ', this.getSvgId(), 'transform="',
        /* avoiding styles intentionally */
        this.getSvgTransform(),
        this.getSvgTransformMatrix(),
        '" style="',
        this.getSvgFilter(),
        '">\n'
      );

      for (var i = 0, len = this._objects.length; i < len; i++) {
        markup.push('\t', this._objects[i].toSVG(reviver));
      }

      markup.push('</g>\n');

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * Returns requested property
     * @param {String} prop Property to get
     * @return {*}
     */
    get: function(prop) {
      if (prop in _lockProperties) {
        if (this[prop]) {
          return this[prop];
        }
        else {
          for (var i = 0, len = this._objects.length; i < len; i++) {
            if (this._objects[i][prop]) {
              return true;
            }
          }
          return false;
        }
      }
      else {
        if (prop in this.delegatedProperties) {
          return this._objects[0] && this._objects[0].get(prop);
        }
        return this[prop];
      }
    }
  });

  /**
   * Returns {@link fabric.Group} instance from an object representation
   * @static
   * @memberOf fabric.Group
   * @param {Object} object Object to create a group from
   * @param {Function} [callback] Callback to invoke when an group instance is created
   */
  fabric.Group.fromObject = function(object, callback) {
    fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
      delete object.objects;
      callback && callback(new fabric.Group(enlivenedObjects, object, true));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.Group
   * @type Boolean
   * @default
   */
  fabric.Group.async = true;

})(typeof exports !== 'undefined' ? exports : this);
<commit_msg>fix for scale and flip (#3474)<commit_after>(function(global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = { }),
      extend = fabric.util.object.extend,
      min = fabric.util.array.min,
      max = fabric.util.array.max,
      invoke = fabric.util.array.invoke;

  if (fabric.Group) {
    return;
  }

  // lock-related properties, for use in fabric.Group#get
  // to enable locking behavior on group
  // when one of its objects has lock-related properties set
  var _lockProperties = {
    lockMovementX:  true,
    lockMovementY:  true,
    lockRotation:   true,
    lockScalingX:   true,
    lockScalingY:   true,
    lockUniScaling: true
  };

  /**
   * Group class
   * @class fabric.Group
   * @extends fabric.Object
   * @mixes fabric.Collection
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups}
   * @see {@link fabric.Group#initialize} for constructor definition
   */
  fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'group',

    /**
     * Width of stroke
     * @type Number
     * @default
     */
    strokeWidth: 0,

    /**
     * Indicates if click events should also check for subtargets
     * @type Boolean
     * @default
     */
    subTargetCheck: false,

    /**
     * Constructor
     * @param {Object} objects Group objects
     * @param {Object} [options] Options object
     * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already.
     * @return {Object} thisArg
     */
    initialize: function(objects, options, isAlreadyGrouped) {
      options = options || { };

      this._objects = [];
      // if objects enclosed in a group have been grouped already,
      // we cannot change properties of objects.
      // Thus we need to set options to group without objects,
      // because delegatedProperties propagate to objects.
      isAlreadyGrouped && this.callSuper('initialize', options);

      this._objects = objects || [];
      for (var i = this._objects.length; i--; ) {
        this._objects[i].group = this;
      }

      this.originalState = { };

      if (options.originX) {
        this.originX = options.originX;
      }
      if (options.originY) {
        this.originY = options.originY;
      }

      if (isAlreadyGrouped) {
        // do not change coordinate of objects enclosed in a group,
        // because objects coordinate system have been group coodinate system already.
        this._updateObjectsCoords(true);
      }
      else {
        this._calcBounds();
        this._updateObjectsCoords();
        this.callSuper('initialize', options);
      }

      this.setCoords();
      this.saveCoords();
    },

    /**
     * @private
     * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change
     */
    _updateObjectsCoords: function(skipCoordsChange) {
      var center = this.getCenterPoint();
      for (var i = this._objects.length; i--; ){
        this._updateObjectCoords(this._objects[i], center, skipCoordsChange);
      }
    },

    /**
     * @private
     * @param {Object} object
     * @param {fabric.Point} center, current center of group.
     * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change
     */
    _updateObjectCoords: function(object, center, skipCoordsChange) {
      // do not display corners of objects enclosed in a group
      object.__origHasControls = object.hasControls;
      object.hasControls = false;

      if (skipCoordsChange) {
        return;
      }

      var objectLeft = object.getLeft(),
          objectTop = object.getTop(),
          ignoreZoom = true;

      object.set({
        originalLeft: objectLeft,
        originalTop: objectTop,
        left: objectLeft - center.x,
        top: objectTop - center.y
      });
      object.setCoords(ignoreZoom);
    },

    /**
     * Returns string represenation of a group
     * @return {String}
     */
    toString: function() {
      return '#<fabric.Group: (' + this.complexity() + ')>';
    },

    /**
     * Adds an object to a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    addWithUpdate: function(object) {
      this._restoreObjectsState();
      fabric.util.resetObjectTransform(this);
      if (object) {
        this._objects.push(object);
        object.group = this;
        object._set('canvas', this.canvas);
      }
      // since _restoreObjectsState set objects inactive
      this.forEachObject(this._setObjectActive, this);
      this._calcBounds();
      this._updateObjectsCoords();
      this.dirty = true;
      return this;
    },

    /**
     * @private
     */
    _setObjectActive: function(object) {
      object.set('active', true);
      object.group = this;
    },

    /**
     * Removes an object from a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    removeWithUpdate: function(object) {
      this._restoreObjectsState();
      fabric.util.resetObjectTransform(this);
      // since _restoreObjectsState set objects inactive
      this.forEachObject(this._setObjectActive, this);

      this.remove(object);
      this._calcBounds();
      this._updateObjectsCoords();
      this.dirty = true;
      return this;
    },

    /**
     * @private
     */
    _onObjectAdded: function(object) {
      this.dirty = true;
      object.group = this;
      object._set('canvas', this.canvas);
    },

    /**
     * @private
     */
    _onObjectRemoved: function(object) {
      this.dirty = true;
      delete object.group;
      object.set('active', false);
    },

    /**
     * Properties that are delegated to group objects when reading/writing
     * @param {Object} delegatedProperties
     */
    delegatedProperties: {
      fill:             true,
      stroke:           true,
      strokeWidth:      true,
      fontFamily:       true,
      fontWeight:       true,
      fontSize:         true,
      fontStyle:        true,
      lineHeight:       true,
      textDecoration:   true,
      textAlign:        true,
      backgroundColor:  true
    },

    /**
     * @private
     */
    _set: function(key, value) {
      var i = this._objects.length;

      if (this.delegatedProperties[key] || key === 'canvas') {
        while (i--) {
          this._objects[i].set(key, value);
        }
      }
      else {
        while (i--) {
          this._objects[i].setOnGroup(key, value);
        }
      }

      this.callSuper('_set', key, value);
    },

    /**
     * Returns object representation of an instance
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
     * @return {Object} object representation of an instance
     */
    toObject: function(propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        objects: invoke(this._objects, 'toObject', propertiesToInclude)
      });
    },

    /**
     * Renders instance on a given context
     * @param {CanvasRenderingContext2D} ctx context to render instance on
     */
    render: function(ctx) {
      this._transformDone = true;
      this.callSuper('render', ctx);
      this._transformDone = false;
    },

    /**
     * Execute the drawing operation for an object on a specified context
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    drawObject: function(ctx) {
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._renderObject(this._objects[i], ctx);
      }
    },

    /**
     * Check if cache is dirty
     */
    isCacheDirty: function() {
      if (this.callSuper('isCacheDirty')) {
        return true
      }
      if (!this.statefullCache) {
        return false;
      }
      for (var i = 0, len = this._objects.length; i < len; i++) {
        if (this._objects[i].isCacheDirty(true)) {
          var dim = this._getNonTransformedDimensions();
          this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
          return true
        }
      }
      return false;
    },

    /**
     * Renders controls and borders for the object
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    _renderControls: function(ctx, noTransform) {
      ctx.save();
      ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
      this.callSuper('_renderControls', ctx, noTransform);
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._objects[i]._renderControls(ctx);
      }
      ctx.restore();
    },

    /**
     * @private
     */
    _renderObject: function(object, ctx) {
      // do not render if object is not visible
      if (!object.visible) {
        return;
      }

      var originalHasRotatingPoint = object.hasRotatingPoint;
      object.hasRotatingPoint = false;
      object.render(ctx);
      object.hasRotatingPoint = originalHasRotatingPoint;
    },

    /**
     * Retores original state of each of group objects (original state is that which was before group was created).
     * @private
     * @return {fabric.Group} thisArg
     * @chainable
     */
    _restoreObjectsState: function() {
      this._objects.forEach(this._restoreObjectState, this);
      return this;
    },

    /**
     * Realises the transform from this group onto the supplied object
     * i.e. it tells you what would happen if the supplied object was in
     * the group, and then the group was destroyed. It mutates the supplied
     * object.
     * @param {fabric.Object} object
     * @return {fabric.Object} transformedObject
     */
    realizeTransform: function(object) {
      var matrix = object.calcTransformMatrix(),
          options = fabric.util.qrDecompose(matrix),
          center = new fabric.Point(options.translateX, options.translateY);
      object.flipX = false;
      object.flipY = false;
      object.set('scaleX', options.scaleX);
      object.set('scaleY', options.scaleY);
      object.skewX = options.skewX;
      object.skewY = options.skewY;
      object.angle = options.angle;
      object.setPositionByOrigin(center, 'center', 'center');
      return object;
    },

    /**
     * Restores original state of a specified object in group
     * @private
     * @param {fabric.Object} object
     * @return {fabric.Group} thisArg
     */
    _restoreObjectState: function(object) {
      this.realizeTransform(object);
      object.setCoords();
      object.hasControls = object.__origHasControls;
      delete object.__origHasControls;
      object.set('active', false);
      delete object.group;

      return this;
    },

    /**
     * Destroys a group (restoring state of its objects)
     * @return {fabric.Group} thisArg
     * @chainable
     */
    destroy: function() {
      return this._restoreObjectsState();
    },

    /**
     * Saves coordinates of this instance (to be used together with `hasMoved`)
     * @saveCoords
     * @return {fabric.Group} thisArg
     * @chainable
     */
    saveCoords: function() {
      this._originalLeft = this.get('left');
      this._originalTop = this.get('top');
      return this;
    },

    /**
     * Checks whether this group was moved (since `saveCoords` was called last)
     * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
     */
    hasMoved: function() {
      return this._originalLeft !== this.get('left') ||
             this._originalTop !== this.get('top');
    },

    /**
     * Sets coordinates of all group objects
     * @return {fabric.Group} thisArg
     * @chainable
     */
    setObjectsCoords: function() {
      var ignoreZoom = true;
      this.forEachObject(function(object) {
        object.setCoords(ignoreZoom);
      });
      return this;
    },

    /**
     * @private
     */
    _calcBounds: function(onlyWidthHeight) {
      var aX = [],
          aY = [],
          o, prop,
          props = ['tr', 'br', 'bl', 'tl'],
          i = 0, iLen = this._objects.length,
          j, jLen = props.length,
          ignoreZoom = true;

      for ( ; i < iLen; ++i) {
        o = this._objects[i];
        o.setCoords(ignoreZoom);
        for (j = 0; j < jLen; j++) {
          prop = props[j];
          aX.push(o.oCoords[prop].x);
          aY.push(o.oCoords[prop].y);
        }
      }

      this.set(this._getBounds(aX, aY, onlyWidthHeight));
    },

    /**
     * @private
     */
    _getBounds: function(aX, aY, onlyWidthHeight) {
      var minXY = new fabric.Point(min(aX), min(aY)),
          maxXY = new fabric.Point(max(aX), max(aY)),
          obj = {
            width: (maxXY.x - minXY.x) || 0,
            height: (maxXY.y - minXY.y) || 0
          };

      if (!onlyWidthHeight) {
        obj.left = minXY.x || 0;
        obj.top = minXY.y || 0;
        if (this.originX === 'center') {
          obj.left += obj.width / 2;
        }
        if (this.originX === 'right') {
          obj.left += obj.width;
        }
        if (this.originY === 'center') {
          obj.top += obj.height / 2;
        }
        if (this.originY === 'bottom') {
          obj.top += obj.height;
        }
      }
      return obj;
    },

    /* _TO_SVG_START_ */
    /**
     * Returns svg representation of an instance
     * @param {Function} [reviver] Method for further parsing of svg representation.
     * @return {String} svg representation of an instance
     */
    toSVG: function(reviver) {
      var markup = this._createBaseSVGMarkup();
      markup.push(
        '<g ', this.getSvgId(), 'transform="',
        /* avoiding styles intentionally */
        this.getSvgTransform(),
        this.getSvgTransformMatrix(),
        '" style="',
        this.getSvgFilter(),
        '">\n'
      );

      for (var i = 0, len = this._objects.length; i < len; i++) {
        markup.push('\t', this._objects[i].toSVG(reviver));
      }

      markup.push('</g>\n');

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * Returns requested property
     * @param {String} prop Property to get
     * @return {*}
     */
    get: function(prop) {
      if (prop in _lockProperties) {
        if (this[prop]) {
          return this[prop];
        }
        else {
          for (var i = 0, len = this._objects.length; i < len; i++) {
            if (this._objects[i][prop]) {
              return true;
            }
          }
          return false;
        }
      }
      else {
        if (prop in this.delegatedProperties) {
          return this._objects[0] && this._objects[0].get(prop);
        }
        return this[prop];
      }
    }
  });

  /**
   * Returns {@link fabric.Group} instance from an object representation
   * @static
   * @memberOf fabric.Group
   * @param {Object} object Object to create a group from
   * @param {Function} [callback] Callback to invoke when an group instance is created
   */
  fabric.Group.fromObject = function(object, callback) {
    fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
      delete object.objects;
      callback && callback(new fabric.Group(enlivenedObjects, object, true));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.Group
   * @type Boolean
   * @default
   */
  fabric.Group.async = true;

})(typeof exports !== 'undefined' ? exports : this);
<|endoftext|>


<commit_before>(function(){

var IS_NODE = false
if(typeof(module) !== 'undefined'){
	IS_NODE = true
}

var PI = Math.PI

var scadStr = "\n"

function deg2rad(deg){
	return deg / 360 * 2 * PI
}

function rad2deg(rad){
	return rad / 2 / PI * 360
}

function getPointsOnCircle(r, n, startDeg){
	var ret = new Array()
	var startRadius = startDeg/360*2*PI
	var radPerPoint = 2*PI/n
	for(var i = 0; i < n; i++){
		ret.push([Math.cos(startRadius + radPerPoint * i ) * r, Math.sin(startRadius + radPerPoint * i ) * r ] )
	}
	return ret
}

function distanceToDegOnCircle(d, r){
	return rad2deg( 2 * Math.asin(d / 2 / r) )
}

//array to vector string
function a2v(a){
	return JSON.stringify(a)
}

///// 2d
function polygon(aPoints, aPaths){
	var param = ""
	if(typeof(aPaths) != "undefined"){
		param += ", " + a2v(aPaths)
	}

	var s = `polygon(${a2v(aPoints)}${param});\n`
	// scadStr += s
	return s
}

function circle(r, fn){
	var param = ""
	if(typeof(fn) != 'undefined') {
		param = ",$fn="+fn
	}
	return `circle(r=${r}${param});\n`
}

function square(width, height, isCenter){
	var param = ""
	if(typeof(height) == 'undefined'){
		param = width
	}else{
		param = `[${width},${height}]`
	}

	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	return `square(${param});\n`
}

function text(str ,size){
	var param = ""
	if(typeof(size) != "undefined"){
		param = ",size=" + size
	}
	return `text(text=\"${str}\"${param});\n`
}

///// 3d
function cube(x, y, z, isCenter){
	var param = ""
	if(typeof(y) == 'undefined'){
		param = x
	}else{
		param = `[${x},${y},${z}]`
	}

	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	return `cube(${param});\n`
}

function sphere(r, fn){
	var param = ""
	if(typeof(fn) != 'undefined') {
		param = ",$fn="+fn
	}
	return `sphere(r=${r}${param});\n`
}
var ball = sphere

function cylinder(height, r1, r2, isCenter, fn){
	var param = ""
	if(typeof(r2) == 'undefined'){
		var r2 = r1
	}

	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	if(typeof(fn) != 'undefined') {
		param += ",$fn="+fn
	}
	return `cylinder(h=${height}, r1=${r1}, r2=${r2}${param});\n`
}

function polyhedron(aPoints, aFaces){
	var param = ""
	if(typeof(aFaces) != "undefined"){
		param += ",faces=" + a2v(aFaces)
	}
	return "polyhedron(points=${a2v(aPoints)}${param});\n"
}

///// calc //////////////////////////////////////////
function difference(a,b){
	return  "difference(){\n" + a + b + "}\n"
}

var minus = difference

function union(){
	var ret = "union(){\n"
	for(var i = 0; i < arguments.length; i++){
		ret += arguments[i]
	}
	return ret + "}\n"
}

var add = union

function intersection(){
	var ret = "intersection(){\n"
	for(var i = 0; i < arguments.length; i++){
		ret += arguments[i]
	}
	return ret + "}\n"
}

var shared = intersection

////// reform
function rotate(item, xDeg, yDeg, zDeg){
	return `rotate([${xDeg},${yDeg},${zDeg}]) {\n` + item + "}\n"
}

function translate(item, x, y, z){
	return `translate([${x},${y},${z}]) {\n` + item + "}\n"
}

function scale(item, x, y, z){
	return `scale([${x},${y},${z}]) {\n` + item + "}\n"
}

function resize(item, x, y, z){
	return `resize([${x},${y},${z}]) {\n` + item + "}\n"
}

function mirror(item, x, y, z){
	return `mirror([${x},${y},${z}]) {\n` + item + "}\n"
}

function offset(item, r, delta, isChamfer){
	var param = ""
	if(typeof(r) != "undefined"){
		param += "r=" + r
	}else if(typeof(delta) != undefined){
		param += "delta=" + delta
		if(typeof(isChamfer) != "undefined"){
		isChamfer ? isChamfer = true : isChamfer = false
		param += `,chamfer=${isChamfer}`
		}
	}
	return `offset(${param}) {\n` + item + "}\n"

}

function minkowski(item){
	return `minkowski() {\n` + item + "}\n"
}

function hull(item){
	return `hull() {\n` + item + "}\n"
}

////// 3d-> 2d
function projection(item, isCut){
	var param = ""
	if(typeof(isCut) != "undefined"){
		if(isCut){
			param += "cut=true"
		}else{
			param += "cut=false"
		}
	}
	return `projection(${param}) {\n` + item + "}\n"
}

////// 2d -> 3d
function linear_extrude(items, height, twistDeg, scale, slices, isCenter, convexity, fn){
	var param = ""
	if(typeof(twistDeg) != "undefined"){
		param += ",twist=" + twistDeg
	}
	if (typeof(scale) != "undefined"){
		if(typeof(scale) == "number"){
			param += ",scale=" + scale
		}else{
			param += ",scale=" + a2v(scale)
		}
	}
	if (typeof(slices) != "undefined"){
		param += ",slices=" + slices
	}
	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	if(typeof(convexity) !="undefined"){

		param += `,convexity=${convexity}`
	}else{
		param += `,convexity=10`
	}
	if(typeof(fn) != 'undefined') {
		param += ",$fn="+fn
	}

	return `linear_extrude(height=${height}${param}) {` + items + "}\n"

}
function rotate_extrude(items, deg, convexity, fn){
	var param = ""
	if(typeof(convexity) !="undefined"){

		param += `,convexity=${convexity}`
	}else{
		param += `,convexity=10`
	}
	if(typeof(fn) != 'undefined') {
		param += ",$fn="+fn
	}

	return `rotate_extrude(angle=${deg}${param}) {` + items + "}\n"

}
/////// logic
function scadfor(str_i, start, end, item){
	return `for(${str_i}=[${start}:${end}]){\n${item}\n}`
}

//////// others
function color(r,g,b,a){
	return `color([${r/255},${g/255},${b/255},${a}) {\n` + item + "}\n"
}

// same name with node module
function scadmodule(name, params){
	//tbd
}

function children(){
	//tbd
}

if(IS_NODE){
	var fs = require('fs')
	var path = require('path')
}
function save(content, fn){
	if(typeof(fn) != 'undefined'){
		var filename = path.basename(fn,'.js') + '.scad'
	}else{
		var filename = "output.scad"
	}
	console.log("save to ",filename)
	fs.writeFileSync(filename, content)

}

function Scad(str){
	if(typeof(str) == 'undefined'){
		this.str = ""
	}else{
		this.str = str
	}
}

Scad.prototype = {
	toString: function() {return this.str},
	// deg2rad : deg2rad,
	// rad2deg : rad2deg,
	// getPointsOnCircle :getPointsOnCircle,
	// distanceToDegOnCircle : distanceToDegOnCircle,
	// a2v:a2v,
	// polygon:polygon,
	// circle:circle,
	// square:square,
	// rect:square,
	// text:text,
	// cube:cube,
	// sphere:sphere,
	// ball:sphere,
	// cylinder:cylinder,
	// polyhedron:polyhedron,
	// difference:difference,
	// minus:difference,
	// union:union,
	// add:union,
	// intersection:intersection,
	// shared:intersection,
	rotate:function(x,y,z) {this.str = rotate(this.str, x,y,z); return this},
	translate:function(x,y,z) {this.str = translate(this.str, x,y,z); return this},
	scale: function(x,y,z) {this.str = scale(this.str, x,y,z); return this},
	resize:function(x,y,z) {this.str = resize(this.str, x,y,z); return this},
	mirror:function(x,y,z) {this.str = mirror(this.str, x,y,z); return this},
	offset:function(r, delta, isChamfer) {this.str = offset(this.str, r, delta, isChamfer); return this},
	minkowski: function(){ this.str = minkowski(this.str); return this },
	hull: function(){this.str = hull(this.str); return this } ,
	projection: function(isCut){this.str = projection(this.str, isCut); return this},
	linear_extrude: function(height, twistDeg, scale, slices, isCenter, convexity, fn){ this.str = linear_extrude(this.str, height, twistDeg, scale, slices, isCenter, convexity, fn);return this },
	rotate_extrude: function(deg, convexity, fn){ this.str =  rotate_extrude(this.str, deg, convexity, fn); return this},
	for: function(str_i, start, end){ this.str = scadfor(str_i, start, end, this.str); return this},
	// color:color,
	// module:scadmodule,
	// children:children,
	// save:save,
}
Scad.deg2rad = deg2rad
Scad.rad2deg = rad2deg
Scad.getPointsOnCircle =getPointsOnCircle
Scad.distanceToDegOnCircle = distanceToDegOnCircle
Scad.a2v=a2v
Scad.polygon=polygon
Scad.circle=circle
Scad.square=square
Scad.rect=square
Scad.text=text
Scad.cube=cube
Scad.sphere=sphere
Scad.ball=sphere
Scad.cylinder=cylinder
Scad.polyhedron=polyhedron
Scad.difference=difference
Scad.minus=difference
Scad.union=union
Scad.add=union
Scad.intersection=intersection
Scad.shared=intersection
Scad.rotate=rotate
Scad.translate=translate
Scad.scale=scale
Scad.resize=resize
Scad.mirror=mirror
Scad.offset=offset
Scad.minkowski=minkowski
Scad.hull=hull
Scad.projection=projection
Scad.linear_extrude=linear_extrude
Scad.rotate_extrude=rotate_extrude
Scad.for=scadfor
Scad.color=color
Scad.module=scadmodule
Scad.children=children
Scad.save=save
/////////////// exports ////////////////////////////////////
if(IS_NODE){
	module.exports = Scad
}

})()<commit_msg>add copyright. add add() minus() to Class.<commit_after>/* scad.js
* version: 1.1.0
* date: 2017-3-12
* copyright: Jacques Yang
* License: MIT
*/

(function(){

var IS_NODE = false
if(typeof(module) !== 'undefined'){
	IS_NODE = true
}

var PI = Math.PI

var scadStr = "\n"

function deg2rad(deg){
	return deg / 360 * 2 * PI
}

function rad2deg(rad){
	return rad / 2 / PI * 360
}

function getPointsOnCircle(r, n, startDeg){
	var ret = new Array()
	var startRadius = startDeg/360*2*PI
	var radPerPoint = 2*PI/n
	for(var i = 0; i < n; i++){
		ret.push([Math.cos(startRadius + radPerPoint * i ) * r, Math.sin(startRadius + radPerPoint * i ) * r ] )
	}
	return ret
}

function distanceToDegOnCircle(d, r){
	return rad2deg( 2 * Math.asin(d / 2 / r) )
}

//array to vector string
function a2v(a){
	return JSON.stringify(a)
}

///// 2d
function polygon(aPoints, aPaths){
	var param = ""
	if(typeof(aPaths) != "undefined"){
		param += ", " + a2v(aPaths)
	}

	var s = `polygon(${a2v(aPoints)}${param});\n`
	// scadStr += s
	return s
}

function circle(r, fn){
	var param = ""
	if(typeof(fn) != 'undefined') {
		param = ",$fn="+fn
	}
	return `circle(r=${r}${param});\n`
}

function square(width, height, isCenter){
	var param = ""
	if(typeof(height) == 'undefined'){
		param = width
	}else{
		param = `[${width},${height}]`
	}

	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	return `square(${param});\n`
}

function text(str ,size){
	var param = ""
	if(typeof(size) != "undefined"){
		param = ",size=" + size
	}
	return `text(text=\"${str}\"${param});\n`
}

///// 3d
function cube(x, y, z, isCenter){
	var param = ""
	if(typeof(y) == 'undefined'){
		param = x
	}else{
		param = `[${x},${y},${z}]`
	}

	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	return `cube(${param});\n`
}

function sphere(r, fn){
	var param = ""
	if(typeof(fn) != 'undefined') {
		param = ",$fn="+fn
	}
	return `sphere(r=${r}${param});\n`
}
var ball = sphere

function cylinder(height, r1, r2, isCenter, fn){
	var param = ""
	if(typeof(r2) == 'undefined'){
		var r2 = r1
	}

	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	if(typeof(fn) != 'undefined') {
		param += ",$fn="+fn
	}
	return `cylinder(h=${height}, r1=${r1}, r2=${r2}${param});\n`
}

function polyhedron(aPoints, aFaces){
	var param = ""
	if(typeof(aFaces) != "undefined"){
		param += ",faces=" + a2v(aFaces)
	}
	return "polyhedron(points=${a2v(aPoints)}${param});\n"
}

///// calc //////////////////////////////////////////
function difference(a,b){
	return  "difference(){\n" + a + b + "}\n"
}

var minus = difference

function union(){
	var ret = "union(){\n"
	for(var i = 0; i < arguments.length; i++){
		ret += arguments[i]
	}
	return ret + "}\n"
}

var add = union

function intersection(){
	var ret = "intersection(){\n"
	for(var i = 0; i < arguments.length; i++){
		ret += arguments[i]
	}
	return ret + "}\n"
}

var shared = intersection

////// reform
function rotate(item, xDeg, yDeg, zDeg){
	return `rotate([${xDeg},${yDeg},${zDeg}]) {\n` + item + "}\n"
}

function translate(item, x, y, z){
	return `translate([${x},${y},${z}]) {\n` + item + "}\n"
}

function scale(item, x, y, z){
	return `scale([${x},${y},${z}]) {\n` + item + "}\n"
}

function resize(item, x, y, z){
	return `resize([${x},${y},${z}]) {\n` + item + "}\n"
}

function mirror(item, x, y, z){
	return `mirror([${x},${y},${z}]) {\n` + item + "}\n"
}

function offset(item, r, delta, isChamfer){
	var param = ""
	if(typeof(r) != "undefined"){
		param += "r=" + r
	}else if(typeof(delta) != undefined){
		param += "delta=" + delta
		if(typeof(isChamfer) != "undefined"){
		isChamfer ? isChamfer = true : isChamfer = false
		param += `,chamfer=${isChamfer}`
		}
	}
	return `offset(${param}) {\n` + item + "}\n"

}

function minkowski(item){
	return `minkowski() {\n` + item + "}\n"
}

function hull(item){
	return `hull() {\n` + item + "}\n"
}

////// 3d-> 2d
function projection(item, isCut){
	var param = ""
	if(typeof(isCut) != "undefined"){
		if(isCut){
			param += "cut=true"
		}else{
			param += "cut=false"
		}
	}
	return `projection(${param}) {\n` + item + "}\n"
}

////// 2d -> 3d
function linear_extrude(items, height, twistDeg, scale, slices, isCenter, convexity, fn){
	var param = ""
	if(typeof(twistDeg) != "undefined"){
		param += ",twist=" + twistDeg
	}
	if (typeof(scale) != "undefined"){
		if(typeof(scale) == "number"){
			param += ",scale=" + scale
		}else{
			param += ",scale=" + a2v(scale)
		}
	}
	if (typeof(slices) != "undefined"){
		param += ",slices=" + slices
	}
	if(typeof(isCenter) !="undefined"){
		isCenter ? isCenter = true : isCenter = false
		param += `,center=${isCenter}`
	}
	if(typeof(convexity) !="undefined"){

		param += `,convexity=${convexity}`
	}else{
		param += `,convexity=10`
	}
	if(typeof(fn) != 'undefined') {
		param += ",$fn="+fn
	}

	return `linear_extrude(height=${height}${param}) {` + items + "}\n"

}
function rotate_extrude(items, deg, convexity, fn){
	var param = ""
	if(typeof(convexity) !="undefined"){

		param += `,convexity=${convexity}`
	}else{
		param += `,convexity=10`
	}
	if(typeof(fn) != 'undefined') {
		param += ",$fn="+fn
	}

	return `rotate_extrude(angle=${deg}${param}) {` + items + "}\n"

}
/////// logic
function scadfor(str_i, start, end, item){
	return `for(${str_i}=[${start}:${end}]){\n${item}\n}`
}

//////// others
function color(r,g,b,a){
	return `color([${r/255},${g/255},${b/255},${a}) {\n` + item + "}\n"
}

// same name with node module
function scadmodule(name, params){
	//tbd
}

function children(){
	//tbd
}

if(IS_NODE){
	var fs = require('fs')
	var path = require('path')
}
function save(content, fn){
	if(typeof(fn) != 'undefined'){
		var filename = path.basename(fn,'.js') + '.scad'
	}else{
		var filename = "output.scad"
	}
	console.log("save to ",filename)
	fs.writeFileSync(filename, content)

}

function Scad(str){
	if(typeof(str) == 'undefined'){
		this.str = ""
	}else{
		this.str = str
	}
}

Scad.prototype = {
	toString: function() {return this.str},
	// deg2rad : deg2rad,
	// rad2deg : rad2deg,
	// getPointsOnCircle :getPointsOnCircle,
	// distanceToDegOnCircle : distanceToDegOnCircle,
	// a2v:a2v,
	// polygon:polygon,
	// circle:circle,
	// square:square,
	// rect:square,
	// text:text,
	// cube:cube,
	// sphere:sphere,
	// ball:sphere,
	// cylinder:cylinder,
	// polyhedron:polyhedron,
	difference: function(b){this.str = difference(this, b); return this},
	minus: this.difference,
	union: function(){this.str = union.apply(this, arguments); return this},
	add: this.union,
	// intersection:intersection,
	// shared:intersection,
	rotate:function(x,y,z) {this.str = rotate(this.str, x,y,z); return this},
	translate:function(x,y,z) {this.str = translate(this.str, x,y,z); return this},
	scale: function(x,y,z) {this.str = scale(this.str, x,y,z); return this},
	resize:function(x,y,z) {this.str = resize(this.str, x,y,z); return this},
	mirror:function(x,y,z) {this.str = mirror(this.str, x,y,z); return this},
	offset:function(r, delta, isChamfer) {this.str = offset(this.str, r, delta, isChamfer); return this},
	minkowski: function(){ this.str = minkowski(this.str); return this },
	hull: function(){this.str = hull(this.str); return this } ,
	projection: function(isCut){this.str = projection(this.str, isCut); return this},
	linear_extrude: function(height, twistDeg, scale, slices, isCenter, convexity, fn){ this.str = linear_extrude(this.str, height, twistDeg, scale, slices, isCenter, convexity, fn);return this },
	rotate_extrude: function(deg, convexity, fn){ this.str =  rotate_extrude(this.str, deg, convexity, fn); return this},
	for: function(str_i, start, end){ this.str = scadfor(str_i, start, end, this.str); return this},
	// color:color,
	// module:scadmodule,
	// children:children,
	// save:save,
	tranparent: function(){this.str = "#"+this.str; return this},
	_: function(){this.str = "#"+this.str; return this},
}
Scad.deg2rad = deg2rad
Scad.rad2deg = rad2deg
Scad.getPointsOnCircle =getPointsOnCircle
Scad.distanceToDegOnCircle = distanceToDegOnCircle
Scad.a2v=a2v
Scad.polygon=polygon
Scad.circle=circle
Scad.square=square
Scad.rect=square
Scad.text=text
Scad.cube=cube
Scad.sphere=sphere
Scad.ball=sphere
Scad.cylinder=cylinder
Scad.polyhedron=polyhedron
Scad.difference=difference
Scad.minus=difference
Scad.union=union
Scad.add=union
Scad.intersection=intersection
Scad.shared=intersection
Scad.rotate=rotate
Scad.translate=translate
Scad.scale=scale
Scad.resize=resize
Scad.mirror=mirror
Scad.offset=offset
Scad.minkowski=minkowski
Scad.hull=hull
Scad.projection=projection
Scad.linear_extrude=linear_extrude
Scad.rotate_extrude=rotate_extrude
Scad.for=scadfor
Scad.color=color
Scad.module=scadmodule
Scad.children=children
Scad.save=save
/////////////// exports ////////////////////////////////////
if(IS_NODE){
	module.exports = Scad
}

})()<|endoftext|>


<commit_before>/*
 *      This file is part of GPaste.
 *
 *      Copyright 2011 Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
 *
 *      GPaste is free software: you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation, either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      GPaste is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with GPaste.  If not, see <http://www.gnu.org/licenses/>.
 */
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const Main = imports.ui.main;
const Panel = imports.ui.panel;
const Lang = imports.lang;
const Gio = imports.gi.Gio;
const Pango = imports.gi.Pango;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Gettext = imports.gettext;

const _ = Gettext.domain('GPaste').gettext;
const BUS_NAME = 'org.gnome.GPaste';
const OBJECT_PATH = '/org/gnome/GPaste';

const GPasteInterface =
    <interface name="org.gnome.GPaste">
        <method name="GetHistory">
            <arg type="as" direction="out" />
        </method>
        <method name="Select">
            <arg type="u" direction="in" />
        </method>
        <method name="Delete">
            <arg type="u" direction="in" />
        </method>
        <method name="Empty" />
        <method name="Track">
            <arg type="b" direction="in" />
        </method>
        <method name="OnExtensionStateChanged">
            <arg type="b" direction="in" />
        </method>
        <signal name="Changed" />
        <signal name="ToggleHistory" />
        <signal name="Tracking">
            <arg type="b" direction="out" />
        </signal>
        <property name="Active" type="b" access="read" />
    </interface>;
const GPasteProxy = Gio.DBusProxy.makeProxyWrapper(GPasteInterface);

const GPasteIndicator = new Lang.Class({
    Name: 'GPasteIndicator',
    Extends: PanelMenu.SystemStatusButton,

    _init: function() {
        this.parent('edit-paste-symbolic');
        this._killSwitch = new PopupMenu.PopupSwitchMenuItem(_("Track changes"), true);
        this._killSwitch.connect('toggled', Lang.bind(this, this._toggleDaemon));
        this._proxy = new GPasteProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH);
        this._proxy.connectSignal('Changed', Lang.bind(this, this._updateHistory));
        this._proxy.connectSignal('ToggleHistory', Lang.bind(this, this._toggleHistory));
        this._proxy.connectSignal('Tracking', Lang.bind(this, function(proxy, sender, [trackingState]) {
            this._trackingStateChanged(trackingState);
        }));
        this._createHistory();
        this._noHistory = new PopupMenu.PopupMenuItem("");
        this._noHistory.setSensitive(false);
        this._emptyHistory = new PopupMenu.PopupMenuItem(_("Empty history"));
        this._emptyHistory.connect('activate', Lang.bind(this, this._empty));
        this._fillMenu();
    },

    _select: function(index) {
        this._proxy.SelectRemote(index);
    },

    _delete: function(index) {
        this._proxy.DeleteRemote(index);
    },

    _empty: function() {
        this._proxy.EmptyRemote();
    },

    _trackingStateChanged: function(trackingState) {
        this._killSwitch.setToggleState(trackingState);
    },

    _toggleDaemon: function() {
        this._proxy.TrackRemote(this._killSwitch.state);
    },

    _fillMenu: function() {
        let active = this._proxy.Active;
        if (active != null)
            this._killSwitch.setToggleState(active);
        this.menu.addMenuItem(this._killSwitch);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this._addHistoryItems();
        this.menu.addMenuItem(this._noHistory);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(this._emptyHistory);
        this.menu.addSettingsAction(_("GPaste daemon settings"), 'gpaste-settings.desktop');
        this._updateHistory();
    },

    _updateHistory: function() {
        this._proxy.GetHistoryRemote(Lang.bind(this, function(result, err) {
            let [history] = err ? [null] : result;
            if (history != null && history.length != 0) {
                let limit = Math.min(history.length, this._history.length);
                for (let index = 0; index < limit; ++index)
                    this._updateHistoryItem(index, history[index]);
                this._hideHistory(limit);
                this._noHistory.actor.hide();
                this._emptyHistory.actor.show();
            } else {
                this._noHistory.label.text = (history == null) ? _("(Couldn't connect to GPaste daemon)") : _("(Empty)");
                this._hideHistory();
                this._emptyHistory.actor.hide();
                this._noHistory.actor.show();
            }
        }));
    },

    _toggleHistory: function() {
        this.menu.toggle();
    },

    _createHistoryItem: function(index) {
        let item = new PopupMenu.PopupAlternatingMenuItem("");
        item.actor.set_style_class_name('popup-menu-item');
        let label = item.label;
        label.clutter_text.max_length = 60;
        label.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        item.connect('activate', Lang.bind(this, function(actor, event) {
            if (item.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
                this._select(index);
                return false;
            } else {
                this._delete(index);
                return true;
            }
        }));
        return item;
    },

    _createHistory: function() {
        this._history = [];
        for (let index = 0; index < 20; ++index)
            this._history[index] = this._createHistoryItem(index);
        this._history[0].actor.set_style("font-weight: bold;");
    },

    _addHistoryItems: function() {
        for (let index = 0; index < this._history.length; ++index)
            this.menu.addMenuItem(this._history[index]);
    },

    _updateHistoryItem: function(index, element) {
        let displayStr = element.replace(/\n/g, ' ');
        let altDisplayStr = _("delete: %s").format(displayStr);
        this._history[index].updateText(displayStr, altDisplayStr);
        this._history[index].actor.show();
    },

    _hideHistory: function(startIndex) {
        for (let index = startIndex || 0; index < this._history.length; ++index)
            this._history[index].actor.hide();
    },

    _onStateChanged: function (state) {
        this._proxy.OnExtensionStateChangedRemote(state);
    }
});

let _indicator;

function init(metadata) {
    Gettext.bindtextdomain('gpaste', metadata.localedir);
    StatusIconDispatcher.STANDARD_TRAY_ICON_IMPLEMENTATIONS['gpaste-applet'] = 'gpaste';
    Panel.STANDARD_STATUS_AREA_ORDER.unshift('gpaste');
}

function enable() {
    _indicator = new GPasteIndicator();
    _indicator._onStateChanged (true);
    Main.panel.addToStatusArea('gpaste', _indicator);
}

function disable() {
    _indicator._onStateChanged (false);
    _indicator.destroy();
}

<commit_msg>update to latest shell api<commit_after>/*
 *      This file is part of GPaste.
 *
 *      Copyright 2011 Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
 *
 *      GPaste is free software: you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation, either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      GPaste is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with GPaste.  If not, see <http://www.gnu.org/licenses/>.
 */
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const Main = imports.ui.main;
const Panel = imports.ui.panel;
const Lang = imports.lang;
const Gio = imports.gi.Gio;
const Pango = imports.gi.Pango;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Gettext = imports.gettext;

const _ = Gettext.domain('GPaste').gettext;
const BUS_NAME = 'org.gnome.GPaste';
const OBJECT_PATH = '/org/gnome/GPaste';

const GPasteInterface =
    <interface name="org.gnome.GPaste">
        <method name="GetHistory">
            <arg type="as" direction="out" />
        </method>
        <method name="Select">
            <arg type="u" direction="in" />
        </method>
        <method name="Delete">
            <arg type="u" direction="in" />
        </method>
        <method name="Empty" />
        <method name="Track">
            <arg type="b" direction="in" />
        </method>
        <method name="OnExtensionStateChanged">
            <arg type="b" direction="in" />
        </method>
        <signal name="Changed" />
        <signal name="ToggleHistory" />
        <signal name="Tracking">
            <arg type="b" direction="out" />
        </signal>
        <property name="Active" type="b" access="read" />
    </interface>;
const GPasteProxy = Gio.DBusProxy.makeProxyWrapper(GPasteInterface);

const GPasteIndicator = new Lang.Class({
    Name: 'GPasteIndicator',
    Extends: PanelMenu.SystemStatusButton,

    _init: function() {
        this.parent('edit-paste-symbolic');
        this._killSwitch = new PopupMenu.PopupSwitchMenuItem(_("Track changes"), true);
        this._killSwitch.connect('toggled', Lang.bind(this, this._toggleDaemon));
        this._proxy = new GPasteProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH);
        this._proxy.connectSignal('Changed', Lang.bind(this, this._updateHistory));
        this._proxy.connectSignal('ToggleHistory', Lang.bind(this, this._toggleHistory));
        this._proxy.connectSignal('Tracking', Lang.bind(this, function(proxy, sender, [trackingState]) {
            this._trackingStateChanged(trackingState);
        }));
        this._createHistory();
        this._noHistory = new PopupMenu.PopupMenuItem("");
        this._noHistory.setSensitive(false);
        this._emptyHistory = new PopupMenu.PopupMenuItem(_("Empty history"));
        this._emptyHistory.connect('activate', Lang.bind(this, this._empty));
        this._fillMenu();
    },

    _select: function(index) {
        this._proxy.SelectRemote(index);
    },

    _delete: function(index) {
        this._proxy.DeleteRemote(index);
    },

    _empty: function() {
        this._proxy.EmptyRemote();
    },

    _trackingStateChanged: function(trackingState) {
        this._killSwitch.setToggleState(trackingState);
    },

    _toggleDaemon: function() {
        this._proxy.TrackRemote(this._killSwitch.state);
    },

    _fillMenu: function() {
        let active = this._proxy.Active;
        if (active != null)
            this._killSwitch.setToggleState(active);
        this.menu.addMenuItem(this._killSwitch);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this._addHistoryItems();
        this.menu.addMenuItem(this._noHistory);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(this._emptyHistory);
        this.menu.addSettingsAction(_("GPaste daemon settings"), 'gpaste-settings.desktop');
        this._updateHistory();
    },

    _updateHistory: function() {
        this._proxy.GetHistoryRemote(Lang.bind(this, function(result, err) {
            let [history] = err ? [null] : result;
            if (history != null && history.length != 0) {
                let limit = Math.min(history.length, this._history.length);
                for (let index = 0; index < limit; ++index)
                    this._updateHistoryItem(index, history[index]);
                this._hideHistory(limit);
                this._noHistory.actor.hide();
                this._emptyHistory.actor.show();
            } else {
                this._noHistory.label.text = (history == null) ? _("(Couldn't connect to GPaste daemon)") : _("(Empty)");
                this._hideHistory();
                this._emptyHistory.actor.hide();
                this._noHistory.actor.show();
            }
        }));
    },

    _toggleHistory: function() {
        this.menu.toggle();
    },

    _createHistoryItem: function(index) {
        let item = new PopupMenu.PopupAlternatingMenuItem("");
        item.actor.set_style_class_name('popup-menu-item');
        let label = item.label;
        label.clutter_text.max_length = 60;
        label.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        item.connect('activate', Lang.bind(this, function(actor, event) {
            if (item.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
                this._select(index);
                return false;
            } else {
                this._delete(index);
                return true;
            }
        }));
        return item;
    },

    _createHistory: function() {
        this._history = [];
        for (let index = 0; index < 20; ++index)
            this._history[index] = this._createHistoryItem(index);
        this._history[0].actor.set_style("font-weight: bold;");
    },

    _addHistoryItems: function() {
        for (let index = 0; index < this._history.length; ++index)
            this.menu.addMenuItem(this._history[index]);
    },

    _updateHistoryItem: function(index, element) {
        let displayStr = element.replace(/\n/g, ' ');
        let altDisplayStr = _("delete: %s").format(displayStr);
        this._history[index].updateText(displayStr, altDisplayStr);
        this._history[index].actor.show();
    },

    _hideHistory: function(startIndex) {
        for (let index = startIndex || 0; index < this._history.length; ++index)
            this._history[index].actor.hide();
    },

    _onStateChanged: function (state) {
        this._proxy.OnExtensionStateChangedRemote(state);
    }
});

let _indicator;

function init(extension) {
    Gettext.bindtextdomain('gpaste', extension.metadata.localedir);
    StatusIconDispatcher.STANDARD_TRAY_ICON_IMPLEMENTATIONS['gpaste-applet'] = 'gpaste';
    Panel.STANDARD_STATUS_AREA_ORDER.unshift('gpaste');
}

function enable() {
    _indicator = new GPasteIndicator();
    _indicator._onStateChanged (true);
    Main.panel.addToStatusArea('gpaste', _indicator);
}

function disable() {
    _indicator._onStateChanged (false);
    _indicator.destroy();
}

<|endoftext|>


<commit_before>/*
 *      This file is part of GPaste.
 *
 *      Copyright 2011-2013 Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
 *
 *      GPaste is free software: you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation, either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      GPaste is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with GPaste.  If not, see <http://www.gnu.org/licenses/>.
 */
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

const Clutter = imports.gi.Clutter;
const Gettext = imports.gettext;
const GPaste = imports.gi.GPaste;
const Lang = imports.lang;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const Pango = imports.gi.Pango;
const PopupMenu = imports.ui.popupMenu;
const St = imports.gi.St;

const _ = Gettext.domain('GPaste').gettext;
const BUS_NAME = 'org.gnome.GPaste';
const OBJECT_PATH = '/org/gnome/GPaste';

const GPasteDeleteButton = new Lang.Class({
    Name: 'GPasteDeleteButton',
    Extends: St.Button,

    _init: function(client, index) {
        this.parent();

        this.child = new St.Icon({
            icon_name: 'edit-delete-symbolic',
            style_class: 'system-status-icon'
        });

        this.connect('clicked', function() {
            client.delete(index);
            return true;
        });

    }
});

const GPasteDeleteMenuItemPart = new Lang.Class({
    Name: 'GPasteDeleteMenuItemPart',
    Extends: St.Bin,

    _init: function(client, index) {
        this.parent({ x_align: St.Align.END });

        this.child = new GPasteDeleteButton(client, index);
    }
});

const GPasteMenuItem = new Lang.Class({
    Name: 'GPasteMenuItem',
    Extends: PopupMenu.PopupMenuItem,

    _init: function(client, index) {
        this.parent("");

        let text = this.label.clutter_text;
        text.max_length = 60;
        text.ellipsize = Pango.EllipsizeMode.END;

        this.connect('activate', Lang.bind(this, function(actor, event) {
            client.select(index);
        }));

        this.actor.connect('key-press-event', Lang.bind(this, function(actor, event) {
            let symbol = event.get_key_symbol();
            if (symbol == Clutter.KEY_BackSpace || symbol == Clutter.KEY_Delete) {
                client.delete(index);
                return true;
            }
            return false;
        }));

        this.actor.add(new GPasteDeleteMenuItemPart(client, index), { expand: true, x_align: St.Align.END });
    }
});

const GPasteIndicator = new Lang.Class({
    Name: 'GPasteIndicator',
    Extends: PanelMenu.Button,

    _init: function() {
        this.parent(0.0, "GPaste");

        let hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
        hbox.add_child(new St.Icon({
            icon_name: 'edit-paste-symbolic',
            style_class: 'system-status-icon'
        }));
        hbox.add_child(new St.Label({
            text: '\u25BE',
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER
        }));
        this.actor.add_child(hbox);

        this._killSwitch = new PopupMenu.PopupSwitchMenuItem(_("Track changes"), true);
        this._killSwitch.connect('toggled', Lang.bind(this, this._toggleDaemon));
        this._client = new GPaste.Client();
        this._client.connect('changed', Lang.bind(this, this._updateHistory));
        this._client.connect('show-history', Lang.bind(this, this._showHistory));
        this._client.connect('tracking', Lang.bind(this, function(c, state) {
            this._killSwitch.setToggleState(state);
        }));
        this._createHistory();
        this._noHistory = new PopupMenu.PopupMenuItem("");
        this._noHistory.setSensitive(false);
        this._emptyHistory = new PopupMenu.PopupMenuItem(_("Empty history"));
        this._emptyHistory.connect('activate', Lang.bind(this, this._empty));
        this._fillMenu();
    },

    _select: function(index) {
        this._client.select(index);
    },

    _delete: function(index) {
        this._client.delete(index);
    },

    _empty: function() {
        this._client.empty();
    },

    _toggleDaemon: function() {
        this._client.track(this._killSwitch.state);
    },

    _fillMenu: function() {
        this._killSwitch.setToggleState(this._client.is_active());
        this.menu.addMenuItem(this._killSwitch);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this._addHistoryItems();
        this.menu.addMenuItem(this._noHistory);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(this._emptyHistory);
        this.menu.addSettingsAction(_("GPaste daemon settings"), 'gpaste-settings.desktop');
        this._updateHistory();
    },

    _updateHistory: function() {
        let history = this._client.get_history();
        if (history != null && history.length != 0) {
            let limit = Math.min(history.length, this._history.length);
            for (let index = 0; index < limit; ++index)
                this._updateHistoryItem(index, history[index]);
            this._hideHistory(limit);
            this._noHistory.actor.hide();
            this._emptyHistory.actor.show();
        } else {
            this._noHistory.label.text = (history == null) ? _("(Couldn't connect to GPaste daemon)") : _("(Empty)");
            this._hideHistory();
            this._emptyHistory.actor.hide();
            this._noHistory.actor.show();
        }
    },

    _showHistory: function() {
        this.menu.open(true);
        this.menu.firstMenuItem.setActive(true);
    },

    _createHistory: function() {
        this._history = [];
        for (let index = 0; index < 20; ++index)
            this._history[index] = new GPasteMenuItem(this._client, index);
        this._history[0].label.set_style("font-weight: bold;");
    },

    _addHistoryItems: function() {
        for (let index = 0; index < this._history.length; ++index)
            this.menu.addMenuItem(this._history[index]);
    },

    _updateHistoryItem: function(index, element) {
        let displayStr = element.replace(/\n/g, ' ');
        let altDisplayStr = _("delete: %s").format(displayStr);
        this._history[index].label.set_text (displayStr);
        this._history[index].actor.show();
    },

    _hideHistory: function(startIndex) {
        for (let index = startIndex || 0; index < this._history.length; ++index)
            this._history[index].actor.hide();
    },

    _onStateChanged: function (state) {
        this._client.on_extension_state_changed(state);
    }
});

let _indicator;

function init(extension) {
    Gettext.bindtextdomain('gpaste', extension.metadata.localedir);
}

function enable() {
    _indicator = new GPasteIndicator();
    _indicator._onStateChanged (true);
    Main.panel.addToStatusArea('gpaste', _indicator);
}

function disable() {
    _indicator._onStateChanged (false);
    _indicator.destroy();
}

<commit_msg>gnome-shell: split GPasteStatusIcon<commit_after>/*
 *      This file is part of GPaste.
 *
 *      Copyright 2011-2013 Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
 *
 *      GPaste is free software: you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation, either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      GPaste is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with GPaste.  If not, see <http://www.gnu.org/licenses/>.
 */
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

const Clutter = imports.gi.Clutter;
const Gettext = imports.gettext;
const GPaste = imports.gi.GPaste;
const Lang = imports.lang;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const Pango = imports.gi.Pango;
const PopupMenu = imports.ui.popupMenu;
const St = imports.gi.St;

const _ = Gettext.domain('GPaste').gettext;
const BUS_NAME = 'org.gnome.GPaste';
const OBJECT_PATH = '/org/gnome/GPaste';

const GPasteStatusIcon = new Lang.Class({
    Name: 'GPasteStatusIcon',
    Extends: St.BoxLayout,

    _init: function() {
        this.parent({ style_class: 'panel-status-menu-box' });

        this.add_child(new St.Icon({
            icon_name: 'edit-paste-symbolic',
            style_class: 'system-status-icon'
        }));

        this.add_child(new St.Label({
            text: '\u25BE',
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER
        }));
    }
});

const GPasteDeleteButton = new Lang.Class({
    Name: 'GPasteDeleteButton',
    Extends: St.Button,

    _init: function(client, index) {
        this.parent();

        this.child = new St.Icon({
            icon_name: 'edit-delete-symbolic',
            style_class: 'system-status-icon'
        });

        this.connect('clicked', function() {
            client.delete(index);
            return true;
        });

    }
});

const GPasteDeleteMenuItemPart = new Lang.Class({
    Name: 'GPasteDeleteMenuItemPart',
    Extends: St.Bin,

    _init: function(client, index) {
        this.parent({ x_align: St.Align.END });

        this.child = new GPasteDeleteButton(client, index);
    }
});

const GPasteMenuItem = new Lang.Class({
    Name: 'GPasteMenuItem',
    Extends: PopupMenu.PopupMenuItem,

    _init: function(client, index) {
        this.parent("");

        let text = this.label.clutter_text;
        text.max_length = 60;
        text.ellipsize = Pango.EllipsizeMode.END;

        this.connect('activate', Lang.bind(this, function(actor, event) {
            client.select(index);
        }));

        this.actor.connect('key-press-event', Lang.bind(this, function(actor, event) {
            let symbol = event.get_key_symbol();
            if (symbol == Clutter.KEY_BackSpace || symbol == Clutter.KEY_Delete) {
                client.delete(index);
                return true;
            }
            return false;
        }));

        this.actor.add(new GPasteDeleteMenuItemPart(client, index), { expand: true, x_align: St.Align.END });
    }
});

const GPasteIndicator = new Lang.Class({
    Name: 'GPasteIndicator',
    Extends: PanelMenu.Button,

    _init: function() {
        this.parent(0.0, "GPaste");

        this.actor.add_child(new GPasteStatusIcon());

        this._killSwitch = new PopupMenu.PopupSwitchMenuItem(_("Track changes"), true);
        this._killSwitch.connect('toggled', Lang.bind(this, this._toggleDaemon));
        this._client = new GPaste.Client();
        this._client.connect('changed', Lang.bind(this, this._updateHistory));
        this._client.connect('show-history', Lang.bind(this, this._showHistory));
        this._client.connect('tracking', Lang.bind(this, function(c, state) {
            this._killSwitch.setToggleState(state);
        }));
        this._createHistory();
        this._noHistory = new PopupMenu.PopupMenuItem("");
        this._noHistory.setSensitive(false);
        this._emptyHistory = new PopupMenu.PopupMenuItem(_("Empty history"));
        this._emptyHistory.connect('activate', Lang.bind(this, this._empty));
        this._fillMenu();
    },

    _empty: function() {
        this._client.empty();
    },

    _toggleDaemon: function() {
        this._client.track(this._killSwitch.state);
    },

    _fillMenu: function() {
        this._killSwitch.setToggleState(this._client.is_active());
        this.menu.addMenuItem(this._killSwitch);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this._addHistoryItems();
        this.menu.addMenuItem(this._noHistory);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(this._emptyHistory);
        this.menu.addSettingsAction(_("GPaste daemon settings"), 'gpaste-settings.desktop');
        this._updateHistory();
    },

    _updateHistory: function() {
        let history = this._client.get_history();
        if (history != null && history.length != 0) {
            let limit = Math.min(history.length, this._history.length);
            for (let index = 0; index < limit; ++index)
                this._updateHistoryItem(index, history[index]);
            this._hideHistory(limit);
            this._noHistory.actor.hide();
            this._emptyHistory.actor.show();
        } else {
            this._noHistory.label.text = (history == null) ? _("(Couldn't connect to GPaste daemon)") : _("(Empty)");
            this._hideHistory();
            this._emptyHistory.actor.hide();
            this._noHistory.actor.show();
        }
    },

    _showHistory: function() {
        this.menu.open(true);
        this.menu.firstMenuItem.setActive(true);
    },

    _createHistory: function() {
        this._history = [];
        for (let index = 0; index < 20; ++index)
            this._history[index] = new GPasteMenuItem(this._client, index);
        this._history[0].label.set_style("font-weight: bold;");
    },

    _addHistoryItems: function() {
        for (let index = 0; index < this._history.length; ++index)
            this.menu.addMenuItem(this._history[index]);
    },

    _updateHistoryItem: function(index, element) {
        let displayStr = element.replace(/\n/g, ' ');
        let altDisplayStr = _("delete: %s").format(displayStr);
        this._history[index].label.set_text (displayStr);
        this._history[index].actor.show();
    },

    _hideHistory: function(startIndex) {
        for (let index = startIndex || 0; index < this._history.length; ++index)
            this._history[index].actor.hide();
    },

    _onStateChanged: function (state) {
        this._client.on_extension_state_changed(state);
    }
});

let _indicator;

function init(extension) {
    Gettext.bindtextdomain('gpaste', extension.metadata.localedir);
}

function enable() {
    _indicator = new GPasteIndicator();
    _indicator._onStateChanged (true);
    Main.panel.addToStatusArea('gpaste', _indicator);
}

function disable() {
    _indicator._onStateChanged (false);
    _indicator.destroy();
}

<|endoftext|>


<commit_before>import React from 'react';
import './menu.css';
import ArrowRight from './arrow_right.png';

const Styles = {
  menuLink: {
    textDecoration: 'none',
  },
  arrowRight: {
    position: 'absolute',
    right: 5,
    top: '35%',
    width: 12,
    height: 12,
  },
};

const Menu = (props) => {
  const {
    link,
    onClick,
    children,
    text,
  } = props;
  return (

        {text}

      {children ?  : null}
      {children}

  );
};

Menu.propTypes = {
  link: React.PropTypes.string,
  text: React.PropTypes.string.isRequired,
  children: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.object,
    React.PropTypes.func,
  ]),
  onClick: React.PropTypes.func,
};

Menu.defaultProps = {
  link: '#',
  onClick: null,
  children: null,
};

export default Menu;
<commit_msg>Add onClick for menu<commit_after>import React from 'react';
import './menu.css';
import ArrowRight from './arrow_right.png';

const Styles = {
  menuLink: {
    textDecoration: 'none',
  },
  arrowRight: {
    position: 'absolute',
    right: 5,
    top: '35%',
    width: 12,
    height: 12,
  },
};

const Menu = (props) => {
  const {
    link,
    onClick,
    children,
    text,
  } = props;
  return (

       onClick(text)}
        className="Menu-link"
        style={Styles.menuLink}
      >
        {text}

      {children ?  : null}
      {children}

  );
};

Menu.propTypes = {
  link: React.PropTypes.string,
  text: React.PropTypes.string.isRequired,
  children: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.object,
    React.PropTypes.func,
  ]),
  onClick: React.PropTypes.func,
};

Menu.defaultProps = {
  link: '#',
  onClick: value => console.info(value),
  children: null,
};

export default Menu;
<|endoftext|>


<commit_before>/*
Copyright (C) 2011 by Gregory Burlet

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// this pattern was taken from http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
(function($) {
    var Neon = function(element, options)
    {
        var elem = $(element);
        var f_canvas;
        var glyphs;

        // These are variables which can be overridden upon instantiation
        var defaults = {
            width: 1000,
            height: 1000,
            autoLoad: false,
            filename: ""
        };

        var settings = $.extend({}, defaults, options);

        // These are variables which can not be overridden by the user
        var globals = {
            canvasid: "neon-canvas"
        };

        $.extend(settings, globals);

        /******************************
         *      PUBLIC FUNCTIONS      *
         ******************************/
        this.importFile = function()
        {
            console.log('importing MEI in JSON format ...');
        };

        this.exportFile = function()
        {
            console.log("exporting JSON ...");
        }

        /******************************
         *      PRIVATE FUNCTIONS     *
         ******************************/
        var loadGlyphs = function() {
            console.log("loading svg glyphs ...");
            glyphs = new Object();

            $.get("/static/img/neumes_concat.svg", function(svg) {
                // for each glyph, load it into fabric
                $(svg).find("svg").each(function(it, el) {
                    // http://stackoverflow.com/questions/652763/jquery-object-to-string
                    var rawSVG = $("<lol>").append($(el).clone()).remove().html();
                    fabric.loadSVGFromString(rawSVG, function(objects) {
                        glyphs[$(el).find("path").attr("id")] = objects[0].scale(0.5);
                    });
                });
            });
        };

        var loadPage = function(fileName) {
            $.get("/"+fileName+"/mei", function(data) {
                console.debug(data);
            });
        };

        var init = function() {
            // add canvas element to the element tied to the jQuery plugin
            var canvas = $("<canvas>").attr("id", settings.canvasid);

            // TODO: getPageWidth and getPageHeight parse from JSONified MEI file
            canvas.attr("width", settings.width);
            canvas.attr("height", settings.height);

            elem.prepend(canvas);

            f_canvas = new fabric.Canvas(settings.canvasid);

            loadGlyphs();

        };

        // Call the init function when this object is created.
        init();
        if (settings.autoLoad) {
            loadPage(settings.filename);
        }

    };

    $.fn.neon = function(options)
    {
        return this.each(function()
        {
            var element = $(this);

            // Return early if this element already has a plugin instance
            if (element.data('neon'))
            {
                return;
            }

            // pass options to plugin constructor
            var neon = new Neon(this, options);

            // Store plugin object in this element's data
            element.data('neon', neon);
        });
    };
})(jQuery);

<commit_msg>wait for AJAX requests to finish<commit_after>/*
Copyright (C) 2011 by Gregory Burlet

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// this pattern was taken from http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
(function($) {
    var Neon = function(element, options)
    {
        var elem = $(element);
        var f_canvas;
        var glyphs;

        // These are variables which can be overridden upon instantiation
        var defaults = {
            width: 1000,
            height: 1000,
            autoLoad: false,
            filename: ""
        };

        var settings = $.extend({}, defaults, options);

        // These are variables which can not be overridden by the user
        var globals = {
            canvasid: "neon-canvas"
        };

        $.extend(settings, globals);

        /******************************
         *      PUBLIC FUNCTIONS      *
         ******************************/
        this.importFile = function()
        {
            console.log('importing MEI in JSON format ...');
        };

        this.exportFile = function()
        {
            console.log("exporting JSON ...");
        }

        /******************************
         *      PRIVATE FUNCTIONS     *
         ******************************/
        var loadGlyphs = function() {
            console.log("loading svg glyphs ...");
            glyphs = new Object();

            $.get("/static/img/neumes_concat.svg", function(svg) {
                // for each glyph, load it into fabric
                $(svg).find("svg").each(function(it, el) {
                    // http://stackoverflow.com/questions/652763/jquery-object-to-string
                    var rawSVG = $("<lol>").append($(el).clone()).remove().html();
                    fabric.loadSVGFromString(rawSVG, function(objects) {
                        glyphs[$(el).find("path").attr("id")] = objects[0].scale(0.5);
                    });
                });
            });
        };

        var loadPage = function(fileName) {
            $.get("/"+fileName+"/mei", function(data) {
                console.debug(data);
            });
        };

        var init = function() {
            // add canvas element to the element tied to the jQuery plugin
            var canvas = $("<canvas>").attr("id", settings.canvasid);

            // TODO: getPageWidth and getPageHeight parse from JSONified MEI file
            canvas.attr("width", settings.width);
            canvas.attr("height", settings.height);

            elem.prepend(canvas);

            f_canvas = new fabric.Canvas(settings.canvasid);

            // load neume glyphs from svg file
            loadGlyphs();

            // load MEI file from server
            if (settings.autoLoad) {
                loadPage(settings.filename);
            }
        };

        // Call the init function when this object is created.
        init();

        // handler for when ajax calls have been completed
        var loaded = function() {
            console.log("Load successful. Neon.js ready.");
        }

        elem.ajaxStop(loaded);
    };

    $.fn.neon = function(options)
    {
        return this.each(function()
        {
            var element = $(this);

            // Return early if this element already has a plugin instance
            if (element.data('neon'))
            {
                return;
            }

            // pass options to plugin constructor
            var neon = new Neon(this, options);

            // Store plugin object in this element's data
            element.data('neon', neon);
        });
    };
})(jQuery);

<|endoftext|>