<commit_before>import Events from '../events.js';
import defineGetterSetter from '../utils/get-set.js';
import _ from '../utils/underscore.js';

import Stop from './stop.js';
import Gradient from './gradient.js';
import Vector from '../vector.js';

/**
 * @name Two.RadialGradient
 * @class
 * @extends Two.Gradient
 * @param {Number} [x=0] - The x position of the origin of the radial gradient.
 * @param {Number} [y=0] - The y position of the origin of the radial gradient.
 * @param {Number} [radius=0] - The radius of the radial gradient.
 * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
 * @param {Number} [focalX=0] - The x position of the focal point on the radial gradient.
 * @param {Number} [focalY=0] - The y position of the focal point on the radial gradient.
 * @nota-bene The radial gradient lives within the space of the parent object's matrix space.
 */
function RadialGradient(cx, cy, r, stops, fx, fy) {

  Gradient.call(this, stops);

  this._renderer.type = 'radial-gradient';

  /**
   * @name Two.RadialGradient#center
   * @property {Two.Vector} - The x and y value for where the origin of the radial gradient is.
   */
  this.center = new Vector()
    .bind(Events.Types.change, (function() {
      this._flagCenter = true;
    }).bind(this));

  this.radius = typeof r === 'number' ? r : 20;

  /**
   * @name Two.RadialGradient#focal
   * @property {Two.Vector} - The x and y value for where the focal point of the radial gradient is.
   * @nota-bene This effects the spray or spread of the radial gradient.
   */
  this.focal = new Vector()
    .bind(Events.Types.change, (function() {
      this._flagFocal = true;
    }).bind(this));

  if (typeof cx === 'number') {
    this.center.x = cx;
  }
  if (typeof cy === 'number') {
    this.center.y = cy;
  }

  this.focal.copy(this.center);

  if (typeof fx === 'number') {
    this.focal.x = fx;
  }
  if (typeof fy === 'number') {
    this.focal.y = fy;
  }

}

_.extend(RadialGradient, {

  /**
   * @name Two.RadialGradient.Stop
   * @see {@link Two.Stop}
   */
  Stop: Stop,

  /**
   * @name Two.RadialGradient.Properties
   * @property {String[]} - A list of properties that are on every {@link Two.RadialGradient}.
   */
  Properties: [
    'radius'
  ],

  /**
   * @name Two.RadialGradient.MakeObservable
   * @function
   * @param {Object} object - The object to make observable.
   * @description Convenience function to apply observable qualities of a {@link Two.RadialGradient} to any object. Handy if you'd like to extend the {@link Two.RadialGradient} class on a custom class.
   */
  MakeObservable: function(object) {

    Gradient.MakeObservable(object);

    _.each(RadialGradient.Properties, defineGetterSetter, object);

  }

});

_.extend(RadialGradient.prototype, Gradient.prototype, {

  constructor: RadialGradient,

  /**
   * @name Two.RadialGradient#_flagRadius
   * @private
   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#radius} changed and needs to update.
   */
  _flagRadius: false,
  /**
   * @name Two.RadialGradient#_flagCenter
   * @private
   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#center} changed and needs to update.
   */
  _flagCenter: false,
  /**
   * @name Two.RadialGradient#_flagFocal
   * @private
   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#focal} changed and needs to update.
   */
  _flagFocal: false,

  /**
   * @name Two.RadialGradient#clone
   * @function
   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
   * @returns {Two.Gradient}
   * @description Create a new instance of {@link Two.RadialGradient} with the same properties of the current path.
   */
  clone: function(parent) {

    var stops = this.stops.map(function(stop) {
      return stop.clone();
    });

    var clone = new RadialGradient(this.center._x, this.center._y,
        this._radius, stops, this.focal._x, this.focal._y);

    _.each(Gradient.Properties.concat(RadialGradient.Properties), function(k) {
      clone[k] = this[k];
    }, this);

    if (parent) {
      parent.add(clone);
    }

    return clone;

  },

  /**
   * @name Two.RadialGradient#toObject
   * @function
   * @returns {Object}
   * @description Return a JSON compatible plain object that represents the path.
   */
  toObject: function() {

    var result = Gradient.prototype.toObject.call(this);

    _.each(RadialGradient.Properties, function(k) {
      result[k] = this[k];
    }, this);

    result.center = this.center.toObject();
    result.focal = this.focal.toObject();

    return result;

  },

  /**
   * @name Two.RadialGradient#_update
   * @function
   * @private
   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
   * @nota-bene Try not to call this method more than once a frame.
   */
  _update: function() {

    if (this._flagRadius || this._flatCenter || this._flagFocal
      || this._flagSpread || this._flagStops) {
      this.trigger(Events.Types.change);
    }

    return this;

  },

  /**
   * @name Two.RadialGradient#flagReset
   * @function
   * @private
   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
   */
  flagReset: function() {

    this._flagRadius = this._flagCenter = this._flagFocal = false;

    Gradient.prototype.flagReset.call(this);

    return this;

  }

});

RadialGradient.MakeObservable(RadialGradient.prototype);

export default RadialGradient;
<commit_msg>Converted Two.RadialGradient to ES6 Class<commit_after>import { Events } from '../events.js';
import { _ } from '../utils/underscore.js';

import { Stop } from './stop.js';
import { Gradient } from './gradient.js';
import { Vector } from '../vector.js';

/**
 * @name Two.RadialGradient
 * @class
 * @extends Two.Gradient
 * @param {Number} [x=0] - The x position of the origin of the radial gradient.
 * @param {Number} [y=0] - The y position of the origin of the radial gradient.
 * @param {Number} [radius=0] - The radius of the radial gradient.
 * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
 * @param {Number} [focalX=0] - The x position of the focal point on the radial gradient.
 * @param {Number} [focalY=0] - The y position of the focal point on the radial gradient.
 * @nota-bene The radial gradient lives within the space of the parent object's matrix space.
 */
export class RadialGradient extends Gradient {

  /**
   * @name Two.RadialGradient#_flagRadius
   * @private
   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#radius} changed and needs to update.
   */
  _flagRadius = false;
  /**
   * @name Two.RadialGradient#_flagCenter
   * @private
   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#center} changed and needs to update.
   */
  _flagCenter = false;
  /**
   * @name Two.RadialGradient#_flagFocal
   * @private
   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#focal} changed and needs to update.
   */
  _flagFocal = false;

  _radius = 0;
  _center = null
  _focal = null;

  constructor(cx, cy, r, stops, fx, fy) {

    super(stops);

    for (let prop in proto) {
      Object.defineProperty(this, prop, proto[prop]);
    }

    this._renderer.type = 'radial-gradient';
    this._renderer.flagCenter = FlagCenter.bind(this);
    this._renderer.flagFocal = FlagFocal.bind(this);

    /**
     * @name Two.RadialGradient#center
     * @property {Two.Vector} - The x and y value for where the origin of the radial gradient is.
     */
    this.center = new Vector();

    this.radius = typeof r === 'number' ? r : 20;

    /**
     * @name Two.RadialGradient#focal
     * @property {Two.Vector} - The x and y value for where the focal point of the radial gradient is.
     * @nota-bene This effects the spray or spread of the radial gradient.
     */
    this.focal = new Vector();

    if (typeof cx === 'number') {
      this.center.x = cx;
    }
    if (typeof cy === 'number') {
      this.center.y = cy;
    }

    this.focal.copy(this.center);

    if (typeof fx === 'number') {
      this.focal.x = fx;
    }
    if (typeof fy === 'number') {
      this.focal.y = fy;
    }

  }

  /**
   * @name Two.RadialGradient.Stop
   * @see {@link Two.Stop}
   */
  static Stop = Stop;

  /**
   * @name Two.RadialGradient.Properties
   * @property {String[]} - A list of properties that are on every {@link Two.RadialGradient}.
   */
  static Properties = ['center', 'radius', 'focal'];

  /**
   * @name Two.RadialGradient#clone
   * @function
   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
   * @returns {Two.Gradient}
   * @description Create a new instance of {@link Two.RadialGradient} with the same properties of the current path.
   */
  clone(parent) {

    const stops = this.stops.map(function(stop) {
      return stop.clone();
    });

    const clone = new RadialGradient(this.center._x, this.center._y,
        this._radius, stops, this.focal._x, this.focal._y);

    _.each(Gradient.Properties.concat(RadialGradient.Properties), function(k) {
      clone[k] = this[k];
    }, this);

    if (parent) {
      parent.add(clone);
    }

    return clone;

  }

  /**
   * @name Two.RadialGradient#toObject
   * @function
   * @returns {Object}
   * @description Return a JSON compatible plain object that represents the path.
   */
  toObject() {

    const result = super.toObject.call(this);

    _.each(RadialGradient.Properties, function(k) {
      result[k] = this[k];
    }, this);

    result.center = this.center.toObject();
    result.focal = this.focal.toObject();

    return result;

  }

  /**
   * @name Two.RadialGradient#_update
   * @function
   * @private
   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
   * @nota-bene Try not to call this method more than once a frame.
   */
  _update() {

    if (this._flagRadius || this._flatCenter || this._flagFocal
      || this._flagSpread || this._flagStops) {
      this.trigger(Events.Types.change);
    }

    return this;

  }

  /**
   * @name Two.RadialGradient#flagReset
   * @function
   * @private
   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
   */
  flagReset() {

    this._flagRadius = this._flagCenter = this._flagFocal = false;

    super.flagReset.call(this);

    return this;

  }

}

const proto = {
  radius: {
    enumerable: true,
    get: function() {
      return this._radius;
    },
    set: function(v) {
      this._radius = v;
      this._flagRadius = true;
    }
  },
  center: {
    enumerable: true,
    get: function() {
      return this._center;
    },
    set: function(v) {
      if (this._center) {
        this._center.unbind(Events.Types.change, this._renderer.flagCenter);
      }
      this._center = v;
      this._center.bind(Events.Types.change, this._renderer.flagCenter);
      this._flagCenter = true;
    }
  },
  focal: {
    enumerable: true,
    get: function() {
      return this._focal;
    },
    set: function(v) {
      if (this._focal) {
        this._focal.unbind(Events.Types.change, this._renderer.flagFocal);
      }
      this._focal = v;
      this._focal.bind(Events.Types.change, this._renderer.flagFocal);
      this._flagFocal = true;
    }
  }
};

function FlagCenter() {
  this._flagCenter = true;
}

function FlagFocal() {
  this._flagFocal = true;
}
<|endoftext|>


