<commit_before>'use strict';
var path = require('path');
var generators = require('yeoman-generator');
var askName = require('inquirer-npm-name');
var _ = require('lodash');
var extend = require('deep-extend');
var mkdirp = require('mkdirp');

function makeGeneratorName(name) {
  name = _.kebabCase(name);
  name = name.indexOf('phovea_') === 0 ? name : 'phovea_' + name;
  return name;
}

module.exports = generators.Base.extend({
  initializing: function () {
    this.props = {};
  },

  prompting: function () {
    return askName({
      name: 'name',
      message: 'Your phovea plugin name',
      default: makeGeneratorName(path.basename(process.cwd())),
      filter: makeGeneratorName,
      validate: function (str) {
        return str.length > 'phovea-'.length;
      }
    }, this).then(function (props) {
      this.props.name = props.name;
    }.bind(this));
  },

  default: function () {
    if (path.basename(this.destinationPath()) !== this.props.name) {
      this.log(
        'Your generator must be inside a folder named ' + this.props.name + '\n' +
        'I\'ll automatically create this folder.'
      );
      mkdirp(this.props.name);
      this.destinationRoot(this.destinationPath(this.props.name));
    }

    this.composeWith('node:app', {
      options: {
        babel: false,
        boilerplate: false,
        name: this.props.name,
        skipInstall: this.options.skipInstall
      }
    }, {
      local: require('generator-node').app
    });
  },

  writing: function () {
    var pkg = this.fs.readJSON(this.destinationPath('package.json'));
    var pkg_patch = JSON.parse(_.template(this.fs.read(this.templatePath('package.tmpl.json')))({
      name: this.props.name
    }));
    extend(pkg, pkg_patch);

    this.fs.writeJSON(this.destinationPath('package.json'), pkg);

    const templates = {
      name: this.props.name,
      repository: pkg.repository.url,
      modules: [],
      libraries: {},
      extensions: []
    };

    this.fs.copy(this.templatPath('plain/**/*'), this.destinationPath(), templates);
    this.fs.copyTpl(this.templatPath('processed/**/*'), this.destinationPath(), templates);
  },

  install: function () {
    this.installDependencies({bower: false});
  }
});
<commit_msg>switch to es6 notation<commit_after>'use strict';
var path = require('path');
var generators = require('yeoman-generator');
var askName = require('inquirer-npm-name');
var _ = require('lodash');
var extend = require('deep-extend');
var mkdirp = require('mkdirp');

function makeGeneratorName(name) {
  name = _.kebabCase(name);
  name = name.indexOf('phovea_') === 0 ? name : 'phovea_' + name;
  return name;
}

class PluginGenerator extends Generators.Base {

  constructor(args, options) {
    super(args, options);

    // Make options available
    this.option('skip-welcome-message', {
      desc: 'Skip the welcome message',
      type: Boolean,
      defaults: false
    });
    this.option('skip-install');

    // Use our plain template as source
    this.sourceRoot(baseRootPath);

    this.config.save();

    this.props = {};
  }

  initializing() {
    if(!this.options['skip-welcome-message']) {
      this.log(require('yeoman-welcome'));
    }
  }

  prompting() {
    return askName({
      name: 'name',
      message: 'Your phovea plugin name',
      default: makeGeneratorName(path.basename(process.cwd())),
      filter: makeGeneratorName,
      validate: (str) => str.length > 'phovea-'.length
    }, this).then((props) => {
      this.props.name = props.name;
      this.config.set('name', this.props.name);
    });
  }

  default() {
    if (path.basename(this.destinationPath()) !== this.props.name) {
      this.log(
        'Your generator must be inside a folder named ' + this.props.name + '\n' +
        'I\'ll automatically create this folder.'
      );
      mkdirp(this.props.name);
      this.destinationRoot(this.destinationPath(this.props.name));
    }

    this.composeWith('node:app', {
      options: {
        babel: false,
        boilerplate: false,
        name: this.props.name,
        skipInstall: this.options.skipInstall
      }
    }, {
      local: require('generator-node').app
    });
  }

  configuring() {
    var pkg = this.fs.readJSON(this.destinationPath('package.json'));
    var pkg_patch = JSON.parse(_.template(this.fs.read(this.templatePath('package.tmpl.json')))({
      name: this.props.name
    }));
    extend(pkg, pkg_patch);

    this.fs.writeJSON(this.destinationPath('package.json'), pkg);

    this.props.repository = pkg.repository.url;
  }

  writing() {
    this.fs.copy(this.templatPath('plain/**/*'), this.destinationPath());
    this.fs.copyTpl(this.templatPath('processed/**/*'), this.destinationPath(), this.props);
  }

  install() {
    if(!this.options['skip-install']) {
      this.installDependencies({ bower: false });
    }
  }
}

module.exports = PluginGenerator;
<|endoftext|>


<commit_before>
function makeHighlightCtrl() {
    hctrl = new OpenLayers.Control.SelectFeature(
        map.getControl('selectcontrol').layers.concat(map.getLayersByName('Others Layer')[0]),
        {
            hover: true,
            highlightOnly: true,
            multiple: true,
            renderIntent: 'highlight',
            eventListeners: {
                featurehighlighted: function (e) {
                    //fix for unhighlight in OpenLayers not always triggering
                    $('ul.feature_comments li:visible').fadeOut(500);
                    var feat_str = '
';
                    feat_str += e.feature.attributes.name + '
';
                    for (i = 0; i < e.feature.attributes.form_values.length; i += 1) {
                                feat_str += '
' + e.feature.attributes.form_values[i].name +
                                                 '
' +
                                                 e.feature.attributes.form_values[i].value +'
';
                            /*} else if ($('ul.feature_comments li.' + e.feature.id + ' span.comment').text() !== e.feature.attributes.form_values[i].value) {
                                $('ul.feature_comments li.' + e.feature.id + ' span.comment').text(e.feature.attributes.form_values[i].value);
                            }*/
                    }
                    var create_time_string,
                        username,
                        anonymous_regexp,
                        show_list_item = $('ul.feature_comments li.' + e.feature.id);
                        username = e.feature.attributes.user;
                        anonymous_regexp = new RegExp('T[0-9]+.[0-9]+R[0-9]+.[0-9]+');
                        if (anonymous_regexp.test(username) || username === undefined) {
                            username = '';
                        }
                        create_time_string = '';
                        if (e.feature.attributes.time !== undefined) {
                            create_time_string = $.datepicker.formatDate('D, d M yy',
                                                     $.datepicker.parseDate('yy-mm-dd', e.feature.attributes.time.create_time.split('T')[0]));
                        }
                    feat_str += '

' +
                                     username +
                                     ' ' +
                                     create_time_string + '
';
                    $('ul.feature_comments').prepend(feat_str);
                    show_list_item = $('ul.feature_comments li.' + e.feature.id);
                    show_list_item.stop(true, true);
                    show_list_item.fadeIn(500);
                },
                featureunhighlighted: function (e) {
                    var hide_list_item = $('ul.feature_comments li.' + e.feature.id);
                    hide_list_item.stop(true, true);
                    hide_list_item.fadeOut(500);
                }
            }
        }
    );
    return hctrl;
}
function make_style_getter(){
    var current = 0;
    var name2style = {};
    var colorlist = ['#CC0000',
                    '#990099',
                    '#3333CC',
                    '#0099FF',
                    '#29A3A3',
                    '#297A29',
                    '#33D633',
                    '#E6E600',
                    '#663300'];
    return function(name){
        if (!(name in name2style))
        {
            name2style[name] = {
                strokeWidth: 5,
                strokeColor: colorlist[current++ % 10],
                fillColor: 'black',
                cursor: 'pointer',
                fillOpacity : 0.1,
                pointRadius: 6
                };
        };
        return name2style[name];
    };
};
var default_style = {
                    strokeWidth: 3,
                    strokeColor: 'red',
                    //strokeColor: '#aaaaff',
                    cursor: 'pointer',
                    fillColor: '#aaaaff',
                    fillOpacity: 0.3,
                    pointRadius: 5
                };
var highlight_style = {
                    strokeWidth: 3,
                    strokeColor: '#555555',
                    cursor: 'pointer',
                    fillColor: '#555555',
                    fillOpacity: 0.3,
                    pointRadius: 5
                }

