<commit_before>import { keys, isUndefined, isArray, isString } from 'underscore';
import Property from './PropertyComposite';
import PropertyBase from './Property';
import Layers from './Layers';
import { camelCase } from 'utils/mixins';

const VALUES_REG = /,(?![^\(]*\))/;
const PARTS_REG = /\s(?![^(]*\))/;

const splitStyleName = (style, name, sep) => {
  return (style[name] || '')
    .split(sep)
    .map(value => value.trim())
    .filter(Boolean);
};

export default Property.extend({
  defaults: {
    ...Property.prototype.defaults,
    // Array of layers (which contain properties)
    layers: [],

    // The separator used to split layer values
    layerSeparator: ', ',

    // The separator used to join layer values
    layerJoin: ', ',

    // Prepend new layers in the list
    prepend: 0,

    // Layer preview
    preview: 0,

    // Parse single layer value string
    parseLayer: null,

    // Current selected layer
    selectedLayer: null,
  },

  initialize(props = {}, opts = {}) {
    Property.callParentInit(Property, this, props, opts);
    const layers = this.get('layers');
    const layersColl = new Layers(layers);
    layersColl.property = this;
    layersColl.properties = this.get('properties');
    this.set('layers', layersColl, { silent: true });
    this.on('change:selectedLayer', this.__upSelected);
    this.listenTo(layersColl, 'add remove', this.__upLayers);
    Property.callInit(this, props, opts);
  },

  __upProperties(prop, opts = {}) {
    const layer = this.getSelectedLayer();
    if (opts.__up || !layer) return;
    const name = prop.getName();
    layer.upValues({ [name]: prop.__getFullValue() });
    const value = this.__getFullValue();
    this.upValue(value, opts);
    this.__upTargetsStyleProps(opts);
  },

  __upLayers(m, c, o) {
    const value = this.__getFullValue();
    this.upValue(value);
    this.__upTargetsStyleProps(o || c);
  },

  __upTargets(p, opts = {}) {
    if (opts.__select) return;
    return PropertyBase.prototype.__upTargets.call(this, p, opts);
  },

  __upTargetsStyle(style, opts) {
    return PropertyBase.prototype.__upTargetsStyle.call(this, style, opts);
  },

  __upTargetsStyleProps(opts = {}) {
    // TODO same as getStyleFromLayers
    if (!this.isDetached()) {
      const style = this.getProperties().reduce((acc, prop) => {
        acc[prop.getName()] = '';
        return acc;
      }, {});
      this.__upTargetsStyle(style, opts);
    }
  },

  __upSelected({ noEvent } = {}, opts = {}) {
    if (!this.__hasCustom()) return;
    const sm = this.em.get('StyleManager');
    const selected = this.getSelectedLayer();
    const values = selected?.getValues();

    // Update properties by layer value
    values &&
      this.getProperties().forEach(prop => {
        const name = prop.getName();
        const value = values[name];
        !isUndefined(value) && prop.upValue(value, { ...opts, __up: true });
      });

    !noEvent && sm.__trgEv(sm.events.layerSelect, { property: this });
  },

  _up(props, opts = {}) {
    const { __layers = [], ...rest } = props;
    const layers = this.getLayers();
    const layersNew = __layers.map(values => ({ values }));

    if (layers.length === layersNew.length) {
      layersNew.map((layer, n) => {
        layers.at(n)?.upValues(layer.values);
      });
    } else {
      this.getLayers().reset(layersNew);
    }

    this.__upSelected({ noEvent: true }, opts);
    return Property.prototype._up.call(this, rest, opts);
  },

  __setLayers(newLayers = []) {
    const layers = this.getLayers();
    const layersNew = newLayers.map(values => ({ values }));

    if (layers.length === layersNew.length) {
      layersNew.map((layer, n) => layers.at(n)?.upValues(layer.values));
    } else {
      this.getLayers().reset(layersNew);
    }
  },

  __parseValue(value) {
    const result = this.parseValue(value);
    result.__layers = value
      .split(VALUES_REG)
      .map(v => v.trim())
      .map(v => this.__parseLayer(v))
      .filter(Boolean);

    return result;
  },

  __parseLayer(value) {
    const parseFn = this.get('parseLayer');
    const values = value.split(PARTS_REG);
    const properties = this.getProperties();
    return parseFn
      ? parseFn({ value, values })
      : properties.reduce((acc, prop, i) => {
          const value = values[i];
          acc[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue();
          return acc;
        }, {});
  },

  __getFromStyle(style = {}) {
    const fromStyle = this.get('fromStyle');

    return fromStyle ? fromStyle(style) : style;
  },

  __getLayersFromStyle(style = {}) {
    const name = this.getName();
    const props = this.getProperties();
    const nameProps = props.map(prop => prop.getName());
    const allNameProps = [name, ...nameProps];
    const hasProps = allNameProps.some(prop => !isUndefined(style[prop]));

    if (!hasProps) {
      return [];
    } else {
      const sep = this.getLayerSeparator();
      const fromStyle = this.get('fromStyle');
      let result = fromStyle ? fromStyle(style, { property: this, separatorLayers: sep }) : [];

      if (!fromStyle) {
        // Get layers from the main property
        const layers = splitStyleName(style, name, sep)
          .map(value => value.split(this.getSplitSeparator()))
          .map(parts => {
            const result = {};
            props.forEach((prop, i) => {
              const value = parts[i];
              result[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue();
            });
            return result;
          });
        // Get layers from the inner properties
        props.forEach(prop => {
          const id = prop.getId();
          splitStyleName(style, prop.getName(), sep)
            .map(value => ({ [id]: value || prop.getDefaultValue() }))
            .forEach((inLayer, i) => {
              layers[i] = layers[i] ? { ...layers[i], ...inLayer } : inLayer;
            });
        });
        result = layers;
      }

      return isArray(result) ? result : [result];
    }
  },

  hasValue(opts) {
    return PropertyBase.prototype.hasValue.call(this, opts);
  },

  /**
   * Add new layer to the stack
   * @param {Object} [props={}] Layer props
   * @param {Object} [opts={}] Options
   * @returns {[Layer]}
   */
  addLayer(props = {}, opts = {}) {
    const values = {};
    this.getProperties().forEach(prop => {
      const name = prop.getName();
      const value = props[name];
      values[name] = isUndefined(value) ? prop.getDefaultValue() : value;
    });
    const layer = this.get('layers').push({ values }, opts);

    return layer;
  },

  /**
   * Remove layer
   * @param {[Layer]} layer
   * @returns {[Layer]} Removed layer
   */
  removeLayer(layer) {
    return this.get('layers').remove(layer);
  },

  /**
   * Remove layer at index
   * @param {Number} index Index of the layer to remove
   * @returns {[Layer] | null} Removed layer
   */
  removeLayerAt(index = 0) {
    const layer = this.getLayer(index);
    return layer ? this.removeLayer(layer) : null;
  },

  /**
   * Select layer
   * @param {[Layer]} layer
   */
  selectLayer(layer) {
    return this.set('selectedLayer', layer, { __select: true });
  },

  /**
   * Select layer at index
   * @param {Number} index Index of the layer to select
   */
  selectLayerAt(index = 0) {
    const layer = this.getLayer(index);
    return layer && this.selectLayer(layer);
  },

  /**
   * Get selected layer
   * @returns {[Layer] | null}
   */
  getSelectedLayer() {
    const layer = this.get('selectedLayer');
    return layer && layer.getIndex() >= 0 ? layer : null;
  },

  /**
   * Get style object from layer
   * @param {[Layer]} layer
   * @returns {Object} Style object
   */
  getStyleFromLayer(layer, opts = {}) {
    const join = this.__getJoin();
    const joinLayers = this.__getJoinLayers();
    const toStyle = this.get('toStyle');
    const name = this.getName();
    const values = layer.getValues();
    let style;

    if (toStyle) {
      style = toStyle(values, { join, joinLayers, name, property: this });
    } else {
      const result = this.getProperties().map(prop => {
        const name = prop.getName();
        const val = values[name];
        const value = isUndefined(val) ? prop.getDefaultValue() : val;
        return { name, value };
      });
      style = this.get('detached')
        ? result.reduce((acc, item) => {
            acc[item.name] = item.value;
            return acc;
          }, {})
        : {
            [this.getName()]: result.map(r => r.value).join(join),
          };
    }

    return opts.camelCase
      ? Object.keys(style).reduce((res, key) => {
          res[camelCase(key)] = style[key];
          return res;
        }, {})
      : style;
  },

  /**
   * Get style object from current layers
   * @returns {Object} Style object
   */
  getStyleFromLayers() {
    let result = {};
    const layers = this.getLayers();
    const styles = layers.map(l => this.getStyleFromLayer(l));
    styles.forEach(style => {
      keys(style).map(key => {
        if (!result[key]) result[key] = [];
        result[key].push(style[key]);
      });
    });
    keys(result).map(key => {
      result[key] = result[key].join(this.__getJoinLayers());
    });

    if (this.isDetached()) {
      result[this.getName()] = '';
    } else {
      const style = this.getProperties().reduce((acc, prop) => {
        acc[prop.getName()] = '';
        return acc;
      }, {});
      result = { ...result, ...style };
    }

    return result;
  },

  __getJoinLayers() {
    return this.get('layerJoin');
  },

  __getFullValue() {
    if (this.get('detached')) return '';
    const name = this.getName();

    return this.getLayers()
      .map(l => this.getStyleFromLayer(l))
      .map(s => s[name])
      .filter(Boolean)
      .map(v => v?.trim())
      .join(this.get('layerSeparator'));
  },

  /**
   * Get layer sperator
   * @return {RegExp}
   */
  getLayerSeparator() {
    const sep = this.get('layerSeparator');
    return isString(sep) ? new RegExp(`${sep}(?![^\\(]*\\))`) : sep;
  },

  getLayers() {
    return this.get('layers');
  },

  getLayer(index = 0) {
    return this.getLayers().at(index) || null;
  },

  getCurrentLayer() {
    return this.getLayers().filter(layer => layer.get('active'))[0];
  },

  getFullValue() {
    return this.get('detached') ? '' : this.get('layers').getFullValue();
  },

  getValueFromStyle(styles = {}) {
    const layers = this.getLayers().getLayersFromStyle(styles);
    return new Layers(layers).getFullValue();
  },

  clearValue() {
    this.getLayers().reset();
    return Property.prototype.clearValue.apply(this, arguments);
  },

  getValueFromTarget(target) {
    const { detached, property, properties } = this.attributes;
    const style = target.getStyle();
    const validStyles = {};

    properties.forEach(prop => {
      const name = prop.get('property');
      const value = style[name];
      if (value) validStyles[name] = value;
    });

    return !detached ? style[property] : keys(validStyles).length ? validStyles : '';
  },

  /**
   * This method allows to customize layers returned from the target
   * @param  {Object} target
   * @return {Array} Should return an array of layers
   * @example
   * // return example
   * [
   *  {
   *    properties: [
   *      { property: 'width', ... }
   *      { property: 'height', ... }
   *    ]
   *  }
   * ]
   */
  getLayersFromTarget(target) {
    return;
  },
});
<commit_msg>Update stack _up<commit_after>import { keys, isUndefined, isArray, isString } from 'underscore';
import Property from './PropertyComposite';
import PropertyBase from './Property';
import Layers from './Layers';
import { camelCase } from 'utils/mixins';

const VALUES_REG = /,(?![^\(]*\))/;
const PARTS_REG = /\s(?![^(]*\))/;

const splitStyleName = (style, name, sep) => {
  return (style[name] || '')
    .split(sep)
    .map(value => value.trim())
    .filter(Boolean);
};

export default Property.extend({
  defaults: {
    ...Property.prototype.defaults,
    // Array of layers (which contain properties)
    layers: [],

    // The separator used to split layer values
    layerSeparator: ', ',

    // The separator used to join layer values
    layerJoin: ', ',

    // Prepend new layers in the list
    prepend: 0,

    // Layer preview
    preview: 0,

    // Parse single layer value string
    parseLayer: null,

    // Current selected layer
    selectedLayer: null,
  },

  initialize(props = {}, opts = {}) {
    Property.callParentInit(Property, this, props, opts);
    const layers = this.get('layers');
    const layersColl = new Layers(layers);
    layersColl.property = this;
    layersColl.properties = this.get('properties');
    this.set('layers', layersColl, { silent: true });
    this.on('change:selectedLayer', this.__upSelected);
    this.listenTo(layersColl, 'add remove', this.__upLayers);
    Property.callInit(this, props, opts);
  },

  __upProperties(prop, opts = {}) {
    const layer = this.getSelectedLayer();
    if (opts.__up || !layer) return;
    const name = prop.getName();
    layer.upValues({ [name]: prop.__getFullValue() });
    const value = this.__getFullValue();
    this.upValue(value, opts);
    this.__upTargetsStyleProps(opts);
  },

  __upLayers(m, c, o) {
    this.__upTargetsStyleProps(o || c);
  },

  __upTargets(p, opts = {}) {
    if (opts.__select) return;
    return PropertyBase.prototype.__upTargets.call(this, p, opts);
  },

  __upTargetsStyleProps(opts = {}) {
    this.__upTargetsStyle(this.getStyleFromLayers(), opts);
  },

  __upTargetsStyle(style, opts) {
    return PropertyBase.prototype.__upTargetsStyle.call(this, style, opts);
  },

  __upSelected({ noEvent } = {}, opts = {}) {
    if (!this.__hasCustom()) return;
    const sm = this.em.get('StyleManager');
    const selected = this.getSelectedLayer();
    const values = selected?.getValues();

    // Update properties by layer value
    values &&
      this.getProperties().forEach(prop => {
        const name = prop.getName();
        const value = values[name];
        !isUndefined(value) && prop.upValue(value, { ...opts, __up: true });
      });

    !noEvent && sm.__trgEv(sm.events.layerSelect, { property: this });
  },

  _up(props, opts = {}) {
    const { __layers = [], ...rest } = props;
    this.__setLayers(__layers);
    this.__upSelected({ noEvent: true }, opts);
    return Property.prototype._up.call(this, rest, opts);
  },

  __setLayers(newLayers = []) {
    const layers = this.getLayers();
    const layersNew = newLayers.map(values => ({ values }));

    if (layers.length === layersNew.length) {
      layersNew.map((layer, n) => layers.at(n)?.upValues(layer.values));
    } else {
      this.getLayers().reset(layersNew);
    }
  },

  __parseValue(value) {
    const result = this.parseValue(value);
    result.__layers = value
      .split(VALUES_REG)
      .map(v => v.trim())
      .map(v => this.__parseLayer(v))
      .filter(Boolean);

    return result;
  },

  __parseLayer(value) {
    const parseFn = this.get('parseLayer');
    const values = value.split(PARTS_REG);
    const properties = this.getProperties();
    return parseFn
      ? parseFn({ value, values })
      : properties.reduce((acc, prop, i) => {
          const value = values[i];
          acc[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue();
          return acc;
        }, {});
  },

  __getFromStyle(style = {}) {
    const fromStyle = this.get('fromStyle');

    return fromStyle ? fromStyle(style) : style;
  },

  __getLayersFromStyle(style = {}) {
    const name = this.getName();
    const props = this.getProperties();
    const nameProps = props.map(prop => prop.getName());
    const allNameProps = [name, ...nameProps];
    const hasProps = allNameProps.some(prop => !isUndefined(style[prop]));

    if (!hasProps) {
      return [];
    } else {
      const sep = this.getLayerSeparator();
      const fromStyle = this.get('fromStyle');
      let result = fromStyle ? fromStyle(style, { property: this, separatorLayers: sep }) : [];

      if (!fromStyle) {
        // Get layers from the main property
        const layers = splitStyleName(style, name, sep)
          .map(value => value.split(this.getSplitSeparator()))
          .map(parts => {
            const result = {};
            props.forEach((prop, i) => {
              const value = parts[i];
              result[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue();
            });
            return result;
          });
        // Get layers from the inner properties
        props.forEach(prop => {
          const id = prop.getId();
          splitStyleName(style, prop.getName(), sep)
            .map(value => ({ [id]: value || prop.getDefaultValue() }))
            .forEach((inLayer, i) => {
              layers[i] = layers[i] ? { ...layers[i], ...inLayer } : inLayer;
            });
        });
        result = layers;
      }

      return isArray(result) ? result : [result];
    }
  },

  hasValue(opts) {
    return PropertyBase.prototype.hasValue.call(this, opts);
  },

  /**
   * Add new layer to the stack
   * @param {Object} [props={}] Layer props
   * @param {Object} [opts={}] Options
   * @returns {[Layer]}
   */
  addLayer(props = {}, opts = {}) {
    const values = {};
    this.getProperties().forEach(prop => {
      const name = prop.getName();
      const value = props[name];
      values[name] = isUndefined(value) ? prop.getDefaultValue() : value;
    });
    const layer = this.get('layers').push({ values }, opts);

    return layer;
  },

  /**
   * Remove layer
   * @param {[Layer]} layer
   * @returns {[Layer]} Removed layer
   */
  removeLayer(layer) {
    return this.get('layers').remove(layer);
  },

  /**
   * Remove layer at index
   * @param {Number} index Index of the layer to remove
   * @returns {[Layer] | null} Removed layer
   */
  removeLayerAt(index = 0) {
    const layer = this.getLayer(index);
    return layer ? this.removeLayer(layer) : null;
  },

  /**
   * Select layer
   * @param {[Layer]} layer
   */
  selectLayer(layer) {
    return this.set('selectedLayer', layer, { __select: true });
  },

  /**
   * Select layer at index
   * @param {Number} index Index of the layer to select
   */
  selectLayerAt(index = 0) {
    const layer = this.getLayer(index);
    return layer && this.selectLayer(layer);
  },

  /**
   * Get selected layer
   * @returns {[Layer] | null}
   */
  getSelectedLayer() {
    const layer = this.get('selectedLayer');
    return layer && layer.getIndex() >= 0 ? layer : null;
  },

  /**
   * Get style object from layer
   * @param {[Layer]} layer
   * @returns {Object} Style object
   */
  getStyleFromLayer(layer, opts = {}) {
    const join = this.__getJoin();
    const joinLayers = this.__getJoinLayers();
    const toStyle = this.get('toStyle');
    const name = this.getName();
    const values = layer.getValues();
    let style;

    if (toStyle) {
      style = toStyle(values, { join, joinLayers, name, property: this });
    } else {
      const result = this.getProperties().map(prop => {
        const name = prop.getName();
        const val = values[name];
        const value = isUndefined(val) ? prop.getDefaultValue() : val;
        return { name, value };
      });
      style = this.get('detached')
        ? result.reduce((acc, item) => {
            acc[item.name] = item.value;
            return acc;
          }, {})
        : {
            [this.getName()]: result.map(r => r.value).join(join),
          };
    }

    return opts.camelCase
      ? Object.keys(style).reduce((res, key) => {
          res[camelCase(key)] = style[key];
          return res;
        }, {})
      : style;
  },

  /**
   * Get style object from current layers
   * @returns {Object} Style object
   */
  getStyleFromLayers() {
    let result = {};
    const layers = this.getLayers();
    const styles = layers.map(l => this.getStyleFromLayer(l));
    styles.forEach(style => {
      keys(style).map(key => {
        if (!result[key]) result[key] = [];
        result[key].push(style[key]);
      });
    });
    keys(result).map(key => {
      result[key] = result[key].join(this.__getJoinLayers());
    });

    if (this.isDetached()) {
      result[this.getName()] = '';
    } else {
      const style = this.getProperties().reduce((acc, prop) => {
        acc[prop.getName()] = '';
        return acc;
      }, {});
      result = { ...result, ...style };
    }

    return result;
  },

  __getJoinLayers() {
    return this.get('layerJoin');
  },

  __getFullValue() {
    if (this.get('detached')) return '';
    const name = this.getName();

    return this.getLayers()
      .map(l => this.getStyleFromLayer(l))
      .map(s => s[name])
      .filter(Boolean)
      .map(v => v?.trim())
      .join(this.get('layerSeparator'));
  },

  /**
   * Get layer sperator
   * @return {RegExp}
   */
  getLayerSeparator() {
    const sep = this.get('layerSeparator');
    return isString(sep) ? new RegExp(`${sep}(?![^\\(]*\\))`) : sep;
  },

  getLayers() {
    return this.get('layers');
  },

  getLayer(index = 0) {
    return this.getLayers().at(index) || null;
  },

  getCurrentLayer() {
    return this.getLayers().filter(layer => layer.get('active'))[0];
  },

  getFullValue() {
    return this.get('detached') ? '' : this.get('layers').getFullValue();
  },

  getValueFromStyle(styles = {}) {
    const layers = this.getLayers().getLayersFromStyle(styles);
    return new Layers(layers).getFullValue();
  },

  clearValue() {
    this.getLayers().reset();
    return Property.prototype.clearValue.apply(this, arguments);
  },

  getValueFromTarget(target) {
    const { detached, property, properties } = this.attributes;
    const style = target.getStyle();
    const validStyles = {};

    properties.forEach(prop => {
      const name = prop.get('property');
      const value = style[name];
      if (value) validStyles[name] = value;
    });

    return !detached ? style[property] : keys(validStyles).length ? validStyles : '';
  },

  /**
   * This method allows to customize layers returned from the target
   * @param  {Object} target
   * @return {Array} Should return an array of layers
   * @example
   * // return example
   * [
   *  {
   *    properties: [
   *      { property: 'width', ... }
   *      { property: 'height', ... }
   *    ]
   *  }
   * ]
   */
  getLayersFromTarget(target) {
    return;
  },
});
<|endoftext|>


<commit_before>(function() {
    var gs;

    // canvas size
    var maxW = 1000;
    var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    if (w > maxW) w = maxW;
    if (w * 2/3 > h) {
        w = h * 3/2;
    }
    var objScale = w/maxW/2;
    var margin = 30 * objScale;
    var game = new Phaser.Game(w, h, Phaser.AUTO, "", { preload: preload, create: create, update: update, pauseUpdate: pauseUpdate });
    var time = new Phaser.Time(game);

    // game logic
    var gameStart = false;
    var gameOver = false;
    var first = true;
    var balanced = false;
    var balanceCnt = 0;
    var imbalanceCnt = 0;

    // game score
    var score = 0;
    var startTime;

    // game objects
    var bgImg;
    var bgTween;
    var pilot;
    var meter;
    var warning;

    // rotate angle
    var lastAngle = 0;
    var UNIT_ANGLE = 25;
    var MAX_ANGLE = 90;
    var MAX_VELOCITY = 50;

    // keys
    var spaceKey;
    var cursors;

    // text objects
    var startTxt;
    var scoreTxt;
    var ggTxt;
    var ggScoreTxt;
    var connectTxt;

    function preload() {
        game.load.image("bg", "img/ex05-00.png");
        game.load.image("pilot", "img/ex05-02.png");
        game.load.image("horiz", "img/ex05-03.png");
        game.load.image("meter", "img/ex05-04.png");
    }

    function create() {
        gs = new GaussSense();

        // add background image
        bgImg = game.add.sprite(w / 2, h * 0.8, "bg");
        bgImg.width = w;
        bgImg.height *= w/bgImg.width;
        bgImg.anchor.set(0.5, 0.7);
        game.physics.arcade.enable(bgImg);

        // add pilot
        pilot = game.add.image(w / 2, h * 0.7, "pilot");
        pilot.width = w;
        pilot.height = h;
        pilot.anchor.set(0.5, 0.7);
        meter = game.add.image(w / 2, h * 0.7, "meter");
        var ratio = meter.height/meter.width;
        meter.width = w * 0.82;
        meter.height = meter.width * ratio;
        meter.anchor.set(0.5, 0);

        // Red warning mask
        warning = game.add.graphics(0, 0);
        // warning.lineStyle(30 * objScale, 0xfdcf58, 1);
        warning.beginFill(0xFF0000, 0.2);
        warning.drawRect(0, 0, w, h);
        warning.endFill();
        warning.visible = false;

        // register key events
        spaceKey = game.input.keyboard.addKey(32);

        // add display texts
        var startTxtStyle = { fill: "#fff000", align: "center" };
        startTxt = game.add.text(game.world.centerX, game.world.centerY + 400*objScale, "PRESS \"SPACE\" TO START", startTxtStyle);
        startTxt.anchor.set(0.5);
        startTxt.visible = false;
        var scoreTxtStyle = { fill: "#fff000", align: "right", fontStyle: "italic", fontSize: "80px" };
        scoreTxt = game.add.text(w - 150*objScale, 150 * objScale, "", scoreTxtStyle);
        scoreTxt.anchor.set(0.5);
        scoreTxt.visible = false;
        var ggTxtStyle = { fill: "#fff000", align: "center", fontSize: "64px" };
        ggTxt = game.add.text(game.world.centerX, game.world.centerY, "GAME OVER", ggTxtStyle);
        ggTxt.anchor.set(0.5);
        ggTxt.visible = false;
        var ggScoreTxtStyle = { fill: "#fff000", align: "center", fontSize: "40px" };
        ggScoreTxt = game.add.text(game.world.centerX, game.world.centerY + 200*objScale, "", ggScoreTxtStyle);
        ggScoreTxt.anchor.set(0.5);
        ggScoreTxt.visible = false;
        var connectTxtStyle = { fill: "#ffffff", align: "center", fontSize: "40px" };
        connectTxt = game.add.text(game.world.centerX, 200 * objScale, "GaussSense is not detected", connectTxtStyle);
        connectTxt.anchor.set(0.5);
        connectTxt.visible = false;
    }

    function pauseUpdate() {
        if (gs.isConnected()) {
            connectTxt.visible = false;
            game.paused = false;
        }
    }

    function update() {
        if (!gs.isConnected()) {
            connectTxt.visible = true;
            game.world.bringToTop(connectTxt);
            if (gameStart) {
                game.paused = true;
            }
            return;
        } else {
            connectTxt.visible = false;
        }

        if (gameStart) {

            var mid = gs.getBipolarMidpoint();

            // "pilot" rotation: rotate bg image actually
            var delta = mid.angle - lastAngle;
            if (Math.abs(delta) > 0.05 && Math.abs(delta) < Math.PI/4) {
                if (bgImg.angle > 80) {
                    delta = (delta < 0)? 0 : delta;
                } else if (bgImg.angle < -80) {
                    delta = (delta > 0)? 0 : delta;
                }
                bgImg.rotation -= delta;
                lastAngle = mid.angle;
            }

            if (first) {
                game.time.events.add(Phaser.Timer.SECOND * 2, createRotation, this);
                first = false;
            }

            // check if angle exceed 80 or -80
            if (bgImg.angle > 80) {
                bgImg.body.angularVelocity = -Math.abs(bgImg.body.angularVelocity);
            } else if (bgImg.angle < -80) {
                bgImg.body.angularVelocity = Math.abs(bgImg.body.angularVelocity);
            }

            // check balance
            checkBalance();

            // show score
            var now = new Date();
            if (now - startTime > 2000) {
                startTime = new Date();
                scoreTxt.text = ++score + " ";
            }
            game.world.bringToTop(scoreTxt);

        } else if (gameOver) {
            showGameOver();

            if (bgImg.angle > 45) {
                bgImg.body.angularVelocity = -5;
            } else if (bgImg.angle < -45) {
                bgImg.body.angularVelocity = 5;
            }

            if (spaceKey.isDown) {
                resetGame();
            }
        } else {
            startTxt.visible = true;
            if (spaceKey.isDown) {
                resetGame();
            }
        }
    }

    function resetGame() {
        gameStart = true;
        gameOver = false;
        first = true;
        bgImg.angle = 0;
        bgImg.body.angularVelocity = 0;

        warning.visible = false;
        startTxt.visible = false;
        ggTxt.visible = false;
        ggScoreTxt.visible = false;

        startTime = new Date();
        score = 0;
        scoreTxt.text = score + " ";
        scoreTxt.visible = true;
    }

    function showGameOver() {
        if (!ggTxt.visible) {

            ggTxt.visible = true;
            ggScoreTxt.text = "SCORE: " + score;
            ggScoreTxt.visible = true;
            game.time.events.add(Phaser.Timer.SECOND * 5, function() {
                if (!gameStart) startTxt.visible = true;
            }, this);
        }
        game.world.bringToTop(ggTxt);
        game.world.bringToTop(ggScoreTxt);
        game.world.bringToTop(startTxt);
    }

    function createRotation() {
        var baseVelocity = (score + 5 > MAX_VELOCITY)? MAX_VELOCITY : score + 5;
        bgImg.body.angularVelocity = Math.sign(Math.random() - 0.5) * (Math.random()*baseVelocity + 5);

        if (gameStart) {
            game.time.events.add(Phaser.Timer.SECOND * (Math.random()*(4-baseVelocity/MAX_VELOCITY/2)+1), createRotation, this);
        }
    }

    function checkBalance() {
        balanced = (Math.abs(bgImg.angle) < 5)? true : false;
        if (!balanced) {
            balanceCnt = 0;
            imbalanceCnt++;
        } else {
            balanceCnt++;
            if (balanceCnt > 10) {
                imbalanceCnt = 0;
                warning.visible = false;
            }
        }
        if (imbalanceCnt > 100) {
            if (imbalanceCnt % 10 == 0) {
                warning.visible = !warning.visible;
            }
        }
        if (imbalanceCnt > 300) {
            warning.visible = true;
            imbalanceCnt = 0;
            balanceCnt = 0;
            onShuttleCrashed();
        }
    }

    function onShuttleCrashed() {
        gameOver = true;
        gameStart = false;
    }

})();
<commit_msg>fix rotation method; add tutorial<commit_after>(function() {
    var gs;

    // canvas size
    var maxW = 1000;
    var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    if (w > maxW) w = maxW;
    if (w * 2/3 > h) {
        w = h * 3/2;
    }
    var objScale = w/maxW/2;
    var margin = 30 * objScale;
    var game = new Phaser.Game(w, h, Phaser.AUTO, "", { preload: preload, create: create, update: update, pauseUpdate: pauseUpdate });
    var time = new Phaser.Time(game);

    // game logic
    var gameStart = false;
    var gameOver = false;
    var first = true;
    var balanced = false;
    var balanceCnt = 0;
    var imbalanceCnt = 0;

    // game score
    var score = 0;
    var startTime;

    // game objects
    var bgImg;
    var bgTween;
    var pilot;
    var meter;
    var warning;

    // rotate angle
    var lastAngle = 0;
    var UNIT_ANGLE = 25;
    var MAX_ANGLE = 90;
    var MAX_VELOCITY = 50;

    // tutorial objects
    var graphics;
    var tutorial;
    var tutorialTxt;
    var tutorialDestroyed = false;

    // keys
    var spaceKey;
    var cursors;

    // text objects
    var startTxt;
    var scoreTxt;
    var ggTxt;
    var ggScoreTxt;
    var connectTxt;

    function preload() {
        game.load.image("bg", "img/ex05-00.png");
        game.load.image("pilot", "img/ex05-02.png");
        game.load.image("horiz", "img/ex05-03.png");
        game.load.image("meter", "img/ex05-04.png");
        game.load.image("tutorial", "img/tutorial-rotation.png")
    }

    function create() {
        gs = new GaussSense();

        // add background image
        bgImg = game.add.sprite(w / 2, h * 0.8, "bg");
        bgImg.width = w;
        bgImg.height *= w/bgImg.width;
        bgImg.anchor.set(0.5, 0.7);
        game.physics.arcade.enable(bgImg);

        // add pilot
        pilot = game.add.image(w / 2, h * 0.7, "pilot");
        pilot.width = w;
        pilot.height = h;
        pilot.anchor.set(0.5, 0.7);
        meter = game.add.image(w / 2, h * 0.7, "meter");
        var ratio = meter.height/meter.width;
        meter.width = w * 0.82;
        meter.height = meter.width * ratio;
        meter.anchor.set(0.5, 0);

        // Red warning mask
        warning = game.add.graphics(0, 0);
        // warning.lineStyle(30 * objScale, 0xfdcf58, 1);
        warning.beginFill(0xFF0000, 0.2);
        warning.drawRect(0, 0, w, h);
        warning.endFill();
        warning.visible = false;

        // add tutorial
        graphics = game.add.graphics(0, 0);
        graphics.lineStyle(30 * objScale, 0xfdcf58, 1);
        graphics.beginFill(0xFFFFFF, 1);
        graphics.drawRoundedRect(w * 0.15, h * 0.25, w * 0.7, h * 0.65, 10 * objScale);
        graphics.endFill();

        tutorial = game.add.image(w * 0.2, h * 0.5, "tutorial");
        tutorial.width *= objScale * 1.2;
        tutorial.height *= objScale * 1.2;
        tutorial.anchor.set(0, 0.5);

        var tutorialTxtStyle = { fill: "#404040", align: "left", fontSize: 60 * objScale };
        tutorialTxt = game.add.text(w * 0.4, h * 0.5,
            "ROTATE THE MAGNET TO\nSTAY BALANCED ON \nYOUR JOURNEY.", tutorialTxtStyle);
        tutorialTxt.anchor.set(0, 0.5);

        // register key events
        spaceKey = game.input.keyboard.addKey(32);

        // add display texts
        var startTxtStyle = { fill: "#fdcf58", align: "center", fontSize: 60 * objScale };
        startTxt = game.add.text(game.world.centerX, game.world.centerY + 400*objScale, "PRESS \"SPACE\" TO START", startTxtStyle);
        startTxt.anchor.set(0.5);
        startTxt.visible = false;
        var scoreTxtStyle = { fill: "#fff000", align: "right", fontStyle: "italic", fontSize: 140 * objScale };
        scoreTxt = game.add.text(w - 150*objScale, 150 * objScale, "", scoreTxtStyle);
        scoreTxt.anchor.set(0.5);
        scoreTxt.visible = false;
        var ggTxtStyle = { fill: "#fdcf58", align: "center", fontSize: 120 * objScale };
        ggTxt = game.add.text(game.world.centerX, game.world.centerY, "GAME OVER", ggTxtStyle);
        ggTxt.anchor.set(0.5);
        ggTxt.visible = false;
        var ggScoreTxtStyle = { fill: "#fdcf58", align: "center", fontSize: 100 * objScale };
        ggScoreTxt = game.add.text(game.world.centerX, game.world.centerY + 200*objScale, "", ggScoreTxtStyle);
        ggScoreTxt.anchor.set(0.5);
        ggScoreTxt.visible = false;
        var connectTxtStyle = { fill: "#ffffff", align: "center", fontSize: 80 * objScale };
        connectTxt = game.add.text(game.world.centerX, 200 * objScale, "GaussSense is not detected", connectTxtStyle);
        connectTxt.anchor.set(0.5);
        connectTxt.visible = false;
    }

    function pauseUpdate() {
        if (gs.isConnected()) {
            connectTxt.visible = false;
            game.paused = false;
        }
    }

    function update() {
        if (!gs.isConnected()) {
            connectTxt.visible = true;
            game.world.bringToTop(connectTxt);
            if (gameStart) {
                game.paused = true;
            }
            return;
        } else {
            connectTxt.visible = false;
        }

        if (gameStart) {
            if (!tutorialDestroyed) {
                destroyTutorial();
            }

            var mid = gs.getBipolarMidpoint();

            var ang = mid.angle*180/Math.PI;
            if (Math.abs(ang) > 10) {
                if (bgImg.angle > 80) {
                    ang = (ang < 0)? 0 : ang;
                } else if (bgImg.angle < -80) {
                    ang = (ang > 0)? 0 : ang;
                }
                bgImg.angle -= ang/100;
            }

            if (first) {
                game.time.events.add(Phaser.Timer.SECOND * 2, createRotation, this);
                first = false;
            }

            // check if angle exceed 80 or -80
            if (bgImg.angle > 80) {
                bgImg.body.angularVelocity = -Math.abs(bgImg.body.angularVelocity);
            } else if (bgImg.angle < -80) {
                bgImg.body.angularVelocity = Math.abs(bgImg.body.angularVelocity);
            }

            // check balance
            checkBalance();

            // show score
            var now = new Date();
            if (now - startTime > 2000) {
                startTime = new Date();
                scoreTxt.text = ++score + " ";
            }
            game.world.bringToTop(scoreTxt);

        } else if (gameOver) {
            showGameOver();

            if (bgImg.angle > 45) {
                bgImg.body.angularVelocity = -5;
            } else if (bgImg.angle < -45) {
                bgImg.body.angularVelocity = 5;
            }

            if (spaceKey.isDown) {
                resetGame();
            }
        } else {
            startTxt.visible = true;
            if (spaceKey.isDown) {
                resetGame();
            }
        }
    }

    function resetGame() {
        gameStart = true;
        gameOver = false;
        first = true;
        bgImg.angle = 0;
        bgImg.body.angularVelocity = 0;

        warning.visible = false;
        startTxt.visible = false;
        ggTxt.visible = false;
        ggScoreTxt.visible = false;

        startTime = new Date();
        score = 0;
        scoreTxt.text = score + " ";
        scoreTxt.visible = true;
    }

    function showGameOver() {
        if (!ggTxt.visible) {
            ggTxt.visible = true;
            ggScoreTxt.text = "SCORE: " + score;
            ggScoreTxt.visible = true;
            game.time.events.add(Phaser.Timer.SECOND * 5, function() {
                if (!gameStart) startTxt.visible = true;
            }, this);
        }
        game.world.bringToTop(ggTxt);
        game.world.bringToTop(ggScoreTxt);
        game.world.bringToTop(startTxt);
    }

    function destroyTutorial() {
        graphics.destroy();
        tutorial.destroy();
        tutorialTxt.destroy();
        tutorialDestroyed = true;
    }

    function createRotation() {
        var baseVelocity = (score + 5 > MAX_VELOCITY)? MAX_VELOCITY : score + 5;
        bgImg.body.angularVelocity = Math.sign(Math.random() - 0.5) * (Math.random()*baseVelocity + 5);

        if (gameStart) {
            game.time.events.add(Phaser.Timer.SECOND * (Math.random()*(4-baseVelocity/MAX_VELOCITY/2)+1), createRotation, this);
        }
    }

    function checkBalance() {
        balanced = (Math.abs(bgImg.angle) < 5)? true : false;
        if (!balanced) {
            balanceCnt = 0;
            imbalanceCnt++;
        } else {
            balanceCnt++;
            if (balanceCnt > 10) {
                imbalanceCnt = 0;
                warning.visible = false;
            }
        }
        if (imbalanceCnt > 100) {
            if (imbalanceCnt % 10 == 0) {
                warning.visible = !warning.visible;
            }
        }
        if (imbalanceCnt > 300) {
            warning.visible = true;
            imbalanceCnt = 0;
            balanceCnt = 0;
            onShuttleCrashed();
        }
    }

    function onShuttleCrashed() {
        gameOver = true;
        gameStart = false;
    }

})();
<|endoftext|>


<commit_before>
// An AttributeHandler object is responsible for updating a particular attribute
// of a particular element.  AttributeHandler subclasses implement
// browser-specific logic for dealing with particular attributes across
// different browsers.
//
// To define a new type of AttributeHandler, use
// `var FooHandler = AttributeHandler.extend({ update: function ... })`
// where the `update` function takes arguments `(element, oldValue, value)`.
// The `element` argument is always the same between calls to `update` on
// the same instance.  `oldValue` and `value` are each either `null` or
// a Unicode string of the type that might be passed to the value argument
// of `setAttribute` (i.e. not an HTML string with character references).
// When an AttributeHandler is installed, an initial call to `update` is
// always made with `oldValue = null`.  The `update` method can access
// `this.name` if the AttributeHandler class is a generic one that applies
// to multiple attribute names.
//
// AttributeHandlers can store custom properties on `this`, as long as they
// don't use the names `element`, `name`, `value`, and `oldValue`.
//
// AttributeHandlers can't influence how attributes appear in rendered HTML,
// only how they are updated after materialization as DOM.

AttributeHandler = function (name, value) {
  this.name = name;
  this.value = value;
};

_.extend(AttributeHandler.prototype, {
  update: function (element, oldValue, value) {
    if (value === null) {
      if (oldValue !== null)
        element.removeAttribute(this.name);
    } else {
      element.setAttribute(this.name, this.value);
    }
  }
});

AttributeHandler.extend = function (options) {
  var curType = this;
  var subType = function AttributeHandlerSubtype(/*arguments*/) {
    AttributeHandler.apply(this, arguments);
  };
  subType.prototype = new curType;
  subType.extend = curType.extend;
  if (options)
    _.extend(subType.prototype, options);
  return subType;
};

// Extended below to support both regular and SVG elements
var BaseClassHandler = AttributeHandler.extend({
  update: function (element, oldValue, value) {
    if (!this.getCurrentValue || !this.setValue)
      throw new Error("Missing methods in subclass of 'BaseClassHandler'");

    var oldClasses = oldValue ? _.compact(oldValue.split(' ')) : [];
    var newClasses = value ? _.compact(value.split(' ')) : [];

    // the current classes on the element, which we will mutate.
    var classes = _.compact(this.getCurrentValue(element).split(' '));

    // optimize this later (to be asymptotically faster) if necessary
    _.each(oldClasses, function (c) {
      if (_.indexOf(newClasses, c) < 0)
        classes = _.without(classes, c);
    });
    _.each(newClasses, function (c) {
      if (_.indexOf(oldClasses, c) < 0 &&
          _.indexOf(classes, c) < 0)
        classes.push(c);
    });

    this.setValue(element, classes.join(' '));
  }
});

var ClassHandler = BaseClassHandler.extend({
  // @param rawValue {String}
  getCurrentValue: function (element) {
    return element.className;
  },
  setValue: function (element, className) {
    element.className = className;
  }
});

var SVGClassHandler = BaseClassHandler.extend({
  getCurrentValue: function (element) {
    return element.className.baseVal;
  },
  setValue: function (element, className) {
    element.setAttribute('class', className);
  }
});

var BooleanHandler = AttributeHandler.extend({
  update: function (element, oldValue, value) {
    var name = this.name;
    if (value == null) {
      if (oldValue != null)
        element[name] = false;
    } else {
      element[name] = true;
    }
  }
});

var ValueHandler = AttributeHandler.extend({
  update: function (element, oldValue, value) {
    element.value = value;
  }
});

// cross-browser version of `instanceof SVGElement`
var isSVGElement = function (elem) {
  return 'ownerSVGElement' in elem;
};

// XXX make it possible for users to register attribute handlers!
makeAttributeHandler = function (elem, name, value) {
  // generally, use setAttribute but certain attributes need to be set
  // by directly setting a JavaScript property on the DOM element.
  if (name === 'class') {
    if (isSVGElement(elem)) {
      return new SVGClassHandler(name, value);
    } else {
      return new ClassHandler(name, value);
    }
  } else if (name === 'selected' || name === 'checked') {
    return new BooleanHandler(name, value);
  } else if ((elem.tagName === 'TEXTAREA' || elem.tagName === 'INPUT')
             && name === 'value') {
    // internally, TEXTAREAs tracks their value in the 'value'
    // attribute just like INPUTs.
    return new ValueHandler(name, value);
  } else {
    return new AttributeHandler(name, value);
  }

  // XXX will need one for 'style' on IE, though modern browsers
  // seem to handle setAttribute ok.
};
<commit_msg>Don't update an input field when its focused<commit_after>
// An AttributeHandler object is responsible for updating a particular attribute
// of a particular element.  AttributeHandler subclasses implement
// browser-specific logic for dealing with particular attributes across
// different browsers.
//
// To define a new type of AttributeHandler, use
// `var FooHandler = AttributeHandler.extend({ update: function ... })`
// where the `update` function takes arguments `(element, oldValue, value)`.
// The `element` argument is always the same between calls to `update` on
// the same instance.  `oldValue` and `value` are each either `null` or
// a Unicode string of the type that might be passed to the value argument
// of `setAttribute` (i.e. not an HTML string with character references).
// When an AttributeHandler is installed, an initial call to `update` is
// always made with `oldValue = null`.  The `update` method can access
// `this.name` if the AttributeHandler class is a generic one that applies
// to multiple attribute names.
//
// AttributeHandlers can store custom properties on `this`, as long as they
// don't use the names `element`, `name`, `value`, and `oldValue`.
//
// AttributeHandlers can't influence how attributes appear in rendered HTML,
// only how they are updated after materialization as DOM.

AttributeHandler = function (name, value) {
  this.name = name;
  this.value = value;
};

_.extend(AttributeHandler.prototype, {
  update: function (element, oldValue, value) {
    if (value === null) {
      if (oldValue !== null)
        element.removeAttribute(this.name);
    } else {
      element.setAttribute(this.name, this.value);
    }
  }
});

AttributeHandler.extend = function (options) {
  var curType = this;
  var subType = function AttributeHandlerSubtype(/*arguments*/) {
    AttributeHandler.apply(this, arguments);
  };
  subType.prototype = new curType;
  subType.extend = curType.extend;
  if (options)
    _.extend(subType.prototype, options);
  return subType;
};

// Extended below to support both regular and SVG elements
var BaseClassHandler = AttributeHandler.extend({
  update: function (element, oldValue, value) {
    if (!this.getCurrentValue || !this.setValue)
      throw new Error("Missing methods in subclass of 'BaseClassHandler'");

    var oldClasses = oldValue ? _.compact(oldValue.split(' ')) : [];
    var newClasses = value ? _.compact(value.split(' ')) : [];

    // the current classes on the element, which we will mutate.
    var classes = _.compact(this.getCurrentValue(element).split(' '));

    // optimize this later (to be asymptotically faster) if necessary
    _.each(oldClasses, function (c) {
      if (_.indexOf(newClasses, c) < 0)
        classes = _.without(classes, c);
    });
    _.each(newClasses, function (c) {
      if (_.indexOf(oldClasses, c) < 0 &&
          _.indexOf(classes, c) < 0)
        classes.push(c);
    });

    this.setValue(element, classes.join(' '));
  }
});

var ClassHandler = BaseClassHandler.extend({
  // @param rawValue {String}
  getCurrentValue: function (element) {
    return element.className;
  },
  setValue: function (element, className) {
    element.className = className;
  }
});

var SVGClassHandler = BaseClassHandler.extend({
  getCurrentValue: function (element) {
    return element.className.baseVal;
  },
  setValue: function (element, className) {
    element.setAttribute('class', className);
  }
});

var BooleanHandler = AttributeHandler.extend({
  update: function (element, oldValue, value) {
    var name = this.name;
    if (value == null) {
      if (oldValue != null)
        element[name] = false;
    } else {
      element[name] = true;
    }
  }
});

var ValueHandler = AttributeHandler.extend({
  update: function (element, oldValue, value) {
    var focused = (element === document.activeElement);

    if (!focused)
      element.value = value;
  }
});

// cross-browser version of `instanceof SVGElement`
var isSVGElement = function (elem) {
  return 'ownerSVGElement' in elem;
};

// XXX make it possible for users to register attribute handlers!
makeAttributeHandler = function (elem, name, value) {
  // generally, use setAttribute but certain attributes need to be set
  // by directly setting a JavaScript property on the DOM element.
  if (name === 'class') {
    if (isSVGElement(elem)) {
      return new SVGClassHandler(name, value);
    } else {
      return new ClassHandler(name, value);
    }
  } else if (name === 'selected' || name === 'checked') {
    return new BooleanHandler(name, value);
  } else if ((elem.tagName === 'TEXTAREA' || elem.tagName === 'INPUT')
             && name === 'value') {
    // internally, TEXTAREAs tracks their value in the 'value'
    // attribute just like INPUTs.
    return new ValueHandler(name, value);
  } else {
    return new AttributeHandler(name, value);
  }

  // XXX will need one for 'style' on IE, though modern browsers
  // seem to handle setAttribute ok.
};
<|endoftext|>


<commit_before><commit_msg>fix to update method<commit_after><|endoftext|>


<commit_before>// Initialize all modules by requiring them. Also makes sure they get bundled (see gulpfile.js).
var $ = require('jquery-detached').getJQuery();

var getItems = function(){
  var d = $.Deferred();
  $.get('categories?depth=3').done(
      function(data){
        d.resolve(data);
      }
  );
  return d.promise();
};

var jRoot = $('head').attr('data-rooturl');

$.when(getItems()).done(function(data){
  $(function() {

    // The main panel content is hidden by default via an
    // inline style. We're ready to remove that now.
    $('#create-item-panel').removeAttr('style');

    //////////////////////////////
    // helpful reference DOM

    var defaultMinToShow = 2;
    var defaultLooseItems = 'jenkins.category.uncategorized';
    var $root = $('#main-panel');
    var $form = $root.find('form[name="createItem"]').addClass('jenkins-config new-view');
    var $newView = $('
')
      .attr('name','createItem')
      .attr('action','createItem')
      .prependTo($form);
    var $navBox = $('');
    var $widgetBox = $('
').appendTo($newView);
    var $categories = $('
').appendTo($newView);
    var $subBtn = $('#bottom-sticker .yui-submit-button');
    var sectionsToShow = [];

    $widgetBox.prepend($navBox);

    ////////////////////////////////
    // submit button click

    function makeButtonWrapper(){
      var $p = window.$; // jshint ignore:line
      var btn = $p('ok');
      var okButton = window.makeButton(btn, null); // jshint ignore:line

      $subBtn = $('#bottom-sticker .yui-submit-button');

      checkFormReady();

      $subBtn.on('click',function() {
        $(this).addClass('yui-button-disabled yui-submit-button-disabled')
          .find('button')
            .attr('disabled','disabled')
            .text('. . .');
      });

    }

    ////////////////////////////////
    // scroll action......

    var isManualScrolling = false;
    var ignoreNextScrollEvent = false;
    var $window = $(window);
    var $breadcrumbBar = $('#breadcrumbBar');
    var $createItemPanel = $('#create-item-panel');
    var createPanelOffset = $createItemPanel.offset().top;

    function autoActivateTabs(){
      if (isManualScrolling === true) {
        // We ignore scroll events when a manual scroll is in
        // operation e.g. when the user clicks on a category tab.
        return;
      }
      if (ignoreNextScrollEvent === true) {
        // Things like repositioning of the tabbar can trigger scroll
        // events that we want to ignore.
        ignoreNextScrollEvent = false;
        return;
      }

      var winScoll = $window.scrollTop();

      $widgetBox.find('.active').removeClass('active');
      $.each(data.categories,function(i,cat){
        var domId = '#j-add-item-type-'+cat.id;
        var $cat = $(domId);
        var catHeight = ($cat.length > 0)?
        $cat.offset().top + $cat.outerHeight() - createPanelOffset: 0;

        if(winScoll < catHeight){
          var $thisTab = $widgetBox.find(['[href="',cleanHref(domId),'"]'].join(''));
          resetActiveTab($thisTab);
          return false;
        }
      });
    }

    function stickTabbar() {
      var winScoll = $window.scrollTop();
      var setWidth = function() {
          $widgetBox.width($form.outerWidth() - 2);
      };

      if(winScoll > createPanelOffset - $breadcrumbBar.height()){
        setWidth();
        $widgetBox.css({
          'position':'fixed',
          'top':($breadcrumbBar.height())+'px'});
        $categories.css({'margin-top':$widgetBox.outerHeight()+'px'});
        $window.resize(setWidth);
        return true;
      } else{
        $widgetBox.add($categories).removeAttr('style');
        $window.unbind('resize', setWidth);
        return false;
      }
    }

    //////////////////////////
    // helper functions...

    function checkFormReady(){
      //make sure everyone has changed and gotten attached...
      setTimeout(function(){
        var $name = $form.removeClass('no-select').find('input[name="name"]').removeClass('no-val');

        function checkItems(){
          var selected = $form.find('input[type="radio"]:checked').length > 0;
          var named = $.trim($name.val()).length > 0;
          return {selected:selected,named:named};
        }
        if(checkItems().selected && checkItems().named){
          $subBtn.removeClass('yui-button-disabled').find('button').removeAttr('disabled');
        }
        else{
          $subBtn.addClass('yui-button-disabled').find('button').attr('disabled','disabled');
        }
      },10);
    }

    function addCopyOption(data){
      var $copy = $('#copy').closest('tr');
      if($copy.length === 0) {return data;} // exit if copy should not be added to page. Jelly page holds that logic.
      var copyTitle = $copy.find('label').text();
      var copyDom = $copy.next().find('.setting-main').html();
      var copy = {
          name:'Copy',
          id:'copy',
          minToShow:0,
          items:[
            {
              class:"copy",
              description:copyDom,
              displayName:copyTitle,
              iconFilePathPattern:'images/48x48/copy.png'
            }
          ]
      };
      var newData = [];

      $.each(data,function(i,elem){
        if(elem.id !== "category-id-copy")
          { newData.push(elem); }
      });

      newData.push(copy);

      return newData;
    }
    function checkForLink(desc){
      if(desc.indexOf('<a href="') === -1) {
        return false;
      }
      var newDesc = desc.replace(/\</g,'<').replace(/\>/g,'>');
      return newDesc;
    }
    function sortItemsByOrder(itemTypes) {
      function sortByOrder(a, b) {
        var result = (b.weight - a.weight);
        return result;
      }
      return itemTypes.sort(sortByOrder);
    }

    function checkCatCount(elem){
      var minToShow = (typeof elem.minToShow === 'number')? elem.minToShow : defaultMinToShow;
      var showIt = ($.isArray(elem.items) && elem.items.length >= minToShow);
      return showIt;
    }

    function cleanClassName(className){
      return className.replace(/\./g,'_');
    }

    function cleanHref(id,reverse){
      if(reverse){
        var gotHash = (id.indexOf('#') === 0)?
           '#j-add-item-type-'+ id.substring(1).replace(/\./g,'_'):
             'j-add-item-type-'+ id.replace(/\./g,'_');
        return gotHash;
      }
      else{
        return id.replace('j-add-item-type-','');
      }
    }

    function cleanLayout(){
      // Do a little shimmy-hack to force legacy code to resize correctly and set tab state.
      $('html,body').animate({scrollTop: 1}, 1);
      $('html,body').animate({scrollTop: 0}, 10);

      setTimeout(fireBottomStickerAdjustEvent,410);
    }

    function resetActiveTab($this){
      var $nav = $this.closest('.nav');
      $nav.find('.active').removeClass('active');
      $this.addClass('active');
    }

    //////////////////////////////////
    // Draw functions

    function drawName() {
      var $name = $('
');

      var $input = $('<input type="text" name="name" class="name" id="name" placeholder="New item name..." />')
        .keyup(function(){
          $form.find('input[name="name"]').val($(this).val());
          checkFormReady();
        })
        .appendTo($name);

      $widgetBox.prepend($name);
      setTimeout(function(){$input.focus();},100);
    }

    function drawTabs(data){
      $('#main-panel').addClass('container');
      var $nav = $('
');

      $.each(data,function(i,elem){

        // little bit hacky here... need to keep track if I have tabs to show, so if there is just 1, I can hide it later....
        if (elem.minToShow !== 0 && checkCatCount(elem)) {sectionsToShow.push(elem.id);}

        var $tab = drawTab(i,elem);
        var $items = drawCategory(elem);
        var $cat = $items.parent();

        $.each(elem.items,function(i,elem){
          var $item = drawItem(elem);
          $items.append($item);
        });

        if(checkCatCount(elem)) {$nav.append($tab);}
        $categories.append($cat);

      });
      $(window).on('scroll', autoActivateTabs);
      $(window).on('scroll', stickTabbar);

      if(sectionsToShow.length > 3){
        $navBox.append($nav);
      }else{
        $categories.find('.category-header').hide();
      }
      drawName();
      cleanLayout();
    }

    function drawTab(i,elem){
      if(!elem) {elem = i;}
      var $tab = $(['
',elem.name,'
'].join(''))
        .click(function(){
          var $this = $(this).children('a');

          var tab = $this.attr('href');
          var scrollTop = $(cleanHref(tab,true)).offset().top - ($newView.children('.jenkins-config-widgets').height() + 15);

          setTimeout(function(){resetActiveTab($this);},510);

          isManualScrolling = true;
          $('html,body').animate({
            scrollTop: scrollTop
          }, 500, function() {
            isManualScrolling = false;
            ignoreNextScrollEvent = stickTabbar();
          });
        });
      return $tab;
    }

    function drawCategory(i,elem){
      // categories are smart, so this is a little tricky....
      if (!elem) {
        elem = i;
      }
      var $category = $('
').addClass('category jenkins-config hide-cat').attr('id', 'j-add-item-type-'+cleanClassName(elem.id));
      var $items = $('
').addClass('j-item-options').appendTo($category);

      // if there are enough items for a category, attach the category and its header...
      if(checkCatCount(elem)){
        var $catHeader = $('
').prependTo($category);
        var catheader = ['
', elem.name, '
'].join('');
        var catDesc = ['
', elem.description, '
'].join('');

        if((elem.minToShow > 0)){
          $(catheader).appendTo($catHeader);
          $(catDesc).appendTo($catHeader);
        }
        $category.removeClass('hide-cat');
      }
      else{
        var targ = elem.remainders || defaultLooseItems;
        $items = $('#'+cleanHref(targ,true)).find('.j-item-options');
      }

      return $items;
    }

    function drawItem(elem){
      var desc = (checkForLink(elem.description))? checkForLink(elem.description):elem.description;
      var $item = $([
          '
<input name="mode" value="',elem.class,'" type="radio" /> ', elem.displayName, '
'
      ].join('')).append([
          '
', desc, '
'
      ].join('')).append(drawIcon(elem));

      function setSelectState(e){
        e.preventDefault();
        var href = $(e.target).attr('href');
        if(href) {
          window.open(href);
        }

        var $this = $(this).closest('li');
        $this.closest('.categories').find('input[type="radio"][name="mode"]').removeAttr('checked');
        //if this is a hyperlink, don't move the selection.
        if($this.find('a:focus').length === 1) {return false;}
        $this.closest('.categories').find('.active').removeClass('active');
        $this.addClass('active');
        $this.find('input[type="radio"]').prop('checked', true);
        checkFormReady();

        $('html, body').animate({
          scrollTop:$this.offset().top - 200
        },50);

      }

      $item.click(setSelectState);

      return $item;
    }

    function drawIcon(elem){
      var $icn = $('
');
      if (!elem.iconFilePathPattern) {
        var colors = ['c-49728B','c-335061','c-D33833','c-6D6B6D','c-DCD9D8','other'];
        var desc = elem.description || '';
        var name = elem.displayName;
        var colorClass= colors[(desc.length) % 6];
        var aName = name.split(' ');
        var a = name.substring(0,1);
        var b = ((aName.length === 1)?
            name.substring(1,2):
              aName[1].substring(0,1));
        $([
          '',a,'',b,''
         ].join(''))
          .appendTo($icn);
        return $icn.addClass('df').addClass(colorClass);
      }

      var iconFilePath = jRoot + '/' + elem.iconFilePathPattern.replace(":size", "48x48");
      $([''].join(''))
        .appendTo($icn);

      return $icn;
    }

    // initialize
    var sortedDCategories = sortItemsByOrder(data.categories);
    var sortedDCategoriesWithCopy = addCopyOption(sortedDCategories);
    makeButtonWrapper();
    drawTabs(sortedDCategoriesWithCopy);

  });
});

function fireBottomStickerAdjustEvent() {
  Event.fire(window, 'jenkins:bottom-sticker-adjust'); // jshint ignore:line
}<commit_msg>Sort the items within each category<commit_after>// Initialize all modules by requiring them. Also makes sure they get bundled (see gulpfile.js).
var $ = require('jquery-detached').getJQuery();

var getItems = function(){
  var d = $.Deferred();
  $.get('categories?depth=3').done(
      function(data){
        d.resolve(data);
      }
  );
  return d.promise();
};

var jRoot = $('head').attr('data-rooturl');

$.when(getItems()).done(function(data){
  $(function() {

    // The main panel content is hidden by default via an
    // inline style. We're ready to remove that now.
    $('#create-item-panel').removeAttr('style');

    //////////////////////////////
    // helpful reference DOM

    var defaultMinToShow = 2;
    var defaultLooseItems = 'jenkins.category.uncategorized';
    var $root = $('#main-panel');
    var $form = $root.find('form[name="createItem"]').addClass('jenkins-config new-view');
    var $newView = $('
')
      .attr('name','createItem')
      .attr('action','createItem')
      .prependTo($form);
    var $navBox = $('');
    var $widgetBox = $('
').appendTo($newView);
    var $categories = $('
').appendTo($newView);
    var $subBtn = $('#bottom-sticker .yui-submit-button');
    var sectionsToShow = [];

    $widgetBox.prepend($navBox);

    ////////////////////////////////
    // submit button click

    function makeButtonWrapper(){
      var $p = window.$; // jshint ignore:line
      var btn = $p('ok');
      var okButton = window.makeButton(btn, null); // jshint ignore:line

      $subBtn = $('#bottom-sticker .yui-submit-button');

      checkFormReady();

      $subBtn.on('click',function() {
        $(this).addClass('yui-button-disabled yui-submit-button-disabled')
          .find('button')
            .attr('disabled','disabled')
            .text('. . .');
      });

    }

    ////////////////////////////////
    // scroll action......

    var isManualScrolling = false;
    var ignoreNextScrollEvent = false;
    var $window = $(window);
    var $breadcrumbBar = $('#breadcrumbBar');
    var $createItemPanel = $('#create-item-panel');
    var createPanelOffset = $createItemPanel.offset().top;

    function autoActivateTabs(){
      if (isManualScrolling === true) {
        // We ignore scroll events when a manual scroll is in
        // operation e.g. when the user clicks on a category tab.
        return;
      }
      if (ignoreNextScrollEvent === true) {
        // Things like repositioning of the tabbar can trigger scroll
        // events that we want to ignore.
        ignoreNextScrollEvent = false;
        return;
      }

      var winScoll = $window.scrollTop();

      $widgetBox.find('.active').removeClass('active');
      $.each(data.categories,function(i,cat){
        var domId = '#j-add-item-type-'+cat.id;
        var $cat = $(domId);
        var catHeight = ($cat.length > 0)?
        $cat.offset().top + $cat.outerHeight() - createPanelOffset: 0;

        if(winScoll < catHeight){
          var $thisTab = $widgetBox.find(['[href="',cleanHref(domId),'"]'].join(''));
          resetActiveTab($thisTab);
          return false;
        }
      });
    }

    function stickTabbar() {
      var winScoll = $window.scrollTop();
      var setWidth = function() {
          $widgetBox.width($form.outerWidth() - 2);
      };

      if(winScoll > createPanelOffset - $breadcrumbBar.height()){
        setWidth();
        $widgetBox.css({
          'position':'fixed',
          'top':($breadcrumbBar.height())+'px'});
        $categories.css({'margin-top':$widgetBox.outerHeight()+'px'});
        $window.resize(setWidth);
        return true;
      } else{
        $widgetBox.add($categories).removeAttr('style');
        $window.unbind('resize', setWidth);
        return false;
      }
    }

    //////////////////////////
    // helper functions...

    function checkFormReady(){
      //make sure everyone has changed and gotten attached...
      setTimeout(function(){
        var $name = $form.removeClass('no-select').find('input[name="name"]').removeClass('no-val');

        function checkItems(){
          var selected = $form.find('input[type="radio"]:checked').length > 0;
          var named = $.trim($name.val()).length > 0;
          return {selected:selected,named:named};
        }
        if(checkItems().selected && checkItems().named){
          $subBtn.removeClass('yui-button-disabled').find('button').removeAttr('disabled');
        }
        else{
          $subBtn.addClass('yui-button-disabled').find('button').attr('disabled','disabled');
        }
      },10);
    }

    function addCopyOption(data){
      var $copy = $('#copy').closest('tr');
      if($copy.length === 0) {return data;} // exit if copy should not be added to page. Jelly page holds that logic.
      var copyTitle = $copy.find('label').text();
      var copyDom = $copy.next().find('.setting-main').html();
      var copy = {
          name:'Copy',
          id:'copy',
          minToShow:0,
          items:[
            {
              class:"copy",
              description:copyDom,
              displayName:copyTitle,
              iconFilePathPattern:'images/48x48/copy.png'
            }
          ]
      };
      var newData = [];

      $.each(data,function(i,elem){
        if(elem.id !== "category-id-copy")
          { newData.push(elem); }
      });

      newData.push(copy);

      return newData;
    }
    function checkForLink(desc){
      if(desc.indexOf('<a href="') === -1) {
        return false;
      }
      var newDesc = desc.replace(/\</g,'<').replace(/\>/g,'>');
      return newDesc;
    }
    function sortItemsByOrder(itemTypes) {
      function sortByOrder(a, b) {
        return b.weight - a.weight;
      }
      return itemTypes.sort(sortByOrder);
    }

    function checkCatCount(elem){
      var minToShow = (typeof elem.minToShow === 'number')? elem.minToShow : defaultMinToShow;
      var showIt = ($.isArray(elem.items) && elem.items.length >= minToShow);
      return showIt;
    }

    function cleanClassName(className){
      return className.replace(/\./g,'_');
    }

    function cleanHref(id,reverse){
      if(reverse){
        var gotHash = (id.indexOf('#') === 0)?
           '#j-add-item-type-'+ id.substring(1).replace(/\./g,'_'):
             'j-add-item-type-'+ id.replace(/\./g,'_');
        return gotHash;
      }
      else{
        return id.replace('j-add-item-type-','');
      }
    }

    function cleanLayout(){
      // Do a little shimmy-hack to force legacy code to resize correctly and set tab state.
      $('html,body').animate({scrollTop: 1}, 1);
      $('html,body').animate({scrollTop: 0}, 10);

      setTimeout(fireBottomStickerAdjustEvent,410);
    }

    function resetActiveTab($this){
      var $nav = $this.closest('.nav');
      $nav.find('.active').removeClass('active');
      $this.addClass('active');
    }

    //////////////////////////////////
    // Draw functions

    function drawName() {
      var $name = $('
');

      var $input = $('<input type="text" name="name" class="name" id="name" placeholder="New item name..." />')
        .keyup(function(){
          $form.find('input[name="name"]').val($(this).val());
          checkFormReady();
        })
        .appendTo($name);

      $widgetBox.prepend($name);
      setTimeout(function(){$input.focus();},100);
    }

    function drawTabs(data){
      $('#main-panel').addClass('container');
      var $nav = $('
');

      $.each(data,function(i,elem){

        // little bit hacky here... need to keep track if I have tabs to show, so if there is just 1, I can hide it later....
        if (elem.minToShow !== 0 && checkCatCount(elem)) {sectionsToShow.push(elem.id);}

        var $tab = drawTab(i,elem);
        var $items = drawCategory(elem);
        var $cat = $items.parent();

        $.each(elem.items,function(i,elem){
          var $item = drawItem(elem);
          $items.append($item);
        });

        if(checkCatCount(elem)) {$nav.append($tab);}
        $categories.append($cat);

      });
      $(window).on('scroll', autoActivateTabs);
      $(window).on('scroll', stickTabbar);

      if(sectionsToShow.length > 3){
        $navBox.append($nav);
      }else{
        $categories.find('.category-header').hide();
      }
      drawName();
      cleanLayout();
    }

    function drawTab(i,elem){
      if(!elem) {elem = i;}
      var $tab = $(['
',elem.name,'
'].join(''))
        .click(function(){
          var $this = $(this).children('a');

          var tab = $this.attr('href');
          var scrollTop = $(cleanHref(tab,true)).offset().top - ($newView.children('.jenkins-config-widgets').height() + 15);

          setTimeout(function(){resetActiveTab($this);},510);

          isManualScrolling = true;
          $('html,body').animate({
            scrollTop: scrollTop
          }, 500, function() {
            isManualScrolling = false;
            ignoreNextScrollEvent = stickTabbar();
          });
        });
      return $tab;
    }

    function drawCategory(i,elem){
      // categories are smart, so this is a little tricky....
      if (!elem) {
        elem = i;
      }
      var $category = $('
').addClass('category jenkins-config hide-cat').attr('id', 'j-add-item-type-'+cleanClassName(elem.id));
      var $items = $('
').addClass('j-item-options').appendTo($category);

      // if there are enough items for a category, attach the category and its header...
      if(checkCatCount(elem)){
        var $catHeader = $('
').prependTo($category);
        var catheader = ['
', elem.name, '
'].join('');
        var catDesc = ['
', elem.description, '
'].join('');

        if((elem.minToShow > 0)){
          $(catheader).appendTo($catHeader);
          $(catDesc).appendTo($catHeader);
        }
        $category.removeClass('hide-cat');
      }
      else{
        var targ = elem.remainders || defaultLooseItems;
        $items = $('#'+cleanHref(targ,true)).find('.j-item-options');
      }

      return $items;
    }

    function drawItem(elem){
      var desc = (checkForLink(elem.description))? checkForLink(elem.description):elem.description;
      var $item = $([
          '
<input name="mode" value="',elem.class,'" type="radio" /> ', elem.displayName, '
'
      ].join('')).append([
          '
', desc, '
'
      ].join('')).append(drawIcon(elem));

      function setSelectState(e){
        e.preventDefault();
        var href = $(e.target).attr('href');
        if(href) {
          window.open(href);
        }

        var $this = $(this).closest('li');
        $this.closest('.categories').find('input[type="radio"][name="mode"]').removeAttr('checked');
        //if this is a hyperlink, don't move the selection.
        if($this.find('a:focus').length === 1) {return false;}
        $this.closest('.categories').find('.active').removeClass('active');
        $this.addClass('active');
        $this.find('input[type="radio"]').prop('checked', true);
        checkFormReady();

        $('html, body').animate({
          scrollTop:$this.offset().top - 200
        },50);

      }

      $item.click(setSelectState);

      return $item;
    }

    function drawIcon(elem){
      var $icn = $('
');
      if (!elem.iconFilePathPattern) {
        var colors = ['c-49728B','c-335061','c-D33833','c-6D6B6D','c-DCD9D8','other'];
        var desc = elem.description || '';
        var name = elem.displayName;
        var colorClass= colors[(desc.length) % 6];
        var aName = name.split(' ');
        var a = name.substring(0,1);
        var b = ((aName.length === 1)?
            name.substring(1,2):
              aName[1].substring(0,1));
        $([
          '',a,'',b,''
         ].join(''))
          .appendTo($icn);
        return $icn.addClass('df').addClass(colorClass);
      }

      var iconFilePath = jRoot + '/' + elem.iconFilePathPattern.replace(":size", "48x48");
      $([''].join(''))
        .appendTo($icn);

      return $icn;
    }

    // Sort the categories.
    var sortedDCategories = sortItemsByOrder(data.categories);
    var sortedDCategoriesWithCopy = addCopyOption(sortedDCategories);

    // Sort the items in each category.
    for (var i = 0; i < data.categories.length; i++) {
      var category = data.categories[i];
      category.items = sortItemsByOrder(category.items);
    }

    makeButtonWrapper();
    drawTabs(sortedDCategoriesWithCopy);

  });
});

function fireBottomStickerAdjustEvent() {
  Event.fire(window, 'jenkins:bottom-sticker-adjust'); // jshint ignore:line
}<|endoftext|>


<commit_before>// import * as scales from "./colorScales";
import createLegendMatchBound from "./createLegendMatchBounds";
import { genericDomain, colors, genotypeColors } from "./globals";
import { parseGenotype, getGenotype } from "./getGenotype"
import d3 from "d3";

/* this checks if there are more items in the tree compared
   to associated colours in the metadata JSON
*/
const getExtraVals = (nodes, colorBy, color_map) => {
  let valsInTree = []
  nodes.forEach((n)=>valsInTree.push(n.attr[colorBy]))
  valsInTree = [...new Set(valsInTree)]
  const valsInMeta = color_map.map((d) => { return d[0]})
  // only care about values in tree NOT in metadata
  return valsInTree.filter((x) => valsInMeta.indexOf(x) === -1)
}

const genericScale = (cmin, cmax) => {
  // console.log("genericScale called.")
  const offset = +cmin;
  const range = cmax - cmin;
  const tmpColorScale = d3.scale.linear()
    .domain(genericDomain.map((d) => offset + d * range))
    .range(colors[9]);
  return tmpColorScale;
};

const minMaxAttributeScale = (nodes, attr) => {
  const maxAttr = d3.max(nodes.map((n) => n.attr[attr]));
  const minAttr = d3.min(nodes.map((n) => n.attr[attr]));
  return genericScale(minAttr, maxAttr);
};

const integerAttributeScale = (nodes, attr) => {
  const maxAttr = d3.max(nodes.map((n) => n.attr[attr]));
  const minAttr = d3.min(nodes.map((n) => n.attr[attr]));
  const nStates = maxAttr - minAttr;
  if (nStates < 11) {
    const domain = [];
    for (let i = minAttr; i <= maxAttr; i++) { domain.push(i); }
    return d3.scale.linear().domain(domain).range(colors[maxAttr - minAttr]);
  } else {
    return genericScale(minAttr, maxAttr);
  }
};

/* this creates a (ramped) list of colours
   this is necessary as ordinal scales can't interpololate colours.
   it would be cool to use chroma, but i couldn't import it properly :(
   http://gka.github.io/chroma.js/
   range: [a,b], the colours to go between
*/
const createListOfColors = (n, range) => {
  const scale = d3.scale.linear().domain([0, n])
      .interpolate(d3.interpolateHcl)
      .range(range);
  return d3.range(0, n).map(scale);
};

const discreteAttributeScale = (nodes, attr) => {
  const stateCount = {};
  nodes.forEach((n) => (stateCount[n.attr[attr]]
                         ? stateCount[n.attr[attr]] += 1
                         : stateCount[n.attr[attr]] = 1));
  const domain = Object.keys(stateCount);
  domain.sort((a, b) => stateCount[a] > stateCount[b]);
  // note: colors[n] has n colors
  const colorList = domain.length < colors.length ? colors[domain.length] : colors[colors.length - 1];
  return d3.scale.ordinal()
                 .domain(domain)
                 .range(colorList);
};

const getColorScale = (colorBy, tree, sequences, colorOptions, version) => {
  let colorScale;
  let continuous = false;
  if (!tree.nodes) {
    // make a dummy color scale before the tree is in place
    continuous = true;
    colorScale = genericScale(0, 1);
  } else if (colorBy.slice(0, 2) === "gt") {
    if (!sequences.geneLength) {
      continuous = true;
      colorScale = genericScale(0, 1);
    } else if (parseGenotype(colorBy, sequences.geneLength)) {
      // genotype coloring
      const gt = parseGenotype(colorBy, sequences.geneLength);
      if (gt) {
        const stateCount = {};
        tree.nodes.forEach((n) => (stateCount[getGenotype(gt[0][0], gt[0][1], n, sequences.sequences)]
                               ? stateCount[getGenotype(gt[0][0], gt[0][1], n,   sequences.sequences)] += 1
                               : stateCount[getGenotype(gt[0][0], gt[0][1], n,   sequences.sequences)] = 1));
        const domain = Object.keys(stateCount);
        domain.sort((a, b) => stateCount[a] > stateCount[b]);
        colorScale = d3.scale.ordinal().domain(domain).range(genotypeColors);
      }
    }
  } else if (colorOptions && colorOptions[colorBy]) {
    if (colorOptions[colorBy].color_map) {
      // console.log("Sweet - we've got a color_map for ", colorBy)
      let domain = colorOptions[colorBy].color_map.map((d) => { return d[0]; })
      let range = colorOptions[colorBy].color_map.map((d) => { return d[1]; })
      const extraVals = getExtraVals(tree.nodes, colorBy, colorOptions[colorBy].color_map)
      if (extraVals.length) {
        // we must add these to the domain + provide a value in the range
        domain = domain.concat(extraVals);
        const extrasColorAB = [d3.rgb(192, 192, 192), d3.rgb(32, 32, 32)]
        range = range.concat(createListOfColors(extraVals.length, extrasColorAB))
      }
      continuous = false;
      colorScale = d3.scale.ordinal()
                           .domain(domain)
                           .range(range);
    } else if (colorOptions && colorOptions[colorBy].type === "discrete") {
      // console.log("making a discrete color scale for ", colorBy)
      continuous = false;
      colorScale = discreteAttributeScale(tree.nodes, colorBy);
    } else if (colorOptions && colorOptions[colorBy].type === "integer") {
      // console.log("making an integer color scale for ", colorBy)
      continuous = false;
      colorScale = integerAttributeScale(tree.nodes, colorBy);
    } else if (colorOptions && colorOptions[colorBy].type === "continuous") {
      // console.log("making a continuous color scale for ", colorBy)
      continuous = true;
      colorScale = minMaxAttributeScale(tree.nodes, colorBy);
    }
  } else {
    // This shouldn't ever happen!
    // console.log("no colorOptions for ", colorBy, " returning minMaxAttributeScale")
    continuous = true;
    colorScale = minMaxAttributeScale(tree.nodes, colorBy);
  }
  return {
    "scale": colorScale,
    "continuous": continuous,
    "colorBy": colorBy, // this should be removed
    "legendBoundsMap": createLegendMatchBound(colorScale),
    version
  };
};

export default getColorScale;
<commit_msg>continuous color scale domain improvements for scales with few values<commit_after>// import * as scales from "./colorScales";
import createLegendMatchBound from "./createLegendMatchBounds";
import { genericDomain, colors, genotypeColors } from "./globals";
import { parseGenotype, getGenotype } from "./getGenotype"
import d3 from "d3";

/* this checks if there are more items in the tree compared
   to associated colours in the metadata JSON
*/
const getExtraVals = (nodes, colorBy, color_map) => {
  let valsInTree = []
  nodes.forEach((n)=>valsInTree.push(n.attr[colorBy]))
  valsInTree = [...new Set(valsInTree)]
  const valsInMeta = color_map.map((d) => { return d[0]})
  // only care about values in tree NOT in metadata
  return valsInTree.filter((x) => valsInMeta.indexOf(x) === -1)
}

const genericScale = (cmin, cmax, vals = false) => {
  if (vals && vals.length <= 9) {
    return d3.scale.linear()
      .domain(vals)
      .range(colors[vals.length]);
  }
  /* if we have more than 9 values to display
  (and the scale is continuous at this point)
  create an evenly spaced 9-item domain (this is genericDomain) */
  const offset = +cmin;
  const range = cmax - cmin;
  return d3.scale.linear()
    .domain(genericDomain.map((d) => offset + d * range))
    .range(colors[9]);
};

const minMaxAttributeScale = (nodes, attr) => {
  const vals = nodes.map((n) => n.attr[attr])
    .filter((n) => n !== undefined)
    .filter((item, i, ar) => ar.indexOf(item) === i);
  return genericScale(d3.min(vals), d3.max(vals), vals);
};

const integerAttributeScale = (nodes, attr) => {
  const maxAttr = d3.max(nodes.map((n) => n.attr[attr]));
  const minAttr = d3.min(nodes.map((n) => n.attr[attr]));
  const nStates = maxAttr - minAttr;
  if (nStates < 11) {
    const domain = [];
    for (let i = minAttr; i <= maxAttr; i++) { domain.push(i); }
    return d3.scale.linear().domain(domain).range(colors[maxAttr - minAttr]);
  } else {
    return genericScale(minAttr, maxAttr);
  }
};

/* this creates a (ramped) list of colours
   this is necessary as ordinal scales can't interpololate colours.
   it would be cool to use chroma, but i couldn't import it properly :(
   http://gka.github.io/chroma.js/
   range: [a,b], the colours to go between
*/
const createListOfColors = (n, range) => {
  const scale = d3.scale.linear().domain([0, n])
      .interpolate(d3.interpolateHcl)
      .range(range);
  return d3.range(0, n).map(scale);
};

const discreteAttributeScale = (nodes, attr) => {
  const stateCount = {};
  nodes.forEach((n) => (stateCount[n.attr[attr]]
                         ? stateCount[n.attr[attr]] += 1
                         : stateCount[n.attr[attr]] = 1));
  const domain = Object.keys(stateCount);
  domain.sort((a, b) => stateCount[a] > stateCount[b]);
  // note: colors[n] has n colors
  const colorList = domain.length < colors.length ? colors[domain.length] : colors[colors.length - 1];
  return d3.scale.ordinal()
                 .domain(domain)
                 .range(colorList);
};

const getColorScale = (colorBy, tree, sequences, colorOptions, version) => {
  let colorScale;
  let continuous = false;
  if (!tree.nodes) {
    // make a dummy color scale before the tree is in place
    continuous = true;
    colorScale = genericScale(0, 1);
  } else if (colorBy.slice(0, 2) === "gt") {
    if (!sequences.geneLength) {
      continuous = true;
      colorScale = genericScale(0, 1);
    } else if (parseGenotype(colorBy, sequences.geneLength)) {
      // genotype coloring
      const gt = parseGenotype(colorBy, sequences.geneLength);
      if (gt) {
        const stateCount = {};
        tree.nodes.forEach((n) => (stateCount[getGenotype(gt[0][0], gt[0][1], n, sequences.sequences)]
                               ? stateCount[getGenotype(gt[0][0], gt[0][1], n,   sequences.sequences)] += 1
                               : stateCount[getGenotype(gt[0][0], gt[0][1], n,   sequences.sequences)] = 1));
        const domain = Object.keys(stateCount);
        domain.sort((a, b) => stateCount[a] > stateCount[b]);
        colorScale = d3.scale.ordinal().domain(domain).range(genotypeColors);
      }
    }
  } else if (colorOptions && colorOptions[colorBy]) {
    if (colorOptions[colorBy].color_map) {
      // console.log("Sweet - we've got a color_map for ", colorBy)
      let domain = colorOptions[colorBy].color_map.map((d) => { return d[0]; })
      let range = colorOptions[colorBy].color_map.map((d) => { return d[1]; })
      const extraVals = getExtraVals(tree.nodes, colorBy, colorOptions[colorBy].color_map)
      if (extraVals.length) {
        // we must add these to the domain + provide a value in the range
        domain = domain.concat(extraVals);
        const extrasColorAB = [d3.rgb(192, 192, 192), d3.rgb(32, 32, 32)]
        range = range.concat(createListOfColors(extraVals.length, extrasColorAB))
      }
      continuous = false;
      colorScale = d3.scale.ordinal()
                           .domain(domain)
                           .range(range);
    } else if (colorOptions && colorOptions[colorBy].type === "discrete") {
      // console.log("making a discrete color scale for ", colorBy)
      continuous = false;
      colorScale = discreteAttributeScale(tree.nodes, colorBy);
    } else if (colorOptions && colorOptions[colorBy].type === "integer") {
      // console.log("making an integer color scale for ", colorBy)
      continuous = false;
      colorScale = integerAttributeScale(tree.nodes, colorBy);
    } else if (colorOptions && colorOptions[colorBy].type === "continuous") {
      // console.log("making a continuous color scale for ", colorBy)
      continuous = true;
      colorScale = minMaxAttributeScale(tree.nodes, colorBy);
    }
  } else {
    // This shouldn't ever happen!
    // console.log("no colorOptions for ", colorBy, " returning minMaxAttributeScale")
    continuous = true;
    colorScale = minMaxAttributeScale(tree.nodes, colorBy);
  }
  return {
    "scale": colorScale,
    "continuous": continuous,
    "colorBy": colorBy, // this should be removed
    "legendBoundsMap": createLegendMatchBound(colorScale),
    version
  };
};

export default getColorScale;
<|endoftext|>


<commit_before>/* jshint node:true, esversion: 6 */
'use strict';

const MakeDir       = require('./make-dir');
const fs            = require('fs');
const RSVP          = require('rsvp');
const svg2png       = require('../../vendor/svg2png/svg2png');
const normalizePath = require('path').normalize;
const _forOwn       = require('lodash').forOwn;
const _union        = require('lodash').union;

module.exports = function (opts) {
  return new RSVP.Promise((resolve) => {
    let buffer = fs.readFileSync(opts.source);
    let rasterizeTasks = [];
    let rasterizeQueue = [];

    _forOwn(opts.platformSizes, (icons, platform) => {
      MakeDir('./', `${opts.projectPath}/${opts.dest}/${platform}`);

      icons.sizes.map((size) => {
        size.path = `${opts.dest}/${platform}/${size.name}.png`;
      });

      rasterizeQueue = _union(rasterizeQueue, icons.sizes);
    });

    rasterizeQueue.forEach((rasterize) => {
      const width = rasterize.size;
      const height = rasterize.size;

      let rasterizeTask = svg2png(buffer, { width: width, height: height })
      .then((pngBuffer) => {
        const writePath = `${opts.projectPath}/${rasterize.path}`;
        fs.writeFileSync(normalizePath(writePath), pngBuffer);
      })

      rasterizeTasks.push(rasterizeTask);
    });

    RSVP.all(rasterizeTasks).then(() => resolve(opts.platformSizes));
  });
};
<commit_msg>feat(write-images): Update function to support width and height when no size<commit_after>/* jshint node:true, esversion: 6 */
'use strict';

const MakeDir       = require('./make-dir');
const fs            = require('fs');
const RSVP          = require('rsvp');
const svg2png       = require('../../vendor/svg2png/svg2png');
const normalizePath = require('path').normalize;
const _forOwn       = require('lodash').forOwn;
const _union        = require('lodash').union;

module.exports = function (opts) {
  return new RSVP.Promise((resolve) => {
    let buffer = fs.readFileSync(opts.source);
    let rasterizeTasks = [];
    let rasterizeQueue = [];

    _forOwn(opts.platformSizes, (icons, platform) => {
      MakeDir('./', `${opts.projectPath}/${opts.dest}/${platform}`);

      icons.sizes.map((size) => {
        size.path = `${opts.dest}/${platform}/${size.name}.png`;
      });

      rasterizeQueue = _union(rasterizeQueue, icons.sizes);
    });

    rasterizeQueue.forEach((rasterize) => {
      let width, height;

      if (rasterize.size) {
        width = rasterize.size;
        height = rasterize.size;
      } else {
        width = rasterize.width;
        height = rasterize.height;
      }

      let rasterizeTask = svg2png(buffer, { width: width, height: height })
      .then((pngBuffer) => {
        const writePath = `${opts.projectPath}/${rasterize.path}`;
        fs.writeFileSync(normalizePath(writePath), pngBuffer);
      })

      rasterizeTasks.push(rasterizeTask);
    });

    RSVP.all(rasterizeTasks).then(() => resolve(opts.platformSizes));
  });
};
<|endoftext|>


<commit_before>import { expect } from 'chai';

import getNextEpoch from '../getNextEpoch';

const blinker1 = [
  [0, 1, 0],
  [0, 1, 0],
  [0, 1, 0],
];

const blinker2 = [
  [0, 0, 0],
  [1, 1, 1],
  [0, 0, 0],
];

const toad1 = [
  [0, 0, 0, 0],
  [0, 1, 1, 1],
  [1, 1, 1, 0],
  [0, 0, 0, 0],
];

const toad2 = [
  [0, 0, 1, 0],
  [1, 0, 0, 1],
  [1, 0, 0, 1],
  [0, 1, 0, 0],
];

// For names used in tests see
// https://en.wikipedia.org/wiki/Conway's_Game_of_Life#ExamplesOfPatterns
describe('getNextEpoch', () => {
  it('should return blinker2 as next epoch of blinker1', () => {
    expect(getNextEpoch(blinker1)).to.deep.equal(blinker2);
  });

  it('should return blinker1 as next epoch of blinker2', () => {
    expect(getNextEpoch(blinker2)).to.deep.equal(blinker1);
  });

  it('should return toad2 as next epoch of toad1', () => {
    expect(getNextEpoch(toad2)).to.deep.equal(toad1);
  });

  it('should return toad1 as next epoch of toad2', () => {
    expect(getNextEpoch(toad2)).to.deep.equal(toad1);
  });

  console.log(`\n${getNextEpoch(blinker2)}`);
  console.log(`\n${getNextEpoch(toad1)}`);

});
<commit_msg>removed console.log from getNextEpoch.spec.js<commit_after>import { expect } from 'chai';

import getNextEpoch from '../getNextEpoch';

const blinker1 = [
  [0, 1, 0],
  [0, 1, 0],
  [0, 1, 0],
];

const blinker2 = [
  [0, 0, 0],
  [1, 1, 1],
  [0, 0, 0],
];

const toad1 = [
  [0, 0, 0, 0],
  [0, 1, 1, 1],
  [1, 1, 1, 0],
  [0, 0, 0, 0],
];

const toad2 = [
  [0, 0, 1, 0],
  [1, 0, 0, 1],
  [1, 0, 0, 1],
  [0, 1, 0, 0],
];

// For names used in tests see
// https://en.wikipedia.org/wiki/Conway's_Game_of_Life#ExamplesOfPatterns
describe('getNextEpoch', () => {
  it('should return blinker2 as next epoch of blinker1', () => {
    expect(getNextEpoch(blinker1)).to.deep.equal(blinker2);
  });

  it('should return blinker1 as next epoch of blinker2', () => {
    expect(getNextEpoch(blinker2)).to.deep.equal(blinker1);
  });

  it('should return toad2 as next epoch of toad1', () => {
    expect(getNextEpoch(toad2)).to.deep.equal(toad1);
  });

  it('should return toad1 as next epoch of toad2', () => {
    expect(getNextEpoch(toad2)).to.deep.equal(toad1);
  });

});
<|endoftext|>