<commit_before>//
// Connection to SRE explorer.
//
MathJax.Hub.Register.StartupHook('Sre Ready', function() {
  var FALSE, KEY;
  var SETTINGS = MathJax.Hub.config.menuSettings;

  MathJax.Hub.Register.StartupHook('MathEvents Ready', function() {
    FALSE = MathJax.Extension.MathEvents.Event.False;
    KEY = MathJax.Extension.MathEvents.Event.KEY;
  });

  var Assistive = MathJax.Extension.Assistive = {
    version: "1.0",
    //
    // Default configurations.
    //
    default: {
      walker: 'dummy',
      highlight: 'none',
      background: 'blue',
      foreground: 'black',
      speech: true,
      subtitle: false,
      // Configuration option only.
      generateSpeech: true
    },
    prefix: 'Assistive-',

    addMenuOption: function(key, value) {
      SETTINGS[Assistive.prefix + key] = value;
    },

    addDefaults: function() {
      for (var key in Assistive.default) {
        if (typeof(SETTINGS[Assistive.prefix + key]) === 'undefined') {
          Assistive.addMenuOption(key, Assistive.default[key]);
        }
      }
      Explorer.Reset();
    },

    setOption: function(key, value) {
      if (SETTINGS[Assistive.prefix + key] === value) return;
      Assistive.addMenuOption(key, value);
      Explorer.Reset();
    },

    getOption: function(key) {
      return SETTINGS[Assistive.prefix + key];
    }

  };

  var LiveRegion = MathJax.Object.Subclass({
    div: null,
    inner: null,
    Init: function() {
      this.div = LiveRegion.Create('assertive');
      this.inner = MathJax.HTML.addElement(this.div,'div');
    },
    //
    // Adds the speech div.
    //
    Add: function() {
      if (LiveRegion.announced) return;
      document.body.appendChild(this.div);
      LiveRegion.Announce();
    },
    //
    // Shows the live region as a subtitle of a node.
    //
    Show: function(node, highlighter) {
      this.div.classList.add('MJX_LiveRegion_Show');
      var rect = node.getBoundingClientRect();
      var bot = rect.bottom + 10 + window.pageYOffset;
      var left = rect.left + window.pageXOffset;
      this.div.style.top = bot + 'px';
      this.div.style.left = left + 'px';
      var color = highlighter.colorString();
      this.inner.style.backgroundColor = color.background;
      this.inner.style.color = color.foreground;
    },
    //
    // Takes the live region out of the page flow.
    //
    Hide: function(node) {
      this.div.classList.remove('MJX_LiveRegion_Show');
    },
    //
    // Clears the speech div.
    //
    Clear: function() {
      this.Update('');
      this.inner.style.top = '';
      this.inner.style.backgroundColor = '';
    },
    //
    // Speaks a string by poking it into the speech div.
    //
    Update: function(speech) {
      if (Assistive.getOption('speech')) {
        LiveRegion.Update(this.inner, speech);
      }
    }
  }, {
    ANNOUNCE: 'Navigatable Math in page. Explore with shift space.',
    announced: false,
    styles: {'.MJX_LiveRegion':
             {
               position: 'absolute', top:'0', height: '1px', width: '1px',
               padding: '1px', overflow: 'hidden'
             },
             '.MJX_LiveRegion_Show':
             {
               top:'0', position: 'absolute', width: 'auto', height: 'auto',
               padding: '0px 0px', opacity: 1, 'z-index': '202',
               left: 0, right: 0, 'margin': '0 auto',
               'background-color': 'white', 'box-shadow': '0px 10px 20px #888',
               border: '2px solid #CCCCCC'
             }},
    //
    // Creates a live region with a particular type.
    //
    Create: function(type) {
      var element = MathJax.HTML.Element(
        'div', {className: 'MJX_LiveRegion'});
      element.setAttribute('aria-live', type);
      return element;
    },
    //
    // Updates a live region's text content.
    //
    Update: MathJax.Hub.Browser.isPC ?
      function(div, speech) {
        div.textContent = '';
        setTimeout(function() {div.textContent = speech;}, 100);
      } : function(div, speech) {
        div.textContent = speech;
      },
    //
    // Speaks the announce string.
    //
    Announce: function() {
      if (LiveRegion.announced) return;
      LiveRegion.announced = true;
      MathJax.Ajax.Styles(LiveRegion.styles);
      var div = LiveRegion.Create('polite');
      document.body.appendChild(div);
      LiveRegion.Update(div, LiveRegion.ANNOUNCE);
      setTimeout(function() {document.body.removeChild(div);}, 1000);
    }
  });
  MathJax.Extension.Assistive.LiveRegion = LiveRegion;

  var Explorer = MathJax.Extension.Assistive.Explorer = {
    liveRegion: LiveRegion(),
    walker: null,
    highlighter: null,
    hoverer: null,
    flamer: null,
    speechDiv: null,
    enriched: {},
    earconFile: location.protocol +
      '//progressiveaccess.com/content/invalid_keypress' +
      (['Firefox', 'Chrome', 'Opera'].indexOf(MathJax.Hub.Browser.name) !== -1 ?
       '.ogg' : '.mp3'),
    focusEvent: MathJax.Hub.Browser.isFirefox ? 'blur' : 'focusout',
    //
    // Resets the explorer, rerunning methods not triggered by events.
    //
    Reset: function() {
      Explorer.FlameEnriched();
      sre.Engine.getInstance().speech = Assistive.getOption('generateSpeech');
    },
    //
    // Registers new Maths and adds a key event if it is enriched.
    //
    Register: function(msg) {
      var script = document.getElementById(msg[1]);
      if (script && script.id) {
        var jax = MathJax.Hub.getJaxFor(script.id);
        if (jax && jax.enriched) {
          Explorer.enriched[script.id] = script;
          Explorer.liveRegion.Add();
          Explorer.AddEvent(script);
        }
      }
    },
    //
    // Adds Aria attributes.
    //
    AddAria: function(math) {
      math.setAttribute('role', 'application');
      math.setAttribute('aria-label', 'Math');
    },
    //
    // Adds a key event to an enriched jax.
    //
    AddEvent: function(script) {
      var id = script.id + '-Frame';
      var sibling = script.previousSibling;
      if (sibling) {
        var math = sibling.id !== id ? sibling.firstElementChild : sibling;
        Explorer.AddAria(math);
        Explorer.AddMouseEvents(math);
        if (math.className === 'MathJax_MathML') {
          math = math.firstElementChild;
        }
        if (math) {
          math.onkeydown = Explorer.Keydown;
          math.addEventListener(
            Explorer.focusEvent,
            function(event) {
              if (Explorer.walker) Explorer.DeactivateWalker();
            });
          return;
        }
      }
      MathJax.Hub.Queue(['AddEvent', Explorer, script]);
    },
    //
    // Event execution on keydown. Subsumes the same method of MathEvents.
    //
    Keydown: function(event) {
      if (event.keyCode === KEY.ESCAPE) {
        if (!Explorer.walker) return;
        Explorer.DeactivateWalker();
        FALSE(event);
        return;
      }
      // If walker is active we redirect there.
      if (Explorer.walker && Explorer.walker.isActive()) {
        var move = Explorer.walker.move(event.keyCode);
        if (move === null) return;
        if (move) {
          Explorer.liveRegion.Update(Explorer.walker.speech());
          Explorer.Highlight();
        } else {
          Explorer.PlayEarcon();
        }
        FALSE(event);
        return;
      }
      var math = event.target;
      if (event.keyCode === KEY.SPACE) {
        if (event.shiftKey) {
          Explorer.ActivateWalker(math);
        } else {
          MathJax.Extension.MathEvents.Event.ContextMenu(event, math);
        }
        FALSE(event);
        return;
      }
    },
    GetHighlighter: function(alpha) {
      Explorer.highlighter = sre.HighlighterFactory.highlighter(
        {color: Assistive.getOption('background'), alpha: alpha},
        {color: Assistive.getOption('foreground'), alpha: 1},
        {renderer: MathJax.Hub.outputJax['jax/mml'][0].id,
         browser: MathJax.Hub.Browser.name}
      );
    },
    //
    // Adds mouse events to maction items in an enriched jax.
    //
    AddMouseEvents: function(node) {
      sre.HighlighterFactory.addEvents(
        node,
        {'mouseover': Explorer.MouseOver,
         'mouseout': Explorer.MouseOut},
        {renderer: MathJax.Hub.outputJax['jax/mml'][0].id,
         browser: MathJax.Hub.Browser.name}
      );
    },
    MouseOver: function(event) {
      if (Assistive.getOption('highlight') === 'none') return;
      if (Assistive.getOption('highlight') === 'hover') {
        var frame = event.currentTarget;
        Explorer.GetHighlighter(.1);
        Explorer.highlighter.highlight([frame]);
        Explorer.hoverer = true;
      }
      MathJax.Extension.MathEvents.Event.False(event);
    },
    MouseOut: function (event) {
      if (Explorer.hoverer) {
        Explorer.highlighter.unhighlight();
        Explorer.hoverer = false;
      }
      return MathJax.Extension.MathEvents.Event.False(event);
    },
    //
    // Activates Flaming
    //
    Flame: function(node) {
      Explorer.UnFlame(node);
      if (Assistive.getOption('highlight') === 'flame') {
        Explorer.GetHighlighter(.05);
        Explorer.highlighter.highlightAll(node);
        Explorer.flamer = true;
        return;
      }
    },
    UnFlame: function(node) {
      if (Explorer.flamer) {
        Explorer.highlighter.unhighlightAll();
        Explorer.flamer = null;
      }
    },
    FlameEnriched: function() {
      for (var key in Explorer.enriched) {
        Explorer.Flame(Explorer.enriched[key].previousSibling);
      }
    },
    //
    // Activates the walker.
    //
    Walkers: {
      'syntactic': sre.SyntaxWalker,
      'semantic': sre.SemanticWalker,
      'dummy': sre.DummyWalker
    },
    ActivateWalker: function(math) {
      var speechGenerator = new sre.DirectSpeechGenerator();
      var constructor = Explorer.Walkers[Assistive.getOption('walker')] ||
            Explorer.Walkers['dummy'];
      Explorer.walker = new constructor(math, speechGenerator);
      Explorer.GetHighlighter(.2);
      Explorer.walker.activate();
      if (Assistive.getOption('subtitle')) {
        Explorer.liveRegion.Show(math, Explorer.highlighter);
      }
      Explorer.liveRegion.Update(Explorer.walker.speech());
      Explorer.Highlight();
    },
    //
    // Deactivates the walker.
    //
    DeactivateWalker: function() {
      Explorer.liveRegion.Clear();
      Explorer.liveRegion.Hide();
      Explorer.Unhighlight();
      Explorer.currentHighlight = null;
      Explorer.walker.deactivate();
      Explorer.walker = null;
    },
    //
    // Highlights the focused nodes.
    //
    Highlight: function() {
      Explorer.Unhighlight();
      Explorer.highlighter.highlight(Explorer.walker.getFocus().getNodes());
    },
    //
    // Unhighlights the old nodes.
    //
    Unhighlight: function() {
      Explorer.highlighter.unhighlight();
    },
    //
    // Plays the earcon.
    //
    // Every time we make new Audio element, as some browsers do not allow to
    // play audio elements more than once (e.g., Safari).
    //
    PlayEarcon: function() {
      var audio = new Audio(Explorer.earconFile);
      audio.play();
    }
  };

  MathJax.Extension.Assistive.addDefaults();

  MathJax.Hub.Register.StartupHook('MathMenu Ready', function() {
    var ITEM = MathJax.Menu.ITEM;
    var accessibilityMenu =
          ITEM.SUBMENU(['Accessibility', 'Accessibilty'],
              ITEM.SUBMENU(['Walker', 'Walker'],
                  ITEM.RADIO(['dummy', 'No walker'], 'Assistive-walker'),
                  ITEM.RADIO(['syntactic', 'Syntax walker'], 'Assistive-walker'),
                  ITEM.RADIO(['semantic', 'Semantic walker'], 'Assistive-walker')
                          ),
              ITEM.SUBMENU(['Highlight', 'Highlight'],
                           ITEM.RADIO(['none', 'None'], 'Assistive-highlight'),
                           ITEM.RADIO(['hover', 'Hover'], 'Assistive-highlight'),
                           ITEM.RADIO(['flame','Flame'], 'Assistive-highlight', {action: Explorer.Reset})
                          ),
              ITEM.SUBMENU(['Background', 'Background'],
                           ITEM.RADIO(['blue','Blue'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['red','Red'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['green','Green'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['yellow','Yellow'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['cyan','Cyan'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['magenta','Magenta'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['white','White'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['black','Black'], 'Assistive-background', {action: Explorer.Reset})
                          ),
              ITEM.SUBMENU(['Foreground', 'Foreground'],
                           ITEM.RADIO(['black','Black'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['white','White'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['magenta','Magenta'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['cyan','Cyan'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['yellow','Yellow'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['green','Green'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['red','Red'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['blue','Blue'], 'Assistive-foreground', {action: Explorer.Reset})
                          ),
              ITEM.RULE(),
              ITEM.CHECKBOX(['Speech', 'Speech Output'], 'Assistive-speech'),
              ITEM.CHECKBOX(['Subtitles', 'Subtitles'], 'Assistive-subtitle')
                      );
    // Attaches the menu;
    var about = MathJax.Menu.menu.IndexOfId('About');
    if (about === null) {
      MathJax.Menu.menu.items.push(ITEM.RULE(), accessibilityMenu);
      return;
    }
    MathJax.Menu.menu.items.splice(about, 0, accessibilityMenu, ITEM.RULE());
  });

  MathJax.Hub.Register.MessageHook('New Math', ['Register', MathJax.Extension.Assistive.Explorer]);

  MathJax.Hub.Startup.signal.Post("Explorer Ready");
});

MathJax.Ajax.loadComplete("[RespEq]/Assistive-Explore.js");
<commit_msg>Adds menu item for speech generation.<commit_after>//
// Connection to SRE explorer.
//
MathJax.Hub.Register.StartupHook('Sre Ready', function() {
  var FALSE, KEY;
  var SETTINGS = MathJax.Hub.config.menuSettings;

  MathJax.Hub.Register.StartupHook('MathEvents Ready', function() {
    FALSE = MathJax.Extension.MathEvents.Event.False;
    KEY = MathJax.Extension.MathEvents.Event.KEY;
  });

  var Assistive = MathJax.Extension.Assistive = {
    version: "1.0",
    //
    // Default configurations.
    //
    default: {
      walker: 'dummy',
      highlight: 'none',
      background: 'blue',
      foreground: 'black',
      speech: true,
      subtitle: false,
      // Configuration option only.
      generateSpeech: true
    },
    prefix: 'Assistive-',

    addMenuOption: function(key, value) {
      SETTINGS[Assistive.prefix + key] = value;
    },

    addDefaults: function() {
      for (var key in Object.keys(Assistive.default)) {
        if (typeof(SETTINGS[Assistive.prefix + key]) === 'undefined') {
          Assistive.addMenuOption(key, Assistive.default[key]);
        }
      }
      Explorer.Reset();
    },

    setOption: function(key, value) {
      if (SETTINGS[Assistive.prefix + key] === value) return;
      Assistive.addMenuOption(key, value);
      Explorer.Reset();
    },

    getOption: function(key) {
      return SETTINGS[Assistive.prefix + key];
    }

  };

  var LiveRegion = MathJax.Object.Subclass({
    div: null,
    inner: null,
    Init: function() {
      this.div = LiveRegion.Create('assertive');
      this.inner = MathJax.HTML.addElement(this.div,'div');
    },
    //
    // Adds the speech div.
    //
    Add: function() {
      if (LiveRegion.announced) return;
      document.body.appendChild(this.div);
      LiveRegion.Announce();
    },
    //
    // Shows the live region as a subtitle of a node.
    //
    Show: function(node, highlighter) {
      this.div.classList.add('MJX_LiveRegion_Show');
      var rect = node.getBoundingClientRect();
      var bot = rect.bottom + 10 + window.pageYOffset;
      var left = rect.left + window.pageXOffset;
      this.div.style.top = bot + 'px';
      this.div.style.left = left + 'px';
      var color = highlighter.colorString();
      this.inner.style.backgroundColor = color.background;
      this.inner.style.color = color.foreground;
    },
    //
    // Takes the live region out of the page flow.
    //
    Hide: function(node) {
      this.div.classList.remove('MJX_LiveRegion_Show');
    },
    //
    // Clears the speech div.
    //
    Clear: function() {
      this.Update('');
      this.inner.style.top = '';
      this.inner.style.backgroundColor = '';
    },
    //
    // Speaks a string by poking it into the speech div.
    //
    Update: function(speech) {
      if (Assistive.getOption('speech')) {
        LiveRegion.Update(this.inner, speech);
      }
    }
  }, {
    ANNOUNCE: 'Navigatable Math in page. Explore with shift space.',
    announced: false,
    styles: {'.MJX_LiveRegion':
             {
               position: 'absolute', top:'0', height: '1px', width: '1px',
               padding: '1px', overflow: 'hidden'
             },
             '.MJX_LiveRegion_Show':
             {
               top:'0', position: 'absolute', width: 'auto', height: 'auto',
               padding: '0px 0px', opacity: 1, 'z-index': '202',
               left: 0, right: 0, 'margin': '0 auto',
               'background-color': 'white', 'box-shadow': '0px 10px 20px #888',
               border: '2px solid #CCCCCC'
             }},
    //
    // Creates a live region with a particular type.
    //
    Create: function(type) {
      var element = MathJax.HTML.Element(
        'div', {className: 'MJX_LiveRegion'});
      element.setAttribute('aria-live', type);
      return element;
    },
    //
    // Updates a live region's text content.
    //
    Update: MathJax.Hub.Browser.isPC ?
      function(div, speech) {
        div.textContent = '';
        setTimeout(function() {div.textContent = speech;}, 100);
      } : function(div, speech) {
        div.textContent = speech;
      },
    //
    // Speaks the announce string.
    //
    Announce: function() {
      if (LiveRegion.announced) return;
      LiveRegion.announced = true;
      MathJax.Ajax.Styles(LiveRegion.styles);
      var div = LiveRegion.Create('polite');
      document.body.appendChild(div);
      LiveRegion.Update(div, LiveRegion.ANNOUNCE);
      setTimeout(function() {document.body.removeChild(div);}, 1000);
    }
  });
  MathJax.Extension.Assistive.LiveRegion = LiveRegion;

  var Explorer = MathJax.Extension.Assistive.Explorer = {
    liveRegion: LiveRegion(),
    walker: null,
    highlighter: null,
    hoverer: null,
    flamer: null,
    speechDiv: null,
    enriched: {},
    earconFile: location.protocol +
      '//progressiveaccess.com/content/invalid_keypress' +
      (['Firefox', 'Chrome', 'Opera'].indexOf(MathJax.Hub.Browser.name) !== -1 ?
       '.ogg' : '.mp3'),
    focusEvent: MathJax.Hub.Browser.isFirefox ? 'blur' : 'focusout',
    //
    // Resets the explorer, rerunning methods not triggered by events.
    //
    Reset: function() {
      Explorer.FlameEnriched();
      sre.Engine.getInstance().speech = Assistive.getOption('generateSpeech');
    },
    //
    // Registers new Maths and adds a key event if it is enriched.
    //
    Register: function(msg) {
      var script = document.getElementById(msg[1]);
      if (script && script.id) {
        var jax = MathJax.Hub.getJaxFor(script.id);
        if (jax && jax.enriched) {
          Explorer.enriched[script.id] = script;
          Explorer.liveRegion.Add();
          Explorer.AddEvent(script);
        }
      }
    },
    //
    // Adds Aria attributes.
    //
    AddAria: function(math) {
      math.setAttribute('role', 'application');
      math.setAttribute('aria-label', 'Math');
    },
    //
    // Adds a key event to an enriched jax.
    //
    AddEvent: function(script) {
      var id = script.id + '-Frame';
      var sibling = script.previousSibling;
      if (sibling) {
        var math = sibling.id !== id ? sibling.firstElementChild : sibling;
        Explorer.AddAria(math);
        Explorer.AddMouseEvents(math);
        if (math.className === 'MathJax_MathML') {
          math = math.firstElementChild;
        }
        if (math) {
          math.onkeydown = Explorer.Keydown;
          math.addEventListener(
            Explorer.focusEvent,
            function(event) {
              if (Explorer.walker) Explorer.DeactivateWalker();
            });
          return;
        }
      }
      MathJax.Hub.Queue(['AddEvent', Explorer, script]);
    },
    //
    // Event execution on keydown. Subsumes the same method of MathEvents.
    //
    Keydown: function(event) {
      if (event.keyCode === KEY.ESCAPE) {
        if (!Explorer.walker) return;
        Explorer.DeactivateWalker();
        FALSE(event);
        return;
      }
      // If walker is active we redirect there.
      if (Explorer.walker && Explorer.walker.isActive()) {
        var move = Explorer.walker.move(event.keyCode);
        if (move === null) return;
        if (move) {
          Explorer.liveRegion.Update(Explorer.walker.speech());
          Explorer.Highlight();
        } else {
          Explorer.PlayEarcon();
        }
        FALSE(event);
        return;
      }
      var math = event.target;
      if (event.keyCode === KEY.SPACE) {
        if (event.shiftKey) {
          Explorer.ActivateWalker(math);
        } else {
          MathJax.Extension.MathEvents.Event.ContextMenu(event, math);
        }
        FALSE(event);
        return;
      }
    },
    GetHighlighter: function(alpha) {
      Explorer.highlighter = sre.HighlighterFactory.highlighter(
        {color: Assistive.getOption('background'), alpha: alpha},
        {color: Assistive.getOption('foreground'), alpha: 1},
        {renderer: MathJax.Hub.outputJax['jax/mml'][0].id,
         browser: MathJax.Hub.Browser.name}
      );
    },
    //
    // Adds mouse events to maction items in an enriched jax.
    //
    AddMouseEvents: function(node) {
      sre.HighlighterFactory.addEvents(
        node,
        {'mouseover': Explorer.MouseOver,
         'mouseout': Explorer.MouseOut},
        {renderer: MathJax.Hub.outputJax['jax/mml'][0].id,
         browser: MathJax.Hub.Browser.name}
      );
    },
    MouseOver: function(event) {
      if (Assistive.getOption('highlight') === 'none') return;
      if (Assistive.getOption('highlight') === 'hover') {
        var frame = event.currentTarget;
        Explorer.GetHighlighter(.1);
        Explorer.highlighter.highlight([frame]);
        Explorer.hoverer = true;
      }
      MathJax.Extension.MathEvents.Event.False(event);
    },
    MouseOut: function (event) {
      if (Explorer.hoverer) {
        Explorer.highlighter.unhighlight();
        Explorer.hoverer = false;
      }
      return MathJax.Extension.MathEvents.Event.False(event);
    },
    //
    // Activates Flaming
    //
    Flame: function(node) {
      Explorer.UnFlame(node);
      if (Assistive.getOption('highlight') === 'flame') {
        Explorer.GetHighlighter(.05);
        Explorer.highlighter.highlightAll(node);
        Explorer.flamer = true;
        return;
      }
    },
    UnFlame: function(node) {
      if (Explorer.flamer) {
        Explorer.highlighter.unhighlightAll();
        Explorer.flamer = null;
      }
    },
    FlameEnriched: function() {
      for (var key in Explorer.enriched) {
        Explorer.Flame(Explorer.enriched[key].previousSibling);
      }
    },
    //
    // Activates the walker.
    //
    Walkers: {
      'syntactic': sre.SyntaxWalker,
      'semantic': sre.SemanticWalker,
      'dummy': sre.DummyWalker
    },
    ActivateWalker: function(math) {
      var speechGenerator = new sre.DirectSpeechGenerator();
      var constructor = Explorer.Walkers[Assistive.getOption('walker')] ||
            Explorer.Walkers['dummy'];
      Explorer.walker = new constructor(math, speechGenerator);
      Explorer.GetHighlighter(.2);
      Explorer.walker.activate();
      if (Assistive.getOption('generateSpeech') &&
          Assistive.getOption('speech')) {
        if (Assistive.getOption('subtitle')) {
          Explorer.liveRegion.Show(math, Explorer.highlighter);
        }
        Explorer.liveRegion.Update(Explorer.walker.speech());
      }
      Explorer.Highlight();
    },
    //
    // Deactivates the walker.
    //
    DeactivateWalker: function() {
      Explorer.liveRegion.Clear();
      Explorer.liveRegion.Hide();
      Explorer.Unhighlight();
      Explorer.currentHighlight = null;
      Explorer.walker.deactivate();
      Explorer.walker = null;
    },
    //
    // Highlights the focused nodes.
    //
    Highlight: function() {
      Explorer.Unhighlight();
      Explorer.highlighter.highlight(Explorer.walker.getFocus().getNodes());
    },
    //
    // Unhighlights the old nodes.
    //
    Unhighlight: function() {
      Explorer.highlighter.unhighlight();
    },
    //
    // Plays the earcon.
    //
    // Every time we make new Audio element, as some browsers do not allow to
    // play audio elements more than once (e.g., Safari).
    //
    PlayEarcon: function() {
      var audio = new Audio(Explorer.earconFile);
      audio.play();
    },
    //
    // Regenerates speech.
    //
    Regenerate: function() {
      var speechItems = ['SpeechOutput', 'Subtitles'];
      speechItems.forEach(
        function(x) {
          var item = MathJax.Menu.menu.FindId('Accessibility', x);
          if (item) {
            item.disabled = !item.disabled;
          }});
      for (var jax in MathJax.Hub.getAllJax()) {
        if (jax.enriched) {
          // This does not yet work!
          MathJax.Extension.SemanticMathML(jax);
          MathJax.Hub.Reprocess(jax);
        }
      }
    }
  };

  MathJax.Extension.Assistive.addDefaults();

  MathJax.Hub.Register.StartupHook('MathMenu Ready', function() {
    var ITEM = MathJax.Menu.ITEM;
    var accessibilityMenu =
          ITEM.SUBMENU(['Accessibility', 'Accessibilty'],
              ITEM.SUBMENU(['Walker', 'Walker'],
                  ITEM.RADIO(['dummy', 'No walker'], 'Assistive-walker'),
                  ITEM.RADIO(['syntactic', 'Syntax walker'], 'Assistive-walker'),
                  ITEM.RADIO(['semantic', 'Semantic walker'], 'Assistive-walker')
                          ),
              ITEM.SUBMENU(['Highlight', 'Highlight'],
                           ITEM.RADIO(['none', 'None'], 'Assistive-highlight'),
                           ITEM.RADIO(['hover', 'Hover'], 'Assistive-highlight'),
                           ITEM.RADIO(['flame','Flame'], 'Assistive-highlight', {action: Explorer.Reset})
                          ),
              ITEM.SUBMENU(['Background', 'Background'],
                           ITEM.RADIO(['blue','Blue'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['red','Red'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['green','Green'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['yellow','Yellow'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['cyan','Cyan'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['magenta','Magenta'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['white','White'], 'Assistive-background', {action: Explorer.Reset}),
                           ITEM.RADIO(['black','Black'], 'Assistive-background', {action: Explorer.Reset})
                          ),
              ITEM.SUBMENU(['Foreground', 'Foreground'],
                           ITEM.RADIO(['black','Black'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['white','White'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['magenta','Magenta'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['cyan','Cyan'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['yellow','Yellow'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['green','Green'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['red','Red'], 'Assistive-foreground', {action: Explorer.Reset}),
                           ITEM.RADIO(['blue','Blue'], 'Assistive-foreground', {action: Explorer.Reset})
                          ),
                       ITEM.RULE(),
                       ITEM.CHECKBOX(['GenerateSpeech', 'Generate Speech'], 'Assistive-generateSpeech',
                                     {action: Explorer.Regenerate}),
                       ITEM.CHECKBOX(['SpeechOutput', 'Speech Output'], 'Assistive-speech',
                                     {disabled: !SETTINGS['Assistive-generateSpeech']}),
                       ITEM.CHECKBOX(['Subtitles', 'Subtitles'], 'Assistive-subtitle',
                                     {disabled: !SETTINGS['Assistive-generateSpeech']})
                      );
    // Attaches the menu;
    var about = MathJax.Menu.menu.IndexOfId('About');
    if (about === null) {
      MathJax.Menu.menu.items.push(ITEM.RULE(), accessibilityMenu);
      return;
    }
    MathJax.Menu.menu.items.splice(about, 0, accessibilityMenu, ITEM.RULE());
  });

  MathJax.Hub.Register.MessageHook('New Math', ['Register', MathJax.Extension.Assistive.Explorer]);

  MathJax.Hub.Startup.signal.Post("Explorer Ready");
});

MathJax.Ajax.loadComplete("[RespEq]/Assistive-Explore.js");
<|endoftext|>


<commit_before>//====================================
leaflet_helper.constructors = {};
leaflet_helper.constructors.identifyParser = function(result){
    //Use GeoJSON as the standard to try if no matches are found
    var parser = L.GeoJSON;
    var parserName = "Leaflet GeoJSON";

    if (result &&
        result.geometryType && result.geometryType == "esriGeometryPoint" &&
        result.features && result.features.length &&
        result.features[0] && result.features[0].attributes) {

        //Parser is CAP imagery Format
        parser = leaflet_helper.parsers.addDynamicCapimageData;
        parserName = "CAP GeoJSON";

    } else if (result &&
        result.stat == "ok" && result.photos && result.photos.page &&
        result.photos.photo && result.photos.perpage) {

        parser = leaflet_helper.parsers.flickrImages;
        parserName = "Flickr Photo Search";
    } else if (result &&
        result.meta && result.meta.code && result.meta.code==200 &&
        result.data && _.isArray(result.data)) {

        parser = leaflet_helper.parsers.instagramImages;
        parserName = "Instagram Search";
    } else if (result && result.summary=="YouTube") {

        parser = leaflet_helper.parsers.youTube;
        parserName = "YouTube Videos";
    }
    //ADD new parser detectors here

    return {parser: parser, parserName:parserName};
};

leaflet_helper.constructors.urlTemplater =function(url, map, layer_json){
    if (url.indexOf("{") < 0) return url;

    //Turn search strings into underscore templates to make parseable text (safer than an eval statement)
    _.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
    var _url_template = _.template(url);

    //Get map info that will be added into the url if needed
    var mapExtent=map.getBounds();
    var center = map.getCenter();
    var size = map.getSize();
    var mapState={
        zoom:map.getZoom(),
        lat:center.lat,
        lon:center.lng,
        lng:center.lng,
        n:mapExtent._northEast.lat,
        s:mapExtent._southWest.lat,
        e:mapExtent._northEast.lng,
        w:mapExtent._southWest.lng,
        width:size.x,
        height:size.y,
        radius:2
    };

    //Feed the generated variables into the template, and return the result
    layer_json = $.extend(layer_json,mapState);
    layer_json.tags = aoi_feature_edit.tags  || "disaster";

    var new_url;
    try {
        new_url = _url_template(layer_json);
    } catch (ex) {
        log.error ("Error in parsing a URL template: " + ex.message);
    }

    return new_url || url;
};

leaflet_helper.constructors.geojson_layer_count = 0;
leaflet_helper.constructors.geojson = function(layerConfig, map, useLayerInstead) {

    //Set up base icon
    var MapMarker = L.Icon.extend({
        options: {
            id: 0,
            shadowUrl: null,
            iconAnchor: new L.Point(7, 24),
            iconSize: new L.Point(15, 24),
            repeatMode: true,
            text: 'Social Media Pointer',
            iconUrl: aoi_feature_edit.available_icons[0]
        }
    });

    if (useLayerInstead && (useLayerInstead.geojson_layer_count !== undefined)) {
        layerConfig.geojson_layer_count = useLayerInstead.geojson_layer_count;
    } else {
        leaflet_helper.constructors.geojson_layer_count++;
        layerConfig.geojson_layer_count = leaflet_helper.constructors.geojson_layer_count;
    }

    function iconCallback(feature, latlng){
        var iconUrl = "";
        var iconX = 15;
        var iconY = 24;

        if (layerConfig && layerConfig.layerParams && (layerConfig.layerParams.icon || layerConfig.layerParams.iconUrl)) {
            iconUrl = layerConfig.layerParams.icon || layerConfig.layerParams.iconUrl;
            if (layerConfig.layerParams.iconX && layerConfig.layerParams.iconY) {
                iconX = layerConfig.layerParams.iconX;
                iconY = layerConfig.layerParams.iconY;
            }
        } else {
            var layerNum = layerConfig.geojson_layer_count % aoi_feature_edit.available_icons.length;
            iconUrl = aoi_feature_edit.available_icons[layerNum];
        }
        var icon = new MapMarker({
            iconUrl: iconUrl,
            iconSize: new L.Point(iconX, iconY),
            text: layerConfig.name
        });
        return L.marker(latlng, {icon: icon});
    }

    var outputLayer = useLayerInstead || new L.geoJson(undefined,{
        onEachFeature: leaflet_helper.parsers.standard_onEachFeature,
        pointToLayer: iconCallback
    });
    if (outputLayer.geojson_layer_count == undefined) {
        outputLayer.geojson_layer_count = layerConfig.geojson_layer_count;
    }

    var url = leaflet_helper.constructors.urlTemplater(layerConfig.url, map, layerConfig.layerParams);
    var proxiedURL = leaflet_helper.proxify(url);

    $.ajax({
        type: 'GET',
        url: proxiedURL,
        dataType: layerConfig.format || 'json',
        success: function(data){
            leaflet_helper.constructors.geojson_success(data, proxiedURL, map, outputLayer);
        },
        error: leaflet_helper.constructors.geojson_error
    });

    return outputLayer;
};
leaflet_helper.constructors.geojson_error = function (resultobj){
    log.error ("A JSON layer was requested, but no valid response was received from the server, result:", resultobj);
};
leaflet_helper.constructors.geojson_success = function (data, proxiedURL, map, outputLayer) {
    var result;
    if (typeof data=="object") {
        result = data;
    } else {
        try {
            result = JSON.parse(data);
        } catch (ex){
            log.error("Error parsing JSON returned from server");
            return;
        }
    }

    if (result && result.error && result.error.message){
        log.error("JSON layer error, message was: " + result.error.message + " url: "+ proxiedURL);
    } else {
        var parserInfo = leaflet_helper.constructors.identifyParser(result);
        if (parserInfo && parserInfo.parser) {
            parserInfo.parser(result, map, outputLayer);

            var features = "NONE";
            if (result && result.features && result.features.length) features = result.features.length;
            log.info("JSON layer was created from : "+ proxiedURL+ " - features:"+ features+ " - parser type: ", parserInfo.parserName);
        }
    }

    if (outputLayer) {
        leaflet_helper.update_tree_title(outputLayer);
    }
    if (outputLayer && !outputLayer.options) {
        outputLayer.options = {opacity:1};
    }

    return outputLayer;
};

//====================================
leaflet_helper.parsers = {};
leaflet_helper.parsers.standard_onEachFeature = function (feature, layer) {
    if (feature.properties && feature.properties.popupContent) {
        layer.bindPopup(feature.properties.popupContent);
    }
};
leaflet_helper.addLinksToPopup = function (layerName,id,useMove,useHide,useDrop) {

    var spanLink = ""+layerName+"";
    spanLink += ""+id+"";
    var output = "";
    if (useMove) {
        output += "
Click on a feature to link this to it"+spanLink+""
    }
    if (useHide) {
        output += "
Hide this Item"+spanLink+"";
    }
    if (useDrop) {
        output += "
"+spanLink+"";
    }

    return output;

};
leaflet_helper.parsers.addDynamicCapimageData = function (result, map, outputLayer) {
    //TODO: Handle de-dupes of all features returned

    var jsonObjects = [];
    $(result.features).each(function () {
        var feature = $(this)[0];
        var id = feature.attributes.ID;

        var popupContent = "
CAP Item #"+id+"
";
        popupContent += leaflet_helper.addLinksToPopup(outputLayer.name, id, true, false);

        var json = {
            type: "Feature",
            properties: {
                id: id,
                source: 'CAP Imagery',
                name: id + " - " + feature.attributes.DaysOld + " days old",
                image: feature.attributes.ImageURL,
                thumbnail: feature.attributes.ThumbnailURL,
                popupContent: popupContent
            },
            geometry: {
                type: "Point",
                coordinates: [feature.geometry.x, feature.geometry.y]
            }
        };
        jsonObjects.push(json);
    });
    outputLayer.addData(jsonObjects);
    if (result.features && result.features.length) {
        log.info("A FEMA CAP layer was updated adding "+ result.features.length+ " features");
    } else {
        log.info("A FEMA CAP request was returned, but no features were found");
    }

    return outputLayer;
};
leaflet_helper.parsers.instagramImages = function (result, map, outputLayer) {
    var jsonObjects = [];
    var photos = result.data;

    if (!outputLayer.options) { outputLayer.options = {}}
    if (!outputLayer.options.items) { outputLayer.options.items = []}

    _.each(photos,function(image){
        var itemFound = false;
        _.each(outputLayer.options.items,function(item){
           if (item.id == image.id) itemFound = true;
        });
        if (!itemFound) {
            outputLayer.options.items.push(image);

            var imageURL = image.link;
            var thumbnailURL = image.images.thumbnail.url;

            var id = image.id;
            var title = "Instagram: "+id;
            var location = image.location;
            var tags = image.tags.join(", ");

            var popupContent = "
Instagram Picture
";
            popupContent += "Posted by: "+image.user.username+"
";
            if (tags) popupContent += "Tags: "+tags+"
";
            popupContent += "";
            popupContent += leaflet_helper.addLinksToPopup(outputLayer.name, id, true, true);

            var json = {
                type: "Feature",
                properties: {
                    name: title,
                    id: id,
                    source: 'Instagram',
                    image: imageURL,
                    thumbnail: thumbnailURL,
                    popupContent: popupContent,
                    tags: aoi_feature_edit.tags || "Disaster"
                },
                geometry: {
                    type: "Point",
                    coordinates: [location.longitude, location.latitude]
                }
            };
            jsonObjects.push(json);
        }
    });

    outputLayer.addData(jsonObjects);
    if (photos && photos.length) {
        log.info("An Instagram Social Photos layer was appended with "+ photos.length+" features");
    } else {
        log.info("An Instagram Social Photos response was returned, but with no features.");
    }

    return outputLayer;
};

leaflet_helper.update_tree_title = function(outputLayer) {
    var treeNodes = leaflet_layer_control.$tree.fancytree('getTree').rootNode;

    var layerName = outputLayer.name;
    var treeItem = null;
    _.each(treeNodes.children,function(treeCats){
        _.each(treeCats.children,function(treeNode){
            var treeNodeTitle = treeNode.title;

            if (_.str.startsWith(treeNodeTitle,layerName)){
                treeItem = treeNode;
            }
        });
    });

    if (treeItem){
        if (outputLayer._layers) {
            var numFeatures = _.toArray(outputLayer._layers).length;
            treeItem.setTitle(layerName + " ("+numFeatures+")");
        }
    }

};

leaflet_helper.parsers.flickrImages = function (result, map, outputLayer) {
    var jsonObjects = [];
    var photos = result.photos;

    if (!outputLayer.options) { outputLayer.options = {}};
    if (!outputLayer.options.items) { outputLayer.options.items = []};

    $(photos.photo).each(function () {
        var feature = $(this)[0];
        var id=feature.id;

        var itemFound = false;
        _.each(outputLayer.options.items,function(item){
           if (item.id == id) itemFound = true;
        });
        if (!itemFound) {
            outputLayer.options.items.push(feature);

            var title=feature.title || "Flickr Photo";
            var secret=feature.secret;
            var id=secret;
            var server=feature.server;
            var farm=feature.farm;
            var owner=feature.owner;
            var base=id+'_'+secret+'_s.jpg';
            var major=id+'_'+secret+'_z.jpg';
            var imageURL= 'http://farm'+farm+'.static.flickr.com/'+server+'/'+major;
            var thumbnailURL='http://farm'+farm+'.static.flickr.com/'+server+'/'+base;

            var center = map.getCenter();
            var popupContent = "
Flickr Picture
";
            popupContent += "Posted by: "+owner+"
";
            if (aoi_feature_edit.tags) popupContent += "Tags: "+aoi_feature_edit.tags+"
";
            popupContent += "";
            popupContent += leaflet_helper.addLinksToPopup(outputLayer.name, id, true, true);

            var json = {
                type: "Feature",
                properties: {
                    id: id,
                    name: title,
                    source: 'Flickr',
                    image: imageURL,
                    thumbnail: thumbnailURL,
                    popupContent: popupContent,
                    tags: aoi_feature_edit.tags || "Disaster"
                },
                geometry: {
                    type: "Point",
                    coordinates: [center.lng, center.lat] //TODO: Make this either random, or show in a tray to dnd onto map
                }
            };
            jsonObjects.push(json);
        }
    });

    outputLayer.addData(jsonObjects);
    if (photos.photo && photos.photo.length) {
        log.info("A Flickr Photos layer was loaded, with "+ photos.photo.length+" features");
    } else {
        log.info("An Flickr Photos response was returned, but with no features.");
    }

    return outputLayer;
};
leaflet_helper.parsers.youTube = function (result, map, outputLayer){
    //TODO: Parsing YouTube requires OAuth2, need a server component to do the handshake
    log.info("YouTube changed their API, v3 is not yet supported.")
};<commit_msg>Show GeoJSON layers as checked in layer list<commit_after>//====================================
leaflet_helper.constructors = {};
leaflet_helper.constructors.identifyParser = function(result){
    //Use GeoJSON as the standard to try if no matches are found
    var parser = L.GeoJSON;
    var parserName = "Leaflet GeoJSON";

    if (result &&
        result.geometryType && result.geometryType == "esriGeometryPoint" &&
        result.features && result.features.length &&
        result.features[0] && result.features[0].attributes) {

        //Parser is CAP imagery Format
        parser = leaflet_helper.parsers.addDynamicCapimageData;
        parserName = "CAP GeoJSON";

    } else if (result &&
        result.stat == "ok" && result.photos && result.photos.page &&
        result.photos.photo && result.photos.perpage) {

        parser = leaflet_helper.parsers.flickrImages;
        parserName = "Flickr Photo Search";
    } else if (result &&
        result.meta && result.meta.code && result.meta.code==200 &&
        result.data && _.isArray(result.data)) {

        parser = leaflet_helper.parsers.instagramImages;
        parserName = "Instagram Search";
    } else if (result && result.summary=="YouTube") {

        parser = leaflet_helper.parsers.youTube;
        parserName = "YouTube Videos";
    }
    //ADD new parser detectors here

    return {parser: parser, parserName:parserName};
};

leaflet_helper.constructors.urlTemplater =function(url, map, layer_json){
    if (url.indexOf("{") < 0) return url;

    //Turn search strings into underscore templates to make parseable text (safer than an eval statement)
    _.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
    var _url_template = _.template(url);

    //Get map info that will be added into the url if needed
    var mapExtent=map.getBounds();
    var center = map.getCenter();
    var size = map.getSize();
    var mapState={
        zoom:map.getZoom(),
        lat:center.lat,
        lon:center.lng,
        lng:center.lng,
        n:mapExtent._northEast.lat,
        s:mapExtent._southWest.lat,
        e:mapExtent._northEast.lng,
        w:mapExtent._southWest.lng,
        width:size.x,
        height:size.y,
        radius:2
    };

    //Feed the generated variables into the template, and return the result
    layer_json = $.extend(layer_json,mapState);
    layer_json.tags = aoi_feature_edit.tags  || "disaster";

    var new_url;
    try {
        new_url = _url_template(layer_json);
    } catch (ex) {
        log.error ("Error in parsing a URL template: " + ex.message);
    }

    return new_url || url;
};

leaflet_helper.constructors.geojson_layer_count = 0;
leaflet_helper.constructors.geojson = function(layerConfig, map, useLayerInstead) {

    //Set up base icon
    var MapMarker = L.Icon.extend({
        options: {
            id: 0,
            shadowUrl: null,
            iconAnchor: new L.Point(7, 24),
            iconSize: new L.Point(15, 24),
            repeatMode: true,
            text: 'Social Media Pointer',
            iconUrl: aoi_feature_edit.available_icons[0]
        }
    });

    if (useLayerInstead && (useLayerInstead.geojson_layer_count !== undefined)) {
        layerConfig.geojson_layer_count = useLayerInstead.geojson_layer_count;
    } else {
        leaflet_helper.constructors.geojson_layer_count++;
        layerConfig.geojson_layer_count = leaflet_helper.constructors.geojson_layer_count;
    }

    function iconCallback(feature, latlng){
        var iconUrl = "";
        var iconX = 15;
        var iconY = 24;

        if (layerConfig && layerConfig.layerParams && (layerConfig.layerParams.icon || layerConfig.layerParams.iconUrl)) {
            iconUrl = layerConfig.layerParams.icon || layerConfig.layerParams.iconUrl;
            if (layerConfig.layerParams.iconX && layerConfig.layerParams.iconY) {
                iconX = layerConfig.layerParams.iconX;
                iconY = layerConfig.layerParams.iconY;
            }
        } else {
            var layerNum = layerConfig.geojson_layer_count % aoi_feature_edit.available_icons.length;
            iconUrl = aoi_feature_edit.available_icons[layerNum];
        }
        var icon = new MapMarker({
            iconUrl: iconUrl,
            iconSize: new L.Point(iconX, iconY),
            text: layerConfig.name
        });
        return L.marker(latlng, {icon: icon});
    }

    var outputLayer = useLayerInstead || new L.geoJson(undefined,{
        onEachFeature: leaflet_helper.parsers.standard_onEachFeature,
        pointToLayer: iconCallback
    });
    if (outputLayer.geojson_layer_count == undefined) {
        outputLayer.geojson_layer_count = layerConfig.geojson_layer_count;
    }

    if (outputLayer && !outputLayer.options) {
        outputLayer.options = {};
    }
    outputLayer.options.opacity = 1;

    var url = leaflet_helper.constructors.urlTemplater(layerConfig.url, map, layerConfig.layerParams);
    var proxiedURL = leaflet_helper.proxify(url);

    $.ajax({
        type: 'GET',
        url: proxiedURL,
        dataType: layerConfig.format || 'json',
        success: function(data){
            leaflet_helper.constructors.geojson_success(data, proxiedURL, map, outputLayer);
        },
        error: leaflet_helper.constructors.geojson_error
    });

    return outputLayer;
};
leaflet_helper.constructors.geojson_error = function (resultobj){
    log.error ("A JSON layer was requested, but no valid response was received from the server, result:", resultobj);
};
leaflet_helper.constructors.geojson_success = function (data, proxiedURL, map, outputLayer) {
    var result;
    if (typeof data=="object") {
        result = data;
    } else {
        try {
            result = JSON.parse(data);
        } catch (ex){
            log.error("Error parsing JSON returned from server");
            return;
        }
    }

    if (result && result.error && result.error.message){
        log.error("JSON layer error, message was: " + result.error.message + " url: "+ proxiedURL);
    } else {
        var parserInfo = leaflet_helper.constructors.identifyParser(result);
        if (parserInfo && parserInfo.parser) {
            parserInfo.parser(result, map, outputLayer);

            var features = "NONE";
            if (result && result.features && result.features.length) features = result.features.length;
            log.info("JSON layer was created from : "+ proxiedURL+ " - features:"+ features+ " - parser type: ", parserInfo.parserName);
        }
    }

    if (outputLayer) {
        leaflet_helper.update_tree_title(outputLayer);
    }
    if (outputLayer && !outputLayer.options) {
        outputLayer.options = {};
    }
    outputLayer.options.opacity = 1;

    return outputLayer;
};

//====================================
leaflet_helper.parsers = {};
leaflet_helper.parsers.standard_onEachFeature = function (feature, layer) {
    if (feature.properties && feature.properties.popupContent) {
        layer.bindPopup(feature.properties.popupContent);
    }
};
leaflet_helper.addLinksToPopup = function (layerName,id,useMove,useHide,useDrop) {

    var spanLink = ""+layerName+"";
    spanLink += ""+id+"";
    var output = "";
    if (useMove) {
        output += "
Click on a feature to link this to it"+spanLink+""
    }
    if (useHide) {
        output += "
Hide this Item"+spanLink+"";
    }
    if (useDrop) {
        output += "
"+spanLink+"";
    }

    return output;

};
leaflet_helper.parsers.addDynamicCapimageData = function (result, map, outputLayer) {
    //TODO: Handle de-dupes of all features returned

    var jsonObjects = [];
    $(result.features).each(function () {
        var feature = $(this)[0];
        var id = feature.attributes.ID;

        var popupContent = "
CAP Item #"+id+"
";
        popupContent += leaflet_helper.addLinksToPopup(outputLayer.name, id, true, false);

        var json = {
            type: "Feature",
            properties: {
                id: id,
                source: 'CAP Imagery',
                name: id + " - " + feature.attributes.DaysOld + " days old",
                image: feature.attributes.ImageURL,
                thumbnail: feature.attributes.ThumbnailURL,
                popupContent: popupContent
            },
            geometry: {
                type: "Point",
                coordinates: [feature.geometry.x, feature.geometry.y]
            }
        };
        jsonObjects.push(json);
    });
    outputLayer.addData(jsonObjects);
    if (result.features && result.features.length) {
        log.info("A FEMA CAP layer was updated adding "+ result.features.length+ " features");
    } else {
        log.info("A FEMA CAP request was returned, but no features were found");
    }

    return outputLayer;
};
leaflet_helper.parsers.instagramImages = function (result, map, outputLayer) {
    var jsonObjects = [];
    var photos = result.data;

    if (!outputLayer.options) { outputLayer.options = {}}
    if (!outputLayer.options.items) { outputLayer.options.items = []}

    _.each(photos,function(image){
        var itemFound = false;
        _.each(outputLayer.options.items,function(item){
           if (item.id == image.id) itemFound = true;
        });
        if (!itemFound) {
            outputLayer.options.items.push(image);

            var imageURL = image.link;
            var thumbnailURL = image.images.thumbnail.url;

            var id = image.id;
            var title = "Instagram: "+id;
            var location = image.location;
            var tags = image.tags.join(", ");

            var popupContent = "
Instagram Picture
";
            popupContent += "Posted by: "+image.user.username+"
";
            if (tags) popupContent += "Tags: "+tags+"
";
            popupContent += "";
            popupContent += leaflet_helper.addLinksToPopup(outputLayer.name, id, true, true);

            var json = {
                type: "Feature",
                properties: {
                    name: title,
                    id: id,
                    source: 'Instagram',
                    image: imageURL,
                    thumbnail: thumbnailURL,
                    popupContent: popupContent,
                    tags: aoi_feature_edit.tags || "Disaster"
                },
                geometry: {
                    type: "Point",
                    coordinates: [location.longitude, location.latitude]
                }
            };
            jsonObjects.push(json);
        }
    });

    outputLayer.addData(jsonObjects);
    if (photos && photos.length) {
        log.info("An Instagram Social Photos layer was appended with "+ photos.length+" features");
    } else {
        log.info("An Instagram Social Photos response was returned, but with no features.");
    }

    return outputLayer;
};

leaflet_helper.update_tree_title = function(outputLayer) {
    var treeNodes = leaflet_layer_control.$tree.fancytree('getTree').rootNode;

    var layerName = outputLayer.name;
    var treeItem = null;
    _.each(treeNodes.children,function(treeCats){
        _.each(treeCats.children,function(treeNode){
            var treeNodeTitle = treeNode.title;

            if (_.str.startsWith(treeNodeTitle,layerName)){
                treeItem = treeNode;
            }
        });
    });

    if (treeItem){
        if (outputLayer._layers) {
            var numFeatures = _.toArray(outputLayer._layers).length;
            treeItem.setTitle(layerName + " ("+numFeatures+")");
        }
    }

};

leaflet_helper.parsers.flickrImages = function (result, map, outputLayer) {
    var jsonObjects = [];
    var photos = result.photos;

    if (!outputLayer.options) { outputLayer.options = {}};
    if (!outputLayer.options.items) { outputLayer.options.items = []};

    $(photos.photo).each(function () {
        var feature = $(this)[0];
        var id=feature.id;

        var itemFound = false;
        _.each(outputLayer.options.items,function(item){
           if (item.id == id) itemFound = true;
        });
        if (!itemFound) {
            outputLayer.options.items.push(feature);

            var title=feature.title || "Flickr Photo";
            var secret=feature.secret;
            var id=secret;
            var server=feature.server;
            var farm=feature.farm;
            var owner=feature.owner;
            var base=id+'_'+secret+'_s.jpg';
            var major=id+'_'+secret+'_z.jpg';
            var imageURL= 'http://farm'+farm+'.static.flickr.com/'+server+'/'+major;
            var thumbnailURL='http://farm'+farm+'.static.flickr.com/'+server+'/'+base;

            var center = map.getCenter();
            var popupContent = "
Flickr Picture
";
            popupContent += "Posted by: "+owner+"
";
            if (aoi_feature_edit.tags) popupContent += "Tags: "+aoi_feature_edit.tags+"
";
            popupContent += "";
            popupContent += leaflet_helper.addLinksToPopup(outputLayer.name, id, true, true);

            var json = {
                type: "Feature",
                properties: {
                    id: id,
                    name: title,
                    source: 'Flickr',
                    image: imageURL,
                    thumbnail: thumbnailURL,
                    popupContent: popupContent,
                    tags: aoi_feature_edit.tags || "Disaster"
                },
                geometry: {
                    type: "Point",
                    coordinates: [center.lng, center.lat] //TODO: Make this either random, or show in a tray to dnd onto map
                }
            };
            jsonObjects.push(json);
        }
    });

    outputLayer.addData(jsonObjects);
    if (photos.photo && photos.photo.length) {
        log.info("A Flickr Photos layer was loaded, with "+ photos.photo.length+" features");
    } else {
        log.info("An Flickr Photos response was returned, but with no features.");
    }

    return outputLayer;
};
leaflet_helper.parsers.youTube = function (result, map, outputLayer){
    //TODO: Parsing YouTube requires OAuth2, need a server component to do the handshake
    log.info("YouTube changed their API, v3 is not yet supported.")
};<|endoftext|>


<commit_before>/**
 * SequenceD-Views Module extension.
 *
 * Definition of Backbone Views required for Sequence Diagrams.
 */
var SequenceD = (function (sequenced) {

    var views = sequenced.Views = sequenced.Views || {};

    /**
     * Base View class to extend from.
     *
     * @type {*|void}
     */
    var BaseView = Backbone.View.extend({

        /**
         * Initialize center point for the view.
         *
         * @param options
         */
        initialize: function (options) {
            _.extend(this, _.pick(options, "centerPoint", "paperID"));
        },

        /**
         * Default drag move handler for views which will
         * drag the element along with the mouse pointer.
         *
         * @param dx
         * @param dy
         */
        dragMove: function (dx, dy) {
        },

        /**
         * Default drag start handler which captures original position
         * of the view.
         *
         */
        dragStart: function () {
        },

        /**
         * Default empty drag stop handler. Extending views need to override
         * for custom behaviour.
         */
        dragStop: function () {
        }
    });

    /**
     * LifeLine Backbone View
     */
    var LifeLineView = BaseView.extend({

        // fetch default class from prefs
        className: sequenced.prefs.lifeline.class,

        /**
         * Fetch and init LifeLine title.
         * @param options
         */
        initialize: function (options) {
            _.extend(this, _.pick(options, "title"));
            // call super
            BaseView.prototype.initialize(options);
        },

        render: function (paperID) {
            // set paper
            _.extend(this, {paperID:this.paperID || paperID || sequenced.paper.selector});

            // wrap d3 drawing apis
            var d3Draw = d3_draw.wrap(d3.select(this.paperID));

            // fetch prefs for LifeLines
            var prefs = sequenced.prefs.lifeline;

            var g = d3Draw.lifeLine(this.centerPoint, this.title, prefs);

            this.el = g;
            return g;
        }

    });

    views.BaseView = BaseView;
    views.LifeLineView = LifeLineView;

    return sequenced;

}(SequenceD || {}));
<commit_msg>Refatoring var names<commit_after>/**
 * SequenceD-Views Module extension.
 *
 * Definition of Backbone Views required for Sequence Diagrams.
 */
var SequenceD = (function (sequenced) {

    var views = sequenced.Views = sequenced.Views || {};

    /**
     * Base View class to extend from.
     *
     * @type {*|void}
     */
    var BaseView = Backbone.View.extend({

        /**
         * Initialize center point for the view.
         *
         * @param options
         */
        initialize: function (options) {
            _.extend(this, _.pick(options, "centerPoint", "paperID"));
        },

        /**
         * Default drag move handler for views which will
         * drag the element along with the mouse pointer.
         *
         * @param dx
         * @param dy
         */
        dragMove: function (dx, dy) {
        },

        /**
         * Default drag start handler which captures original position
         * of the view.
         *
         */
        dragStart: function () {
        },

        /**
         * Default empty drag stop handler. Extending views need to override
         * for custom behaviour.
         */
        dragStop: function () {
        }
    });

    /**
     * LifeLine Backbone View
     */
    var LifeLineView = BaseView.extend({

        // fetch default class from prefs
        className: sequenced.prefs.lifeline.class,

        /**
         * Fetch and init LifeLine title.
         * @param options
         */
        initialize: function (options) {
            _.extend(this, _.pick(options, "title"));
            // call super
            BaseView.prototype.initialize(options);
        },

        render: function (paperID) {
            // set paper
            _.extend(this, {paperID:this.paperID || paperID || sequenced.paper.selector});

            // wrap d3 with custom drawing apis
            var d3Draw = d3_draw.wrap(d3.select(this.paperID));

            // fetch global prefs for LifeLines
            var prefs = sequenced.prefs.lifeline;

            var group = d3Draw.lifeLine(this.centerPoint, this.title, prefs);

            this.el = group;
            return group;
        }

    });

    views.BaseView = BaseView;
    views.LifeLineView = LifeLineView;

    return sequenced;

}(SequenceD || {}));
<|endoftext|>


<commit_before>(function (Q, $, window, undefined) {

/**
 * @module Streams-tools
 */

/**
 * Renders a preview for a Streams/file stream
 * @class Streams file preview
 * @constructor
 * @param {Object} [options] options to pass to this tool, besides the ones passed to preview
 *   @param {String} [options.windowName='file'] the name of the window in which to open files. Set it to blank to open in the current window.
 *   @param {Object} [options.inplace] Any options to pass to the Q/inplace tool -- see its options.
 *   @uses Q inplace
 *   @param {Object} [options.templates] Under the keys "views", "edit" and "create" you can override options for Q.Template.render .
 *     @param {Object} [options.templates.view]
 *       @param {String} [options.templates.view.name='Streams/file/preview/view']
 *       @param {Object} [options.templates.view.fields]
 *         @param {String} [options.templates.view.fields.alt]
 *         @param {String} [options.templates.view.fields.titleClass]
 *         @param {String} [options.templates.view.fields.titleTag]
 *     @param {Object} [options.templates.edit]
 *       @param {String} [options.templates.edit.name='Streams/file/preview/edit']
 *       @param {Object} [options.templates.edit.fields]
 *         @param {String} [options.templates.edit.fields.alt]
 *         @param {String} [options.templates.edit.fields.titleClass]
 *         @param {String} [options.templates.edit.fields.titleTag]
 * @param {Object} [preview] this is the preview tool that's been instantiated
 */
Q.Tool.define("Streams/file/preview", "Streams/preview",
function _Streams_file_preview(options, preview) {
	var tool = this;
	tool.preview = preview;
	var $te = $(tool.element);
	var state = this.state;
	var ps = preview.state;
	var src = Q.getObject('templates.create.fields.src', state);
	if (src) {
		state.templates.create.fields.src = Q.url(src);
	}
	ps.templates.create = Q.extend(
		ps.templates.create,
		10,
		state.templates.create
	);
	if (ps.creatable) {
		ps.creatable.streamType = ps.creatable.streamType || 'Streams/file';
		ps.creatable.title = ps.creatable.title || 'Upload File';
		if (ps.creatable.clickable) {
			ps.creatable.clickable.preventDefault = false;
		}
	}
	ps.onRefresh.add(tool.refresh.bind(tool), tool);
	ps.onComposer.add(tool.composer.bind(tool), tool);
	preview.loading = function () {
		var $form = $te.find('form').detach();
		var $img = $te.find('img.Streams_preview_add').detach();
		$te.empty().append($img, $form).addClass('Q_uploading');
	};

	// edit action
	if (ps.editable) {
		Q.setObject(["actions", "actions", "edit"], true, ps);
	}
	if (Q.getObject(["actions", "actions", "edit"], ps)) {
		ps.actions.actions.edit = tool.selectAndUploadFile.bind(this);
	}
},

{
	inplace: {},
	windowName: 'file',
	templates: {
		view: {
			name: 'Streams/file/preview/view',
			fields: { alt: 'icon', titleClass: '', titleTag: 'h3' }
		},
		edit: {
			name: 'Streams/file/preview/edit',
			fields: { alt: 'icon', titleClass: '', titleTag: 'h3' }
		},
		create: {
			name: 'Streams/file/preview/create',
			fields: {
				src: '{{Q}}/img/actions/upload.png'
			}
		}
	}
},

{
	refresh: function (stream, callback) {
		var tool = this;
		var state = tool.state;
		var ps = tool.preview.state;
		var $te = $(tool.element);
		// set up a pipe to know when the icon has loaded
		var p = Q.pipe(['inplace', 'icon'], function () {
			Q.handle(callback, tool);
		});
		$te.removeClass('Q_uploading');
		// set up the inplace options
		var inplace = null;
		if (state.inplace) {
			var inplaceOptions = Q.extend({
				publisherId: ps.publisherId,
				streamName: ps.streamName,
				field: 'title',
				inplaceType: 'text'
			}, state.inplace);
			var se = ps.editable;
			if (!se || (se !== true && se.indexOf('title') < 0)) {
				inplaceOptions.editable = false;
			} else {
				$te.addClass('Streams_editable_title');
			}
			inplace = tool.setUpElementHTML('div', 'Streams/inplace', inplaceOptions);
		}
		// render a template
		var f = state.template && state.template.fields;
		var size = stream.getAttribute('Q.file.size') || stream.getAttribute('file.size');
		var fields = Q.extend({}, state.templates.edit.fields, f, {
			alt: stream.fields.title,
			title: stream.fields.title,
			inplace: inplace,
			size: _formatSize(size),
			streamName: ps.streamName,
			streamType: stream.fields.type
		});
		var tpl = (ps.editable !== false && stream.testWriteLevel('suggest'))
			? 'edit'
			: 'view';
		Q.Template.render(
			'Streams/file/preview/'+tpl,
			fields,
			function (err, html) {
				if (err) return;
				tool.element.innerHTML = html;
				Q.activate(tool, function () {
					// load the icon
					var jq = tool.$('img.Streams_preview_icon');
					tool.preview.icon(jq[0], p.fill('icon'));
					var $pc = tool.$('.Streams_preview_contents');
					$pc.width(0).width($pc[0].remainingWidth());
					Q.onLayout(tool.element).set(function () {
						var $pc = tool.$('.Streams_preview_contents');
						$pc.width($pc[0].remainingWidth());
					}, tool);
					var inplace = tool.child('Streams_inplace');
					if (!inplace) {
						return p.fill('inplace').apply(this, arguments);
					}
					inplace.state.onLoad.add(function () {
						p.fill('inplace').apply(this, arguments);
					});
				});
				$(tool.element).on(Q.Pointer.click, function () {
					var url = stream.fileUrl();
					if (!url) {
						return tool.selectAndUploadFile();
					}
					if (state.windowName) {
						window.open(url, state.windowName);
					} else {
						window.location = url;
					}
				});
			},
			state.templates[tpl]
		);
	},
	composer: function () {
		var tool = this;
		var state = tool.state;
		tool.$('.Streams_file_input')
		.on(Q.Pointer.fastclick, function (event) {
			event.stopPropagation();
		}).change(function (event) {
			if (!this.value) {
				return; // it was canceled
			}
			var $this = $(this);
			var form = $this.closest('form').get(0);
			tool.preview.state.creatable.options.form = form;
			tool.preview.state.creatable.options.resultFunction = 'result';
			tool.preview.create(event, function (data) {
				var fem = Q.firstErrorMessage(data && data.error);
				if (fem) {
					Q.alert(fem);
				}
			});
			form.reset();
		});
	},
	selectAndUploadFile: function () {
		var tool = this;
		var state = tool.state;
		var $te = $(tool.element);
		tool.$('.Streams_file_input')
		.off('click')
		.on("click", function (event) {
			event.stopPropagation();
		}).off('change')
		.on("change", function (event) {
			if (!this.value) {
				return; // it was canceled
			}

			$te.addClass("Q_working");

			var form = $(this).closest('form')[0];

			// send request to replace file
			Q.req("Streams/file", function(err, response){
				$te.removeClass("Q_working");
				var msg = Q.firstErrorMessage(err, response && response.errors);
				if (msg) {
					return Q.alert(msg);
				}

				// refresh tool
				var ps = tool.preview.state;
				Q.Streams.Stream.refresh(
					ps.publisherId, ps.streamName, function () {
						tool.preview.preview();
					}
				);

			}, {
				method: 'POST',
				form: form
			});
		}).trigger("click");

		return false;
	}
}

);

function _formatSize(bytes)
{
	if (isNaN(bytes)) return '';
	if (bytes >= Math.pow(2, 30)) {
		return Math.ceil(bytes / Math.pow(2, 30)) + ' GB';
	} else if (bytes >= Math.pow(2, 20)) {
		return Math.ceil(bytes / Math.pow(2, 20)) + ' MB';
	} else if (bytes >= Math.pow(2, 10)) {
		return Math.ceil(bytes / Math.pow(2, 10)) + ' KB';
	} else {
		return bytes + ' bytes';
	}
}

Q.Template.set('Streams/file/preview/view',
	'
'
	+ ''
	+ '
'
	+ '<{{titleTag}} class="Streams_preview_title">{{title}}</{{titleTag}}>'
	+ '
{{size}}
'
	+ '

'
);

Q.Template.set('Streams/file/preview/edit',
	'
'
	+ ''
	+ '
'
	+ '<{{titleTag}} class="Streams_preview_title">{{& inplace}}</{{titleTag}}>'
	+ '
{{size}}
'
	+ '<form enctype="multipart/form-data" class="Streams_file_form">'
	+ '	<input name="file" type="file" id="{{prefix}}file" class="Streams_file_input">'
	+ ' <input name="streamName" type="hidden" value="{{streamName}}">'
	+ ' <input name="streamType" type="hidden" value="{{streamType}}">'
	+ '</form>'
	+ '

'
);

Q.Template.set('Streams/file/preview/create',
	'
'
	+ ''
	+ '
'
	+ '<{{titleTag}} class="Streams_preview_title">{{title}}</{{titleTag}}>'
	+ '
'
	+ '<form enctype="multipart/form-data" class="Streams_file_form"><input name="file" type="file" id="{{prefix}}file" class="Streams_file_input"></form>'
	+ '
'
);

})(Q, Q.$, window);<commit_msg>Fixed Streams/file/preview tool. Make composer and editable mode works correct.<commit_after>(function (Q, $, window, undefined) {

/**
 * @module Streams-tools
 */

/**
 * Renders a preview for a Streams/file stream
 * @class Streams file preview
 * @constructor
 * @param {Object} [options] options to pass to this tool, besides the ones passed to preview
 *   @param {String} [options.windowName='file'] the name of the window in which to open files. Set it to blank to open in the current window.
 *   @param {Object} [options.inplace] Any options to pass to the Q/inplace tool -- see its options.
 *   @uses Q inplace
 *   @param {Object} [options.templates] Under the keys "views", "edit" and "create" you can override options for Q.Template.render .
 *     @param {Object} [options.templates.view]
 *       @param {String} [options.templates.view.name='Streams/file/preview/view']
 *       @param {Object} [options.templates.view.fields]
 *         @param {String} [options.templates.view.fields.alt]
 *         @param {String} [options.templates.view.fields.titleClass]
 *         @param {String} [options.templates.view.fields.titleTag]
 *     @param {Object} [options.templates.edit]
 *       @param {String} [options.templates.edit.name='Streams/file/preview/edit']
 *       @param {Object} [options.templates.edit.fields]
 *         @param {String} [options.templates.edit.fields.alt]
 *         @param {String} [options.templates.edit.fields.titleClass]
 *         @param {String} [options.templates.edit.fields.titleTag]
 * @param {Object} [preview] this is the preview tool that's been instantiated
 */
Q.Tool.define("Streams/file/preview", "Streams/preview",
function _Streams_file_preview(options, preview) {
	var tool = this;
	tool.preview = preview;
	var $te = $(tool.element);
	var state = this.state;
	var ps = preview.state;
	var src = Q.getObject('templates.create.fields.src', state);
	if (src) {
		state.templates.create.fields.src = Q.url(src);
	}
	ps.templates.create = Q.extend(
		ps.templates.create,
		10,
		state.templates.create
	);
	if (ps.creatable) {
		ps.creatable.streamType = ps.creatable.streamType || 'Streams/file';
		ps.creatable.title = ps.creatable.title || 'Upload File';
		if (ps.creatable.clickable) {
			ps.creatable.clickable.preventDefault = false;
		}
	}
	ps.onRefresh.add(tool.refresh.bind(tool), tool);
	ps.onComposer.add(tool.composer.bind(tool), tool);
	preview.loading = function () {
		var $form = $te.find('form').detach();
		var $img = $te.find('img.Streams_preview_add').detach();
		$te.empty().append($img, $form).addClass('Q_uploading');
	};

	// edit action
	if (ps.editable) {
		Q.setObject(["actions", "actions", "edit"], true, ps);
	}
},

{
	inplace: {},
	windowName: 'file',
	templates: {
		view: {
			name: 'Streams/file/preview/view',
			fields: { alt: 'icon', titleClass: '', titleTag: 'h3' }
		},
		edit: {
			name: 'Streams/file/preview/edit',
			fields: { alt: 'icon', titleClass: '', titleTag: 'h3' }
		},
		create: {
			name: 'Streams/file/preview/create',
			fields: {
				src: '{{Q}}/img/actions/upload.png'
			}
		}
	}
},

{
	refresh: function (stream, callback) {
		var tool = this;
		var state = tool.state;
		var ps = tool.preview.state;
		var $te = $(tool.element);
		// set up a pipe to know when the icon has loaded
		var p = Q.pipe(['inplace', 'icon'], function () {
			Q.handle(callback, tool);
		});
		$te.removeClass('Q_uploading');
		// set up the inplace options
		var inplace = null;
		if (state.inplace) {
			var inplaceOptions = Q.extend({
				publisherId: ps.publisherId,
				streamName: ps.streamName,
				field: 'title',
				inplaceType: 'text'
			}, state.inplace);
			var se = ps.editable;
			if (!se || (se !== true && se.indexOf('title') < 0)) {
				inplaceOptions.editable = false;
			} else {
				$te.addClass('Streams_editable_title');
			}
			inplace = tool.setUpElementHTML('div', 'Streams/inplace', inplaceOptions);
		}
		// render a template
		var f = state.template && state.template.fields;
		var size = stream.getAttribute('Q.file.size') || stream.getAttribute('file.size');
		var fields = Q.extend({}, state.templates.edit.fields, f, {
			alt: stream.fields.title,
			title: stream.fields.title,
			inplace: inplace,
			size: _formatSize(size),
			streamName: ps.streamName,
			streamType: stream.fields.type
		});
		var tpl = (ps.editable !== false && stream.testWriteLevel('suggest'))
			? 'edit'
			: 'view';
		Q.Template.render(
			'Streams/file/preview/'+tpl,
			fields,
			function (err, html) {
				if (err) return;
				tool.element.innerHTML = html;
				Q.activate(tool, function () {
					// load the icon
					var jq = tool.$('img.Streams_preview_icon');
					tool.preview.icon(jq[0], p.fill('icon'));
					var $pc = tool.$('.Streams_preview_contents');
					$pc.width(0).width($pc[0].remainingWidth());
					Q.onLayout(tool.element).set(function () {
						var $pc = tool.$('.Streams_preview_contents');
						$pc.width($pc[0].remainingWidth());
					}, tool);
					var inplace = tool.child('Streams_inplace');
					if (!inplace) {
						return p.fill('inplace').apply(this, arguments);
					}
					inplace.state.onLoad.add(function () {
						p.fill('inplace').apply(this, arguments);
					});
				});
				$(tool.element).on(Q.Pointer.click, function () {
					var url = stream.fileUrl();
					if (!url) {
						return tool.selectAndUploadFile();
					}
					if (state.windowName) {
						window.open(url, state.windowName);
					} else {
						window.location = url;
					}
				});

				// apply edit action
				if (Q.getObject(["actions", "actions", "edit"], ps)) {
					ps.actions.actions.edit = tool.selectAndUploadFile.bind(tool);
					tool.preview.actions();
				}
			},
			state.templates[tpl]
		);
	},
	composer: function () {
		var tool = this;
		var $file = tool.$('.Streams_file_input');

		var $container = this.$('.Streams_preview_container');

		// remove default click handler
		$container.off([Q.Pointer.fastclick, ".Streams_preview"]);

		// attach custom handler
		$container.on([Q.Pointer.fastclick, ".Streams_file_preview"], function () {
			$file.trigger("click");
		});

		$file.on("click", function (event) {
			event.stopPropagation();
		}).change(function (event) {
			if (!this.value) {
				return; // it was canceled
			}
			var $this = $(this);
			var form = $this.closest('form').get(0);
			tool.preview.state.creatable.options.form = form;
			tool.preview.state.creatable.options.resultFunction = 'result';
			tool.preview.create(event, function (data) {
				var fem = Q.firstErrorMessage(data && data.error);
				if (fem) {
					Q.alert(fem);
				}

				// after file uploaded, Q/actions plugin must do this
				$container.off([Q.Pointer.fastclick, ".Streams_file_preview"]);
			});
			form.reset();
		});
	},
	selectAndUploadFile: function () {
		var tool = this;
		var state = tool.state;
		var $te = $(tool.element);
		tool.$('.Streams_file_input')
		.off('click')
		.on("click", function (event) {
			event.stopPropagation();
		}).off('change')
		.on("change", function (event) {
			if (!this.value) {
				return; // it was canceled
			}

			$te.addClass("Q_working");

			var form = $(this).closest('form')[0];

			// send request to replace file
			Q.req("Streams/file", function(err, response){
				$te.removeClass("Q_working");
				var msg = Q.firstErrorMessage(err, response && response.errors);
				if (msg) {
					return Q.alert(msg);
				}

				// refresh tool
				var ps = tool.preview.state;
				Q.Streams.Stream.refresh(
					ps.publisherId, ps.streamName, function () {
						tool.preview.preview();
					}
				);

			}, {
				method: 'POST',
				form: form
			});
		}).trigger("click");

		return false;
	}
}

);

function _formatSize(bytes)
{
	if (isNaN(bytes)) return '';
	if (bytes >= Math.pow(2, 30)) {
		return Math.ceil(bytes / Math.pow(2, 30)) + ' GB';
	} else if (bytes >= Math.pow(2, 20)) {
		return Math.ceil(bytes / Math.pow(2, 20)) + ' MB';
	} else if (bytes >= Math.pow(2, 10)) {
		return Math.ceil(bytes / Math.pow(2, 10)) + ' KB';
	} else {
		return bytes + ' bytes';
	}
}

Q.Template.set('Streams/file/preview/view',
	'
'
	+ ''
	+ '
'
	+ '<{{titleTag}} class="Streams_preview_title">{{title}}</{{titleTag}}>'
	+ '
{{size}}
'
	+ '

'
);

Q.Template.set('Streams/file/preview/edit',
	'
'
	+ ''
	+ '
'
	+ '<{{titleTag}} class="Streams_preview_title">{{& inplace}}</{{titleTag}}>'
	+ '
{{size}}
'
	+ '<form enctype="multipart/form-data" class="Streams_file_form">'
	+ '	<input name="file" type="file" id="{{prefix}}file" class="Streams_file_input">'
	+ ' <input name="streamName" type="hidden" value="{{streamName}}">'
	+ ' <input name="streamType" type="hidden" value="{{streamType}}">'
	+ '</form>'
	+ '

'
);

Q.Template.set('Streams/file/preview/create',
	'
'
	+ ''
	+ '
'
	+ '<{{titleTag}} class="Streams_preview_title">{{title}}</{{titleTag}}>'
	+ '
'
	+ '<form enctype="multipart/form-data" class="Streams_file_form"><input name="file" type="file" id="{{prefix}}file" class="Streams_file_input"></form>'
	+ '
'
);

})(Q, Q.$, window);<|endoftext|>