//
// This is the function to be called as the last argument to gnt.questionnaire.init()
function show_feedback() {

    var get_style_for_name = make_style_getter();

    var i,
        highlightCtrl,
        select_control,
        others_feature_collected = false,
        otherLayer = new OpenLayers.Layer.Vector("Others Layer", {
            styleMap: new OpenLayers.StyleMap({
                'default': default_style,
                'highlight': highlight_style
                })
        });

    otherLayer.setVisibility(false);
    map.addLayer(otherLayer);
    var other = map.getLayersByName('Others Layer')[0];
    function handle_success(data) {
        if (data.features) {
            var gf = new OpenLayers.Format.GeoJSON(),
                user,
                comment,
                i,
                feature,
                anonymous_regexp,
                popupcontent,
                source_proj = new OpenLayers.Projection(data.crs.properties.code),
                target_proj = new OpenLayers.Projection(map.getProjection());
            for (i = 0; i < data.features.length; i += 1) {
                feature = gf.parseFeature(data.features[i]);
                //add values losed in parsing should be added again
                feature['private'] = data.features[i]['private'];
                feature.geometry.transform(source_proj, target_proj);
                feature.lonlat = gnt.questionnaire.get_popup_lonlat(feature.geometry);
                feature.style = get_style_for_name(feature.attributes.name);
                other.addFeatures(feature);
                if (feature.attributes.form_values.length > 0){
                    comment = feature.attributes.form_values[0].value;
                    $('#other .comment').text(comment);
                }
                user = feature.attributes.user;
                // set the right content
                anonymous_regexp = new RegExp('T[0-9]+.[0-9]+R[0-9]+.[0-9]+');
                if (!anonymous_regexp.test(user)) {
                    $('#other .username').text(user);
                }
                //get the content
                popupcontent = $('#other').html();
                feature.popupClass = OpenLayers.Popup.FramedCloud;
                feature.popup = new OpenLayers.Popup.FramedCloud(
                    feature.id,
                    feature.lonlat,
                    null,
                    popupcontent,
                    null,
                    false
                );
            }
        }
    }
    if (others_feature_collected === false) {
        gnt.geo.get_features('@all',
                             data_group,
                             '',
            {'success': handle_success});

        others_feature_collected = true;
        other.setVisibility(true);
    } else if (others_feature_collected === true) {
        other.setVisibility(true);
    }
    highlightCtrl = makeHighlightCtrl();
    map.addControl(highlightCtrl);
    highlightCtrl.activate();
    // The select_control needs to be deactivated and activated to make
    // hover and select on different layers to work together (done by setLayer)
    select_control = map.getControl('selectcontrol');
    select_control.setLayer((select_control.layers).concat(otherLayer));

}
function make_sld_getter(){
    var current = 0;
    var colorlist = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'];
    /* REMOVE WHITESPACE BECAUSE OTHERWISE HTTP-GET WILL BE VERY LONG
var sld = '<?xml version="1.0" encoding="ISO-8859-1"?>';
sld += '';
*/
    var sld = '<?xml version="1.0" encoding="ISO-8859-1"?>';
    return function(name){
        var mysld = sld.replace('NAME',name);
        return mysld.replace('COLOR', colorlist[current++ % 10]);
    };
};
<commit_msg>fixed a bug in wms layer coloring<commit_after>
function makeHighlightCtrl() {
    hctrl = new OpenLayers.Control.SelectFeature(
        map.getControl('selectcontrol').layers.concat(map.getLayersByName('Others Layer')[0]),
        {
            hover: true,
            highlightOnly: true,
            multiple: true,
            renderIntent: 'highlight',
            eventListeners: {
                featurehighlighted: function (e) {
                    //fix for unhighlight in OpenLayers not always triggering
                    $('ul.feature_comments li:visible').fadeOut(500);
                    var feat_str = '
';
                    feat_str += e.feature.attributes.name + '
';
                    for (i = 0; i < e.feature.attributes.form_values.length; i += 1) {
                                feat_str += '
' + e.feature.attributes.form_values[i].name +
                                                 '
' +
                                                 e.feature.attributes.form_values[i].value +'
';
                            /*} else if ($('ul.feature_comments li.' + e.feature.id + ' span.comment').text() !== e.feature.attributes.form_values[i].value) {
                                $('ul.feature_comments li.' + e.feature.id + ' span.comment').text(e.feature.attributes.form_values[i].value);
                            }*/
                    }
                    var create_time_string,
                        username,
                        anonymous_regexp,
                        show_list_item = $('ul.feature_comments li.' + e.feature.id);
                        username = e.feature.attributes.user;
                        anonymous_regexp = new RegExp('T[0-9]+.[0-9]+R[0-9]+.[0-9]+');
                        if (anonymous_regexp.test(username) || username === undefined) {
                            username = '';
                        }
                        create_time_string = '';
                        if (e.feature.attributes.time !== undefined) {
                            create_time_string = $.datepicker.formatDate('D, d M yy',
                                                     $.datepicker.parseDate('yy-mm-dd', e.feature.attributes.time.create_time.split('T')[0]));
                        }
                    feat_str += '

' +
                                     username +
                                     ' ' +
                                     create_time_string + '
';
                    $('ul.feature_comments').prepend(feat_str);
                    show_list_item = $('ul.feature_comments li.' + e.feature.id);
                    show_list_item.stop(true, true);
                    show_list_item.fadeIn(500);
                },
                featureunhighlighted: function (e) {
                    var hide_list_item = $('ul.feature_comments li.' + e.feature.id);
                    hide_list_item.stop(true, true);
                    hide_list_item.fadeOut(500);
                }
            }
        }
    );
    return hctrl;
}
function make_style_getter(){
    var current = 0;
    var name2style = {};
    var colorlist = ['#CC0000',
                    '#990099',
                    '#3333CC',
                    '#0099FF',
                    '#29A3A3',
                    '#297A29',
                    '#33D633',
                    '#E6E600',
                    '#663300'];
    return function(name){
        if (!(name in name2style))
        {
            name2style[name] = {
                strokeWidth: 5,
                strokeColor: colorlist[current++ % 10],
                fillColor: 'black',
                cursor: 'pointer',
                fillOpacity : 0.1,
                pointRadius: 6
                };
        };
        return name2style[name];
    };
};
var default_style = {
                    strokeWidth: 3,
                    strokeColor: 'red',
                    //strokeColor: '#aaaaff',
                    cursor: 'pointer',
                    fillColor: '#aaaaff',
                    fillOpacity: 0.3,
                    pointRadius: 5
                };
var highlight_style = {
                    strokeWidth: 3,
                    strokeColor: '#555555',
                    cursor: 'pointer',
                    fillColor: '#555555',
                    fillOpacity: 0.3,
                    pointRadius: 5
                }

//
// This is the function to be called as the last argument to gnt.questionnaire.init()
function show_feedback() {

    var get_style_for_name = make_style_getter();

    var i,
        highlightCtrl,
        select_control,
        others_feature_collected = false,
        otherLayer = new OpenLayers.Layer.Vector("Others Layer", {
            styleMap: new OpenLayers.StyleMap({
                'default': default_style,
                'highlight': highlight_style
                })
        });

    otherLayer.setVisibility(false);
    map.addLayer(otherLayer);
    var other = map.getLayersByName('Others Layer')[0];
    function handle_success(data) {
        if (data.features) {
            var gf = new OpenLayers.Format.GeoJSON(),
                user,
                comment,
                i,
                feature,
                anonymous_regexp,
                popupcontent,
                source_proj = new OpenLayers.Projection(data.crs.properties.code),
                target_proj = new OpenLayers.Projection(map.getProjection());
            for (i = 0; i < data.features.length; i += 1) {
                feature = gf.parseFeature(data.features[i]);
                //add values losed in parsing should be added again
                feature['private'] = data.features[i]['private'];
                feature.geometry.transform(source_proj, target_proj);
                feature.lonlat = gnt.questionnaire.get_popup_lonlat(feature.geometry);
                feature.style = get_style_for_name(feature.attributes.name);
                other.addFeatures(feature);
                if (feature.attributes.form_values.length > 0){
                    comment = feature.attributes.form_values[0].value;
                    $('#other .comment').text(comment);
                }
                user = feature.attributes.user;
                // set the right content
                anonymous_regexp = new RegExp('T[0-9]+.[0-9]+R[0-9]+.[0-9]+');
                if (!anonymous_regexp.test(user)) {
                    $('#other .username').text(user);
                }
                //get the content
                popupcontent = $('#other').html();
                feature.popupClass = OpenLayers.Popup.FramedCloud;
                feature.popup = new OpenLayers.Popup.FramedCloud(
                    feature.id,
                    feature.lonlat,
                    null,
                    popupcontent,
                    null,
                    false
                );
            }
        }
    }
    if (others_feature_collected === false) {
        gnt.geo.get_features('@all',
                             data_group,
                             '',
            {'success': handle_success});

        others_feature_collected = true;
        other.setVisibility(true);
    } else if (others_feature_collected === true) {
        other.setVisibility(true);
    }
    highlightCtrl = makeHighlightCtrl();
    map.addControl(highlightCtrl);
    highlightCtrl.activate();
    // The select_control needs to be deactivated and activated to make
    // hover and select on different layers to work together (done by setLayer)
    select_control = map.getControl('selectcontrol');
    select_control.setLayer((select_control.layers).concat(otherLayer));

}
function make_sld_getter(){
    /* REMOVE WHITESPACE BECAUSE OTHERWISE HTTP-GET WILL BE VERY LONG
var sld = '<?xml version="1.0" encoding="ISO-8859-1"?>';
sld += '';
*/
    var current = 0;
    var name2color = {};
    var colorlist = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'];
    var sld = '<?xml version="1.0" encoding="ISO-8859-1"?>';
    return function(name){
        var color = '';
        if (!(name in name2color)) {
            color = colorlist[current++ % 10];
            name2color[name] = color;
        } else {
            color = name2color[name];
        }
        var mysld = sld.replace('NAME',name);
        return mysld.replace('COLOR', color);
    };
};
<|endoftext|>


<commit_before>class AsyncChrome
{
  static get tabs() {
    return AsyncTabs;
  }

  static get windows() {
    return AsyncWindows;
  }

  static get notifications() {
    return AsyncNotifications;
  }

  static get storage() {
    return AsyncStorage;
  }
}

class AsyncTabs
{
  static async create(options) {
    return new Promise((resolve, reject) => {
      chrome.tabs.create(options, tab => resolve(tab));
    });
  }

  static async getCurrent() {
    return new Promise((resolve, reject) => {
      chrome.tabs.getCurrent(tab => resolve(tab));
    });
  }

  static async update(tabId, updateProperties) {
    return new Promise((resolve, reject) => {
      chrome.tabs.update(tabId, updateProperties, tab => resolve(tab));
    });
  }
}

class AsyncWindows
{
  static async update(windowId, updateInfo) {
    return new Promise((resolve, reject) => {
      chrome.windows.update(windowId, updateInfo, win => resolve(win));
    });
  }
}

class AsyncNotifications
{
  static async create(options) {
    return new Promise((resolve, reject) => {
      chrome.notifications.create('', options, id => resolve(id));
    });
  }
}

class AsyncStorage
{
  constructor(store) {
    this.store = store;
  }

  get(keys = null) {
    return new Promise((resolve, reject) => {
      this.store.get(keys, result => resolve(result));
    });
  }

  set(obj) {
    return new Promise((resolve, reject) => {
      this.store.set(obj, () => resolve());
    });
  }

  clear() {
    return new Promise((resolve, reject) => {
      this.store.clear(() => resolve());
    });
  }

  static get sync() {
    if (!this._sync) {
      this._sync = new AsyncStorage(chrome.storage.sync);
    }
    return this._sync;
  }

  static get local() {
    if (!this._local) {
      this._local = new AsyncStorage(chrome.storage.local);
    }
    return this._local;
  }
}
<commit_msg>First pass at fixing notifications in Firefox.<commit_after>class AsyncChrome
{
  static get tabs() {
    return AsyncTabs;
  }

  static get windows() {
    return AsyncWindows;
  }

  static get notifications() {
    return AsyncNotifications;
  }

  static get storage() {
    return AsyncStorage;
  }
}

class AsyncTabs
{
  static async create(options) {
    return new Promise((resolve, reject) => {
      chrome.tabs.create(options, tab => resolve(tab));
    });
  }

  static async getCurrent() {
    return new Promise((resolve, reject) => {
      chrome.tabs.getCurrent(tab => resolve(tab));
    });
  }

  static async update(tabId, updateProperties) {
    return new Promise((resolve, reject) => {
      chrome.tabs.update(tabId, updateProperties, tab => resolve(tab));
    });
  }
}

class AsyncWindows
{
  static async update(windowId, updateInfo) {
    return new Promise((resolve, reject) => {
      chrome.windows.update(windowId, updateInfo, win => resolve(win));
    });
  }
}

class AsyncNotifications
{
  static async create(options) {
    return new Promise((resolve, reject) => {
      try {
        chrome.notifications.create('', options, id => resolve(id));
      } catch (e) {
        // This is failing on Firefox as it doesn't support the buttons option for the notification and raises an exception when this is called. (see http://bugzil.la/1190681)
        // Try again with a subset of options that are more broadly supported
        chrome.notifications.create('', AsyncNotifications.compatible_options(options), id => resolve(id));
      }
    });
  }

  static compatible_options(options) {
    return {
      type: options.type,
      iconUrl: options.iconUrl,
      title: options.title,
      message: options.message
    };
  }
}

class AsyncStorage
{
  constructor(store) {
    this.store = store;
  }

  get(keys = null) {
    return new Promise((resolve, reject) => {
      this.store.get(keys, result => resolve(result));
    });
  }

  set(obj) {
    return new Promise((resolve, reject) => {
      this.store.set(obj, () => resolve());
    });
  }

  clear() {
    return new Promise((resolve, reject) => {
      this.store.clear(() => resolve());
    });
  }

  static get sync() {
    if (!this._sync) {
      this._sync = new AsyncStorage(chrome.storage.sync);
    }
    return this._sync;
  }

  static get local() {
    if (!this._local) {
      this._local = new AsyncStorage(chrome.storage.local);
    }
    return this._local;
  }
}
<|endoftext|>


