<commit_before>import React, { PropTypes } from 'react';
import Dialog from 'material-ui/Dialog';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import { GoogleMapLoader, GoogleMap, Marker, InfoWindow } from "react-google-maps";

const styles = {
  modal: {
    width: '80%',
    maxWidth: 'none',
  },
  textarea: {
    width: '100%',
  },
};

const HelpLocationModal = (props) =>
  <Dialog
    title="Choose your location"
    open={props.locationModalIsOpened}
    contentStyle={styles.modal}
    actions={[
      <FlatButton
        key="cancelButton"
        label="Cancel"
        primary
        onTouchTap={props.handleLocationDiscard}
      />,
      <FlatButton
        key="saveButton"
        label="Save"
        primary
        onTouchTap={props.handleLocationSave}
      />,
    ]}>
    <GoogleMapLoader
      query={{ libraries: "geometry,drawing,places,visualization" }}
      containerElement={
        <div
          style={{
            width: '100%',
            height: '400px',
          }}
        />
      }
      googleMapElement={
        <GoogleMap
          defaultZoom={18}
          defaultCenter={props.center}
        >
          <Marker
            draggable
            position={props.marker}
            onDragend={props.handleMarkerPositionChanged}
          />

        </GoogleMap>
      }
    />
  </Dialog>;

HelpLocationModal.defaultProps = {
  center: { lat: 59.938043, lng: 30.337157 },
  marker: { lat: 59.938043, lng: 30.337157 },
  zoom: 9,
};

HelpLocationModal.propTypes = {
  center: PropTypes.object,
  handleLocationSave: PropTypes.func.isRequired,
  handleLocationDiscard: PropTypes.func.isRequired,
  handleMarkerPositionChanged: PropTypes.func,
  hideLocationModalAction: PropTypes.func.isRequired,
  locationModalIsOpened: PropTypes.bool.isRequired,
  marker: PropTypes.object,
  zoom: PropTypes.number.isRequired,
};

export default HelpLocationModal;
<commit_msg>Refactor location modal to use HelpMap component.<commit_after>import React, { PropTypes } from 'react';
import Dialog from 'material-ui/Dialog';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import HelpMap from './HelpMap';

const styles = {
  modal: {
    width: '80%',
    maxWidth: 'none',
  },
  textarea: {
    width: '100%',
  },
};

const HelpLocationModal = (props) =>
  <Dialog
    title="Choose your location"
    open={props.locationModalIsOpened}
    contentStyle={styles.modal}
    actions={[
      <FlatButton
        key="cancelButton"
        label="Cancel"
        primary
        onTouchTap={props.handleLocationDiscard}
      />,
      <FlatButton
        key="saveButton"
        label="Save"
        primary
        onTouchTap={props.handleLocationSave}
      />,
    ]}>
    <HelpMap
      style={{
        width: '100%',
        height: '400px',
      }}
      defaultZoom={18}
      center={props.center}
      marker={props.marker}
      draggable
      handleDragend={props.handleMarkerPositionChanged} />
  </Dialog>;

HelpLocationModal.defaultProps = {
  center: { lat: 59.938043, lng: 30.337157 },
  marker: { lat: 59.938043, lng: 30.337157 },
  zoom: 9,
};

HelpLocationModal.propTypes = {
  center: PropTypes.object,
  handleLocationSave: PropTypes.func.isRequired,
  handleLocationDiscard: PropTypes.func.isRequired,
  handleMarkerPositionChanged: PropTypes.func,
  hideLocationModalAction: PropTypes.func.isRequired,
  locationModalIsOpened: PropTypes.bool.isRequired,
  marker: PropTypes.object,
  zoom: PropTypes.number.isRequired,
};

export default HelpLocationModal;
<|endoftext|>


<commit_before>define([
  'jquery',
  'DoughBaseComponent',
  'eventsWithPromises'
], function (
  $,
  DoughBaseComponent,
  eventsWithPromises
) {
  'use strict';

  var LinkManagerProto,
      defaultConfig = {
        selectors: {
          context: '[data-dough-linkmanager-context]',
          insertLink: '[data-dough-linkmanager-insertlink]',
          valueTrigger: '[data-dough-linkmanager-value-trigger]',
          tabTrigger: '[data-dough-tab-selector-trigger]',
          linkInputs: '[data-dough-linkmanager-link-type]'
        },
        tabIds: {
          'external-page': 'external',
          'internal-page': 'internal',
          'external-file': 'external',
          'internal-file': 'file'
        }
      };

  function LinkManager($el, config) {
    LinkManager.baseConstructor.call(this, $el, config, defaultConfig);
    this.link = null;
  }

  DoughBaseComponent.extend(LinkManager);

  LinkManagerProto = LinkManager.prototype;

  LinkManagerProto.init = function(initialised) {
    this.context = this.$el.attr(this._stripSquareBrackets(this.config.selectors.context));
    this._cacheComponentElements();
    this._setupUIEvents();
    this._setupAppEvents();
    this._initialisedSuccess(initialised);
  };

  LinkManagerProto._cacheComponentElements = function() {
    this.$tabTriggers = this.$el.find(this.config.selectors.tabTrigger);
    this.$linkInputs = this.$el.find(this.config.selectors.linkInputs);
    this.$insertLinks = this.$el.find(this.config.selectors.insertLink);
    this.$valueTriggers = this.$el.find(this.config.selectors.valueTrigger);
  };

  LinkManagerProto._setupAppEvents = function() {
    eventsWithPromises.subscribe('cmseditor:link-published', $.proxy(this._handleShown, this));
  };

  LinkManagerProto._handleFormControlUpdate = function(e) {
    this.setLink($(e.target).val());
  };

  LinkManagerProto._handleShown = function(eventData) {
    if(!eventData || eventData.emitter !== this.$el.attr(this._stripSquareBrackets(this.config.selectors.context))) return false;

    if(eventData.link) {
      this._setup('existing', eventData.link);
    } else {
      this._setup('new');
    }
  };

  LinkManagerProto._setup = function(type, link) {
    if(type === 'new') {
      this.changeTab(this.$tabTriggers, this.config.tabIds['internal']);
    }
    else if(type === 'existing') {
      this.link = link;
      this.changeTab(this.$tabTriggers, this.config.tabIds[this._getLinkType(link)]);
    }
  };

  LinkManagerProto._setupUIEvents = function() {
    this.$el
      .on('input', '[data-dough-linkmanager-value-trigger][type="text"]', $.proxy(this._handleFormControlUpdate, this))
      .on('keyup', '[data-dough-linkmanager-value-trigger][type="text"]', $.proxy(this._handleFormControlUpdate, this))
      .on('change', '[data-dough-linkmanager-value-trigger][type="radio"]', $.proxy(this._handleFormControlUpdate, this))
      .on('click', '[data-dough-linkmanager-insertlink]', $.proxy(this.publishLink, this));
  };

  LinkManagerProto.setLink = function(link) {
    if(!link) return;
    this.link = link;
  };

  LinkManagerProto.getLink = function() {
    return this.link;
  };

  LinkManagerProto.publishLink = function() {
    // Note: Let's move this out of here at some point.
    eventsWithPromises.publish('dialog:close', {
      emitter: this.context
    });

    eventsWithPromises.publish('linkmanager:link-published', {
      emitter: this.context,
      link: this.link
    });
  };

  LinkManagerProto._getLinkType = function(link) {
    // Note: Awaiting decision on full link formatting
    return 'external-file';
  };

  LinkManagerProto._stripSquareBrackets = function(str) {
    return str.replace(/([\[\]])+/gi,'');
  };

  LinkManagerProto.changeTab = function($tabTriggers, id) {
    // Note: Ideally we would be calling the TabSelector methods directly
    // but currently Dough doesn't allow deferred components so we have
    // to click on the tab
    $tabTriggers.filter('[data-dough-tab-selector-trigger="' + id + '"]').click();
  };

  LinkManagerProto.setInputs = function(type, link) {
    this.$linkInputs.filter('[data-dough-linkmanager-link-type="' + type + '"]').val(link);
  };

  return LinkManager;
});
<commit_msg>Show corresponding tab when editing a link<commit_after>define([
  'jquery',
  'DoughBaseComponent',
  'eventsWithPromises'
], function (
  $,
  DoughBaseComponent,
  eventsWithPromises
) {
  'use strict';

  var LinkManagerProto,
      defaultConfig = {
        selectors: {
          context: '[data-dough-linkmanager-context]',
          insertLink: '[data-dough-linkmanager-insertlink]',
          valueTrigger: '[data-dough-linkmanager-value-trigger]',
          tabTrigger: '[data-dough-tab-selector-trigger]',
          linkInputs: '[data-dough-linkmanager-link-type]'
        },
        tabIds: {
          'external-page': 'external',
          'internal-page': 'internal',
          'external-file': 'external',
          'internal-file': 'file'
        }
      };

  function LinkManager($el, config) {
    LinkManager.baseConstructor.call(this, $el, config, defaultConfig);
    this.link = null;
  }

  DoughBaseComponent.extend(LinkManager);

  LinkManagerProto = LinkManager.prototype;

  LinkManagerProto.init = function(initialised) {
    this.context = this.$el.attr(this._stripSquareBrackets(this.config.selectors.context));
    this._cacheComponentElements();
    this._setupUIEvents();
    this._setupAppEvents();
    this._initialisedSuccess(initialised);
  };

  LinkManagerProto._cacheComponentElements = function() {
    this.$tabTriggers = this.$el.find(this.config.selectors.tabTrigger);
    this.$linkInputs = this.$el.find(this.config.selectors.linkInputs);
    this.$insertLinks = this.$el.find(this.config.selectors.insertLink);
    this.$valueTriggers = this.$el.find(this.config.selectors.valueTrigger);
  };

  LinkManagerProto._setupAppEvents = function() {
    eventsWithPromises.subscribe('cmseditor:link-published', $.proxy(this._handleShown, this));
  };

  LinkManagerProto._handleFormControlUpdate = function(e) {
    this.setLink($(e.target).val());
  };

  LinkManagerProto._handleShown = function(eventData) {
    if(!eventData || eventData.emitter !== this.$el.attr(this._stripSquareBrackets(this.config.selectors.context))) return false;

    if(eventData.link) {
      this._setup('existing', eventData.link);
    } else {
      this._setup('new');
    }
  };

  LinkManagerProto._setup = function(type, link) {
    var linkType = this._getLinkType(link);
    if(type === 'new') {
      this.changeTab(this.$tabTriggers, 'internal');
    }
    else if(type === 'existing') {
      this.link = link;
      this.changeTab(this.$tabTriggers, this.config.tabIds[linkType]);
      this.setInputs(this.config.tabIds[linkType], this.link);
    }
  };

  LinkManagerProto._setupUIEvents = function() {
    this.$el
      .on('input', '[data-dough-linkmanager-value-trigger][type="text"]', $.proxy(this._handleFormControlUpdate, this))
      .on('keyup', '[data-dough-linkmanager-value-trigger][type="text"]', $.proxy(this._handleFormControlUpdate, this))
      .on('change', '[data-dough-linkmanager-value-trigger][type="radio"]', $.proxy(this._handleFormControlUpdate, this))
      .on('click', '[data-dough-linkmanager-insertlink]', $.proxy(this.publishLink, this));
  };

  LinkManagerProto.setLink = function(link) {
    if(!link) return;
    this.link = link;
  };

  LinkManagerProto.getLink = function() {
    return this.link;
  };

  LinkManagerProto.publishLink = function() {
    // Note: Let's move this out of here at some point.
    eventsWithPromises.publish('dialog:close', {
      emitter: this.context
    });

    eventsWithPromises.publish('linkmanager:link-published', {
      emitter: this.context,
      link: this.link
    });
  };

  LinkManagerProto._getLinkType = function(link) {
    // Note: Awaiting decision on full link formatting
    return 'external-file';
  };

  LinkManagerProto._stripSquareBrackets = function(str) {
    return str.replace(/([\[\]])+/gi,'');
  };

  LinkManagerProto.changeTab = function($tabTriggers, id) {
    // Note: Ideally we would be calling the TabSelector methods directly
    // but currently Dough doesn't allow deferred components so we have
    // to click on the tab
    $tabTriggers.filter('[data-dough-tab-selector-trigger="' + id + '"]').click();
  };

  LinkManagerProto.setInputs = function(type, link) {
    this.$linkInputs.filter('[data-dough-linkmanager-link-type="' + type + '"]').val(link);
  };

  return LinkManager;
});
<|endoftext|>


