<commit_before>/**
 * Flotr Adapter
 */
(function () {

var
  V = envision,
  A = envision.adapters,
  E = Flotr.EventAdapter,
  DEFAULTS = A.flotr.defaultOptions;

function Child (options) {
  this.options = options || {};
  this.flotr = null;
  this._flotrDefaultOptions();
}

Child.prototype = {

  destroy : function () {
    this.flotr.destroy();
  },

  draw : function (data, flotr, node) {

    var
      o           = this.options,
      fData       = [];

    data = data || o.data;

    if (flotr) {
      flotr = Flotr.clone(flotr);
      flotr = Flotr.merge(o.flotr, flotr);
    } else {
      flotr = o.flotr;
    }

    o.data = data;
    min = flotr.xaxis.min;
    max = flotr.xaxis.max;

    data = this._getDataArray(data);
    if (o.skipPreprocess) {
      flotrData = data;
    } else {
      _.each(data, function (d, index) {
        // TODO flotr
        /*
        if (!_.isArray(d) && !_.isFunction(d)) {
          fData[index] = _.clone(d);
          fData[index] = this._processData(d.data);
        } else {
        */
          fData[index] = this._processData(d, flotr);
        //}
      }, this);

      fData = fData[0];
      var
        flotrData = [],
        x = fData[0],
        y = fData[1],
        i;
      for (i = 0; i < x.length; i++) {
        flotrData.push([x[i], y[i]]);
      }
      flotrData = [flotrData];
    }

    if (!flotr) throw 'No graph submitted.';

    this.flotr = Flotr.draw(node, flotrData, flotr);
  },

  _processData : function (data, flotr) {

    var
      options     = this.options,
      process     = options.processData,
      resolution  = options.width,
      axis        = flotr.xaxis,
      min         = axis.min,
      max         = axis.max,
      preprocessor;

    if (_.isFunction(data)) {
      return data(min, max, resolution);
    } else if (process) {
      preprocessor = new V.Preprocessor({data : data});
      process.apply(this, [{
        preprocessor : preprocessor,
        min : min,
        max : max,
        resolution : resolution
      }]);
    } else {
      preprocessor = new V.Preprocessor({data : data})
        .bound(min, max)
        .subsampleMinMax(resolution);
    }

    return preprocessor.getData();
  },

  _getDataArray : function (data) {

    if (data[0] && (!_.isArray(data[0]) || (data[0][0] && _.isArray(data[0][0]))))
      return data;
    else
      return [data];
  },

  _flotrDefaultOptions : function (options) {

    var o = options || this.options.flotr,
      i;

    for (i in DEFAULTS) {
      if (DEFAULTS.hasOwnProperty(i)) {
        if (_.isUndefined(o[i])) {
          o[i] = DEFAULTS[i];
        } else {
          _.defaults(o[i], DEFAULTS[i]);
        }
      }
    }
  },

  attach : function (component, name, callback) {

    var
      event = this.events[name] || {},
      name = event.name || false,
      handler = event.handler || false;

    if (handler) {

      return E.observe(component.node, name, function () {

        var
          args = [component].concat(Array.prototype.slice.call(arguments)),
          result = handler.apply(this, args);

        return callback.apply(null, [component, result]);

      });
    } else {
      return false;
    }
  },

  detach : function (component, name, callback) {
    return E.stopObserve(component.node, name, handler);
  },

  trigger : function (component, name, options) {

    var
      event = this.events[name],
      consumer = event.consumer || false;

    return consumer ? consumer.apply(this, [component, options]) : false;
  },

  events : {

    hit : {
      name : 'flotr:hit',
      handler : function (component, hit) {

        var
          x = hit.x,
          y = hit.y,
          graph = component.api.flotr,
          options;

        // Normalized hit:
        options = {
          data : {
            index : hit.index,
            x : x,
            y : y
          },
          x : graph.axes.x.d2p(x),
          y : graph.axes.y.d2p(y)
        };

        return options;
      },
      consumer : function (component, hit) {

        var
          graph = component.api.flotr,
          o;

        // TODO this is a hack;
        // the hit plugin should expose an API to do this easily
        o = {
          x : hit.data.x,
          y : hit.data.y || 1,
          relX : hit.x,
          relY : hit.y || 1
        };

        graph.hit.hit(o);
      }
    },

    select : {
      name : 'flotr:selecting',
      handler : function (component, selection) {

        var
          mode = component.options.flotr.selection.mode,
          axes = component.api.flotr.axes,
          datax, datay, x, y, options;

        if (mode.indexOf('x') !== -1) {
          datax = {};
          datax.min = selection.x1;
          datax.max = selection.x2;
          x = {};
          x.min = axes.x.d2p(selection.x1);
          x.max = axes.x.d2p(selection.x2);
        }

        if (mode.indexOf('y') !== -1) {
          datay = {};
          datay.min = selection.y1;
          datay.max = selection.y2;
          y = {};
          y.min = axes.y.d2p(selection.y1);
          y.max = axes.y.d2p(selection.y2);
        }

        // Normalized selection:
        options = {
          data : {
            x : datax,
            y : datay
          },
          x : x,
          y : y
        }

        return options;
      },
      consumer : function (component, selection) {

        var
          graph = component.api.flotr,
          axes = graph.axes,
          data = selection.data || {},
          options = {},
          x = selection.x,
          y = selection.y;

        if (!x && data.x) {
          // Translate data to pixels
          x = data.x;
          options.x1 = axes.x.d2p(x.min);
          options.x2 = axes.x.d2p(x.max);
        } else if (x) {
          // Use pixels
          options.x1 = x.min;
          options.x2 = x.max;
        }

        if (!y && data.y) {
          // Translate data to pixels
          y = data.y;
          options.y1 = axes.y.d2p(y.min);
          options.y2 = axes.y.d2p(y.max);
        } else if (y) {
          // Use pixels
          options.y1 = y.min;
          options.y2 = y.max;
        }

        graph.selection.setSelection(options);
      }
    },

    zoom : {
      consumer : function (component, selection) {

        var
          x = selection.data.x,
          y = selection.data.y,
          options = {};

        if (x) {
          options.xaxis = {
            min : x.min,
            max : x.max
          };
        }

        if (y) {
          options.yaxis = {
            min : y.min,
            max : y.max
          };
        }

        component.draw(null, options);
      }
    },

    mouseout : {
      name : 'flotr:mouseout',
      handler : function (component) {
      },
      consumer : function (component) {
        component.api.flotr.hit.clearHit();
      }
    },

    reset : {
      name : 'flotr:click',
      handler : function (component) {
        component.draw();
      },
      consumer : function (component) {
        component.draw();
      }
    },

    click : {
      name : 'flotr:click',
      handler : function (component) {

        var
          min = component.api.flotr.axes.x.min,
          max = component.api.flotr.axes.x.max;

        return {
          data : {
            x : {
              min : min,
              max : max
            }
          },
          x : {
            min : component.api.flotr.axes.x.d2p(min),
            max : component.api.flotr.axes.x.d2p(max)
          }
        };
      },
      consumer : function (component, selection) {

        var
          x = selection.data.x,
          y = selection.data.y,
          options = {};

        if (x) {
          options.xaxis = {
            min : x.min,
            max : x.max
          };
        }

        if (y) {
          options.yaxis = {
            min : y.min,
            max : y.max
          };
        }

        component.draw(null, options);
      }
    }
  }
};

A.flotr.Child = Child;

})();
<commit_msg>Added zoom handler.<commit_after>/**
 * Flotr Adapter
 */