<commit_before>mi2JS.addCompClass('base/RenderTable', 'Base', '<-TEMPLATE->',

// component initializer function that defines constructor and adds methods to the prototype
function(proto, superProto, comp, superComp){
/*
data sample:  { offset:5, limit:5, rowcount:25, data: [{},{},{},{},{}] }
*/
	var $ = mi2JS;
	var h = $.h;
	var t = $.t;

	proto.isTransitive = function(){ return true; };

	proto.construct = function(el, parent){
		superProto.construct.call(this, el, parent);

		this.hideHeader = this.attr("hide_header");
		this.showPager = this.attr('show_pager') || 'when_needed';// when_neede / always
		this.opts = {};
		this.sortInline = true;

	};

	proto.initChildren = function(){
		superProto.initChildren.call(this);
		// if(this.noData)
			this.noData.innerHTML = t(this.attrDef("no_data","no_data_to_display"));

		this.listen(this.tbody.el,"click",this.rowClick);

	};

	function tdFunc(func,def){
		if(!func) return def;
		if(typeof(func) == "string") return function(tr,td){td.innerHTML = func;};
		return func;
	}

	function defRender(tr,td,code, data, table){
		td.innerHTML = data[code] === null || typeof(data[code]) == 'undefined' ? '' : data[code];
	}

	// NOTE:
	// different order would be more optimizer friendly based on use
	// also order in Array forEach is like the first 3 params
	// function defTitle(value, idx, data, td, tr, table)
	function defTitle(tr,th,code,table){
		th.innerHTML = table.opts.title  ? t(table.opts.title) : t(code);
	}

	proto.defCompareFunc = function(d1,d2, code){
		if(d1[code] == d2[code]) return 0;
		if(d1[code] <  d2[code]) return -1;
		return 1;
	};

	function makeCompareFunc(self,func,code){
		return function(d1,d2){
			return func.call(self,d1,d2,code);
		};
	}

	proto.setup = function(optsIn){
		this.pager1.setup(optsIn.pager || {});
		this.pager2.setup(optsIn.pager || {});
		this.cols = optsIn.columns || {};
		this.pager1.setVisible(optsIn.pager != 'hidden');
		this.pager2.setVisible(optsIn.pager != 'hidden');
		var i=0;
		this.thead.el.innerHTML = '';
		var tr = $.addTag(this.thead.el, "TR");
		var tr2 = $.addTag(this.thead.el,"TR");
		var span = 0;
		for(var code in this.cols){
			var th = $.addTag(tr,"TH");
			var opts = this.cols[code];
			th.code = code;
			if(opts.compareFunc) opts.sortable = 1;
			if(!opts.hasOwnProperty('sortable')) opts.sortable = optsIn.sortable;
			th.sortable =  opts.sortable;
			th.className = 'cell_'+code;
			if(opts.sortable){
				th.className = 'cell_'+code+" sortable";
				opts.compareFunc = opts.compareFunc || this.defCompareFunc;
			}

			opts.code = code; opts.index = i;

			opts.th = tdFunc(opts.th, defTitle);
			opts.td = opts.td || defRender;
			opts.th(tr,th,code,this);
			if(opts.group){
				var thGroup = $.addTag(tr,{tag:"TH",attr:{'class':"group"}});
				thGroup.innerHTML = opts.group.title;
				span = opts.group.span || 2;
				thGroup.setAttribute("colspan",span);
				$(th).addClass('group_first');
			}

			if(span > 0){
				if(span == 1) $(th).addClass('group_last');
				tr2.appendChild(th);
			}

			else th.setAttribute("rowspan",2);

			i++;
			span--;
		}
		if(this.hideHeader) this.thead.setVisible(false);
		this.listen(this.thead.el,"click",this.theadClick);
	};

	proto.theadClick = function(evt){
		var th = evt.target; if(th.tagName != 'TH') th = $.parent(th,'TH');
		// if it was asc(ascending) before the click, we will reverse it
		// this is also when is not sorted as well
		var asc = !$(th).hasClass("asc");
		this.sortBy(th,asc);
	};

	proto._findTh = function(code){
		var tr = this.thead.find('TR');
		while(tr){
			var tmp = tr.firstElementChild;
			while(tmp){
				if(tmp.code == code && tmp.tagName == 'TH') return tmp;
				tmp = tmp.nextElementSibling;
			}
			tr = tr.nextElementSibling;
		}
		return null;
	};

	proto.sortBy = function(th,asc){
		if(typeof th == "string") {
			var thNode = this._findTh(th);
			if(!thNode) return console.error('column not found '+th);
			else th = thNode;
		}
		this.sortColumn = th;
		if(th.sortable){
			var opts = this.cols[th.code];
			if(opts.compareFunc){
				var tmp = th.parentNode.firstElementChild;
				while(tmp){
					if(tmp.sortable) tmp.className = "sortable";
					tmp = tmp.nextElementSibling;
				}
				th.className = asc ? "sortable asc": "sortable desc";
				this.applySort(th,opts, asc);
			}
		}
	};

	proto.applySort = function(th, opts, asc){
		if(this.tData && this.tData.data){
			this.tData.data.sort(makeCompareFunc(this, opts.compareFunc,th.code));
			if(!asc) this.tData.data.reverse();
		}
		if(this.tData)
			this.__update(this.tData);
	};

	proto.update = function(tData){
		this.tData = tData;

		if(this.sortColumn && this.sortInline){
			var asc = !$(this.sortColumn).hasClass("desc");
			this.sortBy(this.sortColumn,asc);
		}
		this.__update(tData);
		if(this.toBlink) this.blink(this.toBlink.col, this.toBlink.value);
	};

	proto.__update = function(tData){
		var empty = tData.data.length == 0;
		this.noData.setVisible(empty);
		this.dataArea.setVisible(!empty);

		this.pager1.update(tData);
		this.pager2.update(tData);

		this.pager1.setVisible(this.showPager == 'always' ||  this.pager1.pagesCount > 1);
		this.pager2.setVisible(this.showPager == 'always' ||  this.pager2.pagesCount > 1);

		this.tbody.setHtml('');
		for(var i=0; i<tData.data.length; i++){
			var row = tData.data[i];
			var tr = $.addTag(this.tbody.el,{tag:"TR",attr:{'class':"high"}});
			tr.data = tData.data[i];
			for(var code in this.cols){
				var td = $.addTag(tr,{tag:"TD",attr:{'class':'cell_'+code}});
				td.code = code;
				var opts = this.cols[code];
				var def = opts.td(tr,td,code,tr.data,this);

				if(opts.render){
					def = opts.render(tr.data[code], code, tr.data, mi2JS.h, mi2JS.t, this);
					if(typeof(def) == 'string') td.innerHTML = def;
				}
				if(def instanceof mi2JS.TagDef){
					if(def.tag == 'td'){
						mi2JS.vdiffNode(td, def,true);
					}else{
						var chDef = def.children;
						if(def.tagName != 'frag'){
							chDef = [def];
						}
						mi2JS.vdiffChildren(td, chDef);
					}
				}
			}
//			this.tbody ... save list
		}
	};

	proto.makeBt = function(action, text, param){
		return ''+t(text || action)+'';
	};

	proto.prepBlink = function(col, value){
		this.blink(col,value, true);
	};

	proto.blink = function(col, value, isBefore){

		if(isBefore) this.toBlink = {col:col, value: value};
		else this.toBlink = null;

		var tr = this.tbody.firstElementChild;
		while(tr){
			if(tr.tagName == 'TR' && tr.data[col] == value){
				if(isBefore){
					tr.style.opacity = '0';
				}else
					$(tr).fadeIn();
				return;
			}
			tr = tr.nextElementSibling;
		}
	};

	proto.rowClick = function(evt, mouseDown){
		var bt = evt.target;
		var td = bt;
		var action = null, param = null;
		while(td.tagName != 'TD' && !action ){
			action = td.getAttribute('action');
			param = td.getAttribute('param');
			td=td.parentNode;
		}
		if(! action ) action = 'edit';
		var tr = td.parentNode;
		this.fireEvent({
			name:'rowClick',
			fireTo:'parent',
			tr: tr,
			data:tr.data,
			td:td,
			code:td.code,
			action:action,
			param:param,
			src:this,
			domEvent: evt
		});
	};

});