<commit_before>// jquery
import { $ } from 'meteor/jquery';
// Viz
import d3 from 'd3';
// Load Projects and Settings
import { Projects } from '../../../../../lib/collections/projects.js';
import { Settings } from '../../../../../lib/collections/settings.js';
import { Activities } from '../../../../../lib/collections/activities.js';
import { ActivityElements } from '../../../../../lib/collections/activity_elements.js';
import { Contradictions } from '../../../../../lib/collections/contradictions.js';

/*****************************************************************************/
/* ContradictionView: Event Handlers */
/*****************************************************************************/
Template.ContradictionView.events({
});

/*****************************************************************************/
/* ContradictionView: Helpers */
/*****************************************************************************/
Template.ContradictionView.helpers({
    data: function() {
        // Get the contradiction data
        var thisData = Contradictions.findOne({
            '_id': this.contradictionId
        });
        // If there is data... then return it
        if (thisData) {
            // Add the data for the nodes
            thisData.firstNodeData = Activities.findOne({
                '_id': thisData.contradictionData.firstNode
            });
            thisData.secondNodeData = Activities.findOne({
                '_id': thisData.contradictionData.secondNode
            });
            // Return the data
            return thisData;
        }
    },
});

/*****************************************************************************/
/* ContradictionView: Lifecycle Hooks */
/*****************************************************************************/
Template.ContradictionView.onCreated(function () {
    Meteor.subscribe('projects');
    Meteor.subscribe('activities');
    Meteor.subscribe('contradictions');
    // Load variables
    thisProjectID = this.data.projectId;
    thisContradictionID = this.data.contradictionId;
});

Template.ContradictionView.onRendered(function () {

    // Visualize the Contradiction with D3

    // Function for loading an SVG
    var loadSVG = function(url, parent) {
        var loadedSVG = parent.append("g");
        d3.xml(Meteor.absoluteUrl(url)).then(function(xml) {
            loadedSVG.node().appendChild(xml.documentElement);
        });
        return loadedSVG;
    }

    // Get the data
    thisContradiction = Contradictions.findOne({
        '_id': thisContradictionID
    });
    var activityElementNode1 = ActivityElements.findOne({
        '_id': thisContradiction.contradictionData.firstNode
    });
    var activityElementNode2 = ActivityElements.findOne({
        '_id': thisContradiction.contradictionData.secondNode
    });
    var activityNode1 = Activities.findOne({
        '_id': activityElementNode1.activityId
    });
    var activityNode2 = Activities.findOne({
        '_id': activityElementNode2.activityId
    });

    // Margins
    // https://bl.ocks.org/mbostock/3019563
    var margin = {
        top: 15,
        right: 0,
        bottom: 0,
        left: 0
    };

    // The container for the viz
    var d3Container = document.getElementById("showContradictionDivSVG");

    // Get dimensions of the container on window resize
    window.addEventListener("resize", function(d) {
        width = d3Container.clientWidth;
        height = d3Container.clientHeight;
        //console.log(width, height);
    });

    // Remove previous SVG
    d3.select('#showContradictionDivSVG').select('svg').remove();

    // Add the visualization SVG to the container
    var svg = d3.select('#showContradictionDivSVG').append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Load SVG activity icons and create two groups
    // Activity 1
    var activityIcon1 = loadSVG("as_full_nolabel_contradiction.svg", svg).attr("transform", "translate(0,-15)");
    // Activity 2
    var activityIcon2 = loadSVG("as_full_nolabel_contradiction.svg", svg).attr("transform", "translate(460,-15)");

    // Visualize the contradiction in the SVG
    // Number and title of activities
    // Activity 1
    svg.append('text')
        .attr("x", 50)
        .attr("y", 100)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text("#" + activityNode1.activityData.number);
    svg.append('text')
        .attr("x", 50)
        .attr("y", 120)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text(activityNode1.activityData.title.slice(0, 7)+"...");
    // Activity 2
    svg.append('text')
        .attr("x", 50+460)
        .attr("y", 100)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text("#" + activityNode2.activityData.number);
    svg.append('text')
        .attr("x", 50+460)
        .attr("y", 120)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text(activityNode2.activityData.title.slice(0, 7)+"...");
    // Arrow
    //Find centers of activity elements
    var activityIconSize = {
        width: 100,
        height: 90
    };
    var getElementCoordinates = function(x,y,title) {
        var activityElementsCenters = {
            subject: {
                x: x + (activityIconSize.width / 2) - 19,
                y: y + (activityIconSize.height / 2) - 34,
                title: "Subject"
            },
            object: {
                x: x + (activityIconSize.width / 2) + 39,
                y: y + (activityIconSize.height / 2),
                title: "Object"
            },
            outcome: {
                x: x + (activityIconSize.width / 2),
                y: y + (activityIconSize.height / 2),
                title: "Outcome"
            },
            tools: {
                x: x + (activityIconSize.width / 2) + 19,
                y: y + (activityIconSize.height / 2) - 34,
                title: "Tools"
            },
            rules: {
                x: x + (activityIconSize.width / 2) - 39,
                y: y + (activityIconSize.height / 2),
                title: "Rules"
            },
            roles: {
                x: x + (activityIconSize.width / 2) + 19,
                y: y + (activityIconSize.height / 2) + 34,
                title: "Roles"
            },
            community: {
                x: x + (activityIconSize.width / 2) - 19,
                y: y + (activityIconSize.height / 2) + 34,
                title: "Community"
            },
        }
        return activityElementsCenters[title];
    }
    var firstNodeCoordinates = getElementCoordinates(0,-15,activityElementNode1.activityElementData.title);
    var secondNodeCoordinates = getElementCoordinates(460,-15,activityElementNode2.activityElementData.title);
    // Draw conntradiction start and end
    svg.append("ellipse")
        .attr("cx", firstNodeCoordinates.x)
        .attr("cy", firstNodeCoordinates.y)
        .attr("fill", "#63dfff")
        .attr("rx", 5)
        .attr("ry", 5);
    svg.append("ellipse")
        .attr("cx", secondNodeCoordinates.x)
        .attr("cy", secondNodeCoordinates.y)
        .attr("fill", "#63dfff")
        .attr("rx", 5)
        .attr("ry", 5);
    // Draw contradiction "arrow"

});

Template.ContradictionView.onDestroyed(function () {
});
<commit_msg>Add path to contradiction modal viz<commit_after>// jquery
import { $ } from 'meteor/jquery';
// Viz
import d3 from 'd3';
// Load Projects and Settings
import { Projects } from '../../../../../lib/collections/projects.js';
import { Settings } from '../../../../../lib/collections/settings.js';
import { Activities } from '../../../../../lib/collections/activities.js';
import { ActivityElements } from '../../../../../lib/collections/activity_elements.js';
import { Contradictions } from '../../../../../lib/collections/contradictions.js';

/*****************************************************************************/
/* ContradictionView: Event Handlers */
/*****************************************************************************/
Template.ContradictionView.events({
});

/*****************************************************************************/
/* ContradictionView: Helpers */
/*****************************************************************************/
Template.ContradictionView.helpers({
    data: function() {
        // Get the contradiction data
        var thisData = Contradictions.findOne({
            '_id': this.contradictionId
        });
        // If there is data... then return it
        if (thisData) {
            // Add the data for the nodes
            thisData.firstNodeData = Activities.findOne({
                '_id': thisData.contradictionData.firstNode
            });
            thisData.secondNodeData = Activities.findOne({
                '_id': thisData.contradictionData.secondNode
            });
            // Return the data
            return thisData;
        }
    },
});

/*****************************************************************************/
/* ContradictionView: Lifecycle Hooks */
/*****************************************************************************/
Template.ContradictionView.onCreated(function () {
    Meteor.subscribe('projects');
    Meteor.subscribe('activities');
    Meteor.subscribe('contradictions');
    // Load variables
    thisProjectID = this.data.projectId;
    thisContradictionID = this.data.contradictionId;
});