(function () {

var
  V = envision,
  A = envision.adapters,
  E = Flotr.EventAdapter,
  DEFAULTS = A.flotr.defaultOptions;

function Child (options) {
  this.options = options || {};
  this.flotr = null;
  this._flotrDefaultOptions();
}

Child.prototype = {

  destroy : function () {
    this.flotr.destroy();
  },

  draw : function (data, flotr, node) {

    var
      o           = this.options,
      fData       = [];

    data = data || o.data;

    if (flotr) {
      flotr = Flotr.clone(flotr);
      flotr = Flotr.merge(o.flotr, flotr);
    } else {
      flotr = o.flotr;
    }

    o.data = data;
    min = flotr.xaxis.min;
    max = flotr.xaxis.max;

    data = this._getDataArray(data);
    if (o.skipPreprocess) {
      flotrData = data;
    } else {
      _.each(data, function (d, index) {
        // TODO flotr
        /*
        if (!_.isArray(d) && !_.isFunction(d)) {
          fData[index] = _.clone(d);
          fData[index] = this._processData(d.data);
        } else {
        */
          fData[index] = this._processData(d, flotr);
        //}
      }, this);

      fData = fData[0];
      var
        flotrData = [],
        x = fData[0],
        y = fData[1],
        i;
      for (i = 0; i < x.length; i++) {
        flotrData.push([x[i], y[i]]);
      }
      flotrData = [flotrData];
    }

    if (!flotr) throw 'No graph submitted.';

    this.flotr = Flotr.draw(node, flotrData, flotr);
  },

  _processData : function (data, flotr) {

    var
      options     = this.options,
      process     = options.processData,
      resolution  = options.width,
      axis        = flotr.xaxis,
      min         = axis.min,
      max         = axis.max,
      preprocessor;

    if (_.isFunction(data)) {
      return data(min, max, resolution);
    } else if (process) {
      preprocessor = new V.Preprocessor({data : data});
      process.apply(this, [{
        preprocessor : preprocessor,
        min : min,
        max : max,
        resolution : resolution
      }]);
    } else {
      preprocessor = new V.Preprocessor({data : data})
        .bound(min, max)
        .subsampleMinMax(resolution);
    }

    return preprocessor.getData();
  },

  _getDataArray : function (data) {

    if (data[0] && (!_.isArray(data[0]) || (data[0][0] && _.isArray(data[0][0]))))
      return data;
    else
      return [data];
  },

  _flotrDefaultOptions : function (options) {

    var o = options || this.options.flotr,
      i;

    for (i in DEFAULTS) {
      if (DEFAULTS.hasOwnProperty(i)) {
        if (_.isUndefined(o[i])) {
          o[i] = DEFAULTS[i];
        } else {
          _.defaults(o[i], DEFAULTS[i]);
        }
      }
    }
  },

  attach : function (component, name, callback) {

    var
      event = this.events[name] || {},
      name = event.name || false,
      handler = event.handler || false;

    if (handler) {

      return E.observe(component.node, name, function () {

        var
          args = [component].concat(Array.prototype.slice.call(arguments)),
          result = handler.apply(this, args);

        return callback.apply(null, [component, result]);

      });
    } else {
      return false;
    }
  },

  detach : function (component, name, callback) {
    return E.stopObserve(component.node, name, handler);
  },

  trigger : function (component, name, options) {

    var
      event = this.events[name],
      consumer = event.consumer || false;

    return consumer ? consumer.apply(this, [component, options]) : false;
  },

  events : {

    hit : {
      name : 'flotr:hit',
      handler : function (component, hit) {

        var
          x = hit.x,
          y = hit.y,
          graph = component.api.flotr,
          options;

        // Normalized hit:
        options = {
          data : {
            index : hit.index,
            x : x,
            y : y
          },
          x : graph.axes.x.d2p(x),
          y : graph.axes.y.d2p(y)
        };

        return options;
      },
      consumer : function (component, hit) {

        var
          graph = component.api.flotr,
          o;

        // TODO this is a hack;
        // the hit plugin should expose an API to do this easily
        o = {
          x : hit.data.x,
          y : hit.data.y || 1,
          relX : hit.x,
          relY : hit.y || 1
        };

        graph.hit.hit(o);
      }
    },

    select : {
      name : 'flotr:selecting',
      handler : selectHandler,
      consumer : function (component, selection) {

        var
          graph = component.api.flotr,
          axes = graph.axes,
          data = selection.data || {},
          options = {},
          x = selection.x,
          y = selection.y;

        if (!x && data.x) {
          // Translate data to pixels
          x = data.x;
          options.x1 = axes.x.d2p(x.min);
          options.x2 = axes.x.d2p(x.max);
        } else if (x) {
          // Use pixels
          options.x1 = x.min;
          options.x2 = x.max;
        }

        if (!y && data.y) {
          // Translate data to pixels
          y = data.y;
          options.y1 = axes.y.d2p(y.min);
          options.y2 = axes.y.d2p(y.max);
        } else if (y) {
          // Use pixels
          options.y1 = y.min;
          options.y2 = y.max;
        }

        graph.selection.setSelection(options);
      }
    },

    zoom : {
      name : 'flotr:select',
      handler : function (component, selection) {
        var options = selectHandler(component, selection);
        component.trigger('zoom', options);
        return options;
      },
      consumer : function (component, selection) {

        var
          x = selection.data.x,
          y = selection.data.y,
          options = {};

        if (x) {
          options.xaxis = {
            min : x.min,
            max : x.max
          };
        }

        if (y) {
          options.yaxis = {
            min : y.min,
            max : y.max
          };
        }

        component.draw(null, options);
      }
    },

    mouseout : {
      name : 'flotr:mouseout',
      handler : function (component) {
      },
      consumer : function (component) {
        component.api.flotr.hit.clearHit();
      }
    },

    reset : {
      name : 'flotr:click',
      handler : function (component) {
        component.draw();
      },
      consumer : function (component) {
        component.draw();
      }
    },

    click : {
      name : 'flotr:click',
      handler : function (component) {

        var
          min = component.api.flotr.axes.x.min,
          max = component.api.flotr.axes.x.max;

        return {
          data : {
            x : {
              min : min,
              max : max
            }
          },
          x : {
            min : component.api.flotr.axes.x.d2p(min),
            max : component.api.flotr.axes.x.d2p(max)
          }
        };
      },
      consumer : function (component, selection) {

        var
          x = selection.data.x,
          y = selection.data.y,
          options = {};

        if (x) {
          options.xaxis = {
            min : x.min,
            max : x.max
          };
        }

        if (y) {
          options.yaxis = {
            min : y.min,
            max : y.max
          };
        }

        component.draw(null, options);
      }
    }
  }
};

function selectHandler (component, selection) {

  var
    mode = component.options.flotr.selection.mode,
    axes = component.api.flotr.axes,
    datax, datay, x, y, options;

  if (mode.indexOf('x') !== -1) {
    datax = {};
    datax.min = selection.x1;
    datax.max = selection.x2;
    x = {};
    x.min = axes.x.d2p(selection.x1);
    x.max = axes.x.d2p(selection.x2);
  }

  if (mode.indexOf('y') !== -1) {
    datay = {};
    datay.min = selection.y1;
    datay.max = selection.y2;
    y = {};
    y.min = axes.y.d2p(selection.y1);
    y.max = axes.y.d2p(selection.y2);
  }

  // Normalized selection:
  options = {
    data : {
      x : datax,
      y : datay
    },
    x : x,
    y : y
  }

  return options;
}

A.flotr.Child = Child;

})();
<|endoftext|>


<commit_before>var jsontr = require('./json-transform.js');

var items = require('./exhibit-pages.json');
var transform = {
  nid: {
    newName: 'id',
    props: {value:String}
  },
  uuid: {
    props: {value:String}
  },
  title: {
    props: {value: String}
  },
  body: {
    props: {value: String}
  },
  field_parent_page: {
    newName: "parentPage",
    props: {target_id:{newName:'id',type:Number},target_uuid:{newName:'uuid',type:String}}
  },
  field_path: {
    newName: "path",
    props: {value: String}
  },
  field_sidebar: {
    newName: "sidebar",
    props: {target_uuid: {newName:'uuid',type:String}}
  },
  field_subnav: {
    newName: "subnav",
    props: {target_uuid: {newName:'uuid',type:String}}
  },
  field_serve_via_iframe: {
    newName: "iframe",
    props: {value:Boolean}
  },
  field_book_tour: {
    newName: 'bookTour',
    props: {value:String}
  },
  field_category_id: {
    newName: "categoryId",
    props: {value:String}
  },
  field_end_date: {
    newName: "endDate",
    props: {value:String}
  },
  field_start_date: {
    newName: "startDate",
    props: {value:String}
  },
  field_exhibition_css: {
    newName: "exhibition_css",
    props: {value:String}
  },
  field_further_location_informati: {
    newName: "locationInfo",
    props: {value:String}
  },
  field_image: {
    newName: "image",
    props: {alt:String, width:Number, height:Number, url:String}
  },
  field_libcal_id: {
    newName: "libcalId",
    props: {value:String}
  },
  field_library: {
    newName: "library",
    props: {target_id:{newName:"id",type:Number}, target_uuid:{newName:"uuid",type:String}}
  },
  field_press_kit: {
    newName: "pressKit",
    props: {uri:String}
  },
  field_redirects_from_: {
    newName: "redirectsFrom",
    props: {uri:String}
  },
  field_subtitle: {
    newName: "subTitle",
    props: {value:String}
  },
  field_url_slug: {
    newName: "urlSlug",
    props: {value:String}
  },
  field_style_exhibit_header: {
    newName: "styleHeader",
    props: {color:String}
  }
};

console.log( JSON.stringify( jsontr.transform(items,transform) ) );
<commit_msg>added more custom css props for exhibit pages<commit_after>var jsontr = require('./json-transform.js');

var items = require('./exhibit-pages.json');
var transform = {
  nid: {
    newName: 'id',
    props: {value:String}
  },
  uuid: {
    props: {value:String}
  },
  title: {
    props: {value: String}
  },
  body: {
    props: {value: String}
  },
  field_parent_page: {
    newName: "parentPage",
    props: {target_id:{newName:'id',type:Number},target_uuid:{newName:'uuid',type:String}}
  },
  field_path: {
    newName: "path",
    props: {value: String}
  },
  field_sidebar: {
    newName: "sidebar",
    props: {target_uuid: {newName:'uuid',type:String}}
  },
  field_subnav: {
    newName: "subnav",
    props: {target_uuid: {newName:'uuid',type:String}}
  },
  field_serve_via_iframe: {
    newName: "iframe",
    props: {value:Boolean}
  },
  field_book_tour: {
    newName: 'bookTour',
    props: {value:String}
  },
  field_category_id: {
    newName: "categoryId",
    props: {value:String}
  },
  field_end_date: {
    newName: "endDate",
    props: {value:String}
  },
  field_start_date: {
    newName: "startDate",
    props: {value:String}
  },
  field_exhibition_css: {
    newName: "exhibition_css",
    props: {value:String}
  },
  field_further_location_informati: {
    newName: "locationInfo",
    props: {value:String}
  },
  field_image: {
    newName: "image",
    props: {alt:String, width:Number, height:Number, url:String}
  },
  field_libcal_id: {
    newName: "libcalId",
    props: {value:String}
  },
  field_library: {
    newName: "library",
    props: {target_id:{newName:"id",type:Number}, target_uuid:{newName:"uuid",type:String}}
  },
  field_press_kit: {
    newName: "pressKit",
    props: {uri:String}
  },
  field_redirects_from_: {
    newName: "redirectsFrom",
    props: {uri:String}
  },
  field_subtitle: {
    newName: "subTitle",
    props: {value:String}
  },
  field_url_slug: {
    newName: "urlSlug",
    props: {value:String}
  },
  field_style_exhibit_header: {
    newName: "styleHeaderColor",
    props: {color:String}
  },
  field_style_exhibit_header_text_: {
    newName: "styleHeaderTextColor",
    props: {color:String}
  },
  field_style_pre_footer_backgroun: {
    newName: "stylePreFooterBackColor",
    props: {color:String}
  },
  field_news_item_source_text_colo: {
    newName: "styleNewsItemSourceTextColor",
    props: {color:String}
  }
};

console.log( JSON.stringify( jsontr.transform(items,transform) ) );
<|endoftext|>


<commit_before>/*

Search Helpers for NewsSearch - used for all types of search.

Author: David Maxwell
Date: 2013-11-13
Revision: 1

*/

var INTERFACE_ENABLED = true;

function disableClick(event) {
    if (event.button == 2) {
        return false;
    }
}

$(function() {
    bindDocumentClicks();
    bindResultHovering();
    bindFormSubmit();

    if ($('#query')) {
        $('#query').focus();
    }

    $('.searchbox, .smallsearchbox').keypress(function(e) {
        if (INTERFACE_ENABLED) {
            var regex = new RegExp("^[a-zA-Z0-9 ]+$");
            var str = String.fromCharCode(!e.charCode ? e.which : e.charCode);

            if (e.charCode == 13 || e.charCode == 8) {
                //$('#search_form').submit();
                return true;
            }

            if (regex.test(str)) {
                return true;
            }

            e.preventDefault();
            return false;
        }
        else {
            e.preventDefault();
            return false;
        }
    });

    $('body').attr('oncontextmenu', 'return false');
    document.onmousedown=disableClick;
});

/*
A helper function to enable or disable interaction with the user interface.
If enableInterface is set to true, the interface can be interacted with.
Otherwise, form fields and buttons are disabled, as well as document hit links.
*/
function changeInteractionStatus(enableInterface) {
    var delay_results = parseInt($('#delay_results').val());
    var delay_docview = parseInt($('#delay_docview').val());

    if (enableInterface) {
        INTERFACE_ENABLED = true;

        // Turn off the progress cursor
        $('*').css('cursor', 'auto');

        // Turn on the search button
        $('#search-button').removeAttr('disabled');
        $('#search-button').attr('value', 'Search');
        $('#search-button').css('background-color', '#EEEEEE');
        $('#search-button').css('color', '#666666');
        $('#search-button').css('cursor', 'pointer');

        // Enable the query field(s)
        $('.searchbox, .smallsearchbox').removeAttr('disabled');

        // Remove the spinner from the first query field
        $('#query').css('background', 'none');

        // Enable the previous/next buttons
        $('.navButton').removeAttr('disabled');
        $('.navButton').css('cursor', 'pointer');

        // Rebind hover events
        bindResultHovering();
    }
    else {
        INTERFACE_ENABLED = false;

        // Turn on the progress cursor
        $('*').css('cursor', 'progress');

        // Turn off the search button
        $('#search-button').attr('disabled', 'disabled');
        $('#search-button').attr('value', 'Loading...');
        $('#search-button').css('background-color', '#8B3A3A');
        $('#search-button').css('color', 'white');

        // Add the spinner to the first query field
        $('#query').css('background', 'url(\'/static/images/spinner.gif\')');
        $('#query').css('background-repeat', 'no-repeat');
        $('#query').css('background-position', 'right');

        // Disable the previous/next buttons
        $('.navButton').attr('disabled', 'disabled');
        $('.navButton').css('background-color', '#8B3A3A');
        $('.navButton').css('color', 'white');

        // Remove hover bindings
        $('.search_result').unbind();

        // Hide any autocomplete suggestion boxes
        if ($('.searchbox').hasClass('ui-autocomplete-input')) {
            $('.searchbox').autocomplete('close');
        }

        if ($('.smallsearchbox').hasClass('ui-autocomplete-input')) {
            $('.smallsearchbox').autocomplete('close');
        }
    }

}