<commit_msg>Update RenderTable.js<commit_after>mi2JS.addCompClass('base/RenderTable', 'Base', '<-TEMPLATE->',

// component initializer function that defines constructor and adds methods to the prototype
function(proto, superProto, comp, superComp){
/*
data sample:  { offset:5, limit:5, rowcount:25, data: [{},{},{},{},{}] }
*/
	var $ = mi2JS;
	var h = $.h;
	var t = $.t;

	proto.isTransitive = function(){ return true; };

	proto.construct = function(el, parent){
		superProto.construct.call(this, el, parent);
	};

	proto.initChildren = function(){
		superProto.initChildren.call(this);

		this.hideHeader = this.attr("hide_header");
		this.showPager = this.attr('show_pager') || 'when_needed';// when_neede / always
		this.opts = {};
		this.sortInline = true;

		// if(this.noData)
			this.noData.innerHTML = t(this.attrDef("no_data","no_data_to_display"));

		this.listen(this.tbody.el,"click",this.rowClick);

	};

	function tdFunc(func,def){
		if(!func) return def;
		if(typeof(func) == "string") return function(tr,td){td.innerHTML = func;};
		return func;
	}

	function defRender(tr,td,code, data, table){
		td.innerHTML = data[code] === null || typeof(data[code]) == 'undefined' ? '' : data[code];
	}

	// NOTE:
	// different order would be more optimizer friendly based on use
	// also order in Array forEach is like the first 3 params
	// function defTitle(value, idx, data, td, tr, table)
	function defTitle(tr,th,code,table){
		th.innerHTML = table.opts.title  ? t(table.opts.title) : t(code);
	}

	proto.defCompareFunc = function(d1,d2, code){
		if(d1[code] == d2[code]) return 0;
		if(d1[code] <  d2[code]) return -1;
		return 1;
	};

	function makeCompareFunc(self,func,code){
		return function(d1,d2){
			return func.call(self,d1,d2,code);
		};
	}

	proto.setup = function(optsIn){
		this.pager1.setup(optsIn.pager || {});
		this.pager2.setup(optsIn.pager || {});
		this.cols = optsIn.columns || {};
		this.pager1.setVisible(optsIn.pager != 'hidden');
		this.pager2.setVisible(optsIn.pager != 'hidden');
		var i=0;
		this.thead.el.innerHTML = '';
		var tr = $.addTag(this.thead.el, "TR");
		var tr2 = $.addTag(this.thead.el,"TR");
		var span = 0;
		for(var code in this.cols){
			var th = $.addTag(tr,"TH");
			var opts = this.cols[code];
			th.code = code;
			if(opts.compareFunc) opts.sortable = 1;
			if(!opts.hasOwnProperty('sortable')) opts.sortable = optsIn.sortable;
			th.sortable =  opts.sortable;
			th.className = 'cell_'+code;
			if(opts.sortable){
				th.className = 'cell_'+code+" sortable";
				opts.compareFunc = opts.compareFunc || this.defCompareFunc;
			}

			opts.code = code; opts.index = i;

			opts.th = tdFunc(opts.th, defTitle);
			opts.td = opts.td || defRender;
			opts.th(tr,th,code,this);
			if(opts.group){
				var thGroup = $.addTag(tr,{tag:"TH",attr:{'class':"group"}});
				thGroup.innerHTML = opts.group.title;
				span = opts.group.span || 2;
				thGroup.setAttribute("colspan",span);
				$(th).addClass('group_first');
			}

			if(span > 0){
				if(span == 1) $(th).addClass('group_last');
				tr2.appendChild(th);
			}

			else th.setAttribute("rowspan",2);

			i++;
			span--;
		}
		if(this.hideHeader) this.thead.setVisible(false);
		this.listen(this.thead.el,"click",this.theadClick);
	};

	proto.theadClick = function(evt){
		var th = evt.target; if(th.tagName != 'TH') th = $.parent(th,'TH');
		// if it was asc(ascending) before the click, we will reverse it
		// this is also when is not sorted as well
		var asc = !$(th).hasClass("asc");
		this.sortBy(th,asc);
	};

	proto._findTh = function(code){
		var tr = this.thead.find('TR');
		while(tr){
			var tmp = tr.firstElementChild;
			while(tmp){
				if(tmp.code == code && tmp.tagName == 'TH') return tmp;
				tmp = tmp.nextElementSibling;
			}
			tr = tr.nextElementSibling;
		}
		return null;
	};

	proto.sortBy = function(th,asc){
		if(typeof th == "string") {
			var thNode = this._findTh(th);
			if(!thNode) return console.error('column not found '+th);
			else th = thNode;
		}
		this.sortColumn = th;
		if(th.sortable){
			var opts = this.cols[th.code];
			if(opts.compareFunc){
				var tmp = th.parentNode.firstElementChild;
				while(tmp){
					if(tmp.sortable) tmp.className = "sortable";
					tmp = tmp.nextElementSibling;
				}
				th.className = asc ? "sortable asc": "sortable desc";
				this.applySort(th,opts, asc);
			}
		}
	};

	proto.applySort = function(th, opts, asc){
		if(this.tData && this.tData.data){
			this.tData.data.sort(makeCompareFunc(this, opts.compareFunc,th.code));
			if(!asc) this.tData.data.reverse();
		}
		if(this.tData)
			this.__update(this.tData);
	};

	proto.update = function(tData){
		this.tData = tData;

		if(this.sortColumn && this.sortInline){
			var asc = !$(this.sortColumn).hasClass("desc");
			this.sortBy(this.sortColumn,asc);
		}
		this.__update(tData);
		if(this.toBlink) this.blink(this.toBlink.col, this.toBlink.value);
	};

	proto.__update = function(tData){
		var empty = tData.data.length == 0;
		this.noData.setVisible(empty);
		this.dataArea.setVisible(!empty);

		this.pager1.update(tData);
		this.pager2.update(tData);

		this.pager1.setVisible(this.showPager == 'always' ||  this.pager1.pagesCount > 1);
		this.pager2.setVisible(this.showPager == 'always' ||  this.pager2.pagesCount > 1);

		this.tbody.setHtml('');
		for(var i=0; i<tData.data.length; i++){
			var row = tData.data[i];
			var tr = $.addTag(this.tbody.el,{tag:"TR",attr:{'class':"high"}});
			tr.data = tData.data[i];
			for(var code in this.cols){
				var td = $.addTag(tr,{tag:"TD",attr:{'class':'cell_'+code}});
				td.code = code;
				var opts = this.cols[code];
				var def = opts.td(tr,td,code,tr.data,this);

				if(opts.render){
					def = opts.render(tr.data[code], code, tr.data, mi2JS.h, mi2JS.t, this);
					if(typeof(def) == 'string') td.innerHTML = def;
				}
				if(def instanceof mi2JS.TagDef){
					if(def.tag == 'td'){
						mi2JS.vdiffNode(td, def,true);
					}else{
						var chDef = def.children;
						if(def.tagName != 'frag'){
							chDef = [def];
						}
						mi2JS.vdiffChildren(td, chDef);
					}
				}
			}
//			this.tbody ... save list
		}
	};

	proto.makeBt = function(action, text, param){
		return ''+t(text || action)+'';
	};

	proto.prepBlink = function(col, value){
		this.blink(col,value, true);
	};

	proto.blink = function(col, value, isBefore){

		if(isBefore) this.toBlink = {col:col, value: value};
		else this.toBlink = null;

		var tr = this.tbody.firstElementChild;
		while(tr){
			if(tr.tagName == 'TR' && tr.data[col] == value){
				if(isBefore){
					tr.style.opacity = '0';
				}else
					$(tr).fadeIn();
				return;
			}
			tr = tr.nextElementSibling;
		}
	};

	proto.rowClick = function(evt, mouseDown){
		var bt = evt.target;
		var td = bt;
		var action = null, param = null;
		while(td.tagName != 'TD' && !action ){
			action = td.getAttribute('action');
			param = td.getAttribute('param');
			td=td.parentNode;
		}
		if(! action ) action = 'edit';
		var tr = td.parentNode;
		this.fireEvent({
			name:'rowClick',
			fireTo:'parent',
			tr: tr,
			data:tr.data,
			td:td,
			code:td.code,
			action:action,
			param:param,
			src:this,
			domEvent: evt
		});
	};

});

<|endoftext|>


<commit_before>
function calendarHeatmap() {
  // defaults
  var width = 750;
  var height = 110;
  var legendWidth = 150;
  var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  var days = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
  var selector = 'body';
  var SQUARE_LENGTH = 11;
  var SQUARE_PADDING = 2;
  var MONTH_LABEL_PADDING = 6;
  var now = moment().endOf('day').toDate();
  var yearAgo = moment().startOf('day').subtract(1, 'year').toDate();
  var data = [];
  var colorRange = ['#D8E6E7', '#218380'];
  var tooltipEnabled = true;
  var tooltipUnit = 'contribution';
  var legendEnabled = true;
  var onClick = null;

  // setters and getters
  chart.data = function (value) {
    if (!arguments.length) { return data; }
    data = value;
    return chart;
  };

  chart.selector = function (value) {
    if (!arguments.length) { return selector; }
    selector = value;
    return chart;
  };

  chart.colorRange = function (value) {
    if (!arguments.length) { return colorRange; }
    colorRange = value;
    return chart;
  };

  chart.tooltipEnabled = function (value) {
    if (!arguments.length) { return tooltipEnabled; }
    tooltipEnabled = value;
    return chart;
  };

  chart.tooltipUnit = function (value) {
    if (!arguments.length) { return tooltipUnit; }
    tooltipUnit = value;
    return chart;
  };

  chart.legendEnabled = function (value) {
    if (!arguments.length) { return legendEnabled; }
    legendEnabled = value;
    return chart;
  };

  chart.onClick = function (value) {
    if (!arguments.length) { return onClick(); }
    onClick = value;
    return chart;
  };

  function chart() {

    d3.select(chart.selector()).selectAll('svg.calendar-heatmap').remove(); // remove the existing chart, if it exists

    var dateRange = d3.time.days(yearAgo, now); // generates an array of date objects within the specified range
    var monthRange = d3.time.months(moment(yearAgo).startOf('month').toDate(), now); // it ignores the first month if the 1st date is after the start of the month
    var firstDate = moment(dateRange[0]);
    var max = d3.max(chart.data(), function (d) { return d.count; }); // max data value

    // color range
    var color = d3.scale.linear()
      .range(chart.colorRange())
      .domain([0, max]);

    var tooltip;
    var dayRects;

    drawChart();

    function drawChart() {
      var svg = d3.select(chart.selector())
        .append('svg')
        .attr('width', width)
        .attr('class', 'calendar-heatmap')
        .attr('height', height)
        .style('padding', '36px');

      dayRects = svg.selectAll('.day-cell')
        .data(dateRange);  //  array of days for the last yr

      dayRects.enter().append('rect')
        .attr('class', 'day-cell')
        .attr('width', SQUARE_LENGTH)
        .attr('height', SQUARE_LENGTH)
        .attr('fill', 'gray')
        .attr('x', function (d, i) {
          var cellDate = moment(d);
          var result = cellDate.week() - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
          return result * (SQUARE_LENGTH + SQUARE_PADDING);
        })
        .attr('y', function (d, i) { return MONTH_LABEL_PADDING + d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING); });

      if (typeof onClick === 'function') {
        dayRects.on('click', function (d) {
          var count = countForDate(d);
          onClick({ date: d, count: count});
        });
      }

      if (chart.tooltipEnabled()) {
        dayRects.on('mouseover', function (d, i) {
          tooltip = d3.select(chart.selector())
            .append('div')
            .attr('class', 'day-cell-tooltip')
            .html(tooltipHTMLForDate(d))
            .style('left', function () { return Math.floor(i / 7) * SQUARE_LENGTH + 'px'; })
            .style('top', function () { return d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING) + MONTH_LABEL_PADDING * 3 + 'px'; });
        })
        .on('mouseout', function (d, i) {
          tooltip.remove();
        });
      }

      if (chart.legendEnabled()) {
        var colorRange = [color(0)];
        for (var i = 3; i > 0; i--) {
          colorRange.push(color(max / i));
        }

        var legendGroup = svg.append('g');
        legendGroup.selectAll('.calendar-heatmap-legend')
            .data(colorRange)
            .enter()
          .append('rect')
            .attr('class', 'calendar-heatmap-legend')
            .attr('width', SQUARE_LENGTH)
            .attr('height', SQUARE_LENGTH)
            .attr('x', function (d, i) { return (width - legendWidth) + (i + 1) * 13; })
            .attr('y', height + SQUARE_PADDING)
            .attr('fill', function (d) { return d; });

        legendGroup.append('text')
          .attr('class', 'calendar-heatmap-legend-text')
          .attr('x', width - legendWidth - 13)
          .attr('y', height + SQUARE_LENGTH)
          .text('Less');

        legendGroup.append('text')
          .attr('class', 'calendar-heatmap-legend-text')
          .attr('x', (width - legendWidth + SQUARE_PADDING) + (colorRange.length + 1) * 13)
          .attr('y', height + SQUARE_LENGTH)
          .text('More');
      }

      dayRects.exit().remove();
      var monthLabels = svg.selectAll('.month')
          .data(monthRange)
          .enter().append('text')
          .attr('class', 'month-name')
          .style()
          .text(function (d) {
            return months[d.getMonth()];
          })
          .attr('x', function (d, i) {
            var matchIndex = 0;
            dateRange.find(function (element, index) {
              matchIndex = index;
              return moment(d).isSame(element, 'month') && moment(d).isSame(element, 'year');
            });

            return Math.floor(matchIndex / 7) * (SQUARE_LENGTH + SQUARE_PADDING);
          })
          .attr('y', 0);  // fix these to the top

      days.forEach(function (day, index) {
        if (index % 2) {
          svg.append('text')
            .attr('class', 'day-initial')
            .attr('transform', 'translate(-8,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
            .style('text-anchor', 'middle')
            .attr('dy', '2')
            .text(day);
        }
      });
    }

    function tooltipHTMLForDate(d) {
      var dateStr = moment(d).format('ddd, MMM Do YYYY');
      var count = countForDate(d);
      return '' + (count ? count : 'No') + ' ' + tooltipUnit + (count === 1 ? '' : 's') + ' on ' + dateStr + '';
    }

    function countForDate(d) {
      var count = 0;
      var match = chart.data().find(function (element, index) {
        return moment(element.date).isSame(d, 'day');
      });
      if (match) {
        count = match.count;
      }
      return count;
    }

    var daysOfChart = chart.data().map(function (day) {
      return day.date.toDateString();
    });

    dayRects.filter(function (d) {
      return daysOfChart.indexOf(d.toDateString()) > -1;
    }).attr('fill', function (d, i) {
      return color(chart.data()[i].count);
    });
  }

  return chart;
}