Template.ContradictionView.onRendered(function () {

    // Visualize the Contradiction with D3

    // Function for loading an SVG
    var loadSVG = function(url, parent) {
        var loadedSVG = parent.append("g");
        d3.xml(Meteor.absoluteUrl(url)).then(function(xml) {
            loadedSVG.node().appendChild(xml.documentElement);
        });
        return loadedSVG;
    }

    // Get the data
    thisContradiction = Contradictions.findOne({
        '_id': thisContradictionID
    });
    var activityElementNode1 = ActivityElements.findOne({
        '_id': thisContradiction.contradictionData.firstNode
    });
    var activityElementNode2 = ActivityElements.findOne({
        '_id': thisContradiction.contradictionData.secondNode
    });
    var activityNode1 = Activities.findOne({
        '_id': activityElementNode1.activityId
    });
    var activityNode2 = Activities.findOne({
        '_id': activityElementNode2.activityId
    });

    // Margins
    // https://bl.ocks.org/mbostock/3019563
    var margin = {
        top: 15,
        right: 0,
        bottom: 0,
        left: 0
    };

    // The container for the viz
    var d3Container = document.getElementById("showContradictionDivSVG");

    // Get dimensions of the container on window resize
    window.addEventListener("resize", function(d) {
        width = d3Container.clientWidth;
        height = d3Container.clientHeight;
        //console.log(width, height);
    });

    // Remove previous SVG
    d3.select('#showContradictionDivSVG').select('svg').remove();

    // Add the visualization SVG to the container
    var svg = d3.select('#showContradictionDivSVG').append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Load SVG activity icons and create two groups
    // Activity 1
    var activityIcon1 = loadSVG("as_full_nolabel_contradiction.svg", svg).attr("transform", "translate(0,-15)");
    // Activity 2
    var activityIcon2 = loadSVG("as_full_nolabel_contradiction.svg", svg).attr("transform", "translate(460,-15)");

    // Visualize the contradiction in the SVG
    // Number and title of activities
    // Activity 1
    svg.append('text')
        .attr("x", 50)
        .attr("y", 100)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text("#" + activityNode1.activityData.number);
    svg.append('text')
        .attr("x", 50)
        .attr("y", 120)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text(activityNode1.activityData.title.slice(0, 7)+"...");
    // Activity 2
    svg.append('text')
        .attr("x", 50+460)
        .attr("y", 100)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text("#" + activityNode2.activityData.number);
    svg.append('text')
        .attr("x", 50+460)
        .attr("y", 120)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .style("font-size", "16px")
        .style("font-weight", "700")
        .text(activityNode2.activityData.title.slice(0, 7)+"...");
    // Arrow
    //Find centers of activity elements
    var activityIconSize = {
        width: 100,
        height: 90
    };
    var getElementCoordinates = function(x,y,title) {
        var activityElementsCenters = {
            subject: {
                x: x + (activityIconSize.width / 2) - 19,
                y: y + (activityIconSize.height / 2) - 34,
                title: "Subject"
            },
            object: {
                x: x + (activityIconSize.width / 2) + 39,
                y: y + (activityIconSize.height / 2),
                title: "Object"
            },
            outcome: {
                x: x + (activityIconSize.width / 2),
                y: y + (activityIconSize.height / 2),
                title: "Outcome"
            },
            tools: {
                x: x + (activityIconSize.width / 2) + 19,
                y: y + (activityIconSize.height / 2) - 34,
                title: "Tools"
            },
            rules: {
                x: x + (activityIconSize.width / 2) - 39,
                y: y + (activityIconSize.height / 2),
                title: "Rules"
            },
            roles: {
                x: x + (activityIconSize.width / 2) + 19,
                y: y + (activityIconSize.height / 2) + 34,
                title: "Roles"
            },
            community: {
                x: x + (activityIconSize.width / 2) - 19,
                y: y + (activityIconSize.height / 2) + 34,
                title: "Community"
            },
        }
        return activityElementsCenters[title];
    }
    var firstNodeCenter = getElementCoordinates(0,-15,activityElementNode1.activityElementData.title);
    var secondNodeCenter = getElementCoordinates(460,-15,activityElementNode2.activityElementData.title);
    // Draw conntradiction start and end
    var contradictionColor = "#63dfff";
    var thisContradictionGroup = svg.append("g").attr("id", thisContradiction._id);
    svg.append("ellipse")
        .attr("cx", firstNodeCenter.x)
        .attr("cy", firstNodeCenter.y)
        .attr("fill", contradictionColor)
        .attr("rx", 5)
        .attr("ry", 5);
    svg.append("ellipse")
        .attr("cx", secondNodeCenter.x)
        .attr("cy", secondNodeCenter.y)
        .attr("fill", contradictionColor)
        .attr("rx", 5)
        .attr("ry", 5);
    // Draw contradiction "arrow"
    // Line
    var line = d3.line()
        .x(function(d) {
            return d.x;
        })
        .y(function(d) {
            return d.y;
        })
        .curve(d3.curveBasis);
    // Calculate the points...
    var points = [];
    // Define curve according to the contradiction levels
    if (thisContradiction.contradictionData.level === "primary") {
        // Primary contradictions as self-loop
        points = [{
                x: firstNodeCenter.x + 4,
                y: firstNodeCenter.y
            },
            {
                x: secondNodeCenter.x,
                y: firstNodeCenter.y - 20
            },
            {
                x: secondNodeCenter.x + 8,
                y: firstNodeCenter.y - 20
            },
            {
                x: secondNodeCenter.x + 4,
                y: secondNodeCenter.y
            },
        ];
    } else if (thisContradiction.contradictionData.level === "secondary") {
        points = [{
                x: firstNodeCenter.x + 4,
                y: firstNodeCenter.y
            },
            {
                x: secondNodeCenter.x + 8,
                y: firstNodeCenter.y
            },
            {
                x: secondNodeCenter.x + 4,
                y: secondNodeCenter.y
            },
        ];
    } else if (thisContradiction.contradictionData.level === "tertiary") {
        points = [{
                x: firstNodeCenter.x + 4,
                y: firstNodeCenter.y
            },
            {
                x: secondNodeCenter.x + 4,
                y: firstNodeCenter.y
            },
            {
                x: secondNodeCenter.x + 4,
                y: secondNodeCenter.y
            },
        ];
    } else if (thisContradiction.contradictionData.level === "quaternary") {
        points = [{
                x: firstNodeCenter.x + 4,
                y: firstNodeCenter.y
            },
            {
                x: secondNodeCenter.x + 4,
                y: firstNodeCenter.y
            },
            {
                x: secondNodeCenter.x + 4,
                y: secondNodeCenter.y
            },
        ];
    }
    // Add the path as the flow viz
    var pathData = line(points);
    var contradictionViz = thisContradictionGroup.selectAll('path')
        .data(points)
        .enter()
        .append('path')
        .attr('d', pathData)
        .attr("stroke", contradictionColor)
        .attr("stroke-width", 4)
        .attr("fill", "none");

});

Template.ContradictionView.onDestroyed(function () {
});
<|endoftext|>


<commit_before>'use strict';

import { EXECUTION_FILTER_SERVICE } from 'core/delivery/filter/executionFilter.service';
import { PIPELINE_CONFIG_PROVIDER } from 'core/pipeline/config/pipelineConfigProvider';

const angular = require('angular');

module.exports = angular.module('spinnaker.executionDetails.controller', [
  require('@uirouter/angularjs').default,
  PIPELINE_CONFIG_PROVIDER,
  EXECUTION_FILTER_SERVICE
])
  .controller('executionDetails', function($scope, $stateParams, $state, pipelineConfig, executionFilterService) {
    var controller = this;

    function getStageParams(stageId) {
      const summaries = (controller.execution.stageSummaries || []);
      const stageIndex = summaries.findIndex(s => (s.stages || []).some(s2 => s2.id === stageId));
      if (stageIndex !== -1) {
        const stepIndex = (summaries[stageIndex].stages || []).findIndex(s => s.id === stageId);
        if (stepIndex !== -1) {
          return {
            stage: stageIndex,
            step: stepIndex,
            stageId: null,
          };
        }
      }
      return null;
    }

    function getCurrentStage() {
      if ($stateParams.stageId) {
        const params = getStageParams($stateParams.stageId);
        if (params) {
          $state.go('.', params, {replace: true});
          return params.stage;
        }
      }
      if ($stateParams.refId) {
        let stages = controller.execution.stageSummaries || [];
        let currentStageIndex = _.findIndex(stages, { refId: $stateParams.refId });
        if (currentStageIndex !== -1) {
          $state.go('.', {
            refId: null,
            stage: currentStageIndex,
          });
          return parseInt(currentStageIndex);
        } else {
          $state.go('.', {
            refId: null,
          });
        }
      }
      return parseInt($stateParams.stage);
    }

    function getCurrentStep() {
      return parseInt($stateParams.step);
    }

    controller.close = function() {
      $state.go('^');
    };

    controller.toggleDetails = function(index) {
      var newStepDetails = getCurrentStep() === index ? null : index;
      if (newStepDetails !== null) {
        $state.go('.', {
          stage: getCurrentStage(),
          step: newStepDetails,
        });
      }
    };

    controller.isStageCurrent = function(index) {
      return index === getCurrentStage();
    };

    controller.isStepCurrent = function(index) {
      return index === getCurrentStep();
    };

    controller.closeDetails = function() {
      $state.go('.', { step: null });
    };

    this.getStageSummary = () => {
      const currentStage = getCurrentStage();
      const stages = controller.execution.stageSummaries || [];
      return stages.length > currentStage ? stages[currentStage] : null;
    };

    const getDetailsSourceUrl = () => {
      if ($stateParams.step !== undefined) {
        const stageSummary = this.getStageSummary();
        if (stageSummary) {
          const step = stageSummary.stages[getCurrentStep()] || stageSummary.masterStage;
          const stageConfig = pipelineConfig.getStageConfig(step);
          if (stageConfig && stageConfig.executionDetailsUrl) {
            if (stageConfig.executionConfigSections) {
              $scope.configSections = stageConfig.executionConfigSections;
            } else {
              if (stageConfig.executionDetailsUrl !== this.executionDetailsUrl) {
                $scope.configSections = []; // assume the stage's execution details controller will set it
              }
            }
            return stageConfig.executionDetailsUrl;
          }
          return require('./defaultExecutionDetails.html');
        }
      }
      return null;
    };

    const getSummarySourceUrl = () => {
      if ($stateParams.stage !== undefined) {
        const stageSummary = this.getStageSummary();
        if (stageSummary) {
          var stageConfig = pipelineConfig.getStageConfig(stageSummary);
          if (stageConfig && stageConfig.executionSummaryUrl) {
            return stageConfig.executionSummaryUrl;
          }
        }
      }
      return require('../../pipeline/config/stages/core/executionSummary.html');
    };

    this.updateStage = (stageSummary) => {
      $scope.stageSummary = stageSummary;
      $scope.stage = stageSummary.stages[getCurrentStep()] || stageSummary.masterStage;
    };

    this.setSourceUrls = () => {
      this.summarySourceUrl = getSummarySourceUrl();
      this.detailsSourceUrl = getDetailsSourceUrl();
      this.updateStage(this.getStageSummary());
    };

    this.$onInit = () => {
      this.setSourceUrls();
      this.standalone = this.standalone || false;

      // This is pretty dirty but executionDetails has its dirty tentacles
      // all over the place. This makes the conversion of the execution directive
      // to a component safe until we tackle converting all the controllers
      // TODO: Convert all the execution details controllers to ES6 controllers and remove references to $scope
      $scope.standalone = this.standalone;
      $scope.application = this.application;
      $scope.execution = this.execution;

      // Since stages and tasks can get updated without the reference to the execution changing, subscribe to the execution updated stream here too
      this.groupsUpdatedSubscription = executionFilterService.groupsUpdatedStream.subscribe(() => $scope.$evalAsync(() => this.updateStage(this.getStageSummary())));
    };

    this.$onChanges = () => {
      $scope.standalone = this.standalone;
      $scope.application = this.application;
      $scope.execution = this.execution;
      this.updateStage(this.getStageSummary());
    };

    this.$onDestroy = () => {
      this.groupsUpdatedSubscription.unsubscribe();
    };

    $scope.$on('$stateChangeSuccess', () => this.setSourceUrls());

    controller.getStepLabel = function(stage) {
      var stageConfig = pipelineConfig.getStageConfig(stage);
      if (stageConfig && stageConfig.executionStepLabelUrl) {
        return stageConfig.executionStepLabelUrl;
      } else {
        return require('../../pipeline/config/stages/core/stepLabel.html');
      }
    };

    controller.isRestartable = function(stage) {
      var stageConfig = pipelineConfig.getStageConfig(stage);
      if (!stageConfig || stage.isRestarting === true) {
        return false;
      }

      const allowRestart = controller.application.attributes.enableRestartRunningExecutions || false;
      if (controller.execution.isRunning && !allowRestart) {
        return false;
      }

      return stageConfig.restartable || false;
    };

  });