/*
Binds anchor clicks with class doc-link with the following code.
Used to enforce "document download" delay times.
*/
function bindDocumentClicks() {
    $('.doc-link').click(
        function(event) {
            event.preventDefault();

            if (INTERFACE_ENABLED) {
                var delay = parseInt($('#delay_docview').val());
                var targetURL = event.target.href;

                if (delay == "") {
                    delay = 0;
                }

                if (delay > 0) {
                    changeInteractionStatus(false);

                    // Gather information for, and initiate, an AJAX call to indicate that the result is delayed.
                    var parent = $(event.target).closest('div[class="search_result"]');
                    var trecID = $(parent[0]).attr('id');
                    var whooshID = $(parent[0]).attr('whooshid');
                    var rank = $(parent[0]).attr('rank');
                    var page = $(parent[0]).attr('page');

                    $.ajax({
                        url: '/treconomics/docview_delay/',
                        data: {'trecID': trecID, 'whooshID': whooshID, 'rank': rank, 'page': page}
                        }).fail(function(data) {
                            var responseData = $.parseJSON(data.responseText);

                            if ('timeout' in responseData) {
                                alert("Your time for this exercise has expired. We will now redirect you to the next step.");
                                window.location = '/treconomics/next/';
                            }
                    });

                    setTimeout(function() {window.location = targetURL;}, (delay * 1000));
                }
                else {
                    window.location = targetURL;
                }
            }
            else {
                alert("We're processing your previous request; please wait.");
            }
    });
}

/*
Bind hovers over search result elements.
Logs the event with an AJAX call for hovering in and a call for hovering out.
*/
function bindResultHovering() {
    $('.search_result').hover(
        function(event) {
            var parent = $(event.target).closest('div[class="search_result"]');
            var trecID = $(parent[0]).attr('id');
            var whooshID = $(parent[0]).attr('whooshid');
            var rank = $(parent[0]).attr('rank');
            var page = $(parent[0]).attr('page');

            $.ajax({
                url: '/treconomics/hover/',
                data: {'status': 'in', 'trecID': trecID, 'whooshID': whooshID, 'rank': rank, 'page': page}
            }).fail(function(data) {
                var responseData = $.parseJSON(data.responseText);

                if ('timeout' in responseData) {
                    alert("Your time for this exercise has expired. We will now redirect you to the next step.");
                    window.location = '/treconomics/next/';
                }
            });
        },
        function(event) {
            var parent = $(event.target).closest('div[class="search_result"]');
            var trecID = $(parent[0]).attr('id');
            var whooshID = $(parent[0]).attr('whooshid');
            var rank = $(parent[0]).attr('rank');
            var page = $(parent[0]).attr('page');

            $.ajax({
                url: '/treconomics/hover/',
                data: {'status': 'out', 'trecID': trecID, 'whooshID': whooshID, 'rank': rank, 'page': page}
            }).fail(function(data) {
                var responseData = $.parseJSON(data.responseText);

                if ('timeout' in responseData) {
                    alert("Your time for this exercise has expired. We will now redirect you to the next step.");
                    window.location = '/treconomics/next/';
                }
            });
    });
}

function bindFormSubmit() {
    $('#search_form').submit(function(event) {
        changeInteractionStatus(false);
    });
}<commit_msg>Keycodes fixed<commit_after>/*

Search Helpers for NewsSearch - used for all types of search.

Author: David Maxwell
Date: 2013-11-13
Revision: 1

*/

var INTERFACE_ENABLED = true;

function disableClick(event) {
    if (event.button == 2) {
        return false;
    }
}

$(function() {
    bindDocumentClicks();
    bindResultHovering();
    bindFormSubmit();

    if ($('#query')) {
        $('#query').focus();
    }

    $('.searchbox, .smallsearchbox').keypress(function(e) {
        if (INTERFACE_ENABLED) {
            var regex = new RegExp("^[a-zA-Z0-9 \b]+$");
            var str = String.fromCharCode(!e.charCode ? e.which : e.charCode);

            if (e.charCode == 13 || e.keyCode == 39 || e.keyCode == 37) {
                //$('#search_form').submit();
                return true;
            }

            if (regex.test(str)) {
                return true;
            }

            e.preventDefault();
            return false;
        }
        else {
            e.preventDefault();
            return false;
        }
    });

    $('body').attr('oncontextmenu', 'return false');
    document.onmousedown=disableClick;
});

/*
A helper function to enable or disable interaction with the user interface.
If enableInterface is set to true, the interface can be interacted with.
Otherwise, form fields and buttons are disabled, as well as document hit links.
*/
function changeInteractionStatus(enableInterface) {
    var delay_results = parseInt($('#delay_results').val());
    var delay_docview = parseInt($('#delay_docview').val());

    if (enableInterface) {
        INTERFACE_ENABLED = true;

        // Turn off the progress cursor
        $('*').css('cursor', 'auto');

        // Turn on the search button
        $('#search-button').removeAttr('disabled');
        $('#search-button').attr('value', 'Search');
        $('#search-button').css('background-color', '#EEEEEE');
        $('#search-button').css('color', '#666666');
        $('#search-button').css('cursor', 'pointer');

        // Enable the query field(s)
        $('.searchbox, .smallsearchbox').removeAttr('disabled');

        // Remove the spinner from the first query field
        $('#query').css('background', 'none');

        // Enable the previous/next buttons
        $('.navButton').removeAttr('disabled');
        $('.navButton').css('cursor', 'pointer');

        // Rebind hover events
        bindResultHovering();
    }
    else {
        INTERFACE_ENABLED = false;

        // Turn on the progress cursor
        $('*').css('cursor', 'progress');

        // Turn off the search button
        $('#search-button').attr('disabled', 'disabled');
        $('#search-button').attr('value', 'Loading...');
        $('#search-button').css('background-color', '#8B3A3A');
        $('#search-button').css('color', 'white');

        // Add the spinner to the first query field
        $('#query').css('background', 'url(\'/static/images/spinner.gif\')');
        $('#query').css('background-repeat', 'no-repeat');
        $('#query').css('background-position', 'right');

        // Disable the previous/next buttons
        $('.navButton').attr('disabled', 'disabled');
        $('.navButton').css('background-color', '#8B3A3A');
        $('.navButton').css('color', 'white');

        // Remove hover bindings
        $('.search_result').unbind();

        // Hide any autocomplete suggestion boxes
        if ($('.searchbox').hasClass('ui-autocomplete-input')) {
            $('.searchbox').autocomplete('close');
        }

        if ($('.smallsearchbox').hasClass('ui-autocomplete-input')) {
            $('.smallsearchbox').autocomplete('close');
        }
    }

}

/*
Binds anchor clicks with class doc-link with the following code.
Used to enforce "document download" delay times.
*/
function bindDocumentClicks() {
    $('.doc-link').click(
        function(event) {
            event.preventDefault();

            if (INTERFACE_ENABLED) {
                var delay = parseInt($('#delay_docview').val());
                var targetURL = event.target.href;

                if (delay == "") {
                    delay = 0;
                }

                if (delay > 0) {
                    changeInteractionStatus(false);

                    // Gather information for, and initiate, an AJAX call to indicate that the result is delayed.
                    var parent = $(event.target).closest('div[class="search_result"]');
                    var trecID = $(parent[0]).attr('id');
                    var whooshID = $(parent[0]).attr('whooshid');
                    var rank = $(parent[0]).attr('rank');
                    var page = $(parent[0]).attr('page');

                    $.ajax({
                        url: '/treconomics/docview_delay/',
                        data: {'trecID': trecID, 'whooshID': whooshID, 'rank': rank, 'page': page}
                        }).fail(function(data) {
                            var responseData = $.parseJSON(data.responseText);

                            if ('timeout' in responseData) {
                                alert("Your time for this exercise has expired. We will now redirect you to the next step.");
                                window.location = '/treconomics/next/';
                            }
                    });

                    setTimeout(function() {window.location = targetURL;}, (delay * 1000));
                }
                else {
                    window.location = targetURL;
                }
            }
            else {
                alert("We're processing your previous request; please wait.");
            }
    });
}

/*
Bind hovers over search result elements.
Logs the event with an AJAX call for hovering in and a call for hovering out.
*/
function bindResultHovering() {
    $('.search_result').hover(
        function(event) {
            var parent = $(event.target).closest('div[class="search_result"]');
            var trecID = $(parent[0]).attr('id');
            var whooshID = $(parent[0]).attr('whooshid');
            var rank = $(parent[0]).attr('rank');
            var page = $(parent[0]).attr('page');

            $.ajax({
                url: '/treconomics/hover/',
                data: {'status': 'in', 'trecID': trecID, 'whooshID': whooshID, 'rank': rank, 'page': page}
            }).fail(function(data) {
                var responseData = $.parseJSON(data.responseText);

                if ('timeout' in responseData) {
                    alert("Your time for this exercise has expired. We will now redirect you to the next step.");
                    window.location = '/treconomics/next/';
                }
            });
        },
        function(event) {
            var parent = $(event.target).closest('div[class="search_result"]');
            var trecID = $(parent[0]).attr('id');
            var whooshID = $(parent[0]).attr('whooshid');
            var rank = $(parent[0]).attr('rank');
            var page = $(parent[0]).attr('page');

            $.ajax({
                url: '/treconomics/hover/',
                data: {'status': 'out', 'trecID': trecID, 'whooshID': whooshID, 'rank': rank, 'page': page}
            }).fail(function(data) {
                var responseData = $.parseJSON(data.responseText);

                if ('timeout' in responseData) {
                    alert("Your time for this exercise has expired. We will now redirect you to the next step.");
                    window.location = '/treconomics/next/';
                }
            });
    });
}

function bindFormSubmit() {
    $('#search_form').submit(function(event) {
        changeInteractionStatus(false);
    });
}<|endoftext|>


<commit_before>/*
* Standardized Resource Server App Registration code
*
* Copyright (C) Province of British Columbia, 2013
*/

var express = require('express')
  , api = require('./api')
  , config = require('../config')
  , async = require('async')
  , db = require('./db')
  , mw = require('./middleware')
  , login = require('./login')
  , api = require('./api')
  , util = require('util')