// polyfill for Array.find() method
/* jshint ignore:start */
if (!Array.prototype.find) {
  Array.prototype.find = function (predicate) {
    if (this === null) {
      throw new TypeError('Array.prototype.find called on null or undefined');
    }
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    var list = Object(this);
    var length = list.length >>> 0;
    var thisArg = arguments[1];
    var value;

    for (var i = 0; i < length; i++) {
      value = list[i];
      if (predicate.call(thisArg, value, i, list)) {
        return value;
      }
    }
    return undefined;
  };
}
/* jshint ignore:end */
<commit_msg>week day start feature<commit_after>
function calendarHeatmap() {
  // defaults
  var width = 750;
  var height = 110;
  var legendWidth = 150;
  var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  var days = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
  var selector = 'body';
  var SQUARE_LENGTH = 11;
  var SQUARE_PADDING = 2;
  var MONTH_LABEL_PADDING = 6;
  var now = moment().endOf('day').toDate();
  var yearAgo = moment().startOf('day').subtract(1, 'year').toDate();
  var data = [];
  var colorRange = ['#D8E6E7', '#218380'];
  var tooltipEnabled = true;
  var tooltipUnit = 'contribution';
  var legendEnabled = true;
  var onClick = null;
  var weekStart = 0; //0 for Sunday, 1 for Monday

  // setters and getters
  chart.data = function (value) {
    if (!arguments.length) { return data; }
    data = value;
    return chart;
  };

  chart.selector = function (value) {
    if (!arguments.length) { return selector; }
    selector = value;
    return chart;
  };

  chart.colorRange = function (value) {
    if (!arguments.length) { return colorRange; }
    colorRange = value;
    return chart;
  };

  chart.tooltipEnabled = function (value) {
    if (!arguments.length) { return tooltipEnabled; }
    tooltipEnabled = value;
    return chart;
  };

  chart.tooltipUnit = function (value) {
    if (!arguments.length) { return tooltipUnit; }
    tooltipUnit = value;
    return chart;
  };

  chart.legendEnabled = function (value) {
    if (!arguments.length) { return legendEnabled; }
    legendEnabled = value;
    return chart;
  };

  chart.onClick = function (value) {
    if (!arguments.length) { return onClick(); }
    onClick = value;
    return chart;
  };

  function chart() {

    d3.select(chart.selector()).selectAll('svg.calendar-heatmap').remove(); // remove the existing chart, if it exists

    var dateRange = d3.time.days(yearAgo, now); // generates an array of date objects within the specified range
    var monthRange = d3.time.months(moment(yearAgo).startOf('month').toDate(), now); // it ignores the first month if the 1st date is after the start of the month
    var firstDate = moment(dateRange[0]);
    var max = d3.max(chart.data(), function (d) { return d.count; }); // max data value

    // color range
    var color = d3.scale.linear()
      .range(chart.colorRange())
      .domain([0, max]);

    var tooltip;
    var dayRects;

    drawChart();

    function drawChart() {
      var svg = d3.select(chart.selector())
        .append('svg')
        .attr('width', width)
        .attr('class', 'calendar-heatmap')
        .attr('height', height)
        .style('padding', '36px');

      dayRects = svg.selectAll('.day-cell')
        .data(dateRange);  //  array of days for the last yr

      dayRects.enter().append('rect')
        .attr('class', 'day-cell')
        .attr('width', SQUARE_LENGTH)
        .attr('height', SQUARE_LENGTH)
        .attr('fill', 'gray')
        .attr('x', function (d, i) {
          var cellDate = moment(d);
          var result = cellDate.week() - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
          return result * (SQUARE_LENGTH + SQUARE_PADDING);
        })
        .attr('y', function (d, i) {
          var weekDay = d.getDay();
          if (weekStart === 1) {
            if (weekDay === 0) {
              weekDay = 6;
            } else {
              weekDay = weekDay - 1;
            }
          }
          return MONTH_LABEL_PADDING + weekDay * (SQUARE_LENGTH + SQUARE_PADDING);
        });

      if (typeof onClick === 'function') {
        dayRects.on('click', function (d) {
          var count = countForDate(d);
          onClick({ date: d, count: count});
        });
      }

      if (chart.tooltipEnabled()) {
        dayRects.on('mouseover', function (d, i) {
          tooltip = d3.select(chart.selector())
            .append('div')
            .attr('class', 'day-cell-tooltip')
            .html(tooltipHTMLForDate(d))
            .style('left', function () { return Math.floor(i / 7) * SQUARE_LENGTH + 'px'; })
            .style('top', function () { return d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING) + MONTH_LABEL_PADDING * 3 + 'px'; });
        })
        .on('mouseout', function (d, i) {
          tooltip.remove();
        });
      }

      if (chart.legendEnabled()) {
        var colorRange = [color(0)];
        for (var i = 3; i > 0; i--) {
          colorRange.push(color(max / i));
        }

        var legendGroup = svg.append('g');
        legendGroup.selectAll('.calendar-heatmap-legend')
            .data(colorRange)
            .enter()
          .append('rect')
            .attr('class', 'calendar-heatmap-legend')
            .attr('width', SQUARE_LENGTH)
            .attr('height', SQUARE_LENGTH)
            .attr('x', function (d, i) { return (width - legendWidth) + (i + 1) * 13; })
            .attr('y', height + SQUARE_PADDING)
            .attr('fill', function (d) { return d; });

        legendGroup.append('text')
          .attr('class', 'calendar-heatmap-legend-text')
          .attr('x', width - legendWidth - 13)
          .attr('y', height + SQUARE_LENGTH)
          .text('Less');

        legendGroup.append('text')
          .attr('class', 'calendar-heatmap-legend-text')
          .attr('x', (width - legendWidth + SQUARE_PADDING) + (colorRange.length + 1) * 13)
          .attr('y', height + SQUARE_LENGTH)
          .text('More');
      }

      dayRects.exit().remove();
      var monthLabels = svg.selectAll('.month')
          .data(monthRange)
          .enter().append('text')
          .attr('class', 'month-name')
          .style()
          .text(function (d) {
            return months[d.getMonth()];
          })
          .attr('x', function (d, i) {
            var matchIndex = 0;
            dateRange.find(function (element, index) {
              matchIndex = index;
              return moment(d).isSame(element, 'month') && moment(d).isSame(element, 'year');
            });

            return Math.floor(matchIndex / 7) * (SQUARE_LENGTH + SQUARE_PADDING);
          })
          .attr('y', 0);  // fix these to the top

      days.forEach(function (day, index) {
        if (weekStart === 1) {
          if (index === 0) {
            index = 6;
          } else {
            index = index - 1;
          }
        }
        if (index % 2) {
          svg.append('text')
            .attr('class', 'day-initial')
            .attr('transform', 'translate(-8,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
            .style('text-anchor', 'middle')
            .attr('dy', '2')
            .text(day);
        }
      });
    }

    function tooltipHTMLForDate(d) {
      var dateStr = moment(d).format('ddd, MMM Do YYYY');
      var count = countForDate(d);
      return '' + (count ? count : 'No') + ' ' + tooltipUnit + (count === 1 ? '' : 's') + ' on ' + dateStr + '';
    }

    function countForDate(d) {
      var count = 0;
      var match = chart.data().find(function (element, index) {
        return moment(element.date).isSame(d, 'day');
      });
      if (match) {
        count = match.count;
      }
      return count;
    }

    var daysOfChart = chart.data().map(function (day) {
      return day.date.toDateString();
    });

    dayRects.filter(function (d) {
      return daysOfChart.indexOf(d.toDateString()) > -1;
    }).attr('fill', function (d, i) {
      return color(chart.data()[i].count);
    });
  }

  return chart;
}

// polyfill for Array.find() method
/* jshint ignore:start */
if (!Array.prototype.find) {
  Array.prototype.find = function (predicate) {
    if (this === null) {
      throw new TypeError('Array.prototype.find called on null or undefined');
    }
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    var list = Object(this);
    var length = list.length >>> 0;
    var thisArg = arguments[1];
    var value;

    for (var i = 0; i < length; i++) {
      value = list[i];
      if (predicate.call(thisArg, value, i, list)) {
        return value;
      }
    }
    return undefined;
  };
}
/* jshint ignore:end */
<|endoftext|>


<commit_before>/**
 * Fittable application
 *
 * This is the main javascript file for fittable app.
 * Implements callbacks using Sirius API as data source. Then initializes the fittable widget.
 *
 * @author      Marián Hlaváč
 * @version     1.0
 */

import Grapnel from 'grapnel'
import ReactCookie from 'react-cookie'
import URL from 'url'

/**
 * Fittable widget instance
 */
var f

/**
 * Main application parameter, specifying the view
 * Choices are: person, room, course
 * @type {string}
 */
var view = 'person'

/**
 * Parameter for the selected view. Used for specifying the room or course.
 * @type {string}
 */
var parameter = ''

/**
 * Logged user's credentials
 * @type {{name: string, token: string}}
 */
var user = {
  name: ReactCookie.load('oauth_nickname'),
  token: ReactCookie.load('oauth_access_token'),
}

/**
 * Actual Sirius API URL
 * @type {string}
 */
var siriusAPIUrl = 'https://sirius.fit.cvut.cz/staging/api/v1/'

/**
 * Currently selected application language setting
 * @type {string}
 */
var appLocale = 'cs'

/**
 * Localized view names
 */
var viewNames = {
  cs: {
    person: 'osoba',
    room: 'místnost',
    course: 'předmět',
    personal: 'osobní rozvrh',
  },
  en: {
    person: 'person',
    room: 'room',
    course: 'course',
    personal: 'personal timetable',
  },
}

var viewPaths = {
  course: 'courses',
  person: 'people',
  room: 'rooms',
}

var defaultLimit = 32

var STATUS_ERROR_TYPES = {
  0: 'connection',
  403: 'access',
  404: 'not-found',
  500: 'server-error',
}
function generateError (status, message = 'No message specified') {
  const error = new Error(message)
  if (status in STATUS_ERROR_TYPES) {
    error.type = STATUS_ERROR_TYPES[status]
  } else {
    error.type = 'generic'
  }
  return error
}

/**
 * @return {Grapnel} an initialized router.
 */
function initRouter () {

  var router = new Grapnel({
    root: getBaseUri(),
    hashBang: true,
    pushState: mayUsePushState()
  })

  var handleView = function (viewName) {
    return (req) => changeView(viewName, req.params.id)
  }

  router.get('/?', req => changeView('person', user.name))
  router.get('/courses/:id', handleView('course'))
  router.get('/people/:id', handleView('person'))
  router.get('/rooms/:id', handleView('room'))

  // Default route, redirect all unmatched to /.
  router.get('/*', (req, e) => {
    if (!e.parent()) {
      router.navigate('/')
    }
  })

  return router
}

/**
 * @return {string} a base URI (aka relative URL root) of this site with
 * trailing slash omitted.
 */
function getBaseUri () {
  return URL.parse(document.baseURI).pathname.replace(/\/$/, '')
}

/**
 * @return {boolean} should we enable pushState?
 */
function mayUsePushState () {
  return window.location.protocol !== 'file:'
}

/**
 * Updates the title on the top
 * @param {string} title Large title
 * @param {string} subtitle Small subtitle under title
 */
