<commit_before>import * as utils from 'base/utils';
import Component from 'base/component';

var precision = 1;

//constants
var class_playing = "vzb-playing";
var class_loading = "vzb-ts-loading";
var class_hide_play = "vzb-ts-hide-play-button";
var class_dragging = "vzb-ts-dragging";
var class_axis_aligned = "vzb-ts-axis-aligned";
var class_show_value = "vzb-ts-show-value";
var class_show_value_when_drag_play = "vzb-ts-show-value-when-drag-play";

var time_formats = {
  "year": "%Y",
  "month": "%b",
  "week": "week %U",
  "day": "%d/%m/%Y",
  "hour": "%d/%m/%Y %H",
  "minute": "%d/%m/%Y %H:%M",
  "second": "%d/%m/%Y %H:%M:%S"
};

//margins for slider
var profiles = {
  small: {
    margin: {
      top: 9,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 8,
    label_spacing: 10
  },
  medium: {
    margin: {
      top: 9,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 10,
    label_spacing: 12
  },
  large: {
    margin: {
      top: 9,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 11,
    label_spacing: 14
  }
};

var presentationProfileChanges = {
  "small": {
    margin: {
    }
  },
  "medium": {
    margin: {
      top: 9
    }
  },
  "large": {
    margin: {
    }
  }
}

var TimeSlider = Component.extend({
  /**
   * Initializes the timeslider.
   * Executed once before any template is rendered.
   * @param config The options passed to the component
   * @param context The component's parent
   */
  init: function(config, context) {

    this.name = "gapminder-timeslider";
    this.template = this.template || "timeslider.html";

    //define expected models/hooks for this component
    this.model_expects = [{
      name: "time",
      type: "time",
      delay: "delay"
    }];

    var _this = this;

    //starts as splash if this is the option
    this._splash = config.ui.splash;

    //binds methods to this model
    this.model_binds = {
      'change:time': function(evt, original) {

        //TODO: readyOnce CANNOT be run twice
        if(_this._splash !== _this.model.time.splash) {
          _this._splash = _this.model.time.splash;
          _this.readyOnce();
          _this.ready();
        }

        if(!_this._splash) {

          if((['change:time:start', 'change:time:end']).indexOf(evt) !== -1) {
            _this.changeLimits();
          }
          _this._optionClasses();
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      }
    };

    this.ui = utils.extend({
      show_limits: false,
      show_value: false,
      show_value_when_drag_play: true,
      show_button: true,
      class_axis_aligned: false
    }, config.ui, this.ui);

    // Same constructor as the superclass
    this._super(config, context);

    //defaults
    this.width = 0;
    this.height = 0;

    this.getValueWidth = utils.memoize(this.getValueWidth);
    this._setTime = utils.throttle(this._setTime, 50);
  },

  //template is ready
  readyOnce: function () {

    if(this._splash) return;

    var _this = this;

    //DOM to d3
    //TODO: remove this ugly hack
    this.element = utils.isArray(this.element) ? this.element : d3.select(this.element);
    this.element.classed(class_loading, false);

    //html elements
    this.slider_outer = this.element.select(".vzb-ts-slider");
    this.slider = this.slider_outer.select("g");
    this.axis = this.element.select(".vzb-ts-slider-axis");
    this.slide = this.element.select(".vzb-ts-slider-slide");
    this.handle = this.slide.select(".vzb-ts-slider-handle");
    this.valueText = this.slide.select('.vzb-ts-slider-value');
    //Scale
    this.xScale = d3.time.scale()
      .clamp(true);

    //Axis
    this.xAxis = d3.svg.axis()
      .orient("bottom")
      .tickSize(0);
    //Value
    this.valueText.attr("text-anchor", "middle").attr("dy", "-1em");

    var brushed = _this._getBrushed(),
      brushedEnd = _this._getBrushedEnd();

    //Brush for dragging
    this.brush = d3.svg.brush()
      .x(this.xScale)
      .extent([0, 0])
      .on("brush", function () {
        brushed.call(this);
      })
      .on("brushend", function () {
        brushedEnd.call(this);
      });

    //Slide
    this.slide.call(this.brush);
    this.slide.selectAll(".extent,.resize")
      .remove();

    this.parent.on('myEvent', function (evt, arg) {
      var layoutProfile = _this.getLayoutProfile();

      if (arg.profile && arg.profile.margin) {
        profiles[layoutProfile].margin = arg.profile.margin;
      }

      // set the right margin that depends on longest label width
      _this.element.select(".vzb-ts-slider-wrapper")
        .style("right", (arg.mRight - profiles[layoutProfile].margin.right) + "px");

      _this.xScale.range([0, arg.rangeMax]);
      _this.resize();
    });
  },

  //template and model are ready
  ready: function () {
    if(this._splash) return;

    var play = this.element.select(".vzb-ts-btn-play");
    var pause = this.element.select(".vzb-ts-btn-pause");
    var _this = this;
    var time = this.model.time;
    var delay = this.model.time.delay;

    play.on('click', function () {
      _this.model.time.play();
    });

    pause.on('click', function () {
      _this.model.time.pause("soft");
    });

    var fmt = time.formatOutput || time_formats[time.unit];
    this.format = d3.time.format(fmt);

    this.changeLimits();
    this.changeTime();
    this.resize();

  },

  changeLimits: function() {
    var minValue = this.model.time.start;
    var maxValue = this.model.time.end;
    //scale
    this.xScale.domain([minValue, maxValue]);
    //axis
    this.xAxis.tickValues([minValue, maxValue])
      .tickFormat(this.format);
  },

  changeTime: function() {
    this.ui.format = this.model.time.unit;
    //time slider should always receive a time model
    var time = this.model.time.value;
    //special classes
    this._optionClasses();
  },

  /**
   * Executes everytime the container or vizabi is resized
   * Ideally,it contains only operations related to size
   */
   resize: function () {

     this.model.time.pause();

     this.profile = this.getActiveProfile(profiles, presentationProfileChanges);

     var slider_w = parseInt(this.slider_outer.style("width"), 10);
     var slider_h = parseInt(this.slider_outer.style("height"), 10);
     this.width = slider_w - this.profile.margin.left - this.profile.margin.right;
     this.height = slider_h - this.profile.margin.bottom - this.profile.margin.top;
     var _this = this;

     //translate according to margins
     this.slider.attr("transform", "translate(" + this.profile.margin.left + "," + this.profile.margin.top + ")");

     //adjust scale width if it was not set manually before
     if (this.xScale.range()[1] = 1) this.xScale.range([0, this.width]);

     //adjust axis with scale
     this.xAxis = this.xAxis.scale(this.xScale)
       .tickPadding(this.profile.label_spacing);

     this.axis.attr("transform", "translate(0," + this.height / 2 + ")")
       .call(this.xAxis);

     this.slide.select(".background")
       .attr("height", this.height);

     //size of handle
     this.handle.attr("transform", "translate(0," + this.height / 2 + ")")
       .attr("r", this.profile.radius);

     this.sliderWidth = _this.slider.node().getBoundingClientRect().width;

     this._setHandle();

   },

  /**
   * Returns width of slider text value.
   * Parameters in this function needed for memoize function, so they are not redundant.
   */
  getValueWidth: function(layout, value) {
    return this.valueText.node().getBoundingClientRect().width;
  },

  /**
   * Gets brushed function to be executed when dragging
   * @returns {Function} brushed function
   */
  _getBrushed: function() {
    var _this = this;
    return function() {
      _this.model.time.pause();

      _this._optionClasses();
      _this.element.classed(class_dragging, true);

      var value = _this.brush.extent()[0];

      //set brushed properties

      if(d3.event.sourceEvent) {
        _this.model.time.dragStart();
        var posX = utils.roundStep(Math.round(d3.mouse(this)[0]), precision);
        value = _this.xScale.invert(posX);
        var maxPosX = _this.width;

        if(posX > maxPosX) {
          posX = maxPosX;
        } else if(posX < 0) {
          posX = 0;
        }

        //set handle position
        _this.handle.attr("cx", posX);
        _this.valueText.attr("transform", "translate(" + posX + "," + (_this.height / 2) + ")");
        _this.valueText.text(_this.format(value));
      }

      //set time according to dragged position
      if(value - _this.model.time.value !== 0) {
        _this._setTime(value);
      }
    };
  },

  /**
   * Gets brushedEnd function to be executed when dragging ends
   * @returns {Function} brushedEnd function
   */
  _getBrushedEnd: function() {
    var _this = this;
    return function() {
      _this.element.classed(class_dragging, false);
      _this.model.time.dragStop();
      _this.model.time.snap();
    };
  },

  /**
   * Sets the handle to the correct position
   * @param {Boolean} transition whether to use transition or not
   */
  _setHandle: function(transition) {
    var value = this.model.time.value;
    this.slide.call(this.brush.extent([value, value]));

    this.valueText.text(this.format(value));

    var old_pos = this.handle.attr("cx");
    var new_pos = this.xScale(value);
    if(old_pos == null) old_pos = new_pos;
    var delayAnimations = new_pos > old_pos ? this.model.time.delayAnimations : 0;

    if(transition) {
      this.handle.attr("cx", old_pos)
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("cx", new_pos);

      this.valueText.attr("transform", "translate(" + old_pos + "," + (this.height / 2) + ")")
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    } else {
      this.handle
        //cancel active transition
        .interrupt()
        .attr("cx", new_pos);

      this.valueText
        //cancel active transition
        .interrupt()
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    }

  },

  /**
   * Sets the current time model to time
   * @param {number} time The time
   */
  _setTime: function(time) {
    //update state
    var _this = this;
    //  frameRate = 50;

    //avoid updating more than once in "frameRate"
    //var now = new Date();
    //if (this._updTime != null && now - this._updTime < frameRate) return;
    //this._updTime = now;

    _this.model.time.value = time;
  },

    /**
   * Sets the current delay model to delay
   * @param {number} time The time
   */
  _setDelay: function (delay) {
    //update state
    var _this = this;

    _this.model.time.delay = delay;
    _this.model.time.delaySet = true;
  },

  /**
   * Applies some classes to the element according to options
   */
  _optionClasses: function() {
    //show/hide classes

    var show_limits = this.ui.show_limits;
    var show_value = this.ui.show_value;
    var show_value_when_drag_play = this.ui.show_value_when_drag_play;
    var axis_aligned = this.ui.axis_aligned;
    var show_play = (this.ui.show_button) && (this.model.time.playable);

    if(!show_limits) {
      this.xAxis.tickValues([]).ticks(0);
    }

    this.element.classed(class_hide_play, !show_play);
    this.element.classed(class_playing, this.model.time.playing);
    this.element.classed(class_show_value, show_value);
    this.element.classed(class_show_value_when_drag_play, show_value_when_drag_play);
    this.element.classed(class_axis_aligned, axis_aligned);
  }
});

export default TimeSlider;
<commit_msg>year truncated<commit_after>import * as utils from 'base/utils';
import Component from 'base/component';

var precision = 1;

//constants
var class_playing = "vzb-playing";
var class_loading = "vzb-ts-loading";
var class_hide_play = "vzb-ts-hide-play-button";
var class_dragging = "vzb-ts-dragging";
var class_axis_aligned = "vzb-ts-axis-aligned";
var class_show_value = "vzb-ts-show-value";
var class_show_value_when_drag_play = "vzb-ts-show-value-when-drag-play";

var time_formats = {
  "year": "%Y",
  "month": "%b",
  "week": "week %U",
  "day": "%d/%m/%Y",
  "hour": "%d/%m/%Y %H",
  "minute": "%d/%m/%Y %H:%M",
  "second": "%d/%m/%Y %H:%M:%S"
};

//margins for slider
var profiles = {
  small: {
    margin: {
      top: 9,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 8,
    label_spacing: 10
  },
  medium: {
    margin: {
      top: 9,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 10,
    label_spacing: 12
  },
  large: {
    margin: {
      top: 9,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 11,
    label_spacing: 14
  }
};

var presentationProfileChanges = {
  "small": {
    margin: {
    }
  },
  "medium": {
    margin: {
      top: 9
    }
  },
  "large": {
    margin: {
    }
  }
}

var TimeSlider = Component.extend({
  /**
   * Initializes the timeslider.
   * Executed once before any template is rendered.
   * @param config The options passed to the component
   * @param context The component's parent
   */
  init: function(config, context) {

    this.name = "gapminder-timeslider";
    this.template = this.template || "timeslider.html";

    //define expected models/hooks for this component
    this.model_expects = [{
      name: "time",
      type: "time",
      delay: "delay"
    }];

    var _this = this;

    //starts as splash if this is the option
    this._splash = config.ui.splash;

    //binds methods to this model
    this.model_binds = {
      'change:time': function(evt, original) {

        //TODO: readyOnce CANNOT be run twice
        if(_this._splash !== _this.model.time.splash) {
          _this._splash = _this.model.time.splash;
          _this.readyOnce();
          _this.ready();
        }

        if(!_this._splash) {

          if((['change:time:start', 'change:time:end']).indexOf(evt) !== -1) {
            _this.changeLimits();
          }
          _this._optionClasses();
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      }
    };

    this.ui = utils.extend({
      show_limits: false,
      show_value: false,
      show_value_when_drag_play: true,
      show_button: true,
      class_axis_aligned: false
    }, config.ui, this.ui);

    // Same constructor as the superclass
    this._super(config, context);

    //defaults
    this.width = 0;
    this.height = 0;

    this.getValueWidth = utils.memoize(this.getValueWidth);
    this._setTime = utils.throttle(this._setTime, 50);
  },

  //template is ready
  readyOnce: function () {

    if(this._splash) return;

    var _this = this;

    //DOM to d3
    //TODO: remove this ugly hack
    this.element = utils.isArray(this.element) ? this.element : d3.select(this.element);
    this.element.classed(class_loading, false);

    //html elements
    this.slider_outer = this.element.select(".vzb-ts-slider");
    this.slider = this.slider_outer.select("g");
    this.axis = this.element.select(".vzb-ts-slider-axis");
    this.slide = this.element.select(".vzb-ts-slider-slide");
    this.handle = this.slide.select(".vzb-ts-slider-handle");
    this.valueText = this.slide.select('.vzb-ts-slider-value');
    //Scale
    this.xScale = d3.time.scale()
      .clamp(true);

    //Axis
    this.xAxis = d3.svg.axis()
      .orient("bottom")
      .tickSize(0);
    //Value
    this.valueText.attr("text-anchor", "middle").attr("dy", "-0.7em");

    var brushed = _this._getBrushed(),
      brushedEnd = _this._getBrushedEnd();

    //Brush for dragging
    this.brush = d3.svg.brush()
      .x(this.xScale)
      .extent([0, 0])
      .on("brush", function () {
        brushed.call(this);
      })
      .on("brushend", function () {
        brushedEnd.call(this);
      });

    //Slide
    this.slide.call(this.brush);
    this.slide.selectAll(".extent,.resize")
      .remove();

    this.parent.on('myEvent', function (evt, arg) {
      var layoutProfile = _this.getLayoutProfile();

      if (arg.profile && arg.profile.margin) {
        profiles[layoutProfile].margin = arg.profile.margin;
      }

      // set the right margin that depends on longest label width
      _this.element.select(".vzb-ts-slider-wrapper")
        .style("right", (arg.mRight - profiles[layoutProfile].margin.right) + "px");

      _this.xScale.range([0, arg.rangeMax]);
      _this.resize();
    });
  },

  //template and model are ready
  ready: function () {
    if(this._splash) return;

    var play = this.element.select(".vzb-ts-btn-play");
    var pause = this.element.select(".vzb-ts-btn-pause");
    var _this = this;
    var time = this.model.time;
    var delay = this.model.time.delay;

    play.on('click', function () {
      _this.model.time.play();
    });

    pause.on('click', function () {
      _this.model.time.pause("soft");
    });

    var fmt = time.formatOutput || time_formats[time.unit];
    this.format = d3.time.format(fmt);

    this.changeLimits();
    this.changeTime();
    this.resize();

  },

  changeLimits: function() {
    var minValue = this.model.time.start;
    var maxValue = this.model.time.end;
    //scale
    this.xScale.domain([minValue, maxValue]);
    //axis
    this.xAxis.tickValues([minValue, maxValue])
      .tickFormat(this.format);
  },

  changeTime: function() {
    this.ui.format = this.model.time.unit;
    //time slider should always receive a time model
    var time = this.model.time.value;
    //special classes
    this._optionClasses();
  },

  /**
   * Executes everytime the container or vizabi is resized
   * Ideally,it contains only operations related to size
   */
   resize: function () {

     this.model.time.pause();

     this.profile = this.getActiveProfile(profiles, presentationProfileChanges);

     var slider_w = parseInt(this.slider_outer.style("width"), 10);
     var slider_h = parseInt(this.slider_outer.style("height"), 10);
     this.width = slider_w - this.profile.margin.left - this.profile.margin.right;
     this.height = slider_h - this.profile.margin.bottom - this.profile.margin.top;
     var _this = this;

     //translate according to margins
     this.slider.attr("transform", "translate(" + this.profile.margin.left + "," + this.profile.margin.top + ")");

     //adjust scale width if it was not set manually before
     if (this.xScale.range()[1] = 1) this.xScale.range([0, this.width]);

     //adjust axis with scale
     this.xAxis = this.xAxis.scale(this.xScale)
       .tickPadding(this.profile.label_spacing);

     this.axis.attr("transform", "translate(0," + this.height / 2 + ")")
       .call(this.xAxis);

     this.slide.select(".background")
       .attr("height", this.height);

     //size of handle
     this.handle.attr("transform", "translate(0," + this.height / 2 + ")")
       .attr("r", this.profile.radius);

     this.sliderWidth = _this.slider.node().getBoundingClientRect().width;

     this._setHandle();

   },

  /**
   * Returns width of slider text value.
   * Parameters in this function needed for memoize function, so they are not redundant.
   */
  getValueWidth: function(layout, value) {
    return this.valueText.node().getBoundingClientRect().width;
  },

  /**
   * Gets brushed function to be executed when dragging
   * @returns {Function} brushed function
   */
  _getBrushed: function() {
    var _this = this;
    return function() {
      _this.model.time.pause();

      _this._optionClasses();
      _this.element.classed(class_dragging, true);

      var value = _this.brush.extent()[0];

      //set brushed properties

      if(d3.event.sourceEvent) {
        _this.model.time.dragStart();
        var posX = utils.roundStep(Math.round(d3.mouse(this)[0]), precision);
        value = _this.xScale.invert(posX);
        var maxPosX = _this.width;

        if(posX > maxPosX) {
          posX = maxPosX;
        } else if(posX < 0) {
          posX = 0;
        }

        //set handle position
        _this.handle.attr("cx", posX);
        _this.valueText.attr("transform", "translate(" + posX + "," + (_this.height / 2) + ")");
        _this.valueText.text(_this.format(value));
      }

      //set time according to dragged position
      if(value - _this.model.time.value !== 0) {
        _this._setTime(value);
      }
    };
  },

  /**
   * Gets brushedEnd function to be executed when dragging ends
   * @returns {Function} brushedEnd function
   */
  _getBrushedEnd: function() {
    var _this = this;
    return function() {
      _this.element.classed(class_dragging, false);
      _this.model.time.dragStop();
      _this.model.time.snap();
    };
  },

  /**
   * Sets the handle to the correct position
   * @param {Boolean} transition whether to use transition or not
   */
  _setHandle: function(transition) {
    var value = this.model.time.value;
    this.slide.call(this.brush.extent([value, value]));

    this.valueText.text(this.format(value));

    var old_pos = this.handle.attr("cx");
    var new_pos = this.xScale(value);
    if(old_pos == null) old_pos = new_pos;
    var delayAnimations = new_pos > old_pos ? this.model.time.delayAnimations : 0;

    if(transition) {
      this.handle.attr("cx", old_pos)
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("cx", new_pos);

      this.valueText.attr("transform", "translate(" + old_pos + "," + (this.height / 2) + ")")
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    } else {
      this.handle
        //cancel active transition
        .interrupt()
        .attr("cx", new_pos);

      this.valueText
        //cancel active transition
        .interrupt()
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    }

  },

  /**
   * Sets the current time model to time
   * @param {number} time The time
   */
  _setTime: function(time) {
    //update state
    var _this = this;
    //  frameRate = 50;

    //avoid updating more than once in "frameRate"
    //var now = new Date();
    //if (this._updTime != null && now - this._updTime < frameRate) return;
    //this._updTime = now;

    _this.model.time.value = time;
  },

    /**
   * Sets the current delay model to delay
   * @param {number} time The time
   */
  _setDelay: function (delay) {
    //update state
    var _this = this;

    _this.model.time.delay = delay;
    _this.model.time.delaySet = true;
  },

  /**
   * Applies some classes to the element according to options
   */
  _optionClasses: function() {
    //show/hide classes

    var show_limits = this.ui.show_limits;
    var show_value = this.ui.show_value;
    var show_value_when_drag_play = this.ui.show_value_when_drag_play;
    var axis_aligned = this.ui.axis_aligned;
    var show_play = (this.ui.show_button) && (this.model.time.playable);

    if(!show_limits) {
      this.xAxis.tickValues([]).ticks(0);
    }

    this.element.classed(class_hide_play, !show_play);
    this.element.classed(class_playing, this.model.time.playing);
    this.element.classed(class_show_value, show_value);
    this.element.classed(class_show_value_when_drag_play, show_value_when_drag_play);
    this.element.classed(class_axis_aligned, axis_aligned);
  }
});

export default TimeSlider;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import superagent from 'superagent';

import { addPost,
				getPosts,
				updateTrailTicketCount,
				uploadS3Photo } from '../actions';

import TrailAddPostButton from './trail_add_post_button';
import PhotoUpload from './photo_upload';
import AddPhotoTempSnackbar from './trail_add_photo_snackbar';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import FontIcon from 'material-ui/FontIcon';
import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';

class AddPostDialog extends Component {
	constructor(props) {
		super(props);
		this.state = {
			open: false,
			msg: '',
			postType: ''
		};

		this.handleChange = this.handleChange.bind(this);
		this.handleSelectChange = this.handleSelectChange.bind(this);
	}

	componentDidMount() {
		const currentUser = this.props.user.user;
		this.setState({userName: currentUser.name, userImgUrl: currentUser.iconUrl, userId: currentUser._id});
	}

	handleOpen = () => {
		this.setState({open: true});
	};

	handleClose = () => {
		this.clearState();
	};

	handlePost = () => {
		const trailId = this.props.currentTrail._id;
		const timeStamp = new Date();
		const newPost = {
			description: this.state.msg,
			postTypeString: this.state.postType,
			userName: this.state.userName,
			userImgUrl: this.state.userImgUrl,
			userId: this.state.userId,
			hasPhoto: false,
			photoUrl: '',
			postDate: timeStamp,
			postTrailId: trailId,
		};
		newPost.postFormatDate = this.formatDate(timeStamp);
		newPost.ticketopen = this.state.postType === 'open-ticket' ? true : false;
		// console.log("posting file?", this.props.uploadPhoto);
		const uploadFile = this.props.uploadPhoto;
		if (uploadFile.length > 0) {
			// this.props.uploadS3Photo(this.props.uploadPhoto);
			superagent.post(`http://localhost:3000/api/photoupload`)
      .attach('theseNamesMustMatch', uploadFile[0])
      .end((err, res) => {
        if (err) console.log(err);
        alert('File uploaded!');
        console.log("res", res);
      })
			const uploadFileName = uploadFile[0].name;
			console.log("uploadFileName", uploadFileName);
			newPost.hasPhoto = true;
			newPost.photoUrl = `https://s3.us-east-2.amazonaws.com/johndhammcodes.trailtracker/open_tickets/${uploadFileName}`;
		}
		console.log("newPost", newPost);

		this.props.addPost(newPost, () => {
			this.clearState();
			if (newPost.ticketopen) {
				const newNumOpenTickets = this.props.currentTrail.numOpenTickets + 1;
				const trailUpdateObj = {
					_id: trailId,
					numOpenTickets: newNumOpenTickets
				};
				this.props.updateTrailTicketCount(trailUpdateObj, () => {
					this.props.getPosts(trailId);
				})
			} else {
				this.props.getPosts(trailId);
			}
		})
	};

	clearState = () => {
		this.setState({
			open: false,
			msg: '',
			postType: ''
		});
	}

	formatDate (newDate) {
		var now = newDate;
		var date = [ now.getMonth() + 1, now.getDate(), now.getFullYear() ];
		var time = [ now.getHours(), now.getMinutes() ];
		var suffix = ( time[0] < 12 ) ? "AM" : "PM";
		time[0] = ( time[0] < 12 ) ? time[0] : time[0] - 12;
		time[0] = time[0] || 12;
		for ( var i = 1; i < 2; i++ ) {
			if ( time[i] < 10 ) {
				time[i] = "0" + time[i];
			}
		}
		return date.join("/") + " - " + time.join(":") + " " + suffix;
	};

	handleChange(event, value) {
		this.setState({
			msg: value
		});
	}

	handleSelectChange(event, index, value) {
		this.setState({
			postType: value
		});

	}

	render() {
		const {values} = this.props;

		const actions = [
			<FlatButton
				label="Cancel"
				primary={true}
				onTouchTap={this.handleClose}
				labelStyle={{color: `${values.openTicket.color}`}}
			/>,
			<FlatButton
				label="Post"
				primary={true}
				labelStyle={{color: `${values.primary.color}`}}
				onTouchTap={this.handlePost}
			/>
		];

		return (

					<TrailAddPostButton />

				<Dialog
					title="Select the type of post and add a message"
					actions={actions}
					modal={false}
					open={this.state.open}
					onRequestClose={this.handleClose}
				>
					<SelectField
						name="postType"
						floatingLabelText="Post type"
						autoWidth={true}
						value={this.state.postType}
						onChange={this.handleSelectChange}
					>
						<MenuItem value={"meetup"} primaryText="Meetup - organize a group ride" />
						<MenuItem value={"ride-report"} primaryText="Ride Report - trail conditions (non-critical)" />
						<MenuItem value={"open-ticket"} primaryText="Open Ticket - for issues that need repair" />
					</SelectField>

					<TextField
						name="msg"
						hintText="Type message here"
						fullWidth={true}
						value={this.state.msg}
						onChange={this.handleChange}
						autoFocus={true}
					/>

					<PhotoUpload />

				</Dialog>

		);
	}
}

function mapStateToProps({values, user, currentTrail, uploadPhoto}) {
	return { values, user, currentTrail, uploadPhoto};
}

export default connect(mapStateToProps, {addPost, getPosts, updateTrailTicketCount, uploadS3Photo })(AddPostDialog);
<commit_msg>upload photo now includes api route with post type<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import superagent from 'superagent';

import { addPost,
				getPosts,
				updateTrailTicketCount,
				uploadS3Photo } from '../actions';

import TrailAddPostButton from './trail_add_post_button';
import PhotoUpload from './photo_upload';
import AddPhotoTempSnackbar from './trail_add_photo_snackbar';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import FontIcon from 'material-ui/FontIcon';
import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';

class AddPostDialog extends Component {
	constructor(props) {
		super(props);
		this.state = {
			open: false,
			msg: '',
			postType: ''
		};

		this.handleChange = this.handleChange.bind(this);
		this.handleSelectChange = this.handleSelectChange.bind(this);
	}

	componentDidMount() {
		const currentUser = this.props.user.user;
		this.setState({userName: currentUser.name, userImgUrl: currentUser.iconUrl, userId: currentUser._id});
	}

	handleOpen = () => {
		this.setState({open: true});
	};

	handleClose = () => {
		this.clearState();
	};

	handlePost = () => {
		const trailId = this.props.currentTrail._id;
		const timeStamp = new Date();
		const newPost = {
			description: this.state.msg,
			postTypeString: this.state.postType,
			userName: this.state.userName,
			userImgUrl: this.state.userImgUrl,
			userId: this.state.userId,
			hasPhoto: false,
			photoUrl: '',
			postDate: timeStamp,
			postTrailId: trailId,
		};
		newPost.postFormatDate = this.formatDate(timeStamp);
		newPost.ticketopen = this.state.postType === 'open-ticket' ? true : false;
		const uploadFile = this.props.uploadPhoto;
		if (uploadFile.length > 0) {
			superagent.post(`http://localhost:3000/api/photoupload/${this.state.postType}`)
      .attach('theseNamesMustMatch', uploadFile[0])
      .end((err, res) => {
        if (err) console.log(err);
        // alert('File uploaded!');
        console.log("res", res);
      })
			const uploadFileName = uploadFile[0].name;
			console.log("uploadFileName", uploadFileName);
			newPost.hasPhoto = true;
			newPost.photoUrl = `https://s3.us-east-2.amazonaws.com/johndhammcodes.trailtracker/${this.state.postType}/${uploadFileName}`;
		}
		console.log("newPost", newPost);

		this.props.addPost(newPost, () => {
			this.clearState();
			if (newPost.ticketopen) {
				const newNumOpenTickets = this.props.currentTrail.numOpenTickets + 1;
				const trailUpdateObj = {
					_id: trailId,
					numOpenTickets: newNumOpenTickets
				};
				this.props.updateTrailTicketCount(trailUpdateObj, () => {
					this.props.getPosts(trailId);
				})
			} else {
				this.props.getPosts(trailId);
			}
		})
	};

	clearState = () => {
		this.setState({
			open: false,
			msg: '',
			postType: ''
		});
	}

	formatDate (newDate) {
		var now = newDate;
		var date = [ now.getMonth() + 1, now.getDate(), now.getFullYear() ];
		var time = [ now.getHours(), now.getMinutes() ];
		var suffix = ( time[0] < 12 ) ? "AM" : "PM";
		time[0] = ( time[0] < 12 ) ? time[0] : time[0] - 12;
		time[0] = time[0] || 12;
		for ( var i = 1; i < 2; i++ ) {
			if ( time[i] < 10 ) {
				time[i] = "0" + time[i];
			}
		}
		return date.join("/") + " - " + time.join(":") + " " + suffix;
	};

	handleChange(event, value) {
		this.setState({
			msg: value
		});
	}

	handleSelectChange(event, index, value) {
		this.setState({
			postType: value
		});

	}

	render() {
		const {values} = this.props;

		const actions = [
			<FlatButton
				label="Cancel"
				primary={true}
				onTouchTap={this.handleClose}
				labelStyle={{color: `${values.openTicket.color}`}}
			/>,
			<FlatButton
				label="Post"
				primary={true}
				labelStyle={{color: `${values.primary.color}`}}
				onTouchTap={this.handlePost}
			/>
		];

		return (

					<TrailAddPostButton />

				<Dialog
					title="Select the type of post and add a message"
					actions={actions}
					modal={false}
					open={this.state.open}
					onRequestClose={this.handleClose}
				>
					<SelectField
						name="postType"
						floatingLabelText="Post type"
						autoWidth={true}
						value={this.state.postType}
						onChange={this.handleSelectChange}
					>
						<MenuItem value={"meetup"} primaryText="Meetup - organize a group ride" />
						<MenuItem value={"ride-report"} primaryText="Ride Report - trail conditions (non-critical)" />
						<MenuItem value={"open-ticket"} primaryText="Open Ticket - for issues that need repair" />
					</SelectField>

					<TextField
						name="msg"
						hintText="Type message here"
						fullWidth={true}
						value={this.state.msg}
						onChange={this.handleChange}
						autoFocus={true}
					/>

					<PhotoUpload />

				</Dialog>

		);
	}
}

function mapStateToProps({values, user, currentTrail, uploadPhoto}) {
	return { values, user, currentTrail, uploadPhoto};
}

export default connect(mapStateToProps, {addPost, getPosts, updateTrailTicketCount, uploadS3Photo })(AddPostDialog);
<|endoftext|>


<commit_before>import { Element as PolymerElement } from '/node_modules/@polymer/polymer/polymer-element.js'
import ReduxMixin from '../../reduxStore.js'
import USBPort from './USBPort.js'
import ArduinoLeonardoETHDriver from './ArduinoLeonardoETHDriver.js'
import { connectUsb } from '../../actions/index.js'

/*
 * Handle the connection to a USB DMX controller using WebUSB
 * Tested with Arduino Leonardo ETH, but should be compatible with every Arduino
 * that can be used as a USB device
 */
class UsbDmxManager extends ReduxMixin(PolymerElement) {

  constructor() {
    super()

    // USBPort
    this.port = null

    // @TODO: Move ALL OF THIS into it's own module
    this.driver = new ArduinoLeonardoETHDriver(this.port, {})

    // Only request the port for specific devices
    this.usbDeviceFilter = [
      // Arduino LLC (9025)
      { vendorId: 0x2341, productId: 0x8036 },
      // Arduino LLC (9025)
      { vendorId: 0x2341, productId: 0x8037 },
      // Arduino LLC (10755), Leonardo ETH (32832)
      { vendorId: 0x2a03, productId: 0x8040 }
    ]

    // Check for USB devices that are already paired
    this.getUsbPorts().then(list => {
      if (list[0] !== undefined && list[0].hasOwnProperty('device')) {
        [this.port] = list

        // Auto connect
        this.connect()
      }
    })
  }

  static get properties() {
    return {
      usbConnection: {
        type: Boolean,
        statePath: 'connectionManager.usb'
      },
      universes: {
        type: Array,
        statePath: 'universeManager'
      }
    }
  }

  connectedCallback() {
    super.connectedCallback()
    window.addEventListener('send-universe-to-usb-dmx-controller', this.listenSendUniverse.bind(this))
  }

  disconnectedCallback() {
    super.disconnectedCallback()
    window.removeEventListener('send-universe-to-usb-dmx-controller', this.listenSendUniverse.bind(this))
  }

  listenSendUniverse(e) {
    // Send universe 0 to the USB DMX controller
    this.driver.send(this.universes[0].channels)
  }

  handleConnectButtonClick() {

    // Disconnect from USB controller
    if (this.usbConnection.connected) {
      this.port.disconnect().then(() => {
        // Disconnected
      }, error => {
        console.error(error)
      })

    // Pair with USB controller
    } else {
      this.enable()
    }
  }

  /*
   * Get a list of USB devices that are already paired
   */
  getUsbPorts() {
    return navigator.usb.getDevices().
      then(devices => {
        this.devices = devices

        return devices.map(device => new USBPort({ device }))
      })
  }

  /*
   * Get access to USB devices that match the provided filters
   */
  requestUsbPort() {
    // Request access to the USB device
    return navigator.usb.requestDevice({ filters: this.usbDeviceFilter }).
      then(device => new USBPort({ device }))
  }

  /*
   * Enable WebUSB and request a USBPort
   */
  enable() {
    this.requestUsbPort().then(selectedPort => {
      this.port = selectedPort
      this.connect()
    }).
    catch(error => {
      console.error(error)
    })
  }

  /*
   * Connect to a selected USBPort
   */
  connect() {

    this.driver.serialport = this.port

    this.port.connect().then(() => {

      // USB is connected
      this.dispatch(connectUsb(true))

      // Receive data
      this.port.onReceive = data => {
        // const textDecoder = new TextDecoder()
        // console.log(textDecoder.decode(data))
      }

      // Receive error
      this.port.onReceiveError = error => {
        // USB is disconnected
        this.dispatch(connectUsb(false))
        console.log(error)
      }

    }, error => {
      // USB is disconnected
      this.dispatch(connectUsb(false))
      console.error(error)
    })
  }

  static get template() {
    return `
      <connect-button type="usb" label="USB" on-click="handleConnectButtonClick"></connect-button>
    `
  }
}

customElements.define('usb-dmx-manager', UsbDmxManager)
<commit_msg>fix(usb-dmx-manager): Port is null<commit_after>import { Element as PolymerElement } from '/node_modules/@polymer/polymer/polymer-element.js'
import ReduxMixin from '../../reduxStore.js'
import USBPort from './USBPort.js'
import ArduinoLeonardoETHDriver from './ArduinoLeonardoETHDriver.js'
import { connectUsb } from '../../actions/index.js'

/*
 * Handle the connection to a USB DMX controller using WebUSB
 * Tested with Arduino Leonardo ETH, but should be compatible with every Arduino
 * that can be used as a USB device
 */
class UsbDmxManager extends ReduxMixin(PolymerElement) {

  constructor() {
    super()

    // USBPort
    this.port = null

    // @TODO: Move ALL OF THIS into it's own module
    this.driver = new ArduinoLeonardoETHDriver(this.port, {})

    // Only request the port for specific devices
    this.usbDeviceFilter = [
      // Arduino LLC (9025)
      { vendorId: 0x2341, productId: 0x8036 },
      // Arduino LLC (9025)
      { vendorId: 0x2341, productId: 0x8037 },
      // Arduino LLC (10755), Leonardo ETH (32832)
      { vendorId: 0x2a03, productId: 0x8040 }
    ]

    // Check for USB devices that are already paired
    this.getUsbPorts().then(list => {
      if (list[0] !== undefined && list[0].hasOwnProperty('device')) {
        [this.port] = list

        // Auto connect
        this.connect()
      }
    })
  }

  static get properties() {
    return {
      usbConnection: {
        type: Boolean,
        statePath: 'connectionManager.usb'
      },
      universes: {
        type: Array,
        statePath: 'universeManager'
      }
    }
  }

  connectedCallback() {
    super.connectedCallback()
    window.addEventListener('send-universe-to-usb-dmx-controller', this.listenSendUniverse.bind(this))
  }

  disconnectedCallback() {
    super.disconnectedCallback()
    window.removeEventListener('send-universe-to-usb-dmx-controller', this.listenSendUniverse.bind(this))
  }

  listenSendUniverse(e) {
    // Send universe 0 to the USB DMX controller
    this.driver.send(this.universes[0].channels)
  }

  handleConnectButtonClick() {

    // Disconnect from USB controller
    if (this.usbConnection.connected) {

      if (this.port === null) {
        this.dispatch(connectUsb(false))
      } else {
        this.port.disconnect().then(() => {
          // Disconnected
        }, error => {
          console.error(error)
        })
      }

    // Pair with USB controller
    } else {
      this.enable()
    }
  }

  /*
   * Get a list of USB devices that are already paired
   */
  getUsbPorts() {
    return navigator.usb.getDevices().
      then(devices => {
        this.devices = devices

        return devices.map(device => new USBPort({ device }))
      })
  }

  /*
   * Get access to USB devices that match the provided filters
   */
  requestUsbPort() {
    // Request access to the USB device
    return navigator.usb.requestDevice({ filters: this.usbDeviceFilter }).
      then(device => new USBPort({ device }))
  }

  /*
   * Enable WebUSB and request a USBPort
   */
  enable() {
    this.requestUsbPort().then(selectedPort => {
      this.port = selectedPort
      this.connect()
    }).
    catch(error => {
      console.error(error)
    })
  }

  /*
   * Connect to a selected USBPort
   */
  connect() {

    this.driver.serialport = this.port

    this.port.connect().then(() => {

      // USB is connected
      this.dispatch(connectUsb(true))

      // Receive data
      this.port.onReceive = data => {
        // const textDecoder = new TextDecoder()
        // console.log(textDecoder.decode(data))
      }

      // Receive error
      this.port.onReceiveError = error => {
        // USB is disconnected
        this.dispatch(connectUsb(false))
        console.log(error)
      }

    }, error => {
      // USB is disconnected
      this.dispatch(connectUsb(false))
      console.error(error)
    })
  }

  static get template() {
    return `
      <connect-button type="usb" label="USB" on-click="handleConnectButtonClick"></connect-button>
    `
  }
}

customElements.define('usb-dmx-manager', UsbDmxManager)
<|endoftext|>


<commit_before>// Whole-script strict mode syntax
"use strict";

var request = require('request');
var GitHub = require("github");
var util = require('util');
var dashboard = require('./dashboard.js');

var allEvents = {};
var allBugSummaries = {};
var allBugData = {};
var allAttachmentData = {};

var github = new GitHub({
    // required
    version: "3.0.0",
    // optional
    debug: false,
    protocol: "https",
    host: "api.github.com",
    timeout: 30000
  });

github.authenticate({
  type: "oauth",
  token: process.env.SEKRIT
});

// go find all the bugs we are tracking for WADI

// TODO get all history https://bugzilla.mozilla.org/rest/bug/707428/history
// TODO get attachments look for review status https://bugzilla.mozilla.org/rest/bug/707428/attachment

function addAttachmentInfo(inBugID) {
  var attachmentURL = "https://bugzilla.mozilla.org/rest/bug/" + inBugID + "/attachment";
  request({ uri: attachmentURL, timeout: 30000 }, function(error, response, body) {
    try {
      if (error) {
        throw new Error(error);
      }

      // try to parse the response and find the list of bugs
      var parsedResult = JSON.parse(body);
      var attachmentBugList = Object.keys(parsedResult.bugs);

      // if this attachments info includes a non-empty list of bugs ...
      if (attachmentBugList.length > 0) {
        // ... use the first one
        var tmpBugID = attachmentBugList[0];

        // ... if that first bug has a non-zero list of attachments
        if (parsedResult.bugs[tmpBugID].length > 0) {
          // store them in the global dictionary
          allAttachmentData[tmpBugID] = parsedResult.bugs[tmpBugID];

          // if we already have the bug details, go redo the summary
          if (allBugData[tmpBugID]) {
            allBugSummaries[tmpBugID] = dashboard.formatForBugBox(allBugData[tmpBugID], parsedResult.bugs[tmpBugID]);
            dashboard.redrawBugs(allBugSummaries, allBugData, allAttachmentData);
          }

          // log about it
          dashboard.logString(tmpBugID, parsedResult.bugs[tmpBugID]);
        }
      }
    }
    catch (e) {
      dashboard.logString(inBugID + ' attachment error: ' + e);
    }
  });
}

function addBugDetails(inBugID) {
  var bugDataURL = "https://bugzilla.mozilla.org/rest/bug/" + inBugID + "?include_fields=id,status,summary,assigned_to";
  request({ uri: bugDataURL, timeout: 30000 }, function(error, response, body) {
    try {
      if (error) {
        throw new Error(error);
      }

      var parsedResult = JSON.parse(body);

      if (parsedResult.bugs) {
        var trackedBug = parsedResult.bugs[0];
        allBugData['' + trackedBug.id] = trackedBug;

        allBugSummaries['' + trackedBug.id] = dashboard.formatForBugBox(trackedBug, allAttachmentData[trackedBug.id]);
        dashboard.redrawBugs(allBugSummaries, allBugData, allAttachmentData);
      } else {
        // missing buglist!
        allBugSummaries['' + inBugID] = inBugID + ' missing bug info';
        dashboard.logString(inBugID + ' missing bug info');
      }
    }
    catch (e) {
      allBugSummaries['' + inBugID] = inBugID + ' details error: ' + e;
      dashboard.logString(inBugID + ' details error: ' + e);
    }
  });
}

function addBugsTrackedBy(inBugID) {
  request("https://bugzilla.mozilla.org/rest/bug/" + inBugID + "?include_fields=id,depends_on", function(error, response, body) {
    if (error) {
      throw new Error(error);
    }

    try {
      var tracker = JSON.parse(body);
      var depends_on_list = tracker.bugs[0].depends_on;

      // loop through the list of tracked bug ID's
      for (var bugIndex in depends_on_list) {
        var bugID = depends_on_list[bugIndex];

        // and for each tracked bug ID, go find info for that bug
        addAttachmentInfo(bugID);
        addBugDetails(bugID);
      }
    }
    catch (e) {
      dashboard.logString('cannot parse tracker ' + e);
    }
  });
}

function addEventsFromRepo(inRepoName) {
  github.events.getFromRepo( { 'user': 'mozilla', 'repo': inRepoName, per_page: 100 }, function(err, activities) {
    try {
      if (err) {
        throw new Error(err.message);
      }

      for (var activityIndex in activities) {
        var anActivity = activities[activityIndex];
        allEvents[anActivity.created_at] = dashboard.formatForEventBox(inRepoName, anActivity);
      }
      dashboard.redrawEvents(allEvents);
    }
    catch(e) {
      dashboard.logString('cannot parse events json ' + e);
      dashboard.logString(e.stack);
    }
  });
}

// go find all the activity for wadi repo's

dashboard.initializeBlessedDashboard();

dashboard.logString('starting');

addEventsFromRepo('oghliner');
addEventsFromRepo('platatus');
addEventsFromRepo('serviceworker-cookbook');

addBugsTrackedBy(1201717);
addBugsTrackedBy(1059784);

dashboard.logString('done starting');

<commit_msg>add long global timeout<commit_after>// Whole-script strict mode syntax
"use strict";

var request = require('request');
var GitHub = require("github");
var util = require('util');
var dashboard = require('./dashboard.js');
var globalTimeout = 90000;

var allEvents = {};
var allBugSummaries = {};
var allBugData = {};
var allAttachmentData = {};

var github = new GitHub({
    // required
    version: "3.0.0",
    // optional
    debug: false,
    protocol: "https",
    host: "api.github.com",
    timeout: globalTimeout
  });

github.authenticate({
  type: "oauth",
  token: process.env.SEKRIT
});

// go find all the bugs we are tracking for WADI

// TODO get all history https://bugzilla.mozilla.org/rest/bug/707428/history
// TODO get attachments look for review status https://bugzilla.mozilla.org/rest/bug/707428/attachment

function addAttachmentInfo(inBugID) {
  var attachmentURL = "https://bugzilla.mozilla.org/rest/bug/" + inBugID + "/attachment";
  request({ uri: attachmentURL, timeout: globalTimeout }, function(error, response, body) {
    try {
      if (error) {
        throw new Error(error);
      }

      // try to parse the response and find the list of bugs
      var parsedResult = JSON.parse(body);
      var attachmentBugList = Object.keys(parsedResult.bugs);

      // if this attachments info includes a non-empty list of bugs ...
      if (attachmentBugList.length > 0) {
        // ... use the first one
        var tmpBugID = attachmentBugList[0];

        // ... if that first bug has a non-zero list of attachments
        if (parsedResult.bugs[tmpBugID].length > 0) {
          // store them in the global dictionary
          allAttachmentData[tmpBugID] = parsedResult.bugs[tmpBugID];

          // if we already have the bug details, go redo the summary
          if (allBugData[tmpBugID]) {
            allBugSummaries[tmpBugID] = dashboard.formatForBugBox(allBugData[tmpBugID], parsedResult.bugs[tmpBugID]);
            dashboard.redrawBugs(allBugSummaries, allBugData, allAttachmentData);
          }

          // log about it
          dashboard.logString(tmpBugID, parsedResult.bugs[tmpBugID]);
        }
      }
    }
    catch (e) {
      dashboard.logString(inBugID + ' attachment error: ' + e);
    }
  });
}

function addBugDetails(inBugID) {
  var bugDataURL = "https://bugzilla.mozilla.org/rest/bug/" + inBugID + "?include_fields=id,status,summary,assigned_to";
  request({ uri: bugDataURL, timeout: globalTimeout }, function(error, response, body) {
    try {
      if (error) {
        throw new Error(error);
      }

      var parsedResult = JSON.parse(body);

      if (parsedResult.bugs) {
        var trackedBug = parsedResult.bugs[0];
        allBugData['' + trackedBug.id] = trackedBug;

        allBugSummaries['' + trackedBug.id] = dashboard.formatForBugBox(trackedBug, allAttachmentData[trackedBug.id]);
        dashboard.redrawBugs(allBugSummaries, allBugData, allAttachmentData);
      } else {
        // missing buglist!
        allBugSummaries['' + inBugID] = inBugID + ' missing bug info';
        dashboard.logString(inBugID + ' missing bug info');
      }
    }
    catch (e) {
      allBugSummaries['' + inBugID] = inBugID + ' details error: ' + e;
      dashboard.logString(inBugID + ' details error: ' + e);
    }
  });
}

function addBugsTrackedBy(inBugID) {
  var trackerURL = "https://bugzilla.mozilla.org/rest/bug/" + inBugID + "?include_fields=id,depends_on";
  request({ uri: trackerURL, timeout: globalTimeout }, function(error, response, body) {
    if (error) {
      throw new Error(error);
    }

    try {
      var tracker = JSON.parse(body);
      var depends_on_list = tracker.bugs[0].depends_on;

      // loop through the list of tracked bug ID's
      for (var bugIndex in depends_on_list) {
        var bugID = depends_on_list[bugIndex];

        // and for each tracked bug ID, go find info for that bug
        addAttachmentInfo(bugID);
        addBugDetails(bugID);
      }
    }
    catch (e) {
      dashboard.logString('cannot parse tracker ' + e);
    }
  });
}

function addEventsFromRepo(inRepoName) {
  github.events.getFromRepo( { 'user': 'mozilla', 'repo': inRepoName, per_page: 100 }, function(err, activities) {
    try {
      if (err) {
        throw new Error(err.message);
      }

      for (var activityIndex in activities) {
        var anActivity = activities[activityIndex];
        allEvents[anActivity.created_at] = dashboard.formatForEventBox(inRepoName, anActivity);
      }
      dashboard.redrawEvents(allEvents);
    }
    catch(e) {
      dashboard.logString('cannot parse events json ' + e);
      dashboard.logString(e.stack);
    }
  });
}

// go find all the activity for wadi repo's

dashboard.initializeBlessedDashboard();

dashboard.logString('starting');

addEventsFromRepo('oghliner');
addEventsFromRepo('platatus');
addEventsFromRepo('serviceworker-cookbook');

addBugsTrackedBy(1201717);
addBugsTrackedBy(1059784);

dashboard.logString('done starting');

<|endoftext|>


<commit_before>define(['dojo/_base/declare', 'dojo/_base/lang', 'dojo/_base/array', './Store', './objectQueryEngine' /*=====, './api/Store' =====*/],
		// TODO: Do we still need the api/Store dep for docs? If not, remove it and rename StoreBase to Store.
		function (declare, lang, arrayUtil, StoreBase, objectQueryEngine /*=====, Store =====*/) {

	function createQuery(updateTotal) {
		return function query () {
			var newCollection = this.inherited(arguments),
				queryer = newCollection.queryLog[newCollection.queryLog.length - 1].queryer;

			var data = newCollection.data = queryer(this.data);
			newCollection.total = updateTotal ? data.length : this.total;
			return newCollection;
		};
	}

	// module:
	//		dstore/Memory
	/* jshint proto: true */
	var hasProto = !!{}.__proto__;
	return declare(StoreBase, {
		constructor: function () {
			// summary:
			//		Creates a memory object store.
			// options: dstore/Memory
			//		This provides any configuration information that will be mixed into the store.
			//		This should generally include the data property to provide the starting set of data.

			this.setData(this.data || []);
		},

		queryEngine: objectQueryEngine,

		// data: Array
		//		The array of all the objects in the memory store
		data: null,

		autoEmitEvents: false, // this is handled by the methods themselves

		get: function (id) {
			// summary:
			//		Retrieves an object by its identity
			// id: Number
			//		The identity to use to lookup the object
			// returns: Object
			//		The object in the store that matches the given id.
			return this.storage.fullData[this.storage.index[id]];
		},
		put: function (object, options) {
			// summary:
			//		Stores an object
			// object: Object
			//		The object to store.
			// options: dstore/Store.PutDirectives?
			//		Additional metadata for storing the data.  Includes an 'id'
			//		property if a specific id is to be used.
			// returns: Number
			var storage = this.storage,
				index = storage.index,
				data = storage.fullData;

			var id = this.getIdentity(object);
			if (id == null) {
				this._setIdentity(object, (options && 'id' in options) ? options.id : Math.random());
				id = this.getIdentity(object);
			}
			var model = this.model;
			if (model && !(object instanceof model)) {
				var prototype = model.prototype;
				if (hasProto) {
					// the fast easy way
					object.__proto__ = prototype;
				} else {
					// create a new object with the correct prototype
					object = lang.delegate(prototype, object);
				}
			}
			if (id in index) {
				// object exists
				if (options && options.overwrite === false) {
					throw new Error('Object already exists');
				}
				// replace the entry in data
				data[index[id]] = object;
				this.emit('update', {target: object});
			} else {
				// add the new object
				index[id] = data.push(object) - 1;
				this.emit('add', {target: object});
			}
			return object;
		},
		add: function (object, options) {
			// summary:
			//		Creates an object, throws an error if the object already exists
			// object: Object
			//		The object to store.
			// options: dstore/Store.PutDirectives?
			//		Additional metadata for storing the data.  Includes an 'id'
			//		property if a specific id is to be used.
			// returns: Number
			(options = options || {}).overwrite = false;
			// call put with overwrite being false
			return this.put(object, options);
		},
		remove: function (id) {
			// summary:
			//		Deletes an object by its identity
			// id: Number
			//		The identity to use to delete the object
			// returns: Boolean
			//		Returns true if an object was removed, falsy (undefined) if no object matched the id
			var storage = this.storage;
			var index = storage.index;
			var data = storage.fullData;
			if (id in index) {
				data.splice(index[id], 1);
				// now we have to reindex
				this._reindex();
				// TODO: The id property makes it seem like an event id. Maybe targetId would be better.
				this.emit('remove', {id: id});
				return true;
			}
		},
		setData: function (data) {
			// summary:
			//		Sets the given data as the source for this store, and indexes it
			// data: Object[]
			//		An array of objects to use as the source of data. Note that this
			//		array will not be copied, it is used directly and mutated as
			//		data changes.

			if (this.parse) {
				data = this.parse(data);
			}
			if (data.items) {
				// just for convenience with the data format ItemFileReadStore expects
				this.idProperty = data.identifier || this.idProperty;
				data = data.items;
			}
			var storage = this.storage;
			storage.fullData = this.data = data;
			this.total = data.length;
			this._reindex();
			this.emit('refresh');
		},

		_reindex: function () {
			var storage = this.storage;
			var index = storage.index = {};
			var data = storage.fullData;
			var model = this.model;
			var prototype = model && model.prototype;
			for (var i = 0, l = data.length; i < l; i++) {
				var object = data[i];
				if (model && !(object instanceof model)) {
					var restoredObject = this._restore(object);
					if (object !== restoredObject) {
						// a new object was generated in the restoration process,
						// so we have to update the item in the data array.
						data[i] = object = restoredObject;
					}
				}
				index[this.getIdentity(object)] = i;
			}
		},

		filter: createQuery(true),
		sort: createQuery(),
		range: createQuery(),

		fetch: function () {
			return this.data;
		}
	});

});
<commit_msg>Order getIdentity so that it can be reliably called against restored objects<commit_after>define(['dojo/_base/declare', 'dojo/_base/lang', 'dojo/_base/array', './Store', './objectQueryEngine' /*=====, './api/Store' =====*/],
		// TODO: Do we still need the api/Store dep for docs? If not, remove it and rename StoreBase to Store.
		function (declare, lang, arrayUtil, StoreBase, objectQueryEngine /*=====, Store =====*/) {

	function createQuery(updateTotal) {
		return function query () {
			var newCollection = this.inherited(arguments),
				queryer = newCollection.queryLog[newCollection.queryLog.length - 1].queryer;

			var data = newCollection.data = queryer(this.data);
			newCollection.total = updateTotal ? data.length : this.total;
			return newCollection;
		};
	}

	// module:
	//		dstore/Memory
	/* jshint proto: true */
	var hasProto = !!{}.__proto__;
	return declare(StoreBase, {
		constructor: function () {
			// summary:
			//		Creates a memory object store.
			// options: dstore/Memory
			//		This provides any configuration information that will be mixed into the store.
			//		This should generally include the data property to provide the starting set of data.

			this.setData(this.data || []);
		},

		queryEngine: objectQueryEngine,

		// data: Array
		//		The array of all the objects in the memory store
		data: null,

		autoEmitEvents: false, // this is handled by the methods themselves

		get: function (id) {
			// summary:
			//		Retrieves an object by its identity
			// id: Number
			//		The identity to use to lookup the object
			// returns: Object
			//		The object in the store that matches the given id.
			return this.storage.fullData[this.storage.index[id]];
		},
		put: function (object, options) {
			// summary:
			//		Stores an object
			// object: Object
			//		The object to store.
			// options: dstore/Store.PutDirectives?
			//		Additional metadata for storing the data.  Includes an 'id'
			//		property if a specific id is to be used.
			// returns: Number
			var storage = this.storage,
				index = storage.index,
				data = storage.fullData;

			var model = this.model;
			if (model && !(object instanceof model)) {
				var prototype = model.prototype;
				if (hasProto) {
					// the fast easy way
					object.__proto__ = prototype;
				} else {
					// create a new object with the correct prototype
					object = lang.delegate(prototype, object);
				}
			}
			var id = this.getIdentity(object);
			if (id == null) {
				this._setIdentity(object, (options && 'id' in options) ? options.id : Math.random());
				id = this.getIdentity(object);
			}
			if (id in index) {
				// object exists
				if (options && options.overwrite === false) {
					throw new Error('Object already exists');
				}
				// replace the entry in data
				data[index[id]] = object;
				this.emit('update', {target: object});
			} else {
				// add the new object
				index[id] = data.push(object) - 1;
				this.emit('add', {target: object});
			}
			return object;
		},
		add: function (object, options) {
			// summary:
			//		Creates an object, throws an error if the object already exists
			// object: Object
			//		The object to store.
			// options: dstore/Store.PutDirectives?
			//		Additional metadata for storing the data.  Includes an 'id'
			//		property if a specific id is to be used.
			// returns: Number
			(options = options || {}).overwrite = false;
			// call put with overwrite being false
			return this.put(object, options);
		},
		remove: function (id) {
			// summary:
			//		Deletes an object by its identity
			// id: Number
			//		The identity to use to delete the object
			// returns: Boolean
			//		Returns true if an object was removed, falsy (undefined) if no object matched the id
			var storage = this.storage;
			var index = storage.index;
			var data = storage.fullData;
			if (id in index) {
				data.splice(index[id], 1);
				// now we have to reindex
				this._reindex();
				// TODO: The id property makes it seem like an event id. Maybe targetId would be better.
				this.emit('remove', {id: id});
				return true;
			}
		},
		setData: function (data) {
			// summary:
			//		Sets the given data as the source for this store, and indexes it
			// data: Object[]
			//		An array of objects to use as the source of data. Note that this
			//		array will not be copied, it is used directly and mutated as
			//		data changes.

			if (this.parse) {
				data = this.parse(data);
			}
			if (data.items) {
				// just for convenience with the data format ItemFileReadStore expects
				this.idProperty = data.identifier || this.idProperty;
				data = data.items;
			}
			var storage = this.storage;
			storage.fullData = this.data = data;
			this.total = data.length;
			this._reindex();
			this.emit('refresh');
		},

		_reindex: function () {
			var storage = this.storage;
			var index = storage.index = {};
			var data = storage.fullData;
			var model = this.model;
			var prototype = model && model.prototype;
			for (var i = 0, l = data.length; i < l; i++) {
				var object = data[i];
				if (model && !(object instanceof model)) {
					var restoredObject = this._restore(object);
					if (object !== restoredObject) {
						// a new object was generated in the restoration process,
						// so we have to update the item in the data array.
						data[i] = object = restoredObject;
					}
				}
				index[this.getIdentity(object)] = i;
			}
		},

		filter: createQuery(true),
		sort: createQuery(),
		range: createQuery(),

		fetch: function () {
			return this.data;
		}
	});

});
<|endoftext|>


<commit_before>/**
 * Copyright 2014 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var Api = function ApiConstructor($resource, server, $http) {

		this.pages = $resource(server + "/api/pages/:id");

		this.pageFragments = $resource(server + "/api/pages/fragments/:id");

		this.pods = $resource(server + "/api/pages/pods");

		this.questionPages = $resource(server + "/api/pages/questions/:id");

		this.conceptPages = $resource(server + "/api/pages/concepts/:id");

		this.questionValidator = $resource(server + "/api/questions/:id/answer", {}, {
			validate: {
				method: "POST",
			},
		});

		this.contactForm = $resource(server + "/api/contact/", {}, {
			send: {
				method: "POST",
			},
		});

		this.gameBoards = $resource(server + "/api/gameboards/:id", {id: "@id"}, {
			filter: {
				method: "GET",
				url: server + "/api/gameboards",
			},
			wildcards: {
				method: "GET",
				url: server + "/api/gameboards/wildcards",
				isArray: true
			},
		});

		this.contentProblems = $resource(server + "/api/admin/content_problems");

		this.currentUser = $resource(server + "/api/users/current_user", {}, {
			'getProgress': {
				method: 'GET',
				url: server + "/api/users/current_user/progress",
			},
		});

		this.user = $resource("", { }, {
			'getProgress': {
				method: 'GET',
				url: server + "/api/users/:userId/progress",
			},
			'getEventsOverTime' : {
				method: 'GET',
				url: server + "/api/users/:userId/event_data/over_time?from_date=:from_date&to_date=:to_date&events=:events"
			},
			'getEmailPreferences' : {
				method: 'GET',
				url: server + "/api/users/email_preferences"
			},
		})

		this.authentication = $resource("", {}, {
			'getAuthRedirect': {
				method: 'GET',
				url: server+"/api/auth/:provider/authenticate"
			},
			'getLinkRedirect': {
				method: 'GET',
				url: server+"/api/auth/:provider/link",
			},
			'getAuthResult': {
				method: 'GET',
				url: server+"/api/auth/:provider/callback",
			},
			'login': {
				method: 'POST',
				url: server+"/api/auth/segue/authenticate",
			},
			'logout': {
				method: 'POST',
				url: server+"/api/auth/logout",
			},
		});

		this.searchEndpoint = $resource(server + "/api/search/:searchTerms?types=:types", {}, {
			'search': {
				method: 'GET',
				isArray: false
			},
		});

		this.adminUserSearch = $resource(server + "/api/admin/users/:userId?email=:email", {}, {
			'search': {
				method: 'GET',
				isArray: true
			},
			'get' : {
				method: 'GET',
				isArray: false
			},
		});

		this.statisticsEndpoint = $resource(server + "/api/admin/stats/", {}, {
			'get' : {
				method: 'GET',
				isArray: false
			},
			'getGameboardPopularity' : {
				method: 'GET',
				url: server + "/api/gameboards/popular",
				isArray: false
			},
			'getSchoolPopularity' : {
				method: 'GET',
				url: server + "/api/admin/stats/schools/",
				isArray: true
			},
			'getSchoolUsers' : {
				method: 'GET',
				url: server + "/api/admin/users/schools/:id",
				params: {id: '@id'},
			},
			'getEventsOverTime' : {
				method: 'GET',
				url: server + "/api/admin/users/event_data/over_time?from_date=:from_date&to_date=:to_date&events=:events&bin_data=:bin_data"
			},
			'getUserLocations' : {
				method: 'GET',
				url: server + "/api/admin/stats/users/last_locations",
				isArray: true
			},
			'getLogEventTypes' : {
				method: 'GET',
				url: server + "/api/info/log_event_types",
			},
		});

		this.adminDeleteUser = $resource(server + "/api/admin/users/:userId", {}, {
			'delete' : {
				method: 'DELETE'
			},
		});

		this.groupManagementEndpoint = $resource(server + "/api/groups/:id", {id: "@id"}, {
			'get' : {
				method: 'GET',
				isArray: true
			},
			'delete' :{
				method: 'DELETE',
				url: server + "/api/groups/:id",
				isArray: true
			},
			'getMembers' : {
				method: 'GET',
				url: server + "/api/groups/:id/membership",
				isArray: true
			},
			'deleteMember' : {
				method: 'DELETE',
				url: server + "/api/groups/:id/membership/:userId",
				isArray: true
			},
			'getToken' : {
				method: 'GET',
				url: server + "/api/authorisations/token/:id",
				isArray: false
			},
		});

		this.authorisations = $resource(server + "/api/authorisations/", {}, {
			'get' : {
				method: 'GET',
				isArray: true
			},
			'useToken' : {
				method: 'POST',
				url: server + "/api/authorisations/use_token/:token",
				params: {token: '@token'}
			},
			'revoke' : {
				method: 'DELETE',
				url: server + "/api/authorisations/:id"
			},
			'getOthers' : {
				method: 'GET',
				url: server + "/api/authorisations/other_users",
				isArray: true
			},
			'getTokenOwner' : {
				method: 'GET',
				url: server + "/api/authorisations/token/:token/owner",
				isArray: false
			},
		});

		this.assignments = $resource(server + "/api/assignments/", {}, {
			'getMyAssignments' : {
				method: 'GET',
				isArray: true,
				params: {assignmentStatus: '@assignmentStatus'}
			},
			'getAssignmentsOwnedByMe' : {
				method: 'GET',
				isArray: true,
				url: server + "/api/assignments/assign",
			},
			'getAssignedGroups' : {
				method: 'GET',
				isArray: true,
				url: server + "/api/assignments/assign/:gameId",
				params: {gameId: '@gameId'}
			},
			'assignBoard' : {
				method: 'POST',
				url: server + "/api/assignments/assign/:gameId/:groupId",
				params: {gameId: '@gameId', groupId: '@groupId'}
			},
			'unassignBoard' : {
				method: 'DELETE',
				url: server + "/api/assignments/assign/:gameId/:groupId",
				params: {gameId: '@gameId', groupId: '@groupId'}
			},
			'getProgress': {
				method: 'GET',
				url: server + "/api/assignments/assign/:assignmentId/progress",
				isArray: true,
			},
		});

        this.events = $resource(server + "/api/events/:id");

		this.eventBookings = $resource(server + "/api/events/:eventId/bookings/:userId", {eventId: '@eventId', userId: '@userId'}, {
			'getAllBookings' : {
				url: server + "/api/events/bookings",
				method: 'GET',
			},
			'getBookings' : {
				url: server + "/api/events/:eventId/bookings",
				method: 'GET',
				isArray: true
			},
			'makeBooking' : {
				method: 'POST',
				url: server + "/api/events/:eventId/bookings/:userId"
			},
			'deleteBooking' : {
				method: 'DELETE',
				url: server + "/api/events/:eventId/bookings/:userId"
			},
		});

		// allows the resource to be constructed with a promise that can be used to cancel a request
		this.getQuestionsResource = function(canceller) {
			return $resource(server + "/api/pages/questions", {}, {
				'query': {
					method: 'GET', isArray: false, timeout: canceller.promise, params: {searchString:"@searchString", tags:"@tags", levels:"@levels", start_index:"@startIndex", limit:"@limit"}
				}
			})
		};

		this.getUnits = function() { return $http.get(server + "/api/content/units").then(function (r) { return r.data; }); };

		var questionsPerPage = 10;
		var questionList = $resource(server + "/api/pages/questions?searchString=:searchString&tags=:tags&start_index=:startIndex&limit=:limit", {}, {'query': {method: 'GET', isArray: false }});
		var conceptList = $resource(server + "/api/pages/concepts?start_index=:startIndex&limit=:limit", {startIndex: 0, limit: 999}, {'query': {method: 'GET', isArray: false }});
		var gameBoardsList = $resource(server + "/api/users/current_user/gameboards?start_index=:startIndex&sort=:sort:filter:limit", {}, {'query': {method: 'GET', isArray: false }});
		var deleteBoard = $resource(server + "/api/users/current_user/gameboards/:id", {}, {'query': {method: 'DELETE'}});
		var saveBoard = $resource(server + "/api/users/current_user/gameboards/:id", {}, {'query': {method: 'POST'}});
		var eventsList = $resource(server + "/api/events");

		this.getQuestionList = function(page){
			return questionList.query({"startIndex" : page*questionsPerPage, "limit" : questionsPerPage});
		}

		this.userGameBoards = function(filter, sort, index, limit){
			return gameBoardsList.query({"filter" : (filter != null) ? '&show_only='+filter : '', "sort" : sort, "startIndex" : index, "limit" : (limit != null) ? '&limit='+limit : ''});
		}

		this.deleteGameBoard = function(id){
			return deleteBoard.query({"id" : id});
		}

		this.saveGameBoard = function(id) {
			return saveBoard.query({"id": id}, {});
		}

		this.removeLinkedAccount = function(provider) {
			return $http.delete(server + "/api/auth/"+provider+"/link");
		}

		this.linkAccount = function(provider, target){
			$http.get(server + "/api/auth/"+provider+"/link?redirect=http://" + target);
		}

		this.getConceptList = function(){
			return conceptList.query();
		}

		this.getEventsList = function(startIndex, limit, showActiveOnly, showInactiveOnly, tags) {
			return eventsList.get({start_index: startIndex, limit: limit, show_active_only: showActiveOnly, show_inactive_only: showInactiveOnly, tags: tags});
		}

		this.getImageUrl = function(path) {
			// check if the image source is a fully qualified link (suggesting it is external to the Isaac site)
			if(path.indexOf("http") > -1){
				return path;
			}
			else{
				return server + "/api/images/" + path;
			}
		}

		this.admin = {
			synchroniseDatastores: function() {
				return $http.post(server + "/api/admin/synchronise_datastores").then(function() {
					console.warn("Synchronising Datastores. The next page load will take a while.");
				});
			}
		};

		this.account = $resource(server + "/api/users", {}, {
			saveSettings: {
				method: "POST",
			}
		});

		this.schools = $resource(server + "/api/schools", {}, {
			'get': {
				method: 'GET',
				isArray: false
			},
			'getSchoolOther' : {
				url: server + "/api/users/schools_other",
				method: 'GET',
				isArray: true
			},
		})

		this.environment = $resource(server + "/api/info/segue_environment");

		this.segueInfo = $resource(server + "/api/search/:searchTerms?types=:types", {}, {
			"segueVersion": {
				method: "GET",
				url: server + "/api/info/segue_version",
			},
			"cachedVersion": {
				method: "GET",
				url: server + "/api/info/content_versions/cached",
			},
		});

		this.password = $resource(server + "/api/users/resetpassword/:token", null, {
			reset: {
				method: "POST",
			},
		});

		this.emailVerification = $resource(server + "/api/users/verifyemail/:userid/:email/:token", null, {
			verify: {
				method: "GET"
			},
		});

		this.verifyEmail = $resource(server + "/api/users/verifyemail/:email", null, {
			requestEmailVerification: {
				method: "GET"
			},
		});

		this.emailTemplate = $resource(server + "/api/email/viewinbrowser/:id", null, {
			get: {
				method: "GET"
			},
		});

		this.logger = $resource(server + "/api/log", {}, {
			log : {
				method: "POST",
			},
		})

		this.contentVersion = $resource("", {}, {
			"get": {
				method: "GET",
				url: server + "/api/info/content_versions/live_version",
			},
			"set": {
				method: "POST",
				url: server + "/api/admin/live_version/:version",
			},
			"currentIndexQueue" : {
				method: "GET",
				url: server + "/api/admin/content_index_queue",
			},
			"emptyIndexQueue" : {
				method: "DELETE",
				url: server + "/api/admin/content_index_queue",
			}
		});

		this.notifications = $resource(server + "/api/notifications", {}, {
			"respond": {
				method: "POST",
				url: server + "/api/notifications/:id/:response",
			}
		})

		this.getCSVDownloadLink = function(assignmentId) {
			return server + "/api/assignments/assign/" + assignmentId + "/progress/download"
		}

	}

	return Api;
});<commit_msg>Added admin email styling<commit_after>/**
 * Copyright 2014 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var Api = function ApiConstructor($resource, server, $http) {

		this.pages = $resource(server + "/api/pages/:id");

		this.pageFragments = $resource(server + "/api/pages/fragments/:id");

		this.pods = $resource(server + "/api/pages/pods");

		this.questionPages = $resource(server + "/api/pages/questions/:id");

		this.conceptPages = $resource(server + "/api/pages/concepts/:id");

		this.questionValidator = $resource(server + "/api/questions/:id/answer", {}, {
			validate: {
				method: "POST",
			},
		});

		this.contactForm = $resource(server + "/api/contact/", {}, {
			send: {
				method: "POST",
			},
		});

		this.gameBoards = $resource(server + "/api/gameboards/:id", {id: "@id"}, {
			filter: {
				method: "GET",
				url: server + "/api/gameboards",
			},
			wildcards: {
				method: "GET",
				url: server + "/api/gameboards/wildcards",
				isArray: true
			},
		});

		this.contentProblems = $resource(server + "/api/admin/content_problems");

		this.currentUser = $resource(server + "/api/users/current_user", {}, {
			'getProgress': {
				method: 'GET',
				url: server + "/api/users/current_user/progress",
			},
		});

		this.user = $resource("", { }, {
			'getProgress': {
				method: 'GET',
				url: server + "/api/users/:userId/progress",
			},
			'getEventsOverTime' : {
				method: 'GET',
				url: server + "/api/users/:userId/event_data/over_time?from_date=:from_date&to_date=:to_date&events=:events"
			},
			'getEmailPreferences' : {
				method: 'GET',
				url: server + "/api/users/email_preferences"
			},
		})

		this.authentication = $resource("", {}, {
			'getAuthRedirect': {
				method: 'GET',
				url: server+"/api/auth/:provider/authenticate"
			},
			'getLinkRedirect': {
				method: 'GET',
				url: server+"/api/auth/:provider/link",
			},
			'getAuthResult': {
				method: 'GET',
				url: server+"/api/auth/:provider/callback",
			},
			'login': {
				method: 'POST',
				url: server+"/api/auth/segue/authenticate",
			},
			'logout': {
				method: 'POST',
				url: server+"/api/auth/logout",
			},
		});

		this.searchEndpoint = $resource(server + "/api/search/:searchTerms?types=:types", {}, {
			'search': {
				method: 'GET',
				isArray: false
			},
		});

		this.adminUserSearch = $resource(server + "/api/admin/users/:userId?email=:email", {}, {
			'search': {
				method: 'GET',
				isArray: true
			},
			'get' : {
				method: 'GET',
				isArray: false
			},
		});

		this.statisticsEndpoint = $resource(server + "/api/admin/stats/", {}, {
			'get' : {
				method: 'GET',
				isArray: false
			},
			'getGameboardPopularity' : {
				method: 'GET',
				url: server + "/api/gameboards/popular",
				isArray: false
			},
			'getSchoolPopularity' : {
				method: 'GET',
				url: server + "/api/admin/stats/schools/",
				isArray: true
			},
			'getSchoolUsers' : {
				method: 'GET',
				url: server + "/api/admin/users/schools/:id",
				params: {id: '@id'},
			},
			'getEventsOverTime' : {
				method: 'GET',
				url: server + "/api/admin/users/event_data/over_time?from_date=:from_date&to_date=:to_date&events=:events&bin_data=:bin_data"
			},
			'getUserLocations' : {
				method: 'GET',
				url: server + "/api/admin/stats/users/last_locations",
				isArray: true
			},
			'getLogEventTypes' : {
				method: 'GET',
				url: server + "/api/info/log_event_types",
			},
		});

		this.adminDeleteUser = $resource(server + "/api/admin/users/:userId", {}, {
			'delete' : {
				method: 'DELETE'
			},
		});

		this.groupManagementEndpoint = $resource(server + "/api/groups/:id", {id: "@id"}, {
			'get' : {
				method: 'GET',
				isArray: true
			},
			'delete' :{
				method: 'DELETE',
				url: server + "/api/groups/:id",
				isArray: true
			},
			'getMembers' : {
				method: 'GET',
				url: server + "/api/groups/:id/membership",
				isArray: true
			},
			'deleteMember' : {
				method: 'DELETE',
				url: server + "/api/groups/:id/membership/:userId",
				isArray: true
			},
			'getToken' : {
				method: 'GET',
				url: server + "/api/authorisations/token/:id",
				isArray: false
			},
		});

		this.authorisations = $resource(server + "/api/authorisations/", {}, {
			'get' : {
				method: 'GET',
				isArray: true
			},
			'useToken' : {
				method: 'POST',
				url: server + "/api/authorisations/use_token/:token",
				params: {token: '@token'}
			},
			'revoke' : {
				method: 'DELETE',
				url: server + "/api/authorisations/:id"
			},
			'getOthers' : {
				method: 'GET',
				url: server + "/api/authorisations/other_users",
				isArray: true
			},
			'getTokenOwner' : {
				method: 'GET',
				url: server + "/api/authorisations/token/:token/owner",
				isArray: false
			},
		});

		this.assignments = $resource(server + "/api/assignments/", {}, {
			'getMyAssignments' : {
				method: 'GET',
				isArray: true,
				params: {assignmentStatus: '@assignmentStatus'}
			},
			'getAssignmentsOwnedByMe' : {
				method: 'GET',
				isArray: true,
				url: server + "/api/assignments/assign",
			},
			'getAssignedGroups' : {
				method: 'GET',
				isArray: true,
				url: server + "/api/assignments/assign/:gameId",
				params: {gameId: '@gameId'}
			},
			'assignBoard' : {
				method: 'POST',
				url: server + "/api/assignments/assign/:gameId/:groupId",
				params: {gameId: '@gameId', groupId: '@groupId'}
			},
			'unassignBoard' : {
				method: 'DELETE',
				url: server + "/api/assignments/assign/:gameId/:groupId",
				params: {gameId: '@gameId', groupId: '@groupId'}
			},
			'getProgress': {
				method: 'GET',
				url: server + "/api/assignments/assign/:assignmentId/progress",
				isArray: true,
			},
		});

        this.events = $resource(server + "/api/events/:id");

		this.eventBookings = $resource(server + "/api/events/:eventId/bookings/:userId", {eventId: '@eventId', userId: '@userId'}, {
			'getAllBookings' : {
				url: server + "/api/events/bookings",
				method: 'GET',
			},
			'getBookings' : {
				url: server + "/api/events/:eventId/bookings",
				method: 'GET',
				isArray: true
			},
			'makeBooking' : {
				method: 'POST',
				url: server + "/api/events/:eventId/bookings/:userId"
			},
			'deleteBooking' : {
				method: 'DELETE',
				url: server + "/api/events/:eventId/bookings/:userId"
			},
		});

		// allows the resource to be constructed with a promise that can be used to cancel a request
		this.getQuestionsResource = function(canceller) {
			return $resource(server + "/api/pages/questions", {}, {
				'query': {
					method: 'GET', isArray: false, timeout: canceller.promise, params: {searchString:"@searchString", tags:"@tags", levels:"@levels", start_index:"@startIndex", limit:"@limit"}
				}
			})
		};

		this.getUnits = function() { return $http.get(server + "/api/content/units").then(function (r) { return r.data; }); };

		var questionsPerPage = 10;
		var questionList = $resource(server + "/api/pages/questions?searchString=:searchString&tags=:tags&start_index=:startIndex&limit=:limit", {}, {'query': {method: 'GET', isArray: false }});
		var conceptList = $resource(server + "/api/pages/concepts?start_index=:startIndex&limit=:limit", {startIndex: 0, limit: 999}, {'query': {method: 'GET', isArray: false }});
		var gameBoardsList = $resource(server + "/api/users/current_user/gameboards?start_index=:startIndex&sort=:sort:filter:limit", {}, {'query': {method: 'GET', isArray: false }});
		var deleteBoard = $resource(server + "/api/users/current_user/gameboards/:id", {}, {'query': {method: 'DELETE'}});
		var saveBoard = $resource(server + "/api/users/current_user/gameboards/:id", {}, {'query': {method: 'POST'}});
		var eventsList = $resource(server + "/api/events");

		this.getQuestionList = function(page){
			return questionList.query({"startIndex" : page*questionsPerPage, "limit" : questionsPerPage});
		}

		this.userGameBoards = function(filter, sort, index, limit){
			return gameBoardsList.query({"filter" : (filter != null) ? '&show_only='+filter : '', "sort" : sort, "startIndex" : index, "limit" : (limit != null) ? '&limit='+limit : ''});
		}

		this.deleteGameBoard = function(id){
			return deleteBoard.query({"id" : id});
		}

		this.saveGameBoard = function(id) {
			return saveBoard.query({"id": id}, {});
		}

		this.removeLinkedAccount = function(provider) {
			return $http.delete(server + "/api/auth/"+provider+"/link");
		}

		this.linkAccount = function(provider, target){
			$http.get(server + "/api/auth/"+provider+"/link?redirect=http://" + target);
		}

		this.getConceptList = function(){
			return conceptList.query();
		}

		this.getEventsList = function(startIndex, limit, showActiveOnly, showInactiveOnly, tags) {
			return eventsList.get({start_index: startIndex, limit: limit, show_active_only: showActiveOnly, show_inactive_only: showInactiveOnly, tags: tags});
		}

		this.getImageUrl = function(path) {
			// check if the image source is a fully qualified link (suggesting it is external to the Isaac site)
			if(path.indexOf("http") > -1){
				return path;
			}
			else{
				return server + "/api/images/" + path;
			}
		}

		this.admin = {
			synchroniseDatastores: function() {
				return $http.post(server + "/api/admin/synchronise_datastores").then(function() {
					console.warn("Synchronising Datastores. The next page load will take a while.");
				});
			}
		};

		this.account = $resource(server + "/api/users", {}, {
			saveSettings: {
				method: "POST",
			}
		});

		this.schools = $resource(server + "/api/schools", {}, {
			'get': {
				method: 'GET',
				isArray: false
			},
			'getSchoolOther' : {
				url: server + "/api/users/schools_other",
				method: 'GET',
				isArray: true
			},
		})

		this.environment = $resource(server + "/api/info/segue_environment");

		this.segueInfo = $resource(server + "/api/search/:searchTerms?types=:types", {}, {
			"segueVersion": {
				method: "GET",
				url: server + "/api/info/segue_version",
			},
			"cachedVersion": {
				method: "GET",
				url: server + "/api/info/content_versions/cached",
			},
		});

		this.password = $resource(server + "/api/users/resetpassword/:token", null, {
			reset: {
				method: "POST",
			},
		});

		this.emailVerification = $resource(server + "/api/users/verifyemail/:userid/:email/:token", null, {
			verify: {
				method: "GET"
			},
		});

		this.verifyEmail = $resource(server + "/api/users/verifyemail/:email", null, {
			requestEmailVerification: {
				method: "GET"
			},
		});

		this.email = $resource(server + "", null, {
			get: {
				method: "GET",
				url: server + "/api/email/viewinbrowser/:id",
				isArray:false
			},
			getPreferences: {
				method: "GET",
				url: server + "/api/email/preferences",
				isArray:false
			},
		});

		this.logger = $resource(server + "/api/log", {}, {
			log : {
				method: "POST",
			},
		})

		this.contentVersion = $resource("", {}, {
			"get": {
				method: "GET",
				url: server + "/api/info/content_versions/live_version",
			},
			"set": {
				method: "POST",
				url: server + "/api/admin/live_version/:version",
			},
			"currentIndexQueue" : {
				method: "GET",
				url: server + "/api/admin/content_index_queue",
			},
			"emptyIndexQueue" : {
				method: "DELETE",
				url: server + "/api/admin/content_index_queue",
			}
		});

		this.notifications = $resource(server + "/api/notifications", {}, {
			"respond": {
				method: "POST",
				url: server + "/api/notifications/:id/:response",
			}
		})

		this.getCSVDownloadLink = function(assignmentId) {
			return server + "/api/assignments/assign/" + assignmentId + "/progress/download"
		}

	}

	return Api;
});<|endoftext|>