// main function that sets up all the routes
exports.routes = function ( app, RS, vault ) {

  var stdApi = new api.Standard( RS, vault )

  function _callAllResources ( api, params, callback ) {
    var tasks = {}
    config.provinces.forEach( function ( province ) {
      var host = RS + '.' + province
      tasks[host] = function( done ) {
        stdApi.call( host, api, params, done )
      }
    })
    async.parallel( tasks, callback )
  }

  function dashboardlistApps ( req, res, next ) {
    db.listApps( RS, req.session.email, function ( e, list ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      return res.send( { result: {'list': list, 'email': req.session.email } } )
    })
  }

  function dashboardAppDetails ( req, res, next ) {
    db.appDetails( RS, req.session.email, req.body.id, function ( e, details ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      return res.send( { result: {'details': details, 'email': req.session.email } } )
    })
  }

  function dashboardNewApp ( req, res, next ) {
    stdApi.call( 'registrar', '/app/verify'
                , {id: req.body.id, token: req.session.tokens[config.host.registrar]}
                , function ( e, result ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      db.newApp( RS, req.body.id, result.name, req.session.email, function ( e ) {
        if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
        _callAllResources( '/std/new/app'
                    , {id: req.body.id, name: result.name, email: req.session.email}
                    , function ( e, results ) {
          if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
          var keys = {}
          Object.keys( results ).forEach( function ( host ) {
            keys[config.host[host]] = results[host].key
          })
          return res.send( {result: keys} )
        })
      })
    })
  }

  function dashboardDeleteApp ( req, res, next ) {
    _callAllResources( '/std/delete/app', {id: req.body.id}, function ( e ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      db.deleteApp( RS, req.body.id, function ( e ) {
        if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
        return res.send( {result:{success: true }} )
      })
    })
  }

  function dashboardRefreshKey ( req, res, next ) {
    _callAllResources( '/std/refresh/key', {id: req.body.id}, function ( e, results ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      var keys = {}
      Object.keys( results ).forEach( function ( host ) {
        if (results[host].key) {
          keys[host] =
            { kid: results[host].key.latest.kid
            , key: results[host].key.latest.key
            }
        }
      })
      return res.send( {result: keys} )
    })
  }

  function dashboardGetKey ( req, res, next ) {
    _callAllResources( '/std/getkey', {id: req.body.id}, function ( e, results ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      var keys = {}
      Object.keys( results ).forEach( function ( host ) {
        if (results[host].key) {
          keys[host] =
            { kid: results[host].key.latest.kid
            , key: results[host].key.latest.key
            }
        }
      })
      return res.send( {result: keys} )
    })
  }

  function checkAdminAuthorization ( req, res, next ) {
    db.checkAdminAuthorization( RS, req.body.id, req.session.di, function ( e, authorized ) {
      if (e) { e.code = e.code || "INTERNAL_ERROR"; return next(e) }
      if (!authorized) {
        var err = new Error( req.session.di + ' not authorized for ' + req.body.id )
        err.code = "ACCESS_DENIED"
        return next(err)
      } else
        next()
    })
  }

  // checks session has required data, otherwise goes and gets it
  function checkSession ( req, res, next ) {

    function badSession( error ) {
      if (req.route.method === 'get') { // we are serving a page, so send user back to homepage
        return res.redirect('/')
      }
      req.session.bad = true
      var err = new Error( error || 'Bad session' )
      err.code = "ACCESS_DENIED"
      return next( err )
    }

    if ( req.session.bad ) return badSession() // been here before

    if (req.session.email) {
      return next()
    } else {
      // first time through, get email from email RS
      if ( !req.session.di ) return badSession('No DI in session')
      if ( !req.session.tokens ) return badSession('No tokens in session')
      var details =
        { host: 'email'
        , api: '/email/default'
        , credentials: vault.keys[config.host.email].latest
        , payload:
          { iss: config.host[RS]
          , aud: config.host.email
          , 'request.a2p3.org': { 'token': req.session.tokens[config.host.email] }
          }
        }
      api.call( details, function ( error, result ) {
        if (error) return badSession( error )
        if (!result.email) badSession( 'No email for user.' )
        req.session.email = result.email
        db.registerAdmin( RS, result.email, req.session.di, function ( e ) {
          if (e) return next (e)
          return next()
        })
      })
    }
  }

  // setup session management and all login routes
  login.router( app, { 'dashboard': RS, 'vault': vault })

  app.get('/', function( req, res ) { res.sendfile( config.rootAppDir + '/html/homepage_rs.html' ) } )

  app.get('/dashboard/list/apps'
          , checkSession
          , dashboardlistApps
          )
  app.post('/dashboard/app/details'
          , checkSession
          , mw.checkParams( {'body':['id'],'session':['email','di']} )
          , checkAdminAuthorization
          , dashboardAppDetails
          )
  app.post('/dashboard/new/app'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , dashboardNewApp
          )
  app.post('/dashboard/delete/app'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , checkAdminAuthorization
          , dashboardDeleteApp
          )
  app.post('/dashboard/refresh/key'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , checkAdminAuthorization
          , dashboardRefreshKey
          )
  app.post('/dashboard/getkey'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , checkAdminAuthorization
          , dashboardGetKey
          )

  app.get('/dashboard', checkSession, function( req, res ) { res.sendfile( config.rootAppDir + '/html/dashboard_std.html' ) } )

}<commit_msg>fix returning wrong values for getKeys for resources<commit_after>/*
* Standardized Resource Server App Registration code
*
* Copyright (C) Province of British Columbia, 2013
*/

var express = require('express')
  , api = require('./api')
  , config = require('../config')
  , async = require('async')
  , db = require('./db')
  , mw = require('./middleware')
  , login = require('./login')
  , api = require('./api')
  , util = require('util')

// main function that sets up all the routes
exports.routes = function ( app, RS, vault ) {

  var stdApi = new api.Standard( RS, vault )

  function _callAllResources ( api, params, callback ) {
    var tasks = {}
    config.provinces.forEach( function ( province ) {
      var host = RS + '.' + province
      tasks[host] = function( done ) {
        stdApi.call( host, api, params, done )
      }
    })
    async.parallel( tasks, callback )
  }

  function dashboardlistApps ( req, res, next ) {
    db.listApps( RS, req.session.email, function ( e, list ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      return res.send( { result: {'list': list, 'email': req.session.email } } )
    })
  }

  function dashboardAppDetails ( req, res, next ) {
    db.appDetails( RS, req.session.email, req.body.id, function ( e, details ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      return res.send( { result: {'details': details, 'email': req.session.email } } )
    })
  }

  function dashboardNewApp ( req, res, next ) {
    stdApi.call( 'registrar', '/app/verify'
                , {id: req.body.id, token: req.session.tokens[config.host.registrar]}
                , function ( e, result ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      db.newApp( RS, req.body.id, result.name, req.session.email, function ( e ) {
        if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
        _callAllResources( '/std/new/app'
                    , {id: req.body.id, name: result.name, email: req.session.email}
                    , function ( e, results ) {
          if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
          var keys = {}
          Object.keys( results ).forEach( function ( host ) {
            keys[config.host[host]] = results[host].key
          })
          return res.send( {result: keys} )
        })
      })
    })
  }

  function dashboardDeleteApp ( req, res, next ) {
    _callAllResources( '/std/delete/app', {id: req.body.id}, function ( e ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      db.deleteApp( RS, req.body.id, function ( e ) {
        if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
        return res.send( {result:{success: true }} )
      })
    })
  }

  function dashboardRefreshKey ( req, res, next ) {
    _callAllResources( '/std/refresh/key', {id: req.body.id}, function ( e, results ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      var keys = {}
      Object.keys( results ).forEach( function ( host ) {
        if (results[host].key) {
          keys[config.host[host]] = results[host].key
        }
      })
      return res.send( {result: keys} )
    })
  }

  function dashboardGetKey ( req, res, next ) {
    _callAllResources( '/std/getkey', {id: req.body.id}, function ( e, results ) {
      if (e) { e.code = "INTERNAL_ERROR"; return next(e) }
      var keys = {}
      Object.keys( results ).forEach( function ( host ) {
        if (results[host].key) {
          keys[config.host[host]] = results[host].key
        }
      })
      return res.send( {result: keys} )
    })
  }

  function checkAdminAuthorization ( req, res, next ) {
    db.checkAdminAuthorization( RS, req.body.id, req.session.di, function ( e, authorized ) {
      if (e) { e.code = e.code || "INTERNAL_ERROR"; return next(e) }
      if (!authorized) {
        var err = new Error( req.session.di + ' not authorized for ' + req.body.id )
        err.code = "ACCESS_DENIED"
        return next(err)
      } else
        next()
    })
  }

  // checks session has required data, otherwise goes and gets it
  function checkSession ( req, res, next ) {

    function badSession( error ) {
      if (req.route.method === 'get') { // we are serving a page, so send user back to homepage
        return res.redirect('/')
      }
      req.session.bad = true
      var err = new Error( error || 'Bad session' )
      err.code = "ACCESS_DENIED"
      return next( err )
    }

    if ( req.session.bad ) return badSession() // been here before

    if (req.session.email) {
      return next()
    } else {
      // first time through, get email from email RS
      if ( !req.session.di ) return badSession('No DI in session')
      if ( !req.session.tokens ) return badSession('No tokens in session')
      var details =
        { host: 'email'
        , api: '/email/default'
        , credentials: vault.keys[config.host.email].latest
        , payload:
          { iss: config.host[RS]
          , aud: config.host.email
          , 'request.a2p3.org': { 'token': req.session.tokens[config.host.email] }
          }
        }
      api.call( details, function ( error, result ) {
        if (error) return badSession( error )
        if (!result.email) badSession( 'No email for user.' )
        req.session.email = result.email
        db.registerAdmin( RS, result.email, req.session.di, function ( e ) {
          if (e) return next (e)
          return next()
        })
      })
    }
  }

  // setup session management and all login routes
  login.router( app, { 'dashboard': RS, 'vault': vault })

  app.get('/', function( req, res ) { res.sendfile( config.rootAppDir + '/html/homepage_rs.html' ) } )

  app.get('/dashboard/list/apps'
          , checkSession
          , dashboardlistApps
          )
  app.post('/dashboard/app/details'
          , checkSession
          , mw.checkParams( {'body':['id'],'session':['email','di']} )
          , checkAdminAuthorization
          , dashboardAppDetails
          )
  app.post('/dashboard/new/app'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , dashboardNewApp
          )
  app.post('/dashboard/delete/app'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , checkAdminAuthorization
          , dashboardDeleteApp
          )
  app.post('/dashboard/refresh/key'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , checkAdminAuthorization
          , dashboardRefreshKey
          )
  app.post('/dashboard/getkey'
          , checkSession
          , mw.checkParams( {'body':['id']} )
          , checkAdminAuthorization
          , dashboardGetKey
          )

  app.get('/dashboard', checkSession, function( req, res ) { res.sendfile( config.rootAppDir + '/html/dashboard_std.html' ) } )

}<|endoftext|>


<commit_before>// Copyright 2014-2018, University of Colorado Boulder