<commit_msg>fix(core/delivery): Stop trying to set stageSummary when null (#4069)<commit_after>'use strict';

import { EXECUTION_FILTER_SERVICE } from 'core/delivery/filter/executionFilter.service';
import { PIPELINE_CONFIG_PROVIDER } from 'core/pipeline/config/pipelineConfigProvider';

const angular = require('angular');

module.exports = angular.module('spinnaker.executionDetails.controller', [
  require('@uirouter/angularjs').default,
  PIPELINE_CONFIG_PROVIDER,
  EXECUTION_FILTER_SERVICE
])
  .controller('executionDetails', function($scope, $stateParams, $state, pipelineConfig, executionFilterService) {
    var controller = this;

    function getStageParams(stageId) {
      const summaries = (controller.execution.stageSummaries || []);
      const stageIndex = summaries.findIndex(s => (s.stages || []).some(s2 => s2.id === stageId));
      if (stageIndex !== -1) {
        const stepIndex = (summaries[stageIndex].stages || []).findIndex(s => s.id === stageId);
        if (stepIndex !== -1) {
          return {
            stage: stageIndex,
            step: stepIndex,
            stageId: null,
          };
        }
      }
      return null;
    }

    function getCurrentStage() {
      if ($stateParams.stageId) {
        const params = getStageParams($stateParams.stageId);
        if (params) {
          $state.go('.', params, {replace: true});
          return params.stage;
        }
      }
      if ($stateParams.refId) {
        let stages = controller.execution.stageSummaries || [];
        let currentStageIndex = _.findIndex(stages, { refId: $stateParams.refId });
        if (currentStageIndex !== -1) {
          $state.go('.', {
            refId: null,
            stage: currentStageIndex,
          });
          return parseInt(currentStageIndex);
        } else {
          $state.go('.', {
            refId: null,
          });
        }
      }
      return parseInt($stateParams.stage);
    }

    function getCurrentStep() {
      return parseInt($stateParams.step);
    }

    controller.close = function() {
      $state.go('^');
    };

    controller.toggleDetails = function(index) {
      var newStepDetails = getCurrentStep() === index ? null : index;
      if (newStepDetails !== null) {
        $state.go('.', {
          stage: getCurrentStage(),
          step: newStepDetails,
        });
      }
    };

    controller.isStageCurrent = function(index) {
      return index === getCurrentStage();
    };

    controller.isStepCurrent = function(index) {
      return index === getCurrentStep();
    };

    controller.closeDetails = function() {
      $state.go('.', { step: null });
    };

    this.getStageSummary = () => {
      const currentStage = getCurrentStage();
      const stages = controller.execution.stageSummaries || [];
      return stages.length > currentStage ? stages[currentStage] : null;
    };

    const getDetailsSourceUrl = () => {
      if ($stateParams.step !== undefined) {
        const stageSummary = this.getStageSummary();
        if (stageSummary) {
          const step = stageSummary.stages[getCurrentStep()] || stageSummary.masterStage;
          const stageConfig = pipelineConfig.getStageConfig(step);
          if (stageConfig && stageConfig.executionDetailsUrl) {
            if (stageConfig.executionConfigSections) {
              $scope.configSections = stageConfig.executionConfigSections;
            } else {
              if (stageConfig.executionDetailsUrl !== this.executionDetailsUrl) {
                $scope.configSections = []; // assume the stage's execution details controller will set it
              }
            }
            return stageConfig.executionDetailsUrl;
          }
          return require('./defaultExecutionDetails.html');
        }
      }
      return null;
    };

    const getSummarySourceUrl = () => {
      if ($stateParams.stage !== undefined) {
        const stageSummary = this.getStageSummary();
        if (stageSummary) {
          var stageConfig = pipelineConfig.getStageConfig(stageSummary);
          if (stageConfig && stageConfig.executionSummaryUrl) {
            return stageConfig.executionSummaryUrl;
          }
        }
      }
      return require('../../pipeline/config/stages/core/executionSummary.html');
    };

    this.updateStage = (stageSummary) => {
      if (stageSummary) {
        $scope.stageSummary = stageSummary;
        $scope.stage = stageSummary.stages[getCurrentStep()] || stageSummary.masterStage;
      }
    };

    this.setSourceUrls = () => {
      this.summarySourceUrl = getSummarySourceUrl();
      this.detailsSourceUrl = getDetailsSourceUrl();
      this.updateStage(this.getStageSummary());
    };

    this.$onInit = () => {
      this.setSourceUrls();
      this.standalone = this.standalone || false;

      // This is pretty dirty but executionDetails has its dirty tentacles
      // all over the place. This makes the conversion of the execution directive
      // to a component safe until we tackle converting all the controllers
      // TODO: Convert all the execution details controllers to ES6 controllers and remove references to $scope
      $scope.standalone = this.standalone;
      $scope.application = this.application;
      $scope.execution = this.execution;

      // Since stages and tasks can get updated without the reference to the execution changing, subscribe to the execution updated stream here too
      this.groupsUpdatedSubscription = executionFilterService.groupsUpdatedStream.subscribe(() => $scope.$evalAsync(() => this.updateStage(this.getStageSummary())));
    };

    this.$onChanges = () => {
      $scope.standalone = this.standalone;
      $scope.application = this.application;
      $scope.execution = this.execution;
      this.updateStage(this.getStageSummary());
    };

    this.$onDestroy = () => {
      this.groupsUpdatedSubscription.unsubscribe();
    };

    $scope.$on('$stateChangeSuccess', () => this.setSourceUrls());

    controller.getStepLabel = function(stage) {
      var stageConfig = pipelineConfig.getStageConfig(stage);
      if (stageConfig && stageConfig.executionStepLabelUrl) {
        return stageConfig.executionStepLabelUrl;
      } else {
        return require('../../pipeline/config/stages/core/stepLabel.html');
      }
    };

    controller.isRestartable = function(stage) {
      var stageConfig = pipelineConfig.getStageConfig(stage);
      if (!stageConfig || stage.isRestarting === true) {
        return false;
      }

      const allowRestart = controller.application.attributes.enableRestartRunningExecutions || false;
      if (controller.execution.isRunning && !allowRestart) {
        return false;
      }

      return stageConfig.restartable || false;
    };

  });
<|endoftext|>


<commit_before>
pk.colorpicker({
    element: document.getElementById('colorpicker1'),
    value: '#49A3DF',
    listeners: {
        change: function(oldCol, newCol) {
            var outputEl = document.getElementById('colorOutput'), cols;
            for (var u = 0; u < outputEl.children.length; u++) {
                if (pk.attribute(outputEl.children[u], 'data-color')) {
                    outputEl.children[u].innerHTML = '';
                    if (pk.attribute(outputEl.children[u], 'data-color') === 'complement' && pk.attribute(outputEl.children[u], 'data-type')) {
                        cols = pk.color[pk.attribute(outputEl.children[u], 'data-color')](newCol, pk.attribute(outputEl.children[u], 'data-type'));
                    } else {
                        cols = pk.color[pk.attribute(outputEl.children[u], 'data-color')](newCol);
                    }
                    for (var c = 0; c < cols.length; c++) {
                        var cEl = pk.createEl("");
                        outputEl.children[u].appendChild(cEl);
                    }
                }
            }
        }
    }
});

pk.datepicker({
    element: document.getElementById('datepicker1')
});

pk.accordian({
    element: document.getElementById('accordian1'),
    animate: true,
    multiple: false
});

pk.slider({
    element: document.getElementById('slider1'),
    units: '%',
    axis: 'y',
    value: 40
});
pk.slider({
    element: document.getElementById('slider2'),
    units: 'mm',
    axis: 'x',
    value: 0,
    min: -444,
    max: 900,
    listeners: {
        change: function(e, v, i) {
            var col = pk.color.percentage(Math.abs(v) / 900, '#0061a3');
            e.children[1].style.backgroundColor = col;
        }
    }
});
pk.slider({
    element: document.getElementById('slider3'),
    units: 'deg',
    value: 50,
    min: 0,
    max: 100,
    circle: {
        stroke: {
            inner: 5,
            outer: 15
        }
    }
});
pk.slider({
    element: document.getElementById('slider4'),
    units: 'nm',
    value: 336,
    min: 0,
    max: 2477,
    circle: {
        length: 30
    },
    listeners: {
        change: function(e, v, i) {
            var col = pk.color.percentage(v / 2477);
            e.children[2].style.color = col;
            e.children[1].childNodes[1].style.stroke = col;
            e.children[1].childNodes[3].style.stroke = col;
        }
    }
});
pk.slider({
    element: document.getElementById('slider5'),
    units: 'mm',
    axis: 'x',
    value: 0,
    min: -444,
    max: 900,
    circle: {
        stroke: {
            inner: 25,
            outer: 8
        }
    }
});
pk.toggle({
    element: document.getElementById('toggle1'),
    options: [{
        value: true,
        name: 'yes'
    }, {
        value: false,
        name: 'no'
    }, {
        value: 'other',
        name: 'other'
    }],
    value: false
});
pk.checkbox({
    element: document.getElementById('checkbox1'),
    label: 'check',
    value: true
});
pk.switch({
    element: document.getElementById('switch1')
});

pk.rating({
    element: document.getElementById('rating1'),
    value: 3
});
pk.carousel({
    element: document.getElementById('carousel1')
});
pk.tooltip({
    element: document.getElementById('tooltip1'),
    content: 'Content on top',
    position: 'top'
});
pk.tooltip({
    element: document.getElementById('tooltip2'),
    content: 'Content to the right',
    position: 'right'
});
pk.tooltip({
    element: document.getElementById('tooltip3'),
    content: 'Content below',
    position: 'bottom'
});
pk.tooltip({
    element: document.getElementById('tooltip4'),
    content: 'Content to the left',
    position: 'left'
});

pk.chart({
    element: document.getElementById('chart1'),
    type: 'pie',
    data: [{
        category: "cat",
        series1: 8,
        series2: 2
    }, {
        category: "pig",
        series1: 2,
        series2: 6
    }, {
        category: "cow",
        series1: 9,
        series2: 9
    }, {
        category: "bird",
        series1: 5,
        series2: 2
    }, {
        category: "dog",
        series1: 2,
        series2: 7
    }, {
        category: "emu",
        series1: 6,
        series2: 3
    }, {
        category: "hamster",
        series1: 2,
        series2: 3
    }],
    center: 20,
    legend: true,
    axis: {
        x: 'category'
    },
    series: ['series1', 'series2']
});

pk.chart({
    element: document.getElementById('chart2'),
    type: 'line',
    data: [{
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }],
    colors: {
        'Petrol': '#F20081',
        'Invoices': '#EBBC00',
        'Accommodation': '#00DECF'
    },
    // center:50,
    legend: true,
    axis: {
        x: 'Last Month',
        y: 'This Month'
    },
    series: 'category'

});

pk.chart({
    element: document.getElementById('chart3'),
    type: 'scatter',
    data: [{
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }],
    colors: {
        'Petrol': '#F20081',
        'Invoices': '#EBBC00',
        'Accommodation': '#00DECF'
    },
    area: true,
    // center:50,
    legend: true,
    axis: {
        x: 'Last Month',
        y: 'This Month'
    },
    series: 'category'

});

pk.chart({
    element: document.getElementById('chart4'),
    type: 'scatter',
    data: [{
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }],
    colors: {
        'Petrol': '#F20081',
        'Invoices': '#EBBC00',
        'Accommodation': '#00DECF'
    },
    area: true,
    margin: {
        top: 40,
        right: 40,
        bottom: 50,
        left: 40

    },
    points: 'This Month',
    // center:50,
    legend: true,
    axis: {
        x: 'Last Month',
        y: 'This Month',
        r: 'Last Month'
    },
    series: 'category'

});

pk.chart({
    element: document.getElementById('chart5'),
    type: 'area',
    data: [{
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }],
    colors: {
        'Petrol': '#F20081',
        'Invoices': '#EBBC00',
        'Accommodation': '#00DECF'
    },
    area: true,
    // center:50,
    legend: true,
    axis: {
        x: 'Last Month',
        y: 'This Month'
    },
    series: 'category'

});

pk.chart({
    element: document.getElementById('chart6'),
    type: 'column',
    data: [{
        block: 'fingers',
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        block: 'fingers',
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        block: 'toes',
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        block: 'toes',
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        block: 'thumbs',
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        block: 'thumbs',
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        block: 'fingers',
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        block: 'toes',
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        block: 'thumbs',
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }],
    tooltip: true,
    colors: {
        'Petrol': '#F27935',
        'Invoices': '#1FBBA6',
        'Accommodation': '#00AFD1'
    },
    margin: {
        left: 50
    },
    legend: true,
    axis: {
        x: 'block',
        y: 'This Month',
        r: 'This Month'
    },
    series: 'category'
});

pk.chart({
    element: document.getElementById('chart7'),
    type: 'bar',
    data: [{
        block: 'fingers',
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        block: 'fingers',
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        block: 'toes',
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        block: 'toes',
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }, {
        block: 'thumbs',
        category: "Invoices",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        block: 'thumbs',
        category: "Petrol",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        block: 'fingers',
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 9
    }, {
        block: 'toes',
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': pk.getRand(1, 8)
    }, {
        block: 'thumbs',
        category: "Accommodation",
        'This Month': pk.getRand(0, 9),
        'Last Month': 0
    }],
    tooltip: true,
    colors: {
        'Petrol': '#F27935',
        'Invoices': '#1FBBA6',
        'Accommodation': '#00AFD1'
    },
    margin: {
        left: 50
    },
    legend: true,
    axis: {
        y: 'block',
        x: 'This Month',
        r: 'This Month'
    },
    series: 'category'
});

pk.select({
    element: document.getElementById('select1'),
    value: 'val2',
    multiple: true
});
pk.select({
    element: document.getElementById('select4'),
    value: 'val1',
    multiple: true,
    dropdown: true
});
pk.select({
    element: document.getElementById('select3'),
    value: 'val3',
    dropdown: true
});
pk.select({
    element: document.getElementById('select2')
});

pk.scroll({
    element: document.getElementById('scroll1'),
    axis: 'xy'
});
<commit_msg>Update site.js<commit_after>window.onscroll=function(e){
	var pagenav=document.getElementById('pagenav'), css=pagenav.getAttribute('class');
	if(document.body.scrollTop>470){
		if(css.indexOf("navbar-inverse")<0){
			pagenav.setAttribute("class",css.trim()+" navbar-inverse");
		}
	}else{
		pagenav.setAttribute("class",css.replace("navbar-inverse","").trim());
	}
};
<|endoftext|>


<commit_before>import React from 'react';
import StepLabel from './step_label';
import StepTitle from './step_title';
import classNames from 'classnames';

class Step extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      completed: false,
    };
  }
  classNames() {
    return classNames(
      'mdl-step',
      {
        'mdl-step--completed': this.state.completed,
        'is-active': this.props.isActive,
      }
    );
  }
  handleClick(event) {
    const { onNext } = this.props;
    if (event.target.dataset.stepperNext) {
      event.preventDefault();
      this.setState({ 'completed': true });
      onNext();
    }
  }
  render() {
    const { children, title, summary, count } = this.props;
    return (

        <StepLabel stepNumber={count} completed={this.state.completed} active={this.props.isActive}>
          <StepTitle text={title} message={summary} />
        </StepLabel>
        {children}

    );
  }
}