function updateTitle (title, subtitle = null)
{
  if ( title !== null ) document.getElementsByTagName('h1')[0].innerHTML = title
  if ( subtitle !== null ) document.getElementsByClassName('sub-header')[0].innerHTML = subtitle
}

function changeView (newView, newParam) {
  // Set new view
  view = newView
  parameter = newParam

  // Change title
  updateTitle(view === 'person' && parameter === user.name ?
    viewNames[appLocale]['personal'] : viewNames[appLocale][view] + ' ' + parameter, null)
}

/**
 * Checks the user variable, if it's valid logged user.
 * @returns {bool}
 */
function isUserLoggedIn () {
  return (user.name && user.token)
}

function setUsername (username) {
  document.getElementById('username').innerHTML = username
}

function makeRequest (parameters) {

  var requestUrl = siriusAPIUrl

  requestUrl += parameters

  var request = new XMLHttpRequest()
  request.open('GET', encodeURI(requestUrl), true)
  request.setRequestHeader('Authorization', 'Bearer ' + user.token)
  request.send(null)

  return request
}

/**
 * Data callback, requesting the events from Sirius API, depending on view type and range.
 * @param rangeFrom Start point of the range
 * @param rangeTo Ending point of the range
 * @param callback Callback to be called after successful request
 */
var dataCallback = function (rangeFrom, rangeTo, callback) {

  // Make the request
  var request = makeRequest(viewPaths[view] + '/' + parameter + '/events?from=' + rangeFrom +
    '&to=' + rangeTo + '&limit=' + defaultLimit + '&include=courses,teachers,schedule_exceptions&deleted=true')

  request.onreadystatechange = function (callback, fittable) {
    if (request.readyState === XMLHttpRequest.DONE) {
      if (request.status === 200) {
        // Request successful
        var ajaxresult = JSON.parse(request.responseText)
        var itemCount = ajaxresult.meta.count

        var data = []
        var linknames = { teachers: [], courses: [], exceptions: [] }

        // Create events from data
        for (var i = 0; i < itemCount; i++) {
          // Get event
          var event = ajaxresult.events[ i ]

          // And add new event to array
          data[ i ] = {
            id: event.id,
            name: event.name,
            course: event.links.course,
            startsAt: event.starts_at,
            endsAt: event.ends_at,
            sequenceNumber: event.sequence_number,
            type: event.event_type,
            room: event.links.room,
            flag: null,
            notification: false,
            cancelled: event.deleted,
            replacement: false,
            teachers: event.links.teachers,
            details: {
              students: event.links.students,
              capacity: event.capacity,
              parallel: event.parallel,
              appliedExceptions: event.links.applied_exceptions
            }
          }

          // If the original data are present, insert one reverted event
          if ('original_data' in event && event.original_data.length > 0) {
            // Convert times to milliseconds
            var rangeFromMs = (new Date(rangeFrom)).getMilliseconds()
            var rangeToMs = (new Date(rangeTo)).getMilliseconds()
            var originalFromMs = (new Date(event.original_data.starts_at)).getMilliseconds()

            // Check if the original start is between the range
            if (rangeFromMs <= originalFromMs && originalFromMs >= rangeToMs) {
              // The event is cancelled, show with original data
              data[i].startsAt = event.original_data.starts_at
              data[i].endsAt = event.original_data.ends_at
              data[i].room = event.original_data.room_id
              data[i].cancelled = true
              data[i].replacedAt = event.starts_at
            } else {
              // The event is replacement
              data[i].replacement = true
              data[i].replaces = event.original_data.starts_at
            }
          }
        }

        // Add teachers links full names
        if ('linked' in ajaxresult) {
          if ('teachers' in ajaxresult.linked) {
            for (i = 0; i < ajaxresult.linked.teachers.length; i++) {
              // Add teacher link full name
              linknames.teachers.push({
                id: ajaxresult.linked.teachers[i].id,
                name: {
                  cs: ajaxresult.linked.teachers[i].full_name,
                  en: ajaxresult.linked.teachers[i].full_name
                }
              })
            }
          }

          // Add courses links full names
          if ('courses' in ajaxresult.linked) {
            for (i = 0; i < ajaxresult.linked.courses.length; i++) {
              // Add course link full name
              linknames.courses.push({
                id: ajaxresult.linked.courses[i].id,
                name: {
                  cs: ajaxresult.linked.courses[i].name.cs,
                  en: ajaxresult.linked.courses[i].name.en
                }
              })
            }
          }

          // Add exceptions links full names
          if ('schedule_exceptions' in ajaxresult.linked) {
            for (i = 0; i < ajaxresult.linked.schedule_exceptions.length; i++) {
              // Add exceptions link full name
              linknames.exceptions.push({
                id: ajaxresult.linked.schedule_exceptions[i].id,
                type: ajaxresult.linked.schedule_exceptions[i].exception_type,
                name: ajaxresult.linked.schedule_exceptions[i].name
              })
            }
          }
        }

        // Send data to fittable
        callback(null, {events: data, linkNames: linknames})
      } else {
        // Request failed
        callback(generateError(request.status, 'dataCallback failed'))
      }
    }
  }.bind(null, callback, f)
}

/**
 * Search callback, returning search results from Sirius API.
 * @param query
 * @param callback
 */
var searchCallback = function (query, callback) {

  // Construct the request URL
  var request = makeRequest('search?q=' + query + '&limit=' + defaultLimit)

  request.onreadystatechange = function (callback, fittable) {
    if (request.readyState === XMLHttpRequest.DONE) {
      if (request.status === 200) {
        // Request successful
        var ajaxresult = JSON.parse(request.responseText)

        // Send data to fittable
        callback(ajaxresult.results)
      } else {
        // Request failed
        fittable.onError(generateError(request.status).type)
      }
    }
  }.bind(null, callback, f)
}

var dateChangeCallback = function (newdate, newsemester) {
  console.log(newdate, newsemester)
  // Display semester name in subheading
  updateTitle(null, newsemester)
}

var semesterDataCallback = function (callback) {

  // Construct the request URL
  var request = makeRequest('semesters?limit=' + defaultLimit)

  request.onreadystatechange = function (callback, fittable) {
    if (request.readyState === XMLHttpRequest.DONE) {
      if (request.status === 200) {
        // Request successful
        var ajaxresult = JSON.parse(request.responseText)
        var itemCount = ajaxresult.meta.count

        // Check if there is semesters object in result
        if (!('semesters' in ajaxresult)) {
          return false
        }

        var semesters = []

        // Create events from data
        for (var i = 0; i < itemCount; i++) {
          var semester = ajaxresult.semesters[i]

          // And add new semester to array
          semesters[ i ] = {
            id: semester.id,
            semester: semester.semester,
            faculty: semester.faculty,
            startsAt: semester.starts_at,
            endsAt: semester.ends_at,
            examsStartsAt: semester.exams_start_at,
            examsEndsAt: semester.exams_end_at,
            hourDuration: semester.hour_duration,
            breakDuration: 15,
            dayStartsAtHour: 7.5,
            dayEndsAtHour: 21.25
          }
        }

        // Send semester data to fittable
        callback(semesters)
      } else {
        // Request failed
        fittable.onError(generateError(request.status).type)
      }
    }
  }.bind(null, callback, f)
}

var settingsChangeCallback = function (settings) {

  // We really don't want to save the user property
  settings.user = null

  // Save settings
  window.localStorage.setItem(user.name + '.settings', JSON.stringify(settings))

  // Update appLocale
  appLocale = settings.locale

  // Update title and subtitle for possible locale change
  updateTitle(view === 'person' && parameter === user.name ?
    viewNames[appLocale]['personal'] : viewNames[appLocale][view] + ' ' + parameter, null)
}

/**
 * --- Initialize the widget ---
 */

document.addEventListener('DOMContentLoaded', function () {

})

function viewChangeCallback (view, param) {
  router.navigate(`/${viewPaths[view]}/${param}`)
}

// If the user is not logged in, redirect him to the landing page
if (!isUserLoggedIn()) {
  window.location.href = 'landing.html'
} else {
  var router = initRouter()

  setUsername(user.name)

  // Prepare the options object with default values
  var options = {
    locale: 'cs',
  }

  // Load settings from localStorage
  var save = window.localStorage.getItem(user.name + '.settings')
  if (save !== null) {
    var usersettings = JSON.parse(save)

    for (var key in usersettings) {
      options[key] = usersettings[key]
    }
  }

  // Set user and callbacks
  options.username = user.name
  options.usertoken = user.token
  options.callbacks = {
    data: dataCallback,
    search: searchCallback,

    dateChange: dateChangeCallback,
    semesterData: semesterDataCallback,
    settingsChange: settingsChangeCallback
  }

  // Get app locale
  appLocale = options.locale

  // Init set title
  updateTitle(view === 'person' && parameter === user.name ?
    viewNames[appLocale]['personal'] : viewNames[appLocale][view] + ' ' + parameter, null)
}

export {
  dataCallback as data,
  searchCallback as search,
  dateChangeCallback as dateChange,
  semesterDataCallback as semesterData,
  settingsChangeCallback as settingsChange,
  viewChangeCallback as viewChange,
}
<commit_msg>Fix data iteration for Sirius data callback<commit_after>/**
 * Fittable application
 *
 * This is the main javascript file for fittable app.
 * Implements callbacks using Sirius API as data source. Then initializes the fittable widget.
 *
 * @author      Marián Hlaváč
 * @version     1.0
 */

import Grapnel from 'grapnel'
import ReactCookie from 'react-cookie'
import URL from 'url'
import R from 'ramda'

const emptyObject = (obj) => R.is(Object, obj) && R.pipe(R.keys, R.propEq('length', 0))

/**
 * Fittable widget instance
 */
var f

/**
 * Main application parameter, specifying the view
 * Choices are: person, room, course
 * @type {string}
 */
var view = 'person'

/**
 * Parameter for the selected view. Used for specifying the room or course.
 * @type {string}
 */
var parameter = ''

/**
 * Logged user's credentials
 * @type {{name: string, token: string}}
 */
var user = {
  name: ReactCookie.load('oauth_nickname'),
  token: ReactCookie.load('oauth_access_token'),
}

/**
 * Actual Sirius API URL
 * @type {string}
 */
var siriusAPIUrl = 'https://sirius.fit.cvut.cz/staging/api/v1/'

/**
 * Currently selected application language setting
 * @type {string}
 */
var appLocale = 'cs'

/**
 * Localized view names
 */
var viewNames = {
  cs: {
    person: 'osoba',
    room: 'místnost',
    course: 'předmět',
    personal: 'osobní rozvrh',
  },
  en: {
    person: 'person',
    room: 'room',
    course: 'course',
    personal: 'personal timetable',
  },
}

var viewPaths = {
  course: 'courses',
  person: 'people',
  room: 'rooms',
}

var defaultLimit = 32

var STATUS_ERROR_TYPES = {
  0: 'connection',
  403: 'access',
  404: 'not-found',
  500: 'server-error',
}
function generateError (status, message = 'No message specified') {
  const error = new Error(message)
  if (status in STATUS_ERROR_TYPES) {
    error.type = STATUS_ERROR_TYPES[status]
  } else {
    error.type = 'generic'
  }
  return error
}

/**
 * @return {Grapnel} an initialized router.
 */