/**
 * Abstract base class for model in Graphing Quadratics.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Bounds2 = require( 'DOT/Bounds2' );
  const DerivedProperty = require( 'AXON/DerivedProperty' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const Graph = require( 'GRAPHING_LINES/common/model/Graph' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  const NullableIO = require( 'TANDEM/types/NullableIO' );
  const PointTool = require( 'GRAPHING_QUADRATICS/common/model/PointTool' );
  const Property = require( 'AXON/Property' );
  const PropertyIO = require( 'AXON/PropertyIO' );
  const Quadratic = require( 'GRAPHING_QUADRATICS/common/model/Quadratic' );
  const QuadraticIO = require( 'GRAPHING_QUADRATICS/common/model/QuadraticIO' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const GRAPH_VIEW_ORIGIN = new Vector2( 315, 330 ); // location of the graph's origin, in view coordinates
  const GRAPH_VIEW_WIDTH = 530; // width of the graph, in view coordinates

  class GQModel {

    /**
     * @param {Property.<Quadratic>} quadraticProperty - the interactive quadratic
     * @param {Tandem} tandem
     * @abstract
     */
    constructor( quadraticProperty, tandem ) {

      // @public {Property.<Quadratic>} the interactive quadratic
      this.quadraticProperty = quadraticProperty;

      // @public (read-only) graph
      this.graph = new Graph( GQConstants.X_AXIS_RANGE, GQConstants.Y_AXIS_RANGE );

      // @public {Property.<Quadratic|null>} the saved quadratic, null if nothing is saved
      this.savedQuadraticProperty = new Property( null, {
        isValidValue: value => ( value instanceof Quadratic || value === null ),
        tandem: tandem.createTandem( 'savedQuadraticProperty' ),
        phetioType: PropertyIO( NullableIO( QuadraticIO ) ),
        phetioDocumentation: 'the saved quadratic, null if there is no saved quadratic'
      } );

      // scale from model to view
      const modelViewTransformScale = GRAPH_VIEW_WIDTH / this.graph.xRange.getLength();

      // @public (read-only) model-view transform, created in the model because it's dependent on graph axes ranges.
      this.modelViewTransform = ModelViewTransform2.createOffsetXYScaleMapping(
        GRAPH_VIEW_ORIGIN,
        modelViewTransformScale,
        -modelViewTransformScale // y is inverted (+y is up in the model, +y is down in the view)
      );

      // Options for {Property.<Quadratic[]>}
      const optionsPropertyQuadraticArray = {
        valueType: Array,
        isValidValue: array => _.every( array, function( value ) { return value instanceof Quadratic; } )
      };

      // @public {Property.<Quadratic[]>} optional quadratic terms to be displayed,
      // in the order that they will be considered by point tools (foreground to background).
      // ObservableArray is not used here because we need to change the entire array contents atomically.
      this.quadraticTermsProperty = new Property( [], optionsPropertyQuadraticArray );

      // {DerivedProperty.<Quadratic[]>} Quadratics that are visible to the point tools,
      // in the order that they will be considered by point tools (foreground to background).
      // ObservableArray is not used here because we need to change the entire array contents atomically.
      const pointToolQuadraticsProperty = new DerivedProperty(
        [ this.quadraticProperty, this.quadraticTermsProperty, this.savedQuadraticProperty ],
        ( quadratic, quadraticTerms, savedQuadratic ) => {
          // order is important! compact to remove nulls
          return _.compact( [ quadratic, ...quadraticTerms, savedQuadratic ] );
        }, optionsPropertyQuadraticArray );

      // @public (read-only)
      this.leftPointTool = new PointTool( pointToolQuadraticsProperty, this.graph, {
        probeSide: 'right', // probe is attached to the right side
        location: new Vector2( -2, -12 ),
        dragBounds: new Bounds2(
          this.graph.xRange.min - 1, this.graph.yRange.min - 3,
          this.graph.xRange.max + 1, this.graph.yRange.max + 1 ),
        tandem: tandem.createTandem( 'leftPointTool' ),
        phetioDocumentation: 'the point tool that is initially on the left'
      } );

      // @public (read-only)
      this.rightPointTool = new PointTool( pointToolQuadraticsProperty, this.graph, {
        probeSide: 'left', // probe is attached to the left side
        location: new Vector2( 2, -12 ),
        dragBounds: new Bounds2(
          this.graph.xRange.min - 1, this.graph.yRange.min - 3,
          this.graph.xRange.max + 1, this.graph.yRange.max + 1 ),
        tandem: tandem.createTandem( 'rightPointTool' ),
        phetioDocumentation: 'the point tool that is initially on the right'
      } );
    }

    // @public
    reset() {
      this.savedQuadraticProperty.reset();
      this.leftPointTool.reset();
      this.rightPointTool.reset();
    }

    /**
     * Saves the interactive quadratic.
     * @public
     */
    saveQuadratic() {
      this.savedQuadraticProperty.value = this.quadraticProperty.value.withColor( GQColors.SAVED_CURVE );
    }

    /**
     * Erases the saved quadratic.
     * @public
     */
    eraseQuadratic() {
      this.savedQuadraticProperty.value = null;
    }
  }

  return graphingQuadratics.register( 'GQModel', GQModel );
} );
<commit_msg>guarantee that point tools will start below graph, #98<commit_after>// Copyright 2014-2018, University of Colorado Boulder

/**
 * Abstract base class for model in Graphing Quadratics.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Bounds2 = require( 'DOT/Bounds2' );
  const DerivedProperty = require( 'AXON/DerivedProperty' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const Graph = require( 'GRAPHING_LINES/common/model/Graph' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  const NullableIO = require( 'TANDEM/types/NullableIO' );
  const PointTool = require( 'GRAPHING_QUADRATICS/common/model/PointTool' );
  const Property = require( 'AXON/Property' );
  const PropertyIO = require( 'AXON/PropertyIO' );
  const Quadratic = require( 'GRAPHING_QUADRATICS/common/model/Quadratic' );
  const QuadraticIO = require( 'GRAPHING_QUADRATICS/common/model/QuadraticIO' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const GRAPH_VIEW_ORIGIN = new Vector2( 315, 330 ); // location of the graph's origin, in view coordinates
  const GRAPH_VIEW_WIDTH = 530; // width of the graph, in view coordinates

  class GQModel {

    /**
     * @param {Property.<Quadratic>} quadraticProperty - the interactive quadratic
     * @param {Tandem} tandem
     * @abstract
     */
    constructor( quadraticProperty, tandem ) {

      // @public {Property.<Quadratic>} the interactive quadratic
      this.quadraticProperty = quadraticProperty;

      // @public (read-only) graph
      this.graph = new Graph( GQConstants.X_AXIS_RANGE, GQConstants.Y_AXIS_RANGE );

      // @public {Property.<Quadratic|null>} the saved quadratic, null if nothing is saved
      this.savedQuadraticProperty = new Property( null, {
        isValidValue: value => ( value instanceof Quadratic || value === null ),
        tandem: tandem.createTandem( 'savedQuadraticProperty' ),
        phetioType: PropertyIO( NullableIO( QuadraticIO ) ),
        phetioDocumentation: 'the saved quadratic, null if there is no saved quadratic'
      } );

      // scale from model to view
      const modelViewTransformScale = GRAPH_VIEW_WIDTH / this.graph.xRange.getLength();

      // @public (read-only) model-view transform, created in the model because it's dependent on graph axes ranges.
      this.modelViewTransform = ModelViewTransform2.createOffsetXYScaleMapping(
        GRAPH_VIEW_ORIGIN,
        modelViewTransformScale,
        -modelViewTransformScale // y is inverted (+y is up in the model, +y is down in the view)
      );

      // Options for {Property.<Quadratic[]>}
      const optionsPropertyQuadraticArray = {
        valueType: Array,
        isValidValue: array => _.every( array, function( value ) { return value instanceof Quadratic; } )
      };

      // @public {Property.<Quadratic[]>} optional quadratic terms to be displayed,
      // in the order that they will be considered by point tools (foreground to background).
      // ObservableArray is not used here because we need to change the entire array contents atomically.
      this.quadraticTermsProperty = new Property( [], optionsPropertyQuadraticArray );

      // {DerivedProperty.<Quadratic[]>} Quadratics that are visible to the point tools,
      // in the order that they will be considered by point tools (foreground to background).
      // ObservableArray is not used here because we need to change the entire array contents atomically.
      const pointToolQuadraticsProperty = new DerivedProperty(
        [ this.quadraticProperty, this.quadraticTermsProperty, this.savedQuadraticProperty ],
        ( quadratic, quadraticTerms, savedQuadratic ) => {
          // order is important! compact to remove nulls
          return _.compact( [ quadratic, ...quadraticTerms, savedQuadratic ] );
        }, optionsPropertyQuadraticArray );

      // @public (read-only)
      this.leftPointTool = new PointTool( pointToolQuadraticsProperty, this.graph, {
        probeSide: 'right', // probe is attached to the right side
        location: new Vector2( -2, this.graph.yRange.min - 2 ), // below the graph
        dragBounds: new Bounds2(
          this.graph.xRange.min - 1, this.graph.yRange.min - 3,
          this.graph.xRange.max + 1, this.graph.yRange.max + 1 ),
        tandem: tandem.createTandem( 'leftPointTool' ),
        phetioDocumentation: 'the point tool that is initially on the left'
      } );

      // @public (read-only)
      this.rightPointTool = new PointTool( pointToolQuadraticsProperty, this.graph, {
        probeSide: 'left', // probe is attached to the left side
        location: new Vector2( 2, this.graph.yRange.min - 2 ), // below the graph
        dragBounds: new Bounds2(
          this.graph.xRange.min - 1, this.graph.yRange.min - 3,
          this.graph.xRange.max + 1, this.graph.yRange.max + 1 ),
        tandem: tandem.createTandem( 'rightPointTool' ),
        phetioDocumentation: 'the point tool that is initially on the right'
      } );
    }

    // @public
    reset() {
      this.savedQuadraticProperty.reset();
      this.leftPointTool.reset();
      this.rightPointTool.reset();
    }

    /**
     * Saves the interactive quadratic.
     * @public
     */
    saveQuadratic() {
      this.savedQuadraticProperty.value = this.quadraticProperty.value.withColor( GQColors.SAVED_CURVE );
    }

    /**
     * Erases the saved quadratic.
     * @public
     */
    eraseQuadratic() {
      this.savedQuadraticProperty.value = null;
    }
  }

  return graphingQuadratics.register( 'GQModel', GQModel );
} );
<|endoftext|>


<commit_before>/**
 * The RaptMedia plugin integrates the RaptMedia Engine to the Kaltura Player.
 * RaptMedia adds clickable interactive layer that accompanies your video content and can do things like:
 * cue or launch different media plays, jump to specific timecode, trigger an event on your webpage and launch a new web page or an app.
 * Learn more at http://docs.raptmedia.com/
 *
 * This plugins is only usable for raptmedia.com accounts who enabled integration with Kaltura.
 * If you don't have a RaptMedia account or need to enable the Kaltura integration, please contact support@raptmedia.com
 *
 * This plugin is only activated when the entryId provided is a Playlist Entry with partnerData == "raptmedia;projectId".
 * This plugin also makes use of accompanying plugin RaptMediaScrubber plugin to override the default scrubber behavior to fit a Rapt Media experience.
 * With RaptMediaScrubber plugin the scrubber can interact within the context of a single RaptMedia clip instead of just the entire stitched playlist.
 * The RaptMedia plugin integrates the RaptMedia Engine to the Kaltura Player.
 * It also makes use of accompanying plugin RaptMediaDurationLabel used to override the default player DurationLabel to behave according to the RaptMedia Sequence rather than show the overall playlist duration.
 */