export default Step;
<commit_msg>Add propTypes to Step<commit_after>import React, { PropTypes } from 'react';
import StepLabel from './step_label';
import StepTitle from './step_title';
import classNames from 'classnames';
import StepContent from './step_content';
import StepActions from './step_actions';

class Step extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      completed: false,
    };
  }
  classNames() {
    return classNames(
      'mdl-step',
      {
        'mdl-step--completed': this.state.completed,
        'is-active': this.props.isActive,
      }
    );
  }
  handleClick(event) {
    const { onNext } = this.props;
    if (event.target.dataset.stepperNext) {
      event.preventDefault();
      this.setState({ completed: true });
      onNext();
    }
  }
  render() {
    const { children, title, summary, count } = this.props;
    return (

        <StepLabel stepNumber={count} completed={this.state.completed} active={this.props.isActive}>
          <StepTitle text={title} message={summary} />
        </StepLabel>
        {children}

    );
  }
}

Step.propTypes = {
  title: PropTypes.string.isRequired,
  summary: PropTypes.string,
  count: PropTypes.number,
  isActive: PropTypes.bool,
  onNext: PropTypes.func.isRequired,
  children(props, propName) {
    const children = props[propName];

    React.Children.forEach(children, (child) => {
      if ((child.type !== StepActions) || (child.type !== StepContent)) {
        throw new Error(
          ```
            Expected \`StepContent\` or \`StepActions\`
            but found \`${child.type.displayName || child.type}\`
          ```
        );
      }
      return;
    });
  },
};

export default Step;
<|endoftext|>


<commit_before>// @flow

import React from 'react';

import selfClosingElements from './selfClosingElements';
import mapAttributes from './mapAttributes';
import groupInlineElements from './groupInlineElements';
import inlineElements from './inlineElements';

type GetTagFunc = (name: string) => any;

export type DOMElement = {
  type: 'tag' | 'text' | 'comment',
  name: string,
  data?: string,
  attribs: { [key: string]: any },
  children: DOMElement[],
  parent?: DOMElement
};

type StepFunc = (
  dom: DOMElement,
  index: number,
  parent?: DOMElement | null,
  getTag: GetTagFunc
) => React.Element<*> | null | string;

const step: StepFunc = (dom, index, parent, getTag) => {
  if (dom.type === 'comment') {
    return null;
  }
  if (dom.type === 'tag' || dom.type === 'script' || dom.type === 'style') {
    const Component = getTag(dom.name);
    if (Component === null) {
      return null;
    }
    if (selfClosingElements.indexOf(dom.name) !== -1) {
      return <Component data-tag={dom.name} key={index} {...dom.attribs} />;
    }
    const isParentBlock = parent && inlineElements.indexOf(parent.name) === -1;
    const children = isParentBlock
      ? groupInlineElements(dom.children)
      : dom.children;
    return (
      <Component
        key={index}
        data-tag={dom.name}
        {...mapAttributes(dom.attribs)}
      >
        {children.map((item, index) => step(item, index, item.parent, getTag))}
      </Component>
    );
  }
  if (dom.type === 'text') {
    if ((dom.data || '').trim() === '') {
      return null;
    }
    if (parent && inlineElements.indexOf(parent.name) !== -1) {
      return dom.data || null;
    }
    const Component = getTag('span');
    return <Component key={index}>{dom.data}</Component>;
  }
  return null;
};

export default step;
<commit_msg>add missing mapAttributes call<commit_after>// @flow

import React from 'react';

import selfClosingElements from './selfClosingElements';
import mapAttributes from './mapAttributes';
import groupInlineElements from './groupInlineElements';
import inlineElements from './inlineElements';

type GetTagFunc = (name: string) => any;

export type DOMElement = {
  type: 'tag' | 'text' | 'comment',
  name: string,
  data?: string,
  attribs: { [key: string]: any },
  children: DOMElement[],
  parent?: DOMElement,
};