function initRouter () {

  var router = new Grapnel({
    root: getBaseUri(),
    hashBang: true,
    pushState: mayUsePushState()
  })

  var handleView = function (viewName) {
    return (req) => changeView(viewName, req.params.id)
  }

  router.get('/?', req => changeView('person', user.name))
  router.get('/courses/:id', handleView('course'))
  router.get('/people/:id', handleView('person'))
  router.get('/rooms/:id', handleView('room'))

  // Default route, redirect all unmatched to /.
  router.get('/*', (req, e) => {
    if (!e.parent()) {
      router.navigate('/')
    }
  })

  return router
}

/**
 * @return {string} a base URI (aka relative URL root) of this site with
 * trailing slash omitted.
 */
function getBaseUri () {
  return URL.parse(document.baseURI).pathname.replace(/\/$/, '')
}

/**
 * @return {boolean} should we enable pushState?
 */
function mayUsePushState () {
  return window.location.protocol !== 'file:'
}

/**
 * Updates the title on the top
 * @param {string} title Large title
 * @param {string} subtitle Small subtitle under title
 */
function updateTitle (title, subtitle = null)
{
  if ( title !== null ) document.getElementsByTagName('h1')[0].innerHTML = title
  if ( subtitle !== null ) document.getElementsByClassName('sub-header')[0].innerHTML = subtitle
}

function changeView (newView, newParam) {
  // Set new view
  view = newView
  parameter = newParam

  // Change title
  updateTitle(view === 'person' && parameter === user.name ?
    viewNames[appLocale]['personal'] : viewNames[appLocale][view] + ' ' + parameter, null)
}

/**
 * Checks the user variable, if it's valid logged user.
 * @returns {bool}
 */
function isUserLoggedIn () {
  return (user.name && user.token)
}

function setUsername (username) {
  document.getElementById('username').innerHTML = username
}

function makeRequest (parameters) {

  var requestUrl = siriusAPIUrl

  requestUrl += parameters

  var request = new XMLHttpRequest()
  request.open('GET', encodeURI(requestUrl), true)
  request.setRequestHeader('Authorization', 'Bearer ' + user.token)
  request.send(null)

  return request
}

/**
 * Data callback, requesting the events from Sirius API, depending on view type and range.
 * @param rangeFrom Start point of the range
 * @param rangeTo Ending point of the range
 * @param callback Callback to be called after successful request
 */
var dataCallback = function (rangeFrom, rangeTo, callback) {

  // Make the request
  var request = makeRequest(viewPaths[view] + '/' + parameter + '/events?from=' + rangeFrom +
    '&to=' + rangeTo + '&limit=' + defaultLimit + '&include=courses,teachers,schedule_exceptions&deleted=true')

  request.onreadystatechange = function (callback, fittable) {
    if (request.readyState === XMLHttpRequest.DONE) {
      if (request.status === 200) {
        // Request successful
        var ajaxresult = JSON.parse(request.responseText)
        var itemCount = ajaxresult.meta.count

        var linknames = { teachers: [], courses: [], exceptions: [] }

        const responseEvents = ajaxresult.events || []
        const data = responseEvents.map(event => {
          // And add new event to array
          const newEvent = {
            id: event.id,
            name: event.name,
            course: event.links.course,
            startsAt: event.starts_at,
            endsAt: event.ends_at,
            sequenceNumber: event.sequence_number,
            type: event.event_type,
            room: event.links.room,
            flag: null,
            notification: false,
            cancelled: event.deleted,
            replacement: false,
            teachers: event.links.teachers,
            details: {
              students: event.links.students,
              capacity: event.capacity,
              parallel: event.parallel,
              appliedExceptions: event.links.applied_exceptions,
            },
          }

          // If the original data are present, insert one reverted event
          if (!emptyObject(event.original_data)) {
            // Convert times to milliseconds
            var rangeFromMs = (new Date(rangeFrom)).getMilliseconds()
            var rangeToMs = (new Date(rangeTo)).getMilliseconds()
            var originalFromMs = (new Date(event.original_data.starts_at)).getMilliseconds()

            // Check if the original start is between the range
            if (rangeFromMs <= originalFromMs && originalFromMs >= rangeToMs) {
              // The event is cancelled, show with original data
              newEvent.startsAt = event.original_data.starts_at
              newEvent.endsAt = event.original_data.ends_at
              newEvent.room = event.original_data.room_id
              newEvent.cancelled = true
              newEvent.replacedAt = event.starts_at
            } else {
              // The event is replacement
              newEvent.replacement = true
              newEvent.replaces = event.original_data.starts_at
            }
          }
          return newEvent
        })

        // Add teachers links full names
        if ('linked' in ajaxresult) {
          if ('teachers' in ajaxresult.linked) {
            for (let i = 0; i < ajaxresult.linked.teachers.length; i++) {
              // Add teacher link full name
              linknames.teachers.push({
                id: ajaxresult.linked.teachers[i].id,
                name: {
                  cs: ajaxresult.linked.teachers[i].full_name,
                  en: ajaxresult.linked.teachers[i].full_name,
                },
              })
            }
          }

          // Add courses links full names
          if ('courses' in ajaxresult.linked) {
            for (let i = 0; i < ajaxresult.linked.courses.length; i++) {
              // Add course link full name
              linknames.courses.push({
                id: ajaxresult.linked.courses[i].id,
                name: {
                  cs: ajaxresult.linked.courses[i].name.cs,
                  en: ajaxresult.linked.courses[i].name.en,
                },
              })
            }
          }

          // Add exceptions links full names
          if ('schedule_exceptions' in ajaxresult.linked) {
            for (let i = 0; i < ajaxresult.linked.schedule_exceptions.length; i++) {
              // Add exceptions link full name
              linknames.exceptions.push({
                id: ajaxresult.linked.schedule_exceptions[i].id,
                type: ajaxresult.linked.schedule_exceptions[i].exception_type,
                name: ajaxresult.linked.schedule_exceptions[i].name
              })
            }
          }
        }

        // Send data to fittable
        callback(null, {events: data, linkNames: linknames})
      } else {
        // Request failed
        callback(generateError(request.status, 'dataCallback failed'))
      }
    }
  }.bind(null, callback, f)
}

/**
 * Search callback, returning search results from Sirius API.
 * @param query
 * @param callback
 */
var searchCallback = function (query, callback) {

  // Construct the request URL
  var request = makeRequest('search?q=' + query + '&limit=' + defaultLimit)

  request.onreadystatechange = function (callback, fittable) {
    if (request.readyState === XMLHttpRequest.DONE) {
      if (request.status === 200) {
        // Request successful
        var ajaxresult = JSON.parse(request.responseText)

        // Send data to fittable
        callback(ajaxresult.results)
      } else {
        // Request failed
        fittable.onError(generateError(request.status).type)
      }
    }
  }.bind(null, callback, f)
}

var dateChangeCallback = function (newdate, newsemester) {
  console.log(newdate, newsemester)
  // Display semester name in subheading
  updateTitle(null, newsemester)
}

var semesterDataCallback = function (callback) {

  // Construct the request URL
  var request = makeRequest('semesters?limit=' + defaultLimit)

  request.onreadystatechange = function (callback, fittable) {
    if (request.readyState === XMLHttpRequest.DONE) {
      if (request.status === 200) {
        // Request successful
        var ajaxresult = JSON.parse(request.responseText)
        var itemCount = ajaxresult.meta.count

        // Check if there is semesters object in result
        if (!('semesters' in ajaxresult)) {
          return false
        }

        var semesters = []

        // Create events from data
        for (var i = 0; i < itemCount; i++) {
          var semester = ajaxresult.semesters[i]

          // And add new semester to array
          semesters[ i ] = {
            id: semester.id,
            semester: semester.semester,
            faculty: semester.faculty,
            startsAt: semester.starts_at,
            endsAt: semester.ends_at,
            examsStartsAt: semester.exams_start_at,
            examsEndsAt: semester.exams_end_at,
            hourDuration: semester.hour_duration,
            breakDuration: 15,
            dayStartsAtHour: 7.5,
            dayEndsAtHour: 21.25
          }
        }

        // Send semester data to fittable
        callback(semesters)
      } else {
        // Request failed
        fittable.onError(generateError(request.status).type)
      }
    }
  }.bind(null, callback, f)
}

var settingsChangeCallback = function (settings) {

  // We really don't want to save the user property
  settings.user = null

  // Save settings
  window.localStorage.setItem(user.name + '.settings', JSON.stringify(settings))

  // Update appLocale
  appLocale = settings.locale

  // Update title and subtitle for possible locale change
  updateTitle(view === 'person' && parameter === user.name ?
    viewNames[appLocale]['personal'] : viewNames[appLocale][view] + ' ' + parameter, null)
}

/**
 * --- Initialize the widget ---
 */

document.addEventListener('DOMContentLoaded', function () {

})

function viewChangeCallback (view, param) {
  router.navigate(`/${viewPaths[view]}/${param}`)
}

// If the user is not logged in, redirect him to the landing page
if (!isUserLoggedIn()) {
  window.location.href = 'landing.html'
} else {
  var router = initRouter()

  setUsername(user.name)

  // Prepare the options object with default values
  var options = {
    locale: 'cs',
  }

  // Load settings from localStorage
  var save = window.localStorage.getItem(user.name + '.settings')
  if (save !== null) {
    var usersettings = JSON.parse(save)

    for (var key in usersettings) {
      options[key] = usersettings[key]
    }
  }

  // Set user and callbacks
  options.username = user.name
  options.usertoken = user.token
  options.callbacks = {
    data: dataCallback,
    search: searchCallback,

    dateChange: dateChangeCallback,
    semesterData: semesterDataCallback,
    settingsChange: settingsChangeCallback
  }

  // Get app locale
  appLocale = options.locale

  // Init set title
  updateTitle(view === 'person' && parameter === user.name ?
    viewNames[appLocale]['personal'] : viewNames[appLocale][view] + ' ' + parameter, null)
}

export {
  dataCallback as data,
  searchCallback as search,
  dateChangeCallback as dateChange,
  semesterDataCallback as semesterData,
  settingsChangeCallback as settingsChange,
  viewChangeCallback as viewChange,
}
<|endoftext|>


<commit_before>import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import DatePicker from '../index';

// TODO: add tests
describe('<DatePickerWrapper />', () => {
    it('should render one <DatePicker /> component', () => {
        const wrapper = shallow(<DatePicker />);
        expect(wrapper).to.have.length(1);
    });
});<commit_msg>fix wrong test<commit_after>import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import DatePicker from '../index';

// TODO: add tests
describe('<DatePicker />', () => {
    it('should render one <DatePicker /> component', () => {
        const wrapper = shallow(<DatePicker />);
        expect(wrapper).to.have.length(1);
    });
});<|endoftext|>


<commit_before>import React from "react";

const StealthAnimation = ({ src, activated, status, id, state }) => {
  const getDisplay = () => {
    if (!id) return "none";
    if (status && !activated) return "none";
    if (state || !activated) return "block";
  };
  return (
    <div
      className="stealth-animation"
      style={{
        WebkitMaskImage: `url('${src}')`,
        maskImage: `url('${src}')`,
        display: getDisplay()
      }}
    >

  );
};

export default StealthAnimation;
<commit_msg>Fixes for the stealth field animation. Closes  #1269<commit_after>import React from "react";

const StealthAnimation = ({ src, activated, status, id, state }) => {
  console.log(activated, status, state);
  const getDisplay = () => {
    if (!id) return "none";
    if (status && !activated) return "none";
    if (state || !activated) return "block";
    return "none";
  };
  return (
    <div
      className="stealth-animation"
      style={{
        WebkitMaskImage: `url('${src}')`,
        maskImage: `url('${src}')`,
        display: getDisplay()
      }}
    >

  );
};

export default StealthAnimation;
<|endoftext|>


<commit_before>/**
 * Created by Serhii from Ubrainians for Imcode
 * on 28.07.16.
 *
 * Service for correct and convenient working with links.
 * Provides context path from <base> tag.
 * Works with links from links.json file.
 *
 * @author Serhii
 */
var Linker = function () {
    this._contextPath = $("base").attr("href"); // tag 'base' should be on every page and should hold context path
    this._getLinks();
};

Linker.prototype = {
    _links: [],
    _simpleURLs: {},
    _contextPath: "",
    _linksCookiesKey: "links.json",

    /**
     * Gets links from server.
     * @private
     */
    _getLinksFromServer: function () {
        var linksPath = this._contextPath + "/api/links"; // should be the only link in app

        $.ajax({
            url: linksPath,
            type: "GET",
            async: false,
            success: function (response) {
                this._links = response;
            }.bind(this)
        });
    },

    /**
     * Writes links json to cookies
     * @private
     */
    _setLinksToCookies: function () {
        Cookies.set(this._linksCookiesKey, this._links, {
            expires: 1
        });
    },

    /**
     * Divides links with args (that needs builder) and simple URL
     * @private
     */
    _divideLinks: function () {
        this._links.filter(function (link) {
            return !link.args.length;
        }).forEach(function (simpleLink) {
            this._links.remove(simpleLink);
            this._simpleURLs[simpleLink.name] = simpleLink.url;
        }.bind(this))
    },

    /**
     * Make all steps to load links from server
     * @private
     */
    _loadLinks: function () {
        this._getLinksFromServer();
        this._setLinksToCookies();
        this._divideLinks();
    },

    /**
     * Gets links from cookies or from server if cookie is empty and then write cookie with 1 day expiration
     * @private
     */
    _getLinks: function () {
        var linksFromCookies = Cookies.getJSON(this._linksCookiesKey);

        if (linksFromCookies && linksFromCookies.length) {
            this._links = linksFromCookies;
            this._divideLinks();

        } else {
            this._loadLinks();
        }
    },

    /**
     * @returns {string} context path
     */
    getContextPath: function () {
        return this._contextPath;
    },

    /**
     * @returns {Linker._simpleURLs|{}} URLs without arguments
     */
    getSimpleURLs: function () {
        return this._simpleURLs;
    },

    /**
     * @returns {Array} all links with args
     */
    getLinks: function () {
        return this._links;
    },

    /**
     * Use it to get some link with context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [args] - arguments for url in correct order
     * @returns {string} built link
     */
    get: function (name, args) {
        return this._contextPath + this._getURL(arguments);
    },

    /**
     * Use it to get some link without context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [args] - arguments for url in correct order
     * @returns {string} built link
     */
    getRelative: function (name, args) {
        return this._getURL(arguments);
    },

    /**
     * Get simple URL or from link with builder
     * @returns {string} requested URL
     * @private
     */
    _getURL: function () {
        if (arguments.length == 1) {
            return this._getSimpleUrl(name);
        } else {
            var urlArgs = this._prepareArgs(arguments);
            return this._getLinkUrl(name, urlArgs);
        }
    },

    /**
     * Returns URL by it's name from links.json file
     * @param {string} name - the name of URL
     * @returns {string} requested URL
     * @private
     */
    _getSimpleUrl: function (name) {
        var link = this._simpleURLs[name];

        if (!link) {
            this._loadLinks();
            link = this._simpleURLs[name];
        }

        return link;
    },

    /**
     * Returns link's url by it's name and arguments.
     * @param {string} name - the name of url, from links.json file.
     * @param {string[]} [args] - arguments for url in correct order
     * @returns {string} built link's url
     * @private
     */
    _getLinkUrl: function (name, args) {
        var link = this._getLink(name, args);

        if (!link) {
            this._loadLinks();
            link = this._getLink(name, args);
        }

        var result = link.url;

        args.forEach(function (arg, index) {
            result = result.replace("{" + (index + 1) + "}", arg);
        });

        return result;
    },

    /**
     * Takes link object
     * @param {string} name - link's name
     * @param {string[]} [args] - link's arguments
     * @returns {object} result link or undefined if not found
     * @private
     */
    _getLink: function (name, args) {
        return this._links.find(function (link) {
            return (link.name == name && link.args.length == args.length);
        });
    },

    /**
     * Function get all arguments and returns only url's arguments.
     * First is name, and as we don't need it here we check all others args and return prepared array.
     * @param args - all arguments, including name
     * @returns {Array} array of only url arguments
     * @private
     */
    _prepareArgs: function (args) {
        return Array.prototype.slice
            .call(args, 1) // 0 argument is link's name, 1.. is args to url so we start from 1
            .flatMap(function (arg) { // [[1],2] -> [1,2]
                return arg;
            })
            .filter(function (arg) {
                return (typeof arg !== 'undefined' && arg !== null);
            });
    }
};

/**
 * Flatmap function, as in Java Stream API
 * @param {function} lambda - lambda to be used for each element
 * @returns {Array} - resulted array
 */
Array.prototype.flatMap = function (lambda) {
    return Array.prototype.concat.apply([], this.map(lambda));
};

/**
 * Extension for Array to made convenient remove
 * @param {*} value to remove from array
 */
Array.prototype.remove = function (value) {
    while (true) {
        var index = this.indexOf(value);
        if (index < 0) return;
        this.splice(index, 1);
    }
};

var Imcms = {};
Imcms.Linker = new Linker();
<commit_msg>IMCMS-119 - ImCMS optimization.: - Fixed bottle neck.<commit_after>/**
 * Created by Serhii from Ubrainians for Imcode
 * on 28.07.16.
 *
 * Service for correct and convenient working with links.
 * Provides context path from <base> tag.
 * Works with links from links.json file.
 *
 * @author Serhii
 */
var Linker = function () {
    this._contextPath = $("base").attr("href"); // tag 'base' should be on every page and should hold context path
    this._getLinks();
};

Linker.prototype = {
    _links: [],
    _simpleURLs: {},
    _contextPath: "",
    _linksCookiesKey: "links.json",

    /**
     * Gets links from server.
     * @private
     */
    _getLinksFromServer: function () {
        var linksPath = this._contextPath + "/api/links"; // should be the only link in app

        $.ajax({
            url: linksPath,
            type: "GET",
            async: false,
            success: function (response) {
                this._links = response;
            }.bind(this)
        });
    },

    /**
     * Writes links json to cookies
     * @private
     */
    _setLinksToCookies: function () {
        Cookies.set(this._linksCookiesKey, this._links, {
            expires: 1
        });
    },

    /**
     * Divides links with args (that needs builder) and simple URL
     * @private
     */
    _divideLinks: function () {
        this._links.filter(function (link) {
            return !link.args.length;
        }).forEach(function (simpleLink) {
            this._links.remove(simpleLink);
            this._simpleURLs[simpleLink.name] = simpleLink.url;
        }.bind(this))
    },

    /**
     * Make all steps to load links from server
     * @private
     */
    _loadLinks: function () {
        this._getLinksFromServer();
        this._setLinksToCookies();
        this._divideLinks();
    },

    /**
     * Gets links from cookies or from server if cookie is empty and then write cookie with 1 day expiration
     * @private
     */
    _getLinks: function () {
        var linksFromCookies = Cookies.getJSON(this._linksCookiesKey);

        if (linksFromCookies && linksFromCookies.length) {
            this._links = linksFromCookies;
            this._divideLinks();

        } else {
            this._loadLinks();
        }
    },

    /**
     * @returns {string} context path
     */
    getContextPath: function () {
        return this._contextPath;
    },

    /**
     * @returns {Linker._simpleURLs|{}} URLs without arguments
     */
    getSimpleURLs: function () {
        return this._simpleURLs;
    },

    /**
     * @returns {Array} all links with args
     */
    getLinks: function () {
        return this._links;
    },

    /**
     * Use it to get some link with context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [argsURL] - arguments for url in correct order
     * @returns {string} built link
     */
    get: function (name, argsURL) {
        return this._contextPath + this._getURL(arguments);
    },

    /**
     * Use it to get some link without context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [argsURL] - arguments for url in correct order
     * @returns {string} built link
     */
    getRelative: function (name, argsURL) {
        return this._getURL(arguments);
    },

    /**
     * Get simple URL or from link with builder
     * @returns {string} requested URL
     * @private
     */
    _getURL: function (args) {
        if (args.length == 1) {
            return this._getSimpleUrl(args[0]);
        } else {
            var urlArgs = this._prepareArgs(args);

            if (!urlArgs.length) {
                return this._getSimpleUrl(args[0]);

            } else {
                return this._getLinkUrl(args[0], urlArgs);
            }
        }
    },

    /**
     * Returns URL by it's name from links.json file
     * @param {string} name - the name of URL
     * @returns {string} requested URL
     * @private
     */
    _getSimpleUrl: function (name) {
        var link = this._simpleURLs[name];

        if (!link) {
            this._loadLinks();
            link = this._simpleURLs[name];
        }

        return link;
    },

    /**
     * Returns link's url by it's name and arguments.
     * @param {string} name - the name of url, from links.json file.
     * @param {string[]} [args] - arguments for url in correct order
     * @returns {string} built link's url
     * @private
     */
    _getLinkUrl: function (name, args) {
        var link = this._getLink(name, args);

        if (!link) {
            this._loadLinks();
            link = this._getLink(name, args);
        }

        var result = link.url;

        args.forEach(function (arg, index) {
            result = result.replace("{" + (index + 1) + "}", arg);
        });

        return result;
    },

    /**
     * Takes link object
     * @param {string} name - link's name
     * @param {string[]} [args] - link's arguments
     * @returns {object} result link or undefined if not found
     * @private
     */
    _getLink: function (name, args) {
        return this._links.find(function (link) {
            return (link.name == name && link.args.length == args.length);
        });
    },

    /**
     * Function get all arguments and returns only url's arguments.
     * First is name, and as we don't need it here we check all others args and return prepared array.
     * @param args - all arguments, including name
     * @returns {Array} array of only url arguments
     * @private
     */
    _prepareArgs: function (args) {
        return Array.prototype.slice
            .call(args, 1) // 0 argument is link's name, 1.. is args to url so we start from 1
            .flatMap(function (arg) { // [[1],2] -> [1,2]
                return arg;
            })
            .filter(function (arg) {
                return (typeof arg !== 'undefined' && arg !== null);
            });
    }
};

/**
 * Flatmap function, as in Java Stream API
 * @param {function} lambda - lambda to be used for each element
 * @returns {Array} - resulted array
 */
Array.prototype.flatMap = function (lambda) {
    return Array.prototype.concat.apply([], this.map(lambda));
};

/**
 * Extension for Array to made convenient remove
 * @param {*} value to remove from array
 */
Array.prototype.remove = function (value) {
    while (true) {
        var index = this.indexOf(value);
        if (index < 0) return;
        this.splice(index, 1);
    }
};

var Imcms = {};
Imcms.Linker = new Linker();
<|endoftext|>