(function ( mw, $ ) {
	"use strict";

	// Required for android playback of stitched playlists
	mw.setConfig("Kaltura.LeadHLSOnAndroid", true);

	mw.PluginManager.add( 'raptMedia', mw.KBaseComponent.extend( {

		defaultConfig: {
			'raptMediaScriptUrl': 'https://cdn1.raptmedia.com/system/player/v1/engine.min.js',
			'behaviorOnEnd': 'pause' //replay | pause
		},

		setup: function(){
			this.raptMediaEngine = null;
			this.raptCleanup();
			this.addBindings();
			this.loadNewEntry();
		},

		parseRaptMediaTags: function () {
			var partnerData = this.getPlayer().evaluate('{mediaProxy.entry.partnerData}');
			if (partnerData != null && partnerData.indexOf("raptmedia") > -1) {
				var partnerDataArr = partnerData.split(';');
				this.raptMediaProjectId = partnerDataArr[1];
				return true;
			}
			return false;
		},

		loadNewEntry: function () {
			this.raptMediaPlaylistEntry = this.parseRaptMediaTags();

			if (!this.raptMediaPlaylistEntry) {
				//this is not a rapt media entry - let the player behave normally
				this.getPlayer().sendNotification('reattachTimeUpdate');
				return;
			}

			//control time from this plugin (allow update the player's time label and scrubber from here)
			this.getPlayer().sendNotification('detachTimeUpdate');

			var raptMediaScriptUrl = this.getConfig( 'raptMediaScriptUrl' );
			var _this = this;
			var raptPlaylistEntries = _this.getPlayer().evaluate('{mediaProxy.entry.playlistContent}');
			this.raptPlaylistContent = raptPlaylistEntries.split(',');
			var requestsArray = Array();
			this.raptPlaylistContent.forEach(function (entryId) {
				requestsArray.push({
					'service': 'media',
					'action': 'get',
					'entryId': entryId
				});
			});

			_this.getKalturaClient().doRequest(requestsArray, function (data) {

				if (!_this.isValidApiResult(data))
					return;

				var msStartOffset = 0;
				var entry = null;

				for (var i = 0; i < data.length; ++i) {
					entry = data[i];

					if (mw.getConfig('debug', true))
						console.log('raptMediaPlugin::[init]', entry);

					var AAC_FRAME_SIZE = 1024;
					var MIN_SAMPLE_RATE = 44100;
					var MIN_FRAME_RATE = 24;

					// Segment duration is based on the duration of all video frames, however on discontinuities HLS.js appears to take the longer of the audio or video duration.
					// Audio will always be a multiple of AAC frame size (1024 samples) so we assume the maximum error of 1 AAC frame at the lowest sampling rate per discontinuity.
					var adjustment = ((AAC_FRAME_SIZE / MIN_SAMPLE_RATE) * i) + (2 / MIN_FRAME_RATE * (i > 1 ? 1 : 0));
					var msAdjustment = Math.ceil(adjustment * 100) * 10;

					var segment = {
						msStartTime: msStartOffset + msAdjustment,
						msDuration: entry.msDuration - msAdjustment,
						width: entry.width,
						height: entry.height,
						entryId: entry.id
					};
					_this.raptSegments[entry.id] = segment;
					_this.raptSequence.push(segment);
					msStartOffset += entry.msDuration;
				}

				$.ajax({ dataType: 'script', url: raptMediaScriptUrl, cache: true })
				.done(function() {
					_this.log('Loaded script successfuly: ' + raptMediaScriptUrl);
				})
				.fail(function( jqxhr, settings, exception ) {
					_this.log('Failed to load script: ' + raptMediaScriptUrl + ', ' + exception);
					// pause the video
					_this.getPlayer().layoutBuilder.displayAlert({
						keepOverlay: true,
						message: 'Error loading the RAPT Media engine. Please try to reload the page, or contact Support stating the URL of this page if the issue persists.',
						title: 'Error loading RAPT Media engine',
						noButtons: true
					});
				})
				.then(function() {
					_this.log('Then, setup the rapt engine');
					_this.getComponent();
					_this.$el.show();
					_this.setupRaptMediaPlugin();
				})
				.then(function(){
					//we can only continue with the rest of the player setup once the initial rapt segment was loaded
					//see raptMedia_newSegment below
					_this.raptMediaInitialSegmentLoad = true;
				});
			});
		},

		addBindings: function() {
			var _this = this;
            this.bind('mediaLoaded', function(event) {
            	_this.raptCleanup();
				_this.loadNewEntry();
			});
		},

		getComponent: function () {

			if ( ! this.$el) {
				this.$el = $( "

" ).attr( 'id', 'raptMediaOverlay' ).addClass( this.getCssClass() );
			}

			return this.$el;
		},

		raptCleanup: function() {
			if (this.$el)
				this.$el.hide();
			this.unbind('updateLayout');
			this.unbind('monitorEvent');
			this.unbind('playerPlayEnd');
			this.unbind('seeked');
			this.unbind('playerPlayed');
			this.unbind('replayEvent');
			this.unbind('seeked.newSegment');
			this.unbind('onPlayerStateChange');
			this.unbind('playerPaused.raptEndOfSegment');
			this.unbind('mediaLoaded');
			//this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
			this.raptMediaPlaylistEntry = false;
			this.engineCurrentSegment = null;
			this.playbackEnded = false;
			this.playlistContent = null;
			this.raptPlaylistContent = null;
			this.raptSegments = Array();
			this.raptSequence = Array();
		},

		setupRaptMediaPlugin: function () {

			var _this = this;

			if ( ! this.raptDelegate) {

				this.raptDelegate = {

					element: this.$el[0],

					load: function(media, flags) {
						if (_this.raptSequence.length == 0) return;
						_this.playbackEnded = false;
						var currentEntryId = media.sources[0].src;
						if (_this.engineCurrentSegment && currentEntryId == _this.engineCurrentSegment.entryId)
							return;

						if (!(currentEntryId in _this.raptSegments) || _this.raptSegments[currentEntryId] == null) {
							_this.getPlayer().layoutBuilder.displayAlert({
								keepOverlay: true,
								message: 'There was an error loading this segment of the RAPT Media experience. Please try to reload the page, or contact Support stating the URL of this page if the issue persists.',
								title: 'Error playing RAPT Media segment',
								noButtons: true
							});
						}

						_this.engineCurrentSegment = _this.raptSegments[currentEntryId];

						var __this = _this;
						if (!_this.raptMediaInitialSegmentLoad) {
							_this.bind('seeked.newSegment', function () {
								__this.seeking = false;
								__this.unbind('seeked.newSegment');
							});
							var segmentStartSec = Math.ceil(_this.engineCurrentSegment.msStartTime / 10) / 100;
							_this.getPlayer().sendNotification("doSeek", segmentStartSec);

							_this.seeking = true;
							_this.transition = true;
							_this.getPlayer().addBlackScreen();

							if (!_this.getPlayer().isMuted()) {
								_this.muted = true;
								_this.getPlayer().toggleMute(true);
							}
						} else {
							// the first rapt segment was loaded, we're ready to continue
							_this.raptMediaInitialSegmentLoad = false;
							_this.log('Initial Rapt Segment was loaded, now we can continue player init');
							_this.initCompleteCallback();
						}
						_this.getPlayer().sendNotification("raptMedia_newSegment", _this.engineCurrentSegment);
						//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
						_this.log('load: ' + _this.engineCurrentSegment);
					},

					play: function() {
						_this.getPlayer().sendNotification("doPlay");
						//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
					},

					pause: function() {
						_this.getPlayer().sendNotification("doPause");
					},

					seek: function(time) {
						_this.getPlayer().sendNotification("doSeek", time);
						//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
					},

					event: function(event) {
						if (event.type != 'media:timeupdate')
							_this.log('RaptMedia Engine Event: ' + event.type);
						switch (event.type) {
							case 'project:ended':
								var behaviorOnEnd = _this.getConfig( 'behaviorOnEnd' );
								if (behaviorOnEnd == 'replay') {
									_this.raptMediaEngine.replay();
								} else {
									_this.raptMediaUpdate();
									_this.getPlayer().onClipDone();
								}
								_this.getPlayer().sendNotification("raptMedia_projectEnd");
								//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
								break;
						}
					},

					error: function(error){
						_this.log('Engine error: ' + error);
						_this.getPlayer().layoutBuilder.displayAlert({
							keepOverlay: true,
							message: 'Somthing gone bad with this RAPT Media experience :( Please try to reload the page, or contact Support stating the URL of this page if the issue persists. (' + error + ')',
							title: 'Error playing RAPT Media experience',
							noButtons: true
						});
					}
				};

				var config = _this.getConfig('raptEngine');
				this.raptMediaEngine = new Rapt.Engine(this.raptDelegate, config);
			}

			this.raptMediaEngine.load(this.raptMediaProjectId);

			this.bind('onPlayerStateChange', function(e, newState, oldState){
				_this.raptMediaResize();
			});

			this.bind('updateLayout', function(){
				_this.raptMediaResize();
			});

			this.bind('monitorEvent', function(){
				_this.raptMediaUpdate();
			});

			this.bind('seeked', function() {
				_this.playbackEnded = false;
				_this.raptMediaUpdate();
			});

			this.bind('replayEvent', function(){
				_this.playbackEnded = false;
				_this.raptMediaEngine.replay();
				_this.raptMediaResize();
				_this.raptMediaUpdate();
			});

			this.raptMediaResize();
			this.raptMediaUpdate();

			this.log('Engine setup complete');
		},

		raptMediaUpdate: function(){

			if (this.engineCurrentSegment == null)
				return;

			if (this.seeking)
				return;

			var playlistTimeMillis = parseFloat(this.getPlayer().currentTime).toFixed(2) * 1000;
			var currentTimeMillis = playlistTimeMillis - this.engineCurrentSegment.msStartTime;

			if (this.playbackEnded) {
				this.getPlayer().pause();
				return;
			}

			if (this.transition && currentTimeMillis > 0) {
				this.transitioning = false;
				this.getPlayer().removeBlackScreen();
				if (this.muted) {
					this.muted = false;
					this.getPlayer().toggleMute();
				}
			}

			if (currentTimeMillis < 0) currentTimeMillis = 0;
			var currentTimeSec = parseFloat((currentTimeMillis / 1000).toFixed(3));

			var segmentDurationMillis = this.engineCurrentSegment.msDuration - 300;
			var segmentDurationSec = parseFloat((this.engineCurrentSegment.msDuration / 1000).toFixed(3));

			if (currentTimeMillis >= segmentDurationMillis) {
				this.playbackEnded = true;
				var _embedPlayer = this.getPlayer();
				var _this = this;
				this.bind('playerPaused.raptEndOfSegment', function () {
					_this.unbind('playerPaused.raptEndOfSegment');
					_embedPlayer.sendNotification('raptMedia_pausedDecisionPoint');
					var segmentStartOffset = parseFloat((_this.engineCurrentSegment.msStartTime / 1000).toFixed(3));
					var seekTimeEndOfSeg = segmentStartOffset + parseFloat(((_this.engineCurrentSegment.msDuration)/1000).toFixed(3));
					_embedPlayer.triggerHelper("userInitiatedSeek", seekTimeEndOfSeg);
					_embedPlayer.stopPlayAfterSeek = true;
					_embedPlayer.seek(seekTimeEndOfSeg, true);
				});
				this.getPlayer().pause();
				//this.getPlayer().sendNotification('enableGui', { 'guiEnabled': false, 'enableType': 'controls' });
			} else {
				this.playbackEnded = false;
			}

			this.log(currentTimeSec + ", " + segmentDurationSec + " , " + this.playbackEnded);

			this.raptMediaEngine.update({
				currentTime: (this.playbackEnded ? segmentDurationSec : currentTimeSec),
				duration: segmentDurationSec,
				ended: this.playbackEnded,
				videoWidth: this.engineCurrentSegment.width,
				videoHeight: this.engineCurrentSegment.height
			});

			this.getPlayer().sendNotification('externalTimeUpdate', currentTimeSec);
		},

		raptMediaResize: function() {
			this.raptMediaEngine.resize({
				width: this.getPlayer().getVideoHolder().width(),
				height: this.getPlayer().getVideoHolder().height()
			});
		},

		isValidApiResult: function (data) {
			if (!data){
				this.error = true;
				this.log("API Error retrieving data");
				return false;
			} else if ( data.code && data.message ) {
				this.error = true;
				this.log("API Error code: " + data.code + ", error message: " + data.message);
				return false;
			}
			this.error = false;
			return true;
		},

	} ) );
} ) ( window.mw, window.jQuery );
<commit_msg>Force LeadWithHLSOnFlash to true for rapt compatibility w/ IE11 Win7 (#3393)<commit_after>/**
 * The RaptMedia plugin integrates the RaptMedia Engine to the Kaltura Player.
 * RaptMedia adds clickable interactive layer that accompanies your video content and can do things like:
 * cue or launch different media plays, jump to specific timecode, trigger an event on your webpage and launch a new web page or an app.
 * Learn more at http://docs.raptmedia.com/
 *
 * This plugins is only usable for raptmedia.com accounts who enabled integration with Kaltura.
 * If you don't have a RaptMedia account or need to enable the Kaltura integration, please contact support@raptmedia.com
 *
 * This plugin is only activated when the entryId provided is a Playlist Entry with partnerData == "raptmedia;projectId".
 * This plugin also makes use of accompanying plugin RaptMediaScrubber plugin to override the default scrubber behavior to fit a Rapt Media experience.
 * With RaptMediaScrubber plugin the scrubber can interact within the context of a single RaptMedia clip instead of just the entire stitched playlist.
 * The RaptMedia plugin integrates the RaptMedia Engine to the Kaltura Player.
 * It also makes use of accompanying plugin RaptMediaDurationLabel used to override the default player DurationLabel to behave according to the RaptMedia Sequence rather than show the overall playlist duration.
 */
(function ( mw, $ ) {
	"use strict";

	// Required for playback of stitched playlists on android
	mw.setConfig("Kaltura.LeadHLSOnAndroid", true);

	// Required for playback of stitched playlists on IE11 on Windows 7
	mw.setConfig("LeadWithHLSOnFlash", true);

	mw.PluginManager.add( 'raptMedia', mw.KBaseComponent.extend( {

		defaultConfig: {
			'raptMediaScriptUrl': 'https://cdn1.raptmedia.com/system/player/v1/engine.min.js',
			'behaviorOnEnd': 'pause' //replay | pause
		},

		setup: function(){
			this.raptMediaEngine = null;
			this.raptCleanup();
			this.addBindings();
			this.loadNewEntry();
		},

		parseRaptMediaTags: function () {
			var partnerData = this.getPlayer().evaluate('{mediaProxy.entry.partnerData}');
			if (partnerData != null && partnerData.indexOf("raptmedia") > -1) {
				var partnerDataArr = partnerData.split(';');
				this.raptMediaProjectId = partnerDataArr[1];
				return true;
			}
			return false;
		},

		loadNewEntry: function () {
			this.raptMediaPlaylistEntry = this.parseRaptMediaTags();

			if (!this.raptMediaPlaylistEntry) {
				//this is not a rapt media entry - let the player behave normally
				this.getPlayer().sendNotification('reattachTimeUpdate');
				return;
			}

			//control time from this plugin (allow update the player's time label and scrubber from here)
			this.getPlayer().sendNotification('detachTimeUpdate');

			var raptMediaScriptUrl = this.getConfig( 'raptMediaScriptUrl' );
			var _this = this;
			var raptPlaylistEntries = _this.getPlayer().evaluate('{mediaProxy.entry.playlistContent}');
			this.raptPlaylistContent = raptPlaylistEntries.split(',');
			var requestsArray = Array();
			this.raptPlaylistContent.forEach(function (entryId) {
				requestsArray.push({
					'service': 'media',
					'action': 'get',
					'entryId': entryId
				});
			});

			_this.getKalturaClient().doRequest(requestsArray, function (data) {

				if (!_this.isValidApiResult(data))
					return;

				var msStartOffset = 0;
				var entry = null;

				for (var i = 0; i < data.length; ++i) {
					entry = data[i];

					if (mw.getConfig('debug', true))
						console.log('raptMediaPlugin::[init]', entry);

					var AAC_FRAME_SIZE = 1024;
					var MIN_SAMPLE_RATE = 44100;
					var MIN_FRAME_RATE = 24;

					// Segment duration is based on the duration of all video frames, however on discontinuities HLS.js appears to take the longer of the audio or video duration.
					// Audio will always be a multiple of AAC frame size (1024 samples) so we assume the maximum error of 1 AAC frame at the lowest sampling rate per discontinuity.
					var adjustment = ((AAC_FRAME_SIZE / MIN_SAMPLE_RATE) * i) + (2 / MIN_FRAME_RATE * (i > 1 ? 1 : 0));
					var msAdjustment = Math.ceil(adjustment * 100) * 10;

					var segment = {
						msStartTime: msStartOffset + msAdjustment,
						msDuration: entry.msDuration - msAdjustment,
						width: entry.width,
						height: entry.height,
						entryId: entry.id
					};
					_this.raptSegments[entry.id] = segment;
					_this.raptSequence.push(segment);
					msStartOffset += entry.msDuration;
				}

				$.ajax({ dataType: 'script', url: raptMediaScriptUrl, cache: true })
				.done(function() {
					_this.log('Loaded script successfuly: ' + raptMediaScriptUrl);
				})
				.fail(function( jqxhr, settings, exception ) {
					_this.log('Failed to load script: ' + raptMediaScriptUrl + ', ' + exception);
					// pause the video
					_this.getPlayer().layoutBuilder.displayAlert({
						keepOverlay: true,
						message: 'Error loading the RAPT Media engine. Please try to reload the page, or contact Support stating the URL of this page if the issue persists.',
						title: 'Error loading RAPT Media engine',
						noButtons: true
					});
				})
				.then(function() {
					_this.log('Then, setup the rapt engine');
					_this.getComponent();
					_this.$el.show();
					_this.setupRaptMediaPlugin();
				})
				.then(function(){
					//we can only continue with the rest of the player setup once the initial rapt segment was loaded
					//see raptMedia_newSegment below
					_this.raptMediaInitialSegmentLoad = true;
				});
			});
		},

		addBindings: function() {
			var _this = this;
            this.bind('mediaLoaded', function(event) {
            	_this.raptCleanup();
				_this.loadNewEntry();
			});
		},

		getComponent: function () {

			if ( ! this.$el) {
				this.$el = $( "

" ).attr( 'id', 'raptMediaOverlay' ).addClass( this.getCssClass() );
			}

			return this.$el;
		},

		raptCleanup: function() {
			if (this.$el)
				this.$el.hide();
			this.unbind('updateLayout');
			this.unbind('monitorEvent');
			this.unbind('playerPlayEnd');
			this.unbind('seeked');
			this.unbind('playerPlayed');
			this.unbind('replayEvent');
			this.unbind('seeked.newSegment');
			this.unbind('onPlayerStateChange');
			this.unbind('playerPaused.raptEndOfSegment');
			this.unbind('mediaLoaded');
			//this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
			this.raptMediaPlaylistEntry = false;
			this.engineCurrentSegment = null;
			this.playbackEnded = false;
			this.playlistContent = null;
			this.raptPlaylistContent = null;
			this.raptSegments = Array();
			this.raptSequence = Array();
		},

		setupRaptMediaPlugin: function () {

			var _this = this;

			if ( ! this.raptDelegate) {

				this.raptDelegate = {

					element: this.$el[0],

					load: function(media, flags) {
						if (_this.raptSequence.length == 0) return;
						_this.playbackEnded = false;
						var currentEntryId = media.sources[0].src;
						if (_this.engineCurrentSegment && currentEntryId == _this.engineCurrentSegment.entryId)
							return;

						if (!(currentEntryId in _this.raptSegments) || _this.raptSegments[currentEntryId] == null) {
							_this.getPlayer().layoutBuilder.displayAlert({
								keepOverlay: true,
								message: 'There was an error loading this segment of the RAPT Media experience. Please try to reload the page, or contact Support stating the URL of this page if the issue persists.',
								title: 'Error playing RAPT Media segment',
								noButtons: true
							});
						}

						_this.engineCurrentSegment = _this.raptSegments[currentEntryId];

						var __this = _this;
						if (!_this.raptMediaInitialSegmentLoad) {
							_this.bind('seeked.newSegment', function () {
								__this.seeking = false;
								__this.unbind('seeked.newSegment');
							});
							var segmentStartSec = Math.ceil(_this.engineCurrentSegment.msStartTime / 10) / 100;
							_this.getPlayer().sendNotification("doSeek", segmentStartSec);

							_this.seeking = true;
							_this.transition = true;
							_this.getPlayer().addBlackScreen();

							if (!_this.getPlayer().isMuted()) {
								_this.muted = true;
								_this.getPlayer().toggleMute(true);
							}
						} else {
							// the first rapt segment was loaded, we're ready to continue
							_this.raptMediaInitialSegmentLoad = false;
							_this.log('Initial Rapt Segment was loaded, now we can continue player init');
							_this.initCompleteCallback();
						}
						_this.getPlayer().sendNotification("raptMedia_newSegment", _this.engineCurrentSegment);
						//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
						_this.log('load: ' + _this.engineCurrentSegment);
					},

					play: function() {
						_this.getPlayer().sendNotification("doPlay");
						//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
					},

					pause: function() {
						_this.getPlayer().sendNotification("doPause");
					},

					seek: function(time) {
						_this.getPlayer().sendNotification("doSeek", time);
						//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
					},

					event: function(event) {
						if (event.type != 'media:timeupdate')
							_this.log('RaptMedia Engine Event: ' + event.type);
						switch (event.type) {
							case 'project:ended':
								var behaviorOnEnd = _this.getConfig( 'behaviorOnEnd' );
								if (behaviorOnEnd == 'replay') {
									_this.raptMediaEngine.replay();
								} else {
									_this.raptMediaUpdate();
									_this.getPlayer().onClipDone();
								}
								_this.getPlayer().sendNotification("raptMedia_projectEnd");
								//_this.getPlayer().sendNotification('enableGui', { 'guiEnabled': true });
								break;
						}
					},

					error: function(error){
						_this.log('Engine error: ' + error);
						_this.getPlayer().layoutBuilder.displayAlert({
							keepOverlay: true,
							message: 'Somthing gone bad with this RAPT Media experience :( Please try to reload the page, or contact Support stating the URL of this page if the issue persists. (' + error + ')',
							title: 'Error playing RAPT Media experience',
							noButtons: true
						});
					}
				};

				var config = _this.getConfig('raptEngine');
				this.raptMediaEngine = new Rapt.Engine(this.raptDelegate, config);
			}

			this.raptMediaEngine.load(this.raptMediaProjectId);

			this.bind('onPlayerStateChange', function(e, newState, oldState){
				_this.raptMediaResize();
			});

			this.bind('updateLayout', function(){
				_this.raptMediaResize();
			});

			this.bind('monitorEvent', function(){
				_this.raptMediaUpdate();
			});

			this.bind('seeked', function() {
				_this.playbackEnded = false;
				_this.raptMediaUpdate();
			});

			this.bind('replayEvent', function(){
				_this.playbackEnded = false;
				_this.raptMediaEngine.replay();
				_this.raptMediaResize();
				_this.raptMediaUpdate();
			});

			this.raptMediaResize();
			this.raptMediaUpdate();

			this.log('Engine setup complete');
		},

		raptMediaUpdate: function(){

			if (this.engineCurrentSegment == null)
				return;

			if (this.seeking)
				return;

			var playlistTimeMillis = parseFloat(this.getPlayer().currentTime).toFixed(2) * 1000;
			var currentTimeMillis = playlistTimeMillis - this.engineCurrentSegment.msStartTime;

			if (this.playbackEnded) {
				this.getPlayer().pause();
				return;
			}

			if (this.transition && currentTimeMillis > 0) {
				this.transitioning = false;
				this.getPlayer().removeBlackScreen();
				if (this.muted) {
					this.muted = false;
					this.getPlayer().toggleMute();
				}
			}

			if (currentTimeMillis < 0) currentTimeMillis = 0;
			var currentTimeSec = parseFloat((currentTimeMillis / 1000).toFixed(3));

			var segmentDurationMillis = this.engineCurrentSegment.msDuration - 300;
			var segmentDurationSec = parseFloat((this.engineCurrentSegment.msDuration / 1000).toFixed(3));

			if (currentTimeMillis >= segmentDurationMillis) {
				this.playbackEnded = true;
				var _embedPlayer = this.getPlayer();
				var _this = this;
				this.bind('playerPaused.raptEndOfSegment', function () {
					_this.unbind('playerPaused.raptEndOfSegment');
					_embedPlayer.sendNotification('raptMedia_pausedDecisionPoint');
					var segmentStartOffset = parseFloat((_this.engineCurrentSegment.msStartTime / 1000).toFixed(3));
					var seekTimeEndOfSeg = segmentStartOffset + parseFloat(((_this.engineCurrentSegment.msDuration)/1000).toFixed(3));
					_embedPlayer.triggerHelper("userInitiatedSeek", seekTimeEndOfSeg);
					_embedPlayer.stopPlayAfterSeek = true;
					_embedPlayer.seek(seekTimeEndOfSeg, true);
				});
				this.getPlayer().pause();
				//this.getPlayer().sendNotification('enableGui', { 'guiEnabled': false, 'enableType': 'controls' });
			} else {
				this.playbackEnded = false;
			}

			this.log(currentTimeSec + ", " + segmentDurationSec + " , " + this.playbackEnded);

			this.raptMediaEngine.update({
				currentTime: (this.playbackEnded ? segmentDurationSec : currentTimeSec),
				duration: segmentDurationSec,
				ended: this.playbackEnded,
				videoWidth: this.engineCurrentSegment.width,
				videoHeight: this.engineCurrentSegment.height
			});

			this.getPlayer().sendNotification('externalTimeUpdate', currentTimeSec);
		},

		raptMediaResize: function() {
			this.raptMediaEngine.resize({
				width: this.getPlayer().getVideoHolder().width(),
				height: this.getPlayer().getVideoHolder().height()
			});
		},

		isValidApiResult: function (data) {
			if (!data){
				this.error = true;
				this.log("API Error retrieving data");
				return false;
			} else if ( data.code && data.message ) {
				this.error = true;
				this.log("API Error code: " + data.code + ", error message: " + data.message);
				return false;
			}
			this.error = false;
			return true;
		},

	} ) );
} ) ( window.mw, window.jQuery );
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react'
import config from '../config'

import '../../css/authoring.less'

function camelCaseToWords (name) {
  return name.replace(/([A-Z])/g, ` $1`).toLowerCase()
}

const HIDDEN_OPTIONS = [ 'authoring' ]
const SEPARATOR_AFTER = [ 'logging', 'endTime' ]

const OPTIONS_WITH_SEPARATORS = (function () {
  const result = []
  Object.keys(config).forEach(name => {
    if (HIDDEN_OPTIONS.indexOf(name) !== -1) {
      return
    }
    result.push(name)
    if (SEPARATOR_AFTER.indexOf(name) !== -1) {
      result.push('___separator___')
    }
  })
  return result
}())

export default class Authoring extends PureComponent {
  constructor (props) {
    super(props)
    this.state = {
      iframeSrc: ''
    }
    Object.keys(config).forEach(name => {
      let value = config[name]
      if (name === 'startTime' || name === 'endTime') {
        value = (new Date(value)).toISOString()
      }
      this.state[name] = value
    })
  }

  componentDidMount () {
    this.setState({
      iframeSrc: this.finalUrl()
    })
  }

  componentDidUpdate () {
    window.clearTimeout(this._iframeUpdate)
    this._iframeUpdate = setTimeout(() => {
      this.setState({
        iframeSrc: this.finalUrl()
      })
    }, 2000)
  }

  renderCheckbox (name) {
    const toggleOption = () => {
      this.setState({[name]: !this.state[name]})
    }
    return (
      <input type='checkbox' checked={this.state[name]} onChange={toggleOption} /> { camelCaseToWords(name) }
    )
  }

  renderTextInput (name) {
    const setOption = (event) => {
      this.setState({[name]: event.target.value})
    }
    return (
      { camelCaseToWords(name) } <input type='text' value={this.state[name]} onChange={setOption} />
    )
  }

  renderConfig () {
    return OPTIONS_WITH_SEPARATORS.map((name, idx) => {
      const value = this.state[name]
      if (name === '___separator___') {
        return

      }
      if (typeof value === 'boolean') {
        return this.renderCheckbox(name)
      }
      if (typeof value === 'string' || typeof value === 'number') {
        return this.renderTextInput(name)
      }
    })
  }

  finalUrl () {
    let url = window.location.href.slice()
    url = url.replace('?authoring', '')

    OPTIONS_WITH_SEPARATORS.forEach(name => {
      let value = this.state[name]
      let configValue = config[name]
      if (name === 'startTime' || name === 'endTime') {
        configValue = (new Date(configValue)).toISOString()
      }
      if (value !== configValue) {
        if (value === true) {
          url += `&${name}`
        } else {
          url += `&${name}=${value}`
        }
      }
    })
    // Remove first &, as it's unnecessary and make sure there's ?
    url = url.replace('/&', '/?')
    return url
  }

  render () {
    const { iframeSrc } = this.state
    return (

Authoring

        { this.renderConfig() }

Final URL

        <textarea value={this.finalUrl()} readOnly />

Preview

    )
  }
}
<commit_msg>Fix authoring preview URL<commit_after>import React, { PureComponent } from 'react'
import config from '../config'

import '../../css/authoring.less'

function camelCaseToWords (name) {
  return name.replace(/([A-Z])/g, ` $1`).toLowerCase()
}

const HIDDEN_OPTIONS = [ 'authoring' ]
const SEPARATOR_AFTER = [ 'logging', 'endTime' ]

const OPTIONS_WITH_SEPARATORS = (function () {
  const result = []
  Object.keys(config).forEach(name => {
    if (HIDDEN_OPTIONS.indexOf(name) !== -1) {
      return
    }
    result.push(name)
    if (SEPARATOR_AFTER.indexOf(name) !== -1) {
      result.push('___separator___')
    }
  })
  return result
}())

export default class Authoring extends PureComponent {
  constructor (props) {
    super(props)
    this.state = {
      iframeSrc: ''
    }
    Object.keys(config).forEach(name => {
      let value = config[name]
      if (name === 'startTime' || name === 'endTime') {
        value = (new Date(value)).toISOString()
      }
      this.state[name] = value
    })
  }

  componentDidMount () {
    this.setState({
      iframeSrc: this.finalUrl()
    })
  }

  componentDidUpdate () {
    window.clearTimeout(this._iframeUpdate)
    this._iframeUpdate = setTimeout(() => {
      this.setState({
        iframeSrc: this.finalUrl()
      })
    }, 2000)
  }

  renderCheckbox (name) {
    const toggleOption = () => {
      this.setState({[name]: !this.state[name]})
    }
    return (
      <input type='checkbox' checked={this.state[name]} onChange={toggleOption} /> { camelCaseToWords(name) }
    )
  }

  renderTextInput (name) {
    const setOption = (event) => {
      this.setState({[name]: event.target.value})
    }
    return (
      { camelCaseToWords(name) } <input type='text' value={this.state[name]} onChange={setOption} />
    )
  }

  renderConfig () {
    return OPTIONS_WITH_SEPARATORS.map((name, idx) => {
      const value = this.state[name]
      if (name === '___separator___') {
        return

      }
      if (typeof value === 'boolean') {
        return this.renderCheckbox(name)
      }
      if (typeof value === 'string' || typeof value === 'number') {
        return this.renderTextInput(name)
      }
    })
  }

  finalUrl () {
    let url = window.location.href.slice()
    url = url.replace('?authoring', '')

    OPTIONS_WITH_SEPARATORS.forEach(name => {
      let value = this.state[name]
      let configValue = config[name]
      if (name === 'startTime' || name === 'endTime') {
        configValue = (new Date(configValue)).toISOString()
      }
      if (value !== configValue) {
        if (value === true) {
          url += `&${name}`
        } else {
          url += `&${name}=${value}`
        }
      }
    })
    // Remove first &, as it's unnecessary and make sure there's ?
    url = url.replace('&', '?')
    return url
  }

  render () {
    const { iframeSrc } = this.state
    return (

Authoring

        { this.renderConfig() }

Final URL

        <textarea value={this.finalUrl()} readOnly />

Preview

    )
  }
}
<|endoftext|>