type StepFunc = (
  dom: DOMElement,
  index: number,
  parent?: DOMElement | null,
  getTag: GetTagFunc
) => React.Element<*> | null | string;

const step: StepFunc = (dom, index, parent, getTag) => {
  if (dom.type === 'comment') {
    return null;
  }
  if (dom.type === 'tag' || dom.type === 'script' || dom.type === 'style') {
    const Component = getTag(dom.name);
    if (Component === null) {
      return null;
    }
    if (selfClosingElements.indexOf(dom.name) !== -1) {
      return (
        <Component
          data-tag={dom.name}
          key={index}
          {...mapAttributes(dom.attribs)}
        />
      );
    }
    const isParentBlock = parent && inlineElements.indexOf(parent.name) === -1;
    const children = isParentBlock
      ? groupInlineElements(dom.children)
      : dom.children;
    return (
      <Component
        key={index}
        data-tag={dom.name}
        {...mapAttributes(dom.attribs)}
      >
        {children.map((item, index) => step(item, index, item.parent, getTag))}
      </Component>
    );
  }
  if (dom.type === 'text') {
    if ((dom.data || '').trim() === '') {
      return null;
    }
    if (parent && inlineElements.indexOf(parent.name) !== -1) {
      return dom.data || null;
    }
    const Component = getTag('span');
    return <Component key={index}>{dom.data}</Component>;
  }
  return null;
};

export default step;
<|endoftext|>


<commit_before>export default sync

function sync (db, remote) {
  if (!db) throw new Error('Please provide a couchdb instance')
  if (!remote) throw new Error('Please provide a remote destination')

  const opts = {
    push: {
      since: 0,
      filter: filterUnsyncable
    },
    pull: { since: 0 }
  }

  const evt = db.sync(remote, opts)
  return evt
}

function filterUnsyncable (doc) {
  return doc.user !== undefined && doc._id.substring(0,8) !== '_design/'
}
<commit_msg>feat(sync): filter pulled entries #232<commit_after>export default sync

function sync (db, remote) {
  if (!db) throw new Error('Please provide a couchdb instance')
  if (!remote) throw new Error('Please provide a remote destination')

  const opts = {
    push: {
      since: 0,
      filter: filterUnsyncable
    },
    pull: {
      since: 0,
      filter: filterUnsyncable
    }
  }

  const evt = db.sync(remote, opts)
  return evt
}

function filterUnsyncable (doc) {
  return doc.user !== undefined && doc._id.substring(0,8) !== '_design/'
}
<|endoftext|>


<commit_before>Spree.Views.Cart.EmptyCartButton = Backbone.View.extend({
  initialize: function() {
    this.listenTo(this.collection, 'update', this.render);
    this.render();
  },

  events: {
    "click": "onClick"
  },

  onClick: function(e) {
    e.preventDefault()
    if (!confirm(Spree.translations.are_you_sure_delete)) {
      return;
    }

    this.model.empty({
      success: () => {
        this.collection.reset()
        this.collection.push({})
      }
    })
  },

  render: function() {
    var isNew = function (item) { return item.isNew() };
    this.$el.prop("disabled", !this.collection.length || this.collection.some(isNew));
  }
});
<commit_msg>Convert ES6 arrow syntax to ES5 for compatibility<commit_after>Spree.Views.Cart.EmptyCartButton = Backbone.View.extend({
  initialize: function() {
    this.listenTo(this.collection, 'update', this.render);
    this.render();
  },

  events: {
    "click": "onClick"
  },

  onClick: function(e) {
    e.preventDefault()
    if (!confirm(Spree.translations.are_you_sure_delete)) {
      return;
    }

    this.model.empty({
      success: function () {
        this.collection.reset()
        this.collection.push({})
      }.bind(this)
    })
  },

  render: function() {
    var isNew = function (item) { return item.isNew() };
    this.$el.prop("disabled", !this.collection.length || this.collection.some(isNew));
  }
});
<|endoftext|>


<commit_before>/**@
* #Crafty Time
* @category Utilities
*/
Crafty.c("Delay", {
	init : function() {
		this._delays = [];
		this.bind("EnterFrame", function() {
			var now = new Date().getTime();
			var index = this._delays.length;
			while (--index >= 0) {
				var item = this._delays[index];
				if(item.start + item.delay + item.pause < now) {
					item.func.call(this);
					// remove item from array
					this._delays.splice(index,1);
				}
			}
		});
		this.bind("Pause", function() {
			var now = new Date().getTime();
			for(var index in this._delays) {
				this._delays[index].pauseBuffer = now;
			}
		});
		this.bind("Unpause", function() {
			var now = new Date().getTime();
			for(var index in this._delays) {
				var item = this._delays[index];
				item.pause += now-item.pauseBuffer;
			}
		});
	},
	/**@
	* #.delay
	* @comp Crafty Time
	* @sign public this.delay(Function callback, Number delay)
	* @param callback - Method to execute after given amount of milliseconds
	* @param delay - Amount of milliseconds to execute the method
	*
	* The delay method will execute a function after a given amount of time in milliseconds.
	*
	* It is not a wrapper for `setTimeout`.
	*
	* If Crafty is paused, the delay is interrupted with the pause and then resume when unpaused
	*
	* If the entity is destroyed, the delay is also destroyed and will not have effect.
	*
	* @example
	* ~~~
	* console.log("start");
	* this.delay(function() {
		 console.log("100ms later");
	* }, 100);
	* ~~~
	*/
	delay : function(func, delay) {
		return this._delays.push({
			start : new Date().getTime(),
			func : func,
			delay : delay,
			pauseBuffer: 0,
			pause: 0
		});
	}
});
<commit_msg>Update time.js<commit_after>/**@
* #Crafty Time
* @category Utilities
*/
Crafty.c("Delay", {
	init : function() {
		this._delays = [];
		this.bind("EnterFrame", function() {
			var now = new Date().getTime();
			var index = this._delays.length;
			while (--index >= 0) {
				var item = this._delays[index];
				if(item.start + item.delay + item.pause < now) {
					item.func.call(this);
					if (item.repeat > 0 ) {
						// reschedule item
						item.start = now;
						item.pause = 0;
						item.pauseBuffer = 0;
						if (item.repeat !== Infinity)
							item.repeat--;
					} else if (item.repeat === 0) {
						// remove item from array
						this._delays.splice(index,1);
					}
				}
			}
		});
		this.bind("Pause", function() {
			var now = new Date().getTime();
			for(var index in this._delays) {
				this._delays[index].pauseBuffer = now;
			}
		});
		this.bind("Unpause", function() {
			var now = new Date().getTime();
			for(var index in this._delays) {
				var item = this._delays[index];
				item.pause += now-item.pauseBuffer;
			}
		});
	},
	/**@
	* #.delay
	* @comp Crafty Time
	* @sign public this.delay(Function callback, Number delay)
	* @param callback - Method to execute after given amount of milliseconds
	* @param delay - Amount of milliseconds to execute the method
	* @param repeat - How often to repeat the delayed function. A value of 0 triggers the delayed
	* function exactly once. A value n > 0 triggers the delayed function exactly n+1 times. A
	* value of Infinity triggers the delayed function indefinitely.
	*
	* The delay method will execute a function after a given amount of time in milliseconds.
	*
	* It is not a wrapper for `setTimeout`.
	*
	* If Crafty is paused, the delay is interrupted with the pause and then resume when unpaused
	*
	* If the entity is destroyed, the delay is also destroyed and will not have effect.
	*
	* @example
	* ~~~
	* console.log("start");
	* this.delay(function() {
		 console.log("100ms later");
	* }, 100, 0);
	* ~~~
	*/
	delay : function(func, delay, repeat) {
		this._delays.push({
			start : new Date().getTime(),
			func : func,
			delay : delay,
			repeat: repeat || 0,
			pauseBuffer: 0,
			pause: 0
		});
		return this;
	}
});
<|endoftext|>


<commit_before>import APIClient from "./apiclient";
import {getId, getDuration, getPoints, getYoutubeId} from "./data/topic-tree-helper";
import _ from "underscore";
import Util from "./util";

const userInfoLocalStorageName = "userInfo-3";

export const signIn = () => APIClient.signIn();
export const signOut = (editUser) => {
    localStorage.removeItem(userInfoLocalStorageName);

    editUser((user) => user.merge({
        userInfo: null,
        startedEntities: [],
        completedEntities: [],
    }));

    return APIClient.signOut();
};

export const isSignedIn = () =>
    APIClient.isSignedIn();

const getLocalStorageName = (base, userInfo) =>
    base + "-uid-" + (userInfo.get("nickname") || userInfo.get("username"));

const completedEntitiesLocalStorageName = _.partial(getLocalStorageName, "completed");
const startedEntitiesLocalStorageName= _.partial(getLocalStorageName, "started");
const userVideosLocalStorageName = _.partial(getLocalStorageName, "userVideos");
const userExercisesLocalStorageName = _.partial(getLocalStorageName, "userExercises");

const saveUserInfo = (userInfo) => {
    localStorage.removeItem(userInfoLocalStorageName);
    localStorage.setItem(userInfoLocalStorageName, JSON.stringify(userInfo));
};

const saveStarted = (userInfo, startedEntityIds) => {
    localStorage.removeItem(startedEntitiesLocalStorageName(userInfo));
    localStorage.setItem(startedEntitiesLocalStorageName(), JSON.stringify(startedEntityIds));
};

const saveCompleted = (userInfo, completedEntityIds) => {
    localStorage.removeItem(completedEntitiesLocalStorageName(userInfo));
    localStorage.setItem(completedEntitiesLocalStorageName(), JSON.stringify(completedEntityIds));
};

const saveUserVideos = (userInfo, userVideos) => {
    userVideos = userVideos.map((userVideo) => {
        return {
            duration: userVideo.duration,
            last_second_watched: userVideo.last_second_watched,
            points: userVideo.points,
            video: {
                id: userVideo.id
            }
        };
    });
    localStorage.removeItem(userVideosLocalStorageName(userInfo));
    localStorage.setItem(userVideosLocalStorageName(), JSON.stringify(userVideos));
};

const saveUserExercises = (userInfo, userExercises) => {
    userExercises = userExercises.map((exercise) => {
        return {
            streak: exercise.streak,
            total_correct: exercise.total_correct,
            total_done: exercise.total_done,
            exercise_model: {
                content_id: exercise.exercise_model.content_id
            }
        };
    });

    // The extra removeItem calls before the setItem calls help in case local storage is almost full
    localStorage.removeItem(userExercisesLocalStorageName(userInfo));
    localStorage.setItem(userExercisesLocalStorageName(), JSON.stringify(userExercises));
};

const loadLocalStorageData = (userInfo) => {
    var result = {};
    var completedEntityIds = localStorage.getItem(completedEntitiesLocalStorageName(userInfo));
    if (completedEntityIds) {
        result.completedEntityIds = JSON.parse(completedEntityIds);
    }
    var startedEntityIds = localStorage.getItem(startedEntitiesLocalStorageName(userInfo));
    if (startedEntityIds) {
        result.startedEntityIds = JSON.parse(startedEntityIds);
    }
    var userVideos = localStorage.getItem(userVideosLocalStorageName(userInfo));
    if (userVideos) {
        result.userVideos = JSON.parse(userVideos);
    }
    var userExercises = localStorage.getItem(userExercisesLocalStorageName(userInfo));
    if (userExercises) {
        result.userExercises = JSON.parse(userExercises);
    }

    return result;
};

export const reportVideoProgress = (user, topicTreeCursor, editVideo, secondsWatched, lastSecondWatched) => {
    return new Promise((resolve, reject) => {
        var youTubeId = getYoutubeId(topicTreeCursor);
        var videoId = getId(topicTreeCursor);
        var duration = getDuration(topicTreeCursor);
        APIClient.reportVideoProgress(videoId, youTubeId, duration, secondsWatched, lastSecondWatched).then((result) => {
            if (!result) {
                Util.warn("Video progress report returned null results!");
                return;
            }
            Util.log("reportVideoProgress result: %o", result);

            var lastPoints = getPoints(topicTreeCursor);
            var newPoints = lastPoints + result.points_earned;
            if (newPoints > 750) {
                newPoints = 750;
            }

            // If they've watched some part of the video, and it's not almost the end
            // Otherwise check if we already have video progress for this item and we
            // therefore no longer need it.
            var lastSecondWatched;
            if (result.last_second_watched > 10 &&
                    duration - result.last_second_watched > 10) {
                lastSecondWatched = result.last_second_watched;
            }

            // If we're just getting a completion of a video update
            // the user's overall points locally.
            if (result.points_earned > 0) {
                // TODO: It would be better to store userInfo properties directly
                // That way notificaitons will go out automatically.
                var userInfo = CurrentUser.get("userInfo");
                userInfo.points += result.points_earned;
                saveUserInfo(user.get("userInfo"));
            }

            editVideo((video) =>
                video.merge({
                    points: newPoints,
                    completed: result.is_video_completed,
                    started: !result.is_video_completed,
                    lastSecondWatched: lastSecondWatched
                }));

            // Update locally stored cached info
            var index;
            if (result.is_video_completed) {
                index = this.get("startedEntityIds").indexOf(getId(topicTreeCursor));
                if (index !== -1) {
                    this.get("startedEntityIds").splice(index, 1);
                }
                index = this.get("completedEntityIds").indexOf(getId(topicTreeCursor));
                if (index === -1) {
                    this.get("completedEntityIds").push(getId(topicTreeCursor));
                }
            } else {
                index = this.get("startedEntityIds").indexOf(getId(topicTreeCursor));
                if (index === -1) {
                    this.get("startedEntityIds").push(getId(topicTreeCursor));
                }
            }

            var foundUserVideo = _(this.get("userVideos")).find((info) => {
                info.video.id === getId(topicTreeCursor);
            });
            var isNew = !foundUserVideo;
            foundUserVideo = foundUserVideo || {
                video: {
                    id: getId(topicTreeNode)
                },
                duration: getDuration(topicTreeNode)
            };
            foundUserVideo["points"] = newPoints;
            foundUserVideo["last_second_watched"] = lastSecondWatched;
            if (isNew) {
                this.get("userVideos").push(foundUserVideo);
            }

            saveStarted(user.get("userInfo"), user.get("startedEntities"));
            saveCompleted(user.get("userInfo"), user.get("completedEntities"));
            saveUserVideos(user.get("userInfo"), user.get("userVideos"));
            saveUserExercises(user.get("userInfo"), user.get("userExercises"));

            resolve({
                completed: result.is_video_completed,
                lastSecondWatched: result.last_second_watched,
                pointsEarned: result.points_earned,
                youtubeId: result.youtube_id,
                videoId: videoId,
                id: getId(topicTreeCursor)
            });
        }).catch(() => {
            reject();
        });
    });
};

export const refreshLoggedInInfo = (user, editUser, forceRefreshAllInfo) => {
    return new Promise((resolve, reject) => {
        if (!isSignedIn()) {
            return resolve();
        }

        // Get the user profile info
        APIClient.getUserInfo().then((result) => {
            Util.log("getUserInfo: %o", result);
            const userInfo = {
                avatarUrl: result.avatar_url,
                joined: result.joined,
                nickname: result.nickname,
                username: result.username,
                points: result.points,
                badgeCounts: result.badge_counts
            };
            editUser((user) => user.merge({
                userInfo,
            }));

            saveUserInfo(userInfo);

            var result = loadLocalStorageData(user.get("userInfo"));
            if (!forceRefreshAllInfo && result) {
                Util.log("User info only obtained. Not obtaining user data because we have it cached already!");
                return result;
            }

            // The call is needed for completed/in progress status of content items
            // Unlike getUserVideos, this includes both articles and videos.
            return APIClient.getUserProgress();

        }).then((data) => {
            Util.log("getUserProgress: %o", data);
            // Get rid of the 'a' and 'v' prefixes, and set the completed / started
            // attributes accordingly.
            const startedEntityIds = _.map(data.started, function(e) {
                return e.substring(1);
            });
            const completedEntityIds = _.map(data.complete, function(e) {
                return e.substring(1);
            });

            editUser((user) => user.merge({
                startedEntities: startedEntityIds,
                completedEntities: completedEntityIds,
            }));

            // Save to local storage
            saveStarted(user.get("userInfo"), startedEntityIds);
            saveCompleted(user.get("userInfo"), completedEntityIds);
            return APIClient.getUserVideos();
        }).then((userVideosResults) => {
            saveUserVideos(user.get("userInfo"), userVideosResults);
            return APIClient.getUserExercises();
        }).then((userExercisesResults) => {
            saveUserExercises(user.get("userInfo"), userExercisesResults);
            resolve();
        }).catch(() => {
            reject();
        });
    });
};

<commit_msg>Fixes for loading user progress from local storage<commit_after>import APIClient from "./apiclient";
import {getId, getDuration, getPoints, getYoutubeId} from "./data/topic-tree-helper";
import _ from "underscore";
import Util from "./util";

const userInfoLocalStorageName = "userInfo-3";

export const signIn = () => APIClient.signIn();
export const signOut = (editUser) => {
    localStorage.removeItem(userInfoLocalStorageName);

    editUser((user) => user.merge({
        userInfo: null,
        startedEntities: [],
        completedEntities: [],
    }));

    return APIClient.signOut();
};

export const isSignedIn = () =>
    APIClient.isSignedIn();

const getLocalStorageName = (base, userInfo) =>
    base + "-uid-" + (userInfo.get("nickname") || userInfo.get("username"));

const completedEntitiesLocalStorageName = _.partial(getLocalStorageName, "completed");
const startedEntitiesLocalStorageName= _.partial(getLocalStorageName, "started");
const userVideosLocalStorageName = _.partial(getLocalStorageName, "userVideos");
const userExercisesLocalStorageName = _.partial(getLocalStorageName, "userExercises");

const saveUserInfo = (userInfo) => {
    localStorage.removeItem(userInfoLocalStorageName);
    localStorage.setItem(userInfoLocalStorageName, JSON.stringify(userInfo));
};

const saveStarted = (userInfo, startedEntityIds) => {
    localStorage.removeItem(startedEntitiesLocalStorageName(userInfo));
    localStorage.setItem(startedEntitiesLocalStorageName(userInfo), JSON.stringify(startedEntityIds));
};

const saveCompleted = (userInfo, completedEntityIds) => {
    localStorage.removeItem(completedEntitiesLocalStorageName(userInfo));
    localStorage.setItem(completedEntitiesLocalStorageName(userInfo), JSON.stringify(completedEntityIds));
};

const saveUserVideos = (userInfo, userVideos) => {
    userVideos = userVideos.map((userVideo) => {
        return {
            duration: userVideo.duration,
            last_second_watched: userVideo.last_second_watched,
            points: userVideo.points,
            video: {
                id: userVideo.id
            }
        };
    });
    localStorage.removeItem(userVideosLocalStorageName(userInfo));
    localStorage.setItem(userVideosLocalStorageName(userInfo), JSON.stringify(userVideos));
};

const saveUserExercises = (userInfo, userExercises) => {
    userExercises = userExercises.map((exercise) => {
        return {
            streak: exercise.streak,
            total_correct: exercise.total_correct,
            total_done: exercise.total_done,
            exercise_model: {
                content_id: exercise.exercise_model.content_id
            }
        };
    });

    // The extra removeItem calls before the setItem calls help in case local storage is almost full
    localStorage.removeItem(userExercisesLocalStorageName(userInfo));
    localStorage.setItem(userExercisesLocalStorageName(userInfo), JSON.stringify(userExercises));
};

const loadLocalStorageData = (userInfo) => {
    var result = {};
    var completedEntityIds = localStorage.getItem(completedEntitiesLocalStorageName(userInfo));
    if (completedEntityIds) {
        result.completedEntityIds = JSON.parse(completedEntityIds);
    }
    var startedEntityIds = localStorage.getItem(startedEntitiesLocalStorageName(userInfo));
    if (startedEntityIds) {
        result.startedEntityIds = JSON.parse(startedEntityIds);
    }
    var userVideos = localStorage.getItem(userVideosLocalStorageName(userInfo));
    if (userVideos) {
        result.userVideos = JSON.parse(userVideos);
    }
    var userExercises = localStorage.getItem(userExercisesLocalStorageName(userInfo));
    if (userExercises) {
        result.userExercises = JSON.parse(userExercises);
    }

    return result;
};

export const reportVideoProgress = (user, topicTreeCursor, editVideo, secondsWatched, lastSecondWatched) => {
    return new Promise((resolve, reject) => {
        var youTubeId = getYoutubeId(topicTreeCursor);
        var videoId = getId(topicTreeCursor);
        var duration = getDuration(topicTreeCursor);
        APIClient.reportVideoProgress(videoId, youTubeId, duration, secondsWatched, lastSecondWatched).then((result) => {
            if (!result) {
                Util.warn("Video progress report returned null results!");
                return;
            }
            Util.log("reportVideoProgress result: %o", result);

            var lastPoints = getPoints(topicTreeCursor);
            var newPoints = lastPoints + result.points_earned;
            if (newPoints > 750) {
                newPoints = 750;
            }

            // If they've watched some part of the video, and it's not almost the end
            // Otherwise check if we already have video progress for this item and we
            // therefore no longer need it.
            var lastSecondWatched;
            if (result.last_second_watched > 10 &&
                    duration - result.last_second_watched > 10) {
                lastSecondWatched = result.last_second_watched;
            }

            // If we're just getting a completion of a video update
            // the user's overall points locally.
            if (result.points_earned > 0) {
                // TODO: It would be better to store userInfo properties directly
                // That way notificaitons will go out automatically.
                var userInfo = CurrentUser.get("userInfo");
                userInfo.points += result.points_earned;
                saveUserInfo(user.get("userInfo"));
            }

            editVideo((video) =>
                video.merge({
                    points: newPoints,
                    completed: result.is_video_completed,
                    started: !result.is_video_completed,
                    lastSecondWatched: lastSecondWatched
                }));

            // Update locally stored cached info
            var index;
            if (result.is_video_completed) {
                index = this.get("startedEntityIds").indexOf(getId(topicTreeCursor));
                if (index !== -1) {
                    this.get("startedEntityIds").splice(index, 1);
                }
                index = this.get("completedEntityIds").indexOf(getId(topicTreeCursor));
                if (index === -1) {
                    this.get("completedEntityIds").push(getId(topicTreeCursor));
                }
            } else {
                index = this.get("startedEntityIds").indexOf(getId(topicTreeCursor));
                if (index === -1) {
                    this.get("startedEntityIds").push(getId(topicTreeCursor));
                }
            }

            var foundUserVideo = _(this.get("userVideos")).find((info) => {
                info.video.id === getId(topicTreeCursor);
            });
            var isNew = !foundUserVideo;
            foundUserVideo = foundUserVideo || {
                video: {
                    id: getId(topicTreeNode)
                },
                duration: getDuration(topicTreeNode)
            };
            foundUserVideo["points"] = newPoints;
            foundUserVideo["last_second_watched"] = lastSecondWatched;
            if (isNew) {
                this.get("userVideos").push(foundUserVideo);
            }

            saveStarted(user.get("userInfo"), user.get("startedEntities"));
            saveCompleted(user.get("userInfo"), user.get("completedEntities"));
            saveUserVideos(user.get("userInfo"), user.get("userVideos"));
            saveUserExercises(user.get("userInfo"), user.get("userExercises"));

            resolve({
                completed: result.is_video_completed,
                lastSecondWatched: result.last_second_watched,
                pointsEarned: result.points_earned,
                youtubeId: result.youtube_id,
                videoId: videoId,
                id: getId(topicTreeCursor)
            });
        }).catch(() => {
            reject();
        });
    });
};

export const refreshLoggedInInfo = (user, editUser, forceRefreshAllInfo) => {
    return new Promise((resolve, reject) => {
        if (!isSignedIn()) {
            return resolve();
        }

        // Get the user profile info
        APIClient.getUserInfo().then((result) => {
            Util.log("getUserInfo: %o", result);
            const userInfo = {
                avatarUrl: result.avatar_url,
                joined: result.joined,
                nickname: result.nickname,
                username: result.username,
                points: result.points,
                badgeCounts: result.badge_counts
            };
            editUser((user) => user.merge({
                userInfo,
            }));

            saveUserInfo(userInfo);

            var result = loadLocalStorageData(user.get("userInfo"));
            if (!forceRefreshAllInfo && result) {
                Util.log("User info only obtained. Not obtaining user data because we have it cached already!");
                return result;
            }

            // The call is needed for completed/in progress status of content items
            // Unlike getUserVideos, this includes both articles and videos.
            return APIClient.getUserProgress();

        }).then((data) => {
            Util.log("getUserProgress: %o", data);
            // Get rid of the 'a' and 'v' prefixes, and set the completed / started
            // attributes accordingly.
            const startedEntityIds = _.map(data.started, function(e) {
                return e.substring(1);
            });
            const completedEntityIds = _.map(data.complete, function(e) {
                return e.substring(1);
            });

            editUser((user) => user.merge({
                startedEntities: startedEntityIds,
                completedEntities: completedEntityIds,
            }));

            // Save to local storage
            saveStarted(user.get("userInfo"), startedEntityIds);
            saveCompleted(user.get("userInfo"), completedEntityIds);
            return APIClient.getUserVideos();
        }).then((userVideosResults) => {
            saveUserVideos(user.get("userInfo"), userVideosResults);
            return APIClient.getUserExercises();
        }).then((userExercisesResults) => {
            saveUserExercises(user.get("userInfo"), userExercisesResults);
            resolve();
        }).catch(() => {
            reject();
        });
    });
};

<|endoftext|>


<commit_before>if (typeof MMui === 'undefined') MMui = {};

// Utils are extracted from other libraries or
// written from scratch to plug holes in browser compatibility.
MMui.u = {
    // From Bonzo
    offset: function(el) {
        // TODO: window margins
        //
        // Okay, so fall back to styles if offsetWidth and height are botched
        // by Firefox.
        var width = el.offsetWidth || parseInt(el.style.width, 10),
            height = el.offsetHeight || parseInt(el.style.height, 10),
            doc_body = document.body,
            top = 0,
            left = 0;

        var calculateOffset = function(el) {
            if (el === doc_body || el === document.documentElement) return;
            top += el.offsetTop;
            left += el.offsetLeft;

            var style = el.style.transform ||
                el.style.WebkitTransform ||
                el.style.OTransform ||
                el.style.MozTransform ||
                el.style.msTransform;

            if (style) {
                var match;
                if (match = style.match(/translate\((.+)px, (.+)px\)/)) {
                    top += parseInt(match[2], 10);
                    left += parseInt(match[1], 10);
                } else if (match = style.match(/translate3d\((.+)px, (.+)px, (.+)px\)/)) {
                    top += parseInt(match[2], 10);
                    left += parseInt(match[1], 10);
                } else if (match = style.match(/matrix3d\(([\-\d,\s]+)\)/)) {
                    var pts = match[1].split(',');
                    top += parseInt(pts[13], 10);
                    left += parseInt(pts[12], 10);
                } else if (match = style.match(/matrix\(.+, .+, .+, .+, (.+), (.+)\)/)) {
                    top += parseInt(match[2], 10);
                    left += parseInt(match[1], 10);
                }
            }
        };

        calculateOffset(el);

        try {
            while (el = el.offsetParent) { calculateOffset(el); }
        } catch(e) {
            // Hello, internet explorer.
        }

        // Offsets from the body
        top += doc_body.offsetTop;
        left += doc_body.offsetLeft;
        // Offsets from the HTML element
        top += doc_body.parentNode.offsetTop;
        left += doc_body.parentNode.offsetLeft;

        // Firefox and other weirdos. Similar technique to jQuery's
        // `doesNotIncludeMarginInBodyOffset`.
        var htmlComputed = document.defaultView ?
            window.getComputedStyle(doc_body.parentNode, null) :
            doc_body.parentNode.currentStyle;
        if (doc_body.parentNode.offsetTop !==
            parseInt(htmlComputed.marginTop, 10) &&
            !isNaN(parseInt(htmlComputed.marginTop, 10))) {
            top += parseInt(htmlComputed.marginTop, 10);
            left += parseInt(htmlComputed.marginLeft, 10);
        }

        return {
            top: top,
            left: left,
            height: height,
            width: width
        };
    },

    '$': function(x) {
        return (typeof x === 'string') ?
            document.getElementById(x) :
            x;
    },

    // From quirksmode: normalize the offset of an event from the top-left
    // of the page.
    eventoffset: function(e) {
        var posx = 0;
        var posy = 0;
        if (!e) { e = window.event; }
        if (e.pageX || e.pageY) {
            // Good browsers
            return {
                x: e.pageX,
                y: e.pageY
            };
        } else if (e.clientX || e.clientY) {
            // Internet Explorer
            return {
                x: e.clientX,
                y: e.clientY
            };
        } else if (e.touches && e.touches.length === 1) {
            // Touch browsers
            return {
                x: e.touches[0].pageX,
                y: e.touches[0].pageY
            };
        }
    },

    // Ripped from underscore.js
    // Internal function used to implement `_.throttle` and `_.debounce`.
    limit: function(func, wait, debounce) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var throttler = function() {
                timeout = null;
                func.apply(context, args);
            };
            if (debounce) clearTimeout(timeout);
            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
        };
    },

    // Returns a function, that, when invoked, will only be triggered at most once
    // during a given window of time.
    throttle: function(func, wait) {
        return this.limit(func, wait, false);
    },

    sanitize: function(content) {
        if (!content) return '';

        function urlX(url) {
            // Data URIs are subject to a bug in Firefox
            // https://bugzilla.mozilla.org/show_bug.cgi?id=255107
            // which let them be a vector. But WebKit does 'the right thing'
            // or at least 'something' about this situation, so we'll tolerate
            // them.
            if (/^(https?:\/\/|data:image)/.test(url)) {
                return url;
            }
        }

        function idX(id) { return id; }

        return html_sanitize(content, urlX, idX);
    }
};
<commit_msg>Remove unused utils<commit_after>if (typeof MMui === 'undefined') MMui = {};

// Utils are extracted from other libraries or
// written from scratch to plug holes in browser compatibility.
MMui.u = {

    '$': function(x) {
        return (typeof x === 'string') ?
            document.getElementById(x) :
            x;
    },

    // Ripped from underscore.js
    // Internal function used to implement `_.throttle` and `_.debounce`.
    limit: function(func, wait, debounce) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var throttler = function() {
                timeout = null;
                func.apply(context, args);
            };
            if (debounce) clearTimeout(timeout);
            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
        };
    },

    // Returns a function, that, when invoked, will only be triggered at most once
    // during a given window of time.
    throttle: function(func, wait) {
        return this.limit(func, wait, false);
    },

    sanitize: function(content) {
        if (!content) return '';

        function urlX(url) {
            // Data URIs are subject to a bug in Firefox
            // https://bugzilla.mozilla.org/show_bug.cgi?id=255107
            // which let them be a vector. But WebKit does 'the right thing'
            // or at least 'something' about this situation, so we'll tolerate
            // them.
            if (/^(https?:\/\/|data:image)/.test(url)) {
                return url;
            }
        }

        function idX(id) { return id; }

        return html_sanitize(content, urlX, idX);
    }
};
<|endoftext|>