<commit_before>/*!
 * ${copyright}
 */

/*
 * IMPORTANT: This is a private module, its API must not be used and is subject to change.
 * Code other than the Core tests must not yet introduce dependencies to this module.
 */

/*global document, sap */
(function(deps, callback) {

	"use strict";

	//extract base URL from script tag
	var oScriptTag, mMatch, sBaseUrl;

	oScriptTag = document.querySelector("[src$='createSuite.js']");
	if (oScriptTag) {
		mMatch = /^(.*\/)?createSuite.js/.exec(oScriptTag.getAttribute("src"));
		if (mMatch) {
			sBaseUrl = mMatch[1] + "../../../../";
		}
	}

	if (sBaseUrl == null) {
		throw new Error("createSuite.js: could not identify script tag!");
	}

	function loadScripts(urls, callback) {
		var pending = urls.length,
			errors = 0;

		function listener(e) {
			pending--;
			if ( e.type === 'error' ) {
				errors++;
			}
			e.target.removeEventListener("load", listener);
			e.target.removeEventListener("error", listener);
			if ( pending === 0 && errors === 0 && callback ) {
				callback();
			}
		}

		for ( var i = 0; i < urls.length; i++ ) {
			var script = document.createElement("script");
			script.addEventListener("load", listener);
			script.addEventListener("error", listener);
			script.src = sBaseUrl + urls[i];
			document.head.appendChild(script);
		}
	}

	// cascade 1: polyfills, can all be loaded in parallel
	loadScripts([
		"sap/ui/thirdparty/baseuri.js",
		"sap/ui/thirdparty/es6-promise.js",
		"sap/ui/thirdparty/es6-string-methods.js",
		"sap/ui/thirdparty/es6-object-assign.js"
	], function() {
		// cascade 2: the loader
		loadScripts([
			"ui5loader.js"
		], function() {
			// cascade 3: the loader configuration script
			sap.ui.loader.config({
				async:true
			});
			loadScripts([
				"ui5loader-autoconfig.js"
			], function() {
				sap.ui.require(deps, callback);
			});
		});
	});

}([
	"sap/ui/test/starter/_utils"
], function(utils) {
	"use strict";

	function render(sHTML) {
		// style is added to the head, no need to wait for DOM ready
		utils.addStylesheet("sap/ui/thirdparty/qunit-2.css");
		utils.addStylesheet("sap/ui/test/starter/testsuite.css");
		return utils.whenDOMReady().then(function() {
			var elem = document.body.querySelector("#qunit");
			if ( elem == null ) {
				elem = document.createElement("div");
				elem.id = "qunit";
				document.body.insertBefore(elem, document.body.firstChild);
			}
			elem.innerHTML = sHTML;
		});
	}

	function redirectToTestRunner() {
		// As IE11 doesn't properly resolve relative URLs when assigning to location.href, use an anchor tag
		var anchor = document.createElement("A");
		document.head.appendChild(anchor);
		anchor.href = sap.ui.require.toUrl("") + "/../test-resources/sap/ui/qunit/testrunner.html"
			+ "?testpage=" + encodeURIComponent(window.location.pathname) + "&autostart=true";
		window.location.href = anchor.href;
	}

	function renderList(oSuiteConfig) {

		document.title = "Available Unit Tests - " + oSuiteConfig.name;

		var sLinkHTML = "
" + document.title + "
"
			+ "

"
			+ "
"
			+ "<button id='redirect'>Run All</button>"
			+ "
"
			+ "
";
		oSuiteConfig.sortedTests.forEach(function(oTestConfig) {
			sLinkHTML += "
" +
					(oTestConfig.group ? "" + oTestConfig.group + ": " : "") +
					"" + oTestConfig.name + "
";
		});
		sLinkHTML += "
"
			+ "

"
			+ "
Tests will start in
"
			+ "
*
"
			+ "
Click or press 'ESC' to cancel

";

		render(sLinkHTML).then(function() {
			// Note: we use a 0.1 second timer resolution so that the blocking div disappears quickly
			var count = 10 * (parseInt(utils.getAttribute("data-sap-ui-delay"), 10) || 2) + 9;

			function countDown() {
				if ( count === 6 ) {
					redirectToTestRunner();
				} else if ( count > 6 ){
					document.getElementById("remaining-time").textContent = String(Math.floor(count / 10));
					count--;
					setTimeout(countDown, 100);
				} else {
					document.removeEventListener("click", maybeStop);
					document.removeEventListener("keydown", maybeStop);
					var hintOverlay = document.getElementById("redirect-hint");
					hintOverlay.parentNode.removeChild(hintOverlay);
				}
			}

			function maybeStop(e) {
				if ( e.type === "click" || e.key === "Escape" ) {
					count = -1;
					e.preventDefault();
				}
			}

			document.addEventListener("keydown", maybeStop);
			document.addEventListener("click", maybeStop);
			document.getElementById("redirect").addEventListener("click", redirectToTestRunner);
			countDown();
		});

	}

	function renderError(oErr) {

		render(
			"
Failed to load Testsuite
"
			+ "

"
			+ "
"
			+ "
" + utils.encode(oErr.message || String(oErr)) + "
"
			+ "
"
		);

	}

	var sSuiteName = utils.getAttribute("data-sap-ui-testsuite");
	var whenLoaded = utils.getSuiteConfig(sSuiteName);

	var JSUnitSuite = parent.jsUnitTestSuite;
	if ( !JSUnitSuite ) {
		// If running in top window, render list and redirect to testrunner after a while
		whenLoaded.then(renderList).catch(renderError);
		return;
	}

	/*
	 * Note: the window.suite function must be provided early,
	 * only its answer can be a promise. The TestRunner.js will call this method
	 * immediately after the window load event.
	 */
	window.suite = function() {

		function createSuite(oSuiteConfig) {
			var sContextPath = "/" + window.location.pathname.split("/")[1] + "/";
			var JSUnitSuite = parent.jsUnitTestSuite;
			var oSuite = new JSUnitSuite();
			oSuiteConfig.sortedTests.forEach(function(oTestConfig) {
				oSuite.addTestPage(sContextPath + oTestConfig.page);
			});
			return oSuite;
		}

		return whenLoaded.then(createSuite).catch(renderError);

	};

}));
<commit_msg>[INTERNAL][FIX] Test Starter: Define window.suite synchronously<commit_after>/*!
 * ${copyright}
 */

/*
 * IMPORTANT: This is a private module, its API must not be used and is subject to change.
 * Code other than the Core tests must not yet introduce dependencies to this module.
 */

/*global document, sap */

(function() {
	"use strict";

	var setup = new Promise(function(resolve, reject) {
		(function(deps, callback) {

			//extract base URL from script tag
			var oScriptTag, mMatch, sBaseUrl;

			oScriptTag = document.querySelector("[src$='createSuite.js']");
			if (oScriptTag) {
				mMatch = /^(.*\/)?createSuite.js/.exec(oScriptTag.getAttribute("src"));
				if (mMatch) {
					sBaseUrl = mMatch[1] + "../../../../";
				}
			}

			if (sBaseUrl == null) {
				throw new Error("createSuite.js: could not identify script tag!");
			}

			function loadScripts(urls, callback) {
				var pending = urls.length,
					errors = 0;

				function listener(e) {
					pending--;
					if ( e.type === 'error' ) {
						errors++;
					}
					e.target.removeEventListener("load", listener);
					e.target.removeEventListener("error", listener);
					if ( pending === 0 && errors === 0 && callback ) {
						callback();
					}
				}

				for ( var i = 0; i < urls.length; i++ ) {
					var script = document.createElement("script");
					script.addEventListener("load", listener);
					script.addEventListener("error", listener);
					script.src = sBaseUrl + urls[i];
					document.head.appendChild(script);
				}
			}

			// cascade 1: polyfills, can all be loaded in parallel
			loadScripts([
				"sap/ui/thirdparty/baseuri.js",
				"sap/ui/thirdparty/es6-promise.js",
				"sap/ui/thirdparty/es6-string-methods.js",
				"sap/ui/thirdparty/es6-object-assign.js"
			], function() {
				// cascade 2: the loader
				loadScripts([
					"ui5loader.js"
				], function() {
					// cascade 3: the loader configuration script
					sap.ui.loader.config({
						async:true
					});
					loadScripts([
						"ui5loader-autoconfig.js"
					], function() {
						sap.ui.require(deps, callback);
					});
				});
			});

		})([
			"sap/ui/test/starter/_utils"
		], resolve);
	}).then(function(utils) {

		function render(sHTML) {
			// style is added to the head, no need to wait for DOM ready
			utils.addStylesheet("sap/ui/thirdparty/qunit-2.css");
			utils.addStylesheet("sap/ui/test/starter/testsuite.css");
			return utils.whenDOMReady().then(function() {
				var elem = document.body.querySelector("#qunit");
				if ( elem == null ) {
					elem = document.createElement("div");
					elem.id = "qunit";
					document.body.insertBefore(elem, document.body.firstChild);
				}
				elem.innerHTML = sHTML;
			});
		}

		function redirectToTestRunner() {
			// As IE11 doesn't properly resolve relative URLs when assigning to location.href, use an anchor tag
			var anchor = document.createElement("A");
			document.head.appendChild(anchor);
			anchor.href = sap.ui.require.toUrl("") + "/../test-resources/sap/ui/qunit/testrunner.html"
				+ "?testpage=" + encodeURIComponent(window.location.pathname) + "&autostart=true";
			window.location.href = anchor.href;
		}

		function renderList(oSuiteConfig) {

			document.title = "Available Unit Tests - " + oSuiteConfig.name;

			var sLinkHTML = "
" + document.title + "
"
				+ "

"
				+ "
"
				+ "<button id='redirect'>Run All</button>"
				+ "
"
				+ "
";
			oSuiteConfig.sortedTests.forEach(function(oTestConfig) {
				sLinkHTML += "
" +
						(oTestConfig.group ? "" + oTestConfig.group + ": " : "") +
						"" + oTestConfig.name + "
";
			});
			sLinkHTML += "
"
				+ "

"
				+ "
Tests will start in
"
				+ "
*
"
				+ "
Click or press 'ESC' to cancel

";

			render(sLinkHTML).then(function() {
				// Note: we use a 0.1 second timer resolution so that the blocking div disappears quickly
				var count = 10 * (parseInt(utils.getAttribute("data-sap-ui-delay"), 10) || 2) + 9;

				function countDown() {
					if ( count === 6 ) {
						redirectToTestRunner();
					} else if ( count > 6 ){
						document.getElementById("remaining-time").textContent = String(Math.floor(count / 10));
						count--;
						setTimeout(countDown, 100);
					} else {
						document.removeEventListener("click", maybeStop);
						document.removeEventListener("keydown", maybeStop);
						var hintOverlay = document.getElementById("redirect-hint");
						hintOverlay.parentNode.removeChild(hintOverlay);
					}
				}

				function maybeStop(e) {
					if ( e.type === "click" || e.key === "Escape" ) {
						count = -1;
						e.preventDefault();
					}
				}

				document.addEventListener("keydown", maybeStop);
				document.addEventListener("click", maybeStop);
				document.getElementById("redirect").addEventListener("click", redirectToTestRunner);
				countDown();
			});

		}

		function renderError(oErr) {

			render(
				"
Failed to load Testsuite
"
				+ "

"
				+ "
"
				+ "
" + utils.encode(oErr.message || String(oErr)) + "
"
				+ "
"
			);

		}

		function createSuite(oSuiteConfig) {
			var sContextPath = "/" + window.location.pathname.split("/")[1] + "/";
			var JSUnitSuite = parent.jsUnitTestSuite;
			var oSuite = new JSUnitSuite();
			oSuiteConfig.sortedTests.forEach(function(oTestConfig) {
				oSuite.addTestPage(sContextPath + oTestConfig.page);
			});
			return oSuite;
		}

		var sSuiteName = utils.getAttribute("data-sap-ui-testsuite");
		var whenLoaded = utils.getSuiteConfig(sSuiteName);

		return {
			renderList: function() {
				return whenLoaded.then(renderList).catch(renderError);
			},
			createSuite: function() {
				return whenLoaded.then(createSuite).catch(renderError);
			}
		};
	});

	var JSUnitSuite = parent.jsUnitTestSuite;
	if ( JSUnitSuite ) {
		/*
		* Note: the window.suite function must be provided synchronously,
		* only its answer can be a promise. The TestRunner.js will call this method
		* immediately after the window load event.
		*/
		window.suite = function() {
			return setup.then(function(suiteSetup) {
				return suiteSetup.createSuite();
			});
		};
	} else {
		// If running in top window, render list and redirect to testrunner after a while
		setup.then(function(suiteSetup) {
			return suiteSetup.renderList();
		});
	}

})();
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { observable } from 'mobx'
import { observer } from 'mobx-react'
import moment from 'moment'
import autobind from 'autobind-decorator'
import CSSModules from 'react-css-modules'
import ReactGA from 'react-ga'
import { IntercomAPI } from 'react-intercom'
import cx from 'classnames'
import Alert from 'react-s-alert'
import Data from 'data'
import ls from 'local-storage'
import styles from './ActionButtons.scss'

// TODO: Optimize this class!!!

@observer
@CSSModules(styles)
export default class ActionButtons extends Component {
  @observable isLiked = false
  @observable isPassed = false
  @observable isSuper = false
  @observable isMatch = false
  @observable counterSuperLike = ''
  @observable counterLike = ''
  @observable superlikeDiffMin = -1
  @observable likeDiffMin = 0

  constructor(props) {
    super(props)

    this.checkLiked(props.user._id)
  }

  componentDidMount() {
    if (this.props.withSuperLikeCounter) {
      this.superLikecount()
      setInterval(() => this.superLikecount(), 500)
    }

    if (this.props.withKeyActions) {
      document.addEventListener('keydown', this.onKeydown)
    }
  }

  componentWillReceiveProps(nextProps) {
    if (!nextProps.user._id) return
    this.checkLiked(nextProps.user._id)
  }

  componentWillUnmount() {
    if (this.props.withKeyActions) {
      document.removeEventListener('keydown', this.onKeydown)
    }
  }

  superLikecount() {
    if (this.superLikeExpiration) {
      this.counterSuperLike = moment.utc(moment(this.superLikeExpiration).diff(moment())).format('HH:mm:ss')
    }

    if (this.likeExpiration) {
      this.counterLike = moment.utc(moment(Number(this.likeExpiration)).diff(moment())).format('HH:mm:ss')
    }
  }

  getSuperLikeDiffInMin() {
    if (this.superLikeExpiration) {
      this.superlikeDiffMin = moment(this.superLikeExpiration).diff(moment(), 'minutes')
    }
  }

  getLikeDiffInMin() {
    this.likeDiffMin = moment.utc(Number(this.likeExpiration)).diff(moment(), 'minutes')
  }

  initExpirationTimes() {
    this.superLikeExpiration = ls.data.superLikeExpiration
    this.likeExpiration = ls.data.likeExpiration

    this.getSuperLikeDiffInMin()
    this.getLikeDiffInMin()
  }

  checkLiked(id) {
    this.initExpirationTimes()

    Data.getActions().where('_id').equals(id).first(r => {
      if (r) {
        this.props.user.done = true
      }

      this.isLiked = (r && r.type === 'like')
      this.isPassed = (r && r.type === 'pass')
      this.isSuper = (r && r.type === 'superlike')
    })
    Data.db().matches.where('userId').equals(id).first(r => {
      if (!r) return

      if (r.isSuperLike === 0) {
        this.isLiked = true
      } else {
        this.isSuper = true
      }

      this.isMatch = true
    })
  }

  showAlert() {
    Alert.success('Match', {
      position: 'top',
      timeout: 2000,
      customFields: {
        userName: this.props.user.name,
        avatar: this.props.user.mainPhoto,
      },
    })
  }

  /**
   * 65 - a
   * 83 - s
   * 68 - d
   */
  @autobind
  onKeydown(e) {
    if (e.keyCode === 65 || e.keyCode === 83 || e.keyCode === 68) {
      e.preventDefault()
    }

    ReactGA.event({
      category: 'User',
      action: 'Keydown',
    })

    switch (e.keyCode) {
      case 65:
        this.handlePass()
        break;
      case 68:
        this.handleLike()
        break;
      case 83:
        this.handleSuperlike()
        break;
      default:
    }
  }

  @autobind
  handleLike() {
    if (this.isLiked || this.likeDiffMin > 0) {
      return
    }

    ReactGA.event({
      category: 'User',
      action: 'Like',
      label: this.props.user._id,
    })

    this.isLiked = true
    this.props.user.like().then(resp => {
      IntercomAPI('trackEvent', 'like', {
        user_id: this.props.user._id,
      })

      if (resp.match) {
        this.showAlert()

        this.isMatch = true

        ReactGA.event({
          category: 'User',
          action: 'Like',
          label: 'Match',
          nonInteraction: true,
        })

        IntercomAPI('trackEvent', 'match');
      }
    }).catch(() => {
      this.initExpirationTimes()

      this.isLiked = false
    })
  }

  @autobind
  handlePass() {
    if (this.isPassed) {
      return
    }

    ReactGA.event({
      category: 'User',
      action: 'Pass',
      label: this.props.user._id,
    })
    IntercomAPI('trackEvent', 'pass');

    this.isPassed = true
    this.props.user.pass()
  }

  @autobind
  handleSuperlike() {
    if (this.isSuper || this.superlikeDiffMin > 0) {
      return
    }

    const { userStore } = this.props

    ReactGA.event({
      category: 'User',
      action: 'Superlike',
      label: this.props.user._id,
    })

    this.isSuper = true

    this.props.user.superLike().then(resp => {
      IntercomAPI('trackEvent', 'superlike', {
        user_id: this.props.user._id,
      })

      userStore.setSuperlikesRemaining(resp.super_likes.remaining)

      if (resp.super_likes.remaining === 0) {
        ls.set({ superLikeExpiration: resp.super_likes.resets_at })
        this.initExpirationTimes()
      }

      if (resp.match) {
        this.showAlert()

        this.isMatch = true

        ReactGA.event({
          category: 'User',
          action: 'Superlike',
          label: 'Match',
          nonInteraction: true,
        })

        IntercomAPI('trackEvent', 'superlike-match');
      }
    }).catch(() => {
      this.initExpirationTimes()

      this.isSuper = false
    })
  }

  render() {
    const { userStore } = this.props
    const matchText = this.isMatch ? 'Match!' : null

    const passedClass = cx({ done: this.isPassed })
    const superClassN = cx({
      done: this.isSuper, disabled: this.superlikeDiffMin > 0 && !this.isSuper,
    })
    const likedClass = cx({ done: this.isLiked, disabled: this.likeDiffMin > 0 && !this.isLiked })

    return (

        {(this.isPassed || (!this.isSuper && !this.isLiked)) &&

}
        {(this.isSuper || (!this.isPassed && !this.isLiked)) &&

            {matchText}
            {this.superlikeDiffMin < 0 && !this.isSuper &&
              {userStore.superLikeRemaining}}
            {this.superlikeDiffMin > 0 && !this.isSuper &&
              {this.counterSuperLike}}

}
        {(this.isLiked || (!this.isPassed && !this.isSuper)) &&

          {matchText}
          {this.likeDiffMin > 0 && !this.isLiked && {this.counterLike}}

}

    );
  }
}
<commit_msg>Fix for match text on buttons<commit_after>import React, { Component } from 'react';
import { observable } from 'mobx'
import { observer } from 'mobx-react'
import moment from 'moment'
import autobind from 'autobind-decorator'
import CSSModules from 'react-css-modules'
import ReactGA from 'react-ga'
import { IntercomAPI } from 'react-intercom'
import cx from 'classnames'
import Alert from 'react-s-alert'
import Data from 'data'
import ls from 'local-storage'
import styles from './ActionButtons.scss'

// TODO: Optimize this class!!!

@observer
@CSSModules(styles)
export default class ActionButtons extends Component {
  @observable isLiked = false
  @observable isPassed = false
  @observable isSuper = false
  @observable isMatch = false
  @observable counterSuperLike = ''
  @observable counterLike = ''
  @observable superlikeDiffMin = -1
  @observable likeDiffMin = 0

  constructor(props) {
    super(props)

    this.checkLiked(props.user._id)
  }

  componentDidMount() {
    if (this.props.withSuperLikeCounter) {
      this.superLikecount()
      setInterval(() => this.superLikecount(), 500)
    }

    if (this.props.withKeyActions) {
      document.addEventListener('keydown', this.onKeydown)
    }
  }

  componentWillReceiveProps(nextProps) {
    if (!nextProps.user._id) return
    this.checkLiked(nextProps.user._id)
  }

  componentWillUnmount() {
    if (this.props.withKeyActions) {
      document.removeEventListener('keydown', this.onKeydown)
    }
  }

  superLikecount() {
    if (this.superLikeExpiration) {
      this.counterSuperLike = moment.utc(moment(this.superLikeExpiration).diff(moment())).format('HH:mm:ss')
    }

    if (this.likeExpiration) {
      this.counterLike = moment.utc(moment(Number(this.likeExpiration)).diff(moment())).format('HH:mm:ss')
    }
  }

  getSuperLikeDiffInMin() {
    if (this.superLikeExpiration) {
      this.superlikeDiffMin = moment(this.superLikeExpiration).diff(moment(), 'minutes')
    }
  }

  getLikeDiffInMin() {
    this.likeDiffMin = moment.utc(Number(this.likeExpiration)).diff(moment(), 'minutes')
  }

  initExpirationTimes() {
    this.superLikeExpiration = ls.data.superLikeExpiration
    this.likeExpiration = ls.data.likeExpiration

    this.getSuperLikeDiffInMin()
    this.getLikeDiffInMin()
  }

  checkLiked(id) {
    this.initExpirationTimes()

    Data.getActions().where('_id').equals(id).first(r => {
      if (r) {
        this.props.user.done = true
      }

      this.isLiked = (r && r.type === 'like')
      this.isPassed = (r && r.type === 'pass')
      this.isSuper = (r && r.type === 'superlike')
    })

    Data.db().matches.where('userId').equals(id).first(r => {
      if (!r) {
        this.isLiked = false
        this.isSuper = false
        this.isMatch = false
        return
      }

      if (r.isSuperLike === 0) {
        this.isLiked = true
      } else {
        this.isSuper = true
      }

      this.isMatch = true
    })
  }

  showAlert() {
    Alert.success('Match', {
      position: 'top',
      timeout: 2000,
      customFields: {
        userName: this.props.user.name,
        avatar: this.props.user.mainPhoto,
      },
    })
  }

  /**
   * 65 - a
   * 83 - s
   * 68 - d
   */
  @autobind
  onKeydown(e) {
    if (e.keyCode === 65 || e.keyCode === 83 || e.keyCode === 68) {
      e.preventDefault()
    }

    ReactGA.event({
      category: 'User',
      action: 'Keydown',
    })

    switch (e.keyCode) {
      case 65:
        this.handlePass()
        break;
      case 68:
        this.handleLike()
        break;
      case 83:
        this.handleSuperlike()
        break;
      default:
    }
  }

  @autobind
  handleLike() {
    if (this.isLiked || this.likeDiffMin > 0) {
      return
    }

    ReactGA.event({
      category: 'User',
      action: 'Like',
      label: this.props.user._id,
    })

    this.isLiked = true
    this.props.user.like().then(resp => {
      IntercomAPI('trackEvent', 'like', {
        user_id: this.props.user._id,
      })

      if (resp.match) {
        this.showAlert()

        this.isMatch = true

        ReactGA.event({
          category: 'User',
          action: 'Like',
          label: 'Match',
          nonInteraction: true,
        })

        IntercomAPI('trackEvent', 'match');
      }
    }).catch(() => {
      this.initExpirationTimes()

      this.isLiked = false
    })
  }

  @autobind
  handlePass() {
    if (this.isPassed) {
      return
    }

    ReactGA.event({
      category: 'User',
      action: 'Pass',
      label: this.props.user._id,
    })
    IntercomAPI('trackEvent', 'pass');

    this.isPassed = true
    this.props.user.pass()
  }

  @autobind
  handleSuperlike() {
    if (this.isSuper || this.superlikeDiffMin > 0) {
      return
    }

    const { userStore } = this.props

    ReactGA.event({
      category: 'User',
      action: 'Superlike',
      label: this.props.user._id,
    })

    this.isSuper = true

    this.props.user.superLike().then(resp => {
      IntercomAPI('trackEvent', 'superlike', {
        user_id: this.props.user._id,
      })

      userStore.setSuperlikesRemaining(resp.super_likes.remaining)

      if (resp.super_likes.remaining === 0) {
        ls.set({ superLikeExpiration: resp.super_likes.resets_at })
        this.initExpirationTimes()
      }

      if (resp.match) {
        this.showAlert()

        this.isMatch = true

        ReactGA.event({
          category: 'User',
          action: 'Superlike',
          label: 'Match',
          nonInteraction: true,
        })

        IntercomAPI('trackEvent', 'superlike-match');
      }
    }).catch(() => {
      this.initExpirationTimes()

      this.isSuper = false
    })
  }

  render() {
    const { userStore } = this.props
    const matchText = this.isMatch ? 'Match!' : null

    const passedClass = cx({ done: this.isPassed })
    const superClassN = cx({
      done: this.isSuper, disabled: this.superlikeDiffMin > 0 && !this.isSuper,
    })
    const likedClass = cx({ done: this.isLiked, disabled: this.likeDiffMin > 0 && !this.isLiked })

    return (

        {(this.isPassed || (!this.isSuper && !this.isLiked)) &&

}
        {(this.isSuper || (!this.isPassed && !this.isLiked)) &&

            {matchText}
            {this.superlikeDiffMin < 0 && !this.isSuper &&
              {userStore.superLikeRemaining}}
            {this.superlikeDiffMin > 0 && !this.isSuper &&
              {this.counterSuperLike}}

}
        {(this.isLiked || (!this.isPassed && !this.isSuper)) &&

          {matchText}
          {this.likeDiffMin > 0 && !this.isLiked && {this.counterLike}}

}

    );
  }
}
<|endoftext|>


<commit_before>import _ from 'lodash';
import JoiBase from 'joi';
import JoiPostcodeExtension from 'joi-postcode';
import {postHandlerFactory, getHandlerFactory} from './common';

const Joi = JoiBase.extend(JoiPostcodeExtension);

const schema = Joi.object().keys({
  'address1': Joi.string().allow('').max(50).label('Address').meta({ componentType: 'textbox' }),
  'address2': Joi.string().allow('').max(50).meta({ componentType: 'textbox' }),
  'address3': Joi.string().allow('').max(50).meta({ componentType: 'textbox' }),
  'locality': Joi.string().allow('').max(100).required().label('Town or City').meta({ componentType: 'textbox' }),
  'postcode': Joi.postcode().uppercase().options({
    language: {
      string: {
        regex: { base: 'must be a valid UK postcode' },
      },
    },
  }).label('Post Code').meta({ componentType: 'textbox', variant: 'short' }),
  'submit': Joi.any().optional().strip(),
})
  .or('address1', 'address2', 'address3');

const title = 'What is your address?';
const key = 'address';
const slug = 'home-address-manual';

const handlers = {
  GET: (prevSteps) => getHandlerFactory(key, title, schema, prevSteps),
  POST: (prevSteps, nextSteps) => postHandlerFactory(key, title, schema, prevSteps, nextSteps),
};

const checkApplies = function(cookieData) {
  const hasPostCode = _.get(cookieData, 'addressLookup.postcode', false);
  const addressChooseIsEmpty = _.get(cookieData, 'addressLookupChoose.address') === undefined;
  return hasPostCode && addressChooseIsEmpty;
};

/**
 * @type Step
 */
export default {
  key,
  slug,
  title,
  schema,
  handlers,
  checkApplies
};
<commit_msg>Content fix to home-address<commit_after>import _ from 'lodash';
import JoiBase from 'joi';
import JoiPostcodeExtension from 'joi-postcode';
import {postHandlerFactory, getHandlerFactory} from './common';

const Joi = JoiBase.extend(JoiPostcodeExtension);

const schema = Joi.object().keys({
  'address1': Joi.string().allow('').max(50).label('Address').meta({ componentType: 'textbox' }),
  'address2': Joi.string().allow('').max(50).meta({ componentType: 'textbox' }),
  'address3': Joi.string().allow('').max(50).meta({ componentType: 'textbox' }),
  'locality': Joi.string().allow('').max(100).required().label('Town or City').meta({ componentType: 'textbox' }),
  'postcode': Joi.postcode().uppercase().options({
    language: {
      string: {
        regex: { base: 'must be a valid UK postcode' },
      },
    },
  }).label('Postcode').meta({ componentType: 'textbox', variant: 'short' }),
  'submit': Joi.any().optional().strip(),
})
  .or('address1', 'address2', 'address3');

const title = 'What is your address?';
const key = 'address';
const slug = 'home-address-manual';

const handlers = {
  GET: (prevSteps) => getHandlerFactory(key, title, schema, prevSteps),
  POST: (prevSteps, nextSteps) => postHandlerFactory(key, title, schema, prevSteps, nextSteps),
};

const checkApplies = function(cookieData) {
  const hasPostCode = _.get(cookieData, 'addressLookup.postcode', false);
  const addressChooseIsEmpty = _.get(cookieData, 'addressLookupChoose.address') === undefined;
  return hasPostCode && addressChooseIsEmpty;
};

/**
 * @type Step
 */
export default {
  key,
  slug,
  title,
  schema,
  handlers,
  checkApplies
};
<|endoftext|>


<commit_before>import Q from 'q';
import {
  sendRequest,
  validateDate,
} from './util';

export default function neo(): object {
  return {

    feed(options: object = {}): undefined {
      const deferred = Q.defer();
      if (!options.start_date) deferred.reject(new Error('start_date is required'));
      if (!validateDate(options.start_date)) {
        deferred.reject(new Error('start_date must be in "YYYY-MM-DD" format'));
      }
      if (options.end_date && !validateDate(options.start_date)) {
        deferred.reject(new Error('end_date must be in "YYYY-MM-DD" format'));
      }
      sendRequest('https://api.nasa.gov/neo/rest/v1/feed',
        options,
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    feedToday(options: object = {}): undefined {
      const deferred = Q.defer();
      sendRequest('https://api.nasa.gov/neo/rest/v1/feed/today',
        options,
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    fetch(asteroidId: string): undefined {
      const deferred = Q.defer();
      if (!asteroidId) deferred.reject(new Error('asteroidId is required'));
      sendRequest(`https://api.nasa.gov/neo/rest/v1/neo/${asteroidId}`,
        {},
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    browse(options: object = {}): undefined {
      const deferred = Q.defer();
      sendRequest('https://api.nasa.gov/neo/rest/v1/neo/browse',
        options,
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    stats(): undefined {
      const deferred = Q.defer();
      sendRequest('https://api.nasa.gov/neo/rest/v1/stats',
        {},
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

  };
}
<commit_msg>remove required start_date from NEO.feed<commit_after>import Q from 'q';
import {
  sendRequest,
  validateDate,
} from './util';

export default function neo(): object {
  return {

    feed(options: object = {}): undefined {
      const deferred = Q.defer();
      if (!validateDate(options.start_date)) {
        deferred.reject(new Error('start_date must be in "YYYY-MM-DD" format'));
      }
      if (options.end_date && !validateDate(options.start_date)) {
        deferred.reject(new Error('end_date must be in "YYYY-MM-DD" format'));
      }
      sendRequest('https://api.nasa.gov/neo/rest/v1/feed',
        options,
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    feedToday(options: object = {}): undefined {
      const deferred = Q.defer();
      sendRequest('https://api.nasa.gov/neo/rest/v1/feed/today',
        options,
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    fetch(asteroidId: string): undefined {
      const deferred = Q.defer();
      if (!asteroidId) deferred.reject(new Error('asteroidId is required'));
      sendRequest(`https://api.nasa.gov/neo/rest/v1/neo/${asteroidId}`,
        {},
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    browse(options: object = {}): undefined {
      const deferred = Q.defer();
      sendRequest('https://api.nasa.gov/neo/rest/v1/neo/browse',
        options,
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

    stats(): undefined {
      const deferred = Q.defer();
      sendRequest('https://api.nasa.gov/neo/rest/v1/stats',
        {},
        (err: string, data: object): undefined => {
          if (err) return deferred.reject(err);
          return deferred.resolve(data);
        }
      );
      return deferred.promise;
    },

  };
}
<|endoftext|>


<commit_before>import { FEN } from './fen';
import { Game } from './game';
import { ChessError } from './error';
import { partition, last } from './util';

export const PGN = {

	parse(pgnStr) {
		// transform the PGN into meaningful tokens:
		const tokens = tokenizePGN(
			// strip out different whitespace chars.
			pgnStr.split(/[\n\r\r\t]+/g).join(' '));

		const game = new Game();

		try {
			tokens.forEach(({ mode, source }) => {
				switch (mode) {
				case MODE_TAG:
					game.addTag(...parseTag(source));
					break;
				case MODE_PLY_NOTATION:
					game.move(source);
					break;
				case MODE_ANNOTATION_NOTATION:
					game.annotate(source);
					break;
				case MODE_RESULT:
					game.finish(source);
					break;
				}
			});
		} catch (err) {
			if (err instanceof ChessError) {
				err.lastPosition = FEN.stringify(last(game.ply).position);
			}
			throw err;
		}

		return game;
	},

	stringify(game) {

	},

};

function parseTag(line) {
	const [key, value] = line.split(/\s+/);
	return [key, cleanValue(value)];
}

function cleanValue(value) {
	return value.replace(/^[\"\']|[\"\']$/g, '');
}

const MODE_TAG = 'tag';
const MODE_RESULT = 'result';
const MODE_MOVE_NUMBER_NOTATION = 'move number';
const MODE_PLY_NOTATION = 'ply';
const MODE_ANNOTATION_NOTATION = 'annotation';

function tokenizePGN(transcript) {
	const tokens = [];
	var mode = null;
	var lastMode = null;
	var buffer = [];
	var halfMove = false;
	var skipping = 0;

	transcript.split('').forEach((char, i) => {
		if (i < skipping) {
			return;
		}
		switch (char) {

		case '[':
		case '{':
			skip(1);
			lastMode = mode;
			finishToken();
			mode = char == '[' ? MODE_TAG : MODE_ANNOTATION_NOTATION;
			break;

		case '}':
		case ']':
			finishToken();
			mode = lastMode;
			lastMode = null;
			return;

		case ' ':
			if (mode === MODE_PLY_NOTATION) {
				if (buffer.length == 0) {
					return;
				}
				finishToken();
				halfMove = !halfMove;
				if (halfMove) {
					mode = MODE_PLY_NOTATION;
				}
				return;
			}
			if (mode === MODE_MOVE_NUMBER_NOTATION) {
				finishToken();
				mode = MODE_PLY_NOTATION;
				return;
			}
			break;

		default:
			if (!/\d/.test(char)) {
				break;
			}
			if (mode === null || mode === MODE_PLY_NOTATION) {
				var p3 = peek(3), p7 = peek(7);
				if (p3 === '1-0' || p3 === '0-1') {
					result(p3);
					skip(3);
					return;
				}
				if (p7 === '1/2-1/2') {
					result(p7);
					skip(7);
					return;
				}
			}
			if (mode === null) {
				mode = MODE_MOVE_NUMBER_NOTATION;
			}
		}
		if (mode !== null && skipping < i) {
			buffer.push(char);
		}

		function skip(n) {
			skipping = i + n - 1;
		}

		function peek(n=1) {
			var str = '';
			for (var j = 0; j < n; j++) {
				str += transcript[i + j];
			}
			return str;
		}
	});

	return tokens;

	function result(source) {
		tokens.push({ mode: MODE_RESULT, source });
		mode = null;
		buffer = [];
	}

	function finishToken() {
		if (mode == null) {
			return;
		}
		if (buffer.length > 0) {
			const source = buffer.join('');
			if (/^\s+$/.test(source)) {
				return;
			}
			tokens.push({ mode, source });
		}
		mode = null;
		buffer = [];
	}
}
<commit_msg>pgn: be more liberal about parsing moves<commit_after>import { FEN } from './fen';
import { Game } from './game';
import { ChessError } from './error';
import { partition, last } from './util';

export const PGN = {

	parse(pgnStr) {
		// transform the PGN into meaningful tokens:
		const tokens = tokenizePGN(
			// strip out different whitespace chars.
			pgnStr.split(/[\n\r\r\t]+/g).join(' '));

		const game = new Game();

		try {
			tokens.forEach(({ mode, source }) => {
				switch (mode) {
				case MODE_TAG:
					game.addTag(...parseTag(source));
					break;
				case MODE_PLY_NOTATION:
					game.move(source);
					break;
				case MODE_ANNOTATION_NOTATION:
					game.annotate(source);
					break;
				case MODE_RESULT:
					game.finish(source);
					break;
				}
			});
		} catch (err) {
			if (err instanceof ChessError) {
				err.lastPosition = FEN.stringify(last(game.ply).position);
			}
			throw err;
		}

		return game;
	},

	stringify(game) {

	},

};

function parseTag(line) {
	const [key, value] = line.split(/\s+/);
	return [key, cleanValue(value)];
}

function cleanValue(value) {
	return value.replace(/^[\"\']|[\"\']$/g, '');
}

const MODE_TAG = 'tag';
const MODE_RESULT = 'result';
const MODE_MOVE_NUMBER_NOTATION = 'move number';
const MODE_PLY_NOTATION = 'ply';
const MODE_ANNOTATION_NOTATION = 'annotation';

function tokenizePGN(transcript) {
	const tokens = [];
	var mode = null;
	var lastMode = null;
	var buffer = [];
	var halfMove = false;
	var skipping = 0;

	transcript.split('').forEach((char, i) => {
		if (i < skipping) {
			return;
		}
		switch (char) {

		case '[':
		case '{':
			skip(1);
			lastMode = mode;
			finishToken();
			mode = char == '[' ? MODE_TAG : MODE_ANNOTATION_NOTATION;
			break;

		case '}':
		case ']':
			finishToken();
			mode = lastMode;
			lastMode = null;
			return;

		case ' ':
		case '.':
			if (mode === MODE_PLY_NOTATION) {
				if (buffer.length == 0) {
					return;
				}
				finishToken();
				halfMove = !halfMove;
				if (halfMove) {
					mode = MODE_PLY_NOTATION;
				}
				return;
			}
			if (mode === MODE_MOVE_NUMBER_NOTATION) {
				if ('.' === char) {
					buffer.push(char);
				}
				finishToken();
				mode = MODE_PLY_NOTATION;
				return;
			}
			break;

		default:
			if (!/\d/.test(char)) {
				break;
			}
			if (mode === null || mode === MODE_PLY_NOTATION) {
				var p3 = peek(3), p7 = peek(7);
				if (p3 === '1-0' || p3 === '0-1') {
					result(p3);
					skip(3);
					return;
				}
				if (p7 === '1/2-1/2') {
					result(p7);
					skip(7);
					return;
				}
			}
			if (mode === null) {
				mode = MODE_MOVE_NUMBER_NOTATION;
			}
		}
		if (mode !== null && skipping < i) {
			buffer.push(char);
		}

		function skip(n) {
			skipping = i + n - 1;
		}

		function peek(n=1) {
			var str = '';
			for (var j = 0; j < n; j++) {
				str += transcript[i + j];
			}
			return str;
		}
	});

	return tokens;

	function result(source) {
		tokens.push({ mode: MODE_RESULT, source });
		mode = null;
		buffer = [];
	}

	function finishToken() {
		if (mode == null) {
			return;
		}
		if (buffer.length > 0) {
			const source = buffer.join('');
			if (/^\s+$/.test(source)) {
				return;
			}
			tokens.push({ mode, source });
		}
		mode = null;
		buffer = [];
	}
}
<|endoftext|>


<commit_before>module.exports = {
    article: {
        Article: 'articles',
        ArticleApproval: 'article-approvals',
        ArticleType: 'article-types',
        ArticleOrigin: 'article-origins',
        ArticleCategory: 'article-categories',
        ArticleSubCategory: 'article-sub-categories',
        ArticleStyle: 'article-styles',
        ArticleSeason: 'article-seasons',
        ArticleMotif: 'article-motifs',
    },
    inventory: {
        Storage: 'storages',
        StockIn: 'stock-ins',
        StockOut: 'stock-outs',
        Inventory: 'inventories',
        InventoryMovement: 'inventory-movements'
    },
    manufacture: {
        Finishing: 'finishings'
    }
}<commit_msg>1.0.0<commit_after>module.exports = {
    article: {
        Article: 'articles',
        ArticleApproval: 'article-approvals',
        ArticleType: 'article-types',
        ArticleOrigin: 'article-origins',
        ArticleCategory: 'article-categories',
        ArticleSubCategory: 'article-sub-categories',
        ArticleStyle: 'article-styles',
        ArticleSeason: 'article-seasons',
        ArticleMotif: 'article-motifs',
        ArticleVariant: 'article-variants',
    },
    inventory: {
        Storage: 'storages',
        StockIn: 'stock-ins',
        StockOut: 'stock-outs',
        Inventory: 'inventories',
        InventoryMovement: 'inventory-movements'
    },
    manufacture: {
        Finishing: 'finishings'
    }
}<|endoftext|>


<commit_before>;(function (context) {

    /*
     * Injection of third-party modules
     * Wrapping up imports under `utils` to keep them in this project. JSON2 is
     * an exception but will only augment globally if it is missing.
     * Global polyfills: JSON2, Object.keys, Array.forEach
     * Helper libraries: cookieStorage, localStorage
     * Bower libraries: Fingerprint, History
     */
    var utils = {};
    (function () {
        /* inject */
        /* endinject */
    }).call(utils);

    // Don't hard code anything
    var queryParamName = 'DA';
    var scriptTagId = 'advocate-things-script';
    var storageName = 'advocate-things';

    var events = {
        TouchpointSaved: 'TouchpointSaved',
        SharepointSaved: 'SharepointSaved',
        ReferredPerson: 'ReferredPerson'
    };

    var points = {
        Sharepoint: {
            name: 'Sharepoint',
            url: 'https://sharepoint-data-collector.herokuapp.com/campaign/token/sharepoint/data'
        },
        Touchpoint: {
            name: 'Touchpoint',
            url: 'https://touchpoint-data-collector.herokuapp.com/campaign/token/touchpoint/data'
        },
        Unknown: {
            name: 'Unknown'
        }
    };

    // Variables initialised by init()
    var clientToken = null;
    var listeners = {};
    var store = null;
    var currentReferredPerson = null; // TODO: implement this!
    var currentSharepointToken = null;

    // PRIVATE FUNCTIONS

    /**
     * Get the client token (API key) for the current page. Sets the clientToken
     * global.
     * @returns {boolean} - true if key exists and is saved, else false.
     */
    function getClientToken() {
        console.info('getClientToken()');
        var scriptElement = document.getElementById(scriptTagId);

        if (!scriptElement) {
            console.warn('No ' + scriptTagId + ' element.');
            return false;
        }

        var scriptUrl = scriptElement.src;
        if (scriptUrl.indexOf('?key=') !== -1) {
            clientToken = scriptUrl.split('?').pop().split('=').pop();
            return true;
        }

        console.warn('No client token defined in ' + scriptTagId + ' element.');
        return false;
    }

    /**
     * Gets an array of strings containing any sharepoint tokens for this client
     * and browser combination in localStorage/cookies.
     * @returns {array} - Array of strings of sharepoint tokens from storage.
     */
    function getSharepointTokens() {
        var tokens = [];

        if (!store.hasItem(storageName)) {
            return [];
        }

        var storeData = JSON.parse(store.getItem(storageName));
        if (!storeData[clientToken]) {
            return [];
        }

        storeData[clientToken].forEach(function (entry) {
            tokens.push(entry.token);
        });

        return tokens;
    }

    /**
     * Initialises the event listener array with empty arrays to contain
     * any event listener functions.
     */
    function initEventListeners() {
        console.info('initEventListeners()');
        Object.keys(events).forEach(function (evt) {
            listeners[events[evt]] = [];
        });
    }

    /**
     * Initialises storage as cookie storage, then, if available, is replaced by
     * local storage.
     */
    function initStorage() {
        console.info('initStorage()');
        store = utils.cookieStorage;
        if (typeof localStorage === 'object') {
            var test = 'test';
            try {
                utils.lclStorage.setItem(test, test);
                utils.lclStorage.removeItem(test);
                store = utils.lclStorage;
            } catch (e) {
                console.warn(e);
            }
        }
    }

    /**
     * Stores touchpoint data in the available storage, keyed under the current
     * client token.
     * TODO: Handle scenario when storage fails (try/catch) on e.g. reaching
     *       storage size limit. Consider what to do in this case (purge old?).
     * TODO: Consider using a new storage item per client token to make storage
     *       cleaner. Would however make traversal etc. more complex.
     * @param {object} d - A touchpoint data object with keys of token
     *                     (sharepoint) and metadata.
     */
    function storeTouchpointData(d) {
        console.info('storeTouchpointData()');

        if (!d.token) {
            return;
        }

        if (!store.hasItem(storageName)) {
            store.setItem(storageName, JSON.stringify({}), Infinity);
        }

        var currentStoreData = JSON.parse(store.getItem(storageName));

        if (!currentStoreData[clientToken]) {
            currentStoreData[clientToken] = [];
        }

        var duplicateData = false;
        for (var i=0, len=currentStoreData[clientToken].length; i<len; i++) {
            if (currentStoreData[clientToken][i].token === d.token) {
                duplicateData = true;
                break;
            }
        }

        if (!duplicateData) {
            currentStoreData[clientToken].push(d);
            store.setItem(storageName, JSON.stringify(currentStoreData), Infinity);
        }
    }

    /**
     * Duplicates any passed data object (bad manners to manipulate other
     * people's variables. Adds useful metadata to _at data object.
     * @param {object} data - Full data object containing _at and other keys.
     * @returns {object} d - Duplicated and augmented equivalent of `data`.
     */
    function tidyDataObject(data) {
        console.info('tidyDataObject()');
        console.log(data);

        var d = JSON.parse(JSON.stringify(data || {}));
        if (!('_at' in d)) {
            d._at = {};
        }

        d._at.clientToken = clientToken;
        d._at.fingerprint = new utils.Fingerprint().get().toString();
        d._at.url = document.location.href;

        d._client = {};
        Object.keys(d).forEach(function (key) {
            if (!(key === '_at' || key === '_client')) {
                d._client[key] = d[key];
                delete d[key];
            }
        });

        return d;
    }

    /**
     * Generic function to call all event listeners for a given event type.
     * @param {string} eventName - Name of the event being triggered. See
     *                             AT.Events.
     * @param {object} data - Data to call event with - allows contextual
     *                        reactions to events.
     */
    function triggerEvent(eventName, data) {
        console.info('triggerEvent(' + eventName + ')');
        listeners[eventName].forEach(function (listenerFunc) {
            listenerFunc.call(data, data);
        });
    }

    /**
     * Appends the current sharepoint token to the URL as a query parameter.
     * This solves the case of sharing by copy/paste of a URL.
     * TODO: Clean up multiple occurences of DA= query params.
     * @param {string} token - The sharepoint token to insert into the URL.
     */
    function urlAppendToken(token) {
        console.info('urlAppendToken()');
        if (!token) {
            return;
        }

        var url = window.location.href;

        var re = new RegExp('([?&]' + queryParamName + '=)([^&#]*)', 'g');

        var hash = '';
        var split = ''; // temp var for obtaining both sides of split.
        if (url.indexOf('#') !== -1) {
            split = url.split('#');
            hash = split[1];
            url = split[0];
        }

        var params = '';
        if (url.indexOf('?') !== -1) {
            split = url.split('?');
            params = '?' + split[1];
            if (params[params.length - 1] === '/') {
                params = params.slice(0, -1);
            }
        }

        if (params.match(re)) {
            // Replace
            params = params.replace(re, '$1' + token);
        } else {
            // Append
            var separator = '?';
            if (params) {
                separator = '&';
            }
            params += separator + queryParamName + '=' + token;
        }

        // We can rebuild it, we have the technology
        /* jshint ignore:start */
        var newParams = (hash)
                      ? params + '#' + hash
                      : params;
        /* jshint ignore:end */

        // Rewrite the URL.
        History.replaceState(null, null, newParams);
    }

    // PUBLIC FUNCTIONS

    /**
     * Initialise the SDK, called automatically on script load. Automatically
     * sends a touchpoint or sharepoint (if specified, else both) on script
     * load using data in `window.advocate_things_data`.
     */
    function init() {
        console.info('init()');

        if (!getClientToken()) {
            return false;
        }

        initStorage();
        initEventListeners();

        var d = tidyDataObject(window.advocate_things_data);
        if (d && d._at) {
            if (d._at.sharepointName) {
                // sharepoint
                sendSharepointInit(d);
            } else if (d._at.touchpointName) {
                // touchpoint
                sendTouchpointInit(d);
            } else {
                // both
                sendTouchpointInit((d), function () {
                    d = tidyDataObject(window.advocate_things_data);
                    sendSharepointInit(d);
                });
            }
        }
    }
    init();

    /**
     * Allow developers to respond to certain 'events'. Adds passed listener
     * functions to the corresponding event name key. Leads to an array of
     * functions (or []) for any event that will be called in order on that
     * event firing.
     * @param {string} type - Type of event to hook into (see AT.Events).
     * @param {function} listener - A function describing what should happen
     *                              when an event of {type} is triggered.
     */
    function addEventListener(type, listener) {
        if (!clientToken) {
            return;
        }
        console.info('addEventListener()');
        listeners[type].push(listener);
    }

    /**
     * Generic send function for sending of touchpoints and/or sharepoints.
     * Allows users to add the *point name directly to the _at object and call
     * send with the data object. If no such parameter is included, send tries
     * to send the data as a touchpoint and a sharepoint.
     * @param {object} data - Parsed JSON data object containing _at and
     *                        _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function send(data, cb) {
        if (!clientToken) {
            return;
        }

        var d = tidyDataObject(data);
        if (d && d._at) {
            if (d._at.sharepointName) {
                // sharepoint
                sendSharepoint(d, cb);
            } else if (d._at.touchpointName) {
                // touchpoint
                sendTouchpoint(d, cb);
            } else {
                // both
                sendTouchpoint(d, function () {
                    sendSharepoint(d, cb);
                });
            }
        }
    }

    /**
     * Send a sharepoint from init(). This differs to normal sharepoint sending
     * as the URL is always given a query parameter from the response.
     * @param {object} d - Parsed JSON data object containing _at and _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendSharepointInit(d, cb) {
        console.info('sendSharepointInit()');
        var xhr = new XMLHttpRequest();
        var async = true;

        var ds = JSON.stringify(d);

        xhr.onload = function () {
            if (/^20[0-9]{1}/.test(xhr.status)) {
                var res = JSON.parse(xhr.responseText);

                currentSharepointToken = res[0].token;

                // Trigger event
                triggerEvent(events.SharepointSaved, res);

                // Append token to URL (blindly in this instance)
                urlAppendToken(currentSharepointToken);

                // Callback
                if (cb) {
                    cb(null, res);
                }
            } else {
                if (cb) {
                    cb(xhr.statusText);
                }
            }
        };

        xhr.open('POST', points.Sharepoint.url, async);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.send(ds);
    }

    /**
     * Send a touchpoint from init(). This is used as part of the standard
     * sendTouchpoint() call (differs to sharepoints in this respect).
     * @param {object} d - Parsed JSON data object containing _at and _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendTouchpointInit(d, cb) {
        console.info('sendTouchpointInit()');
        var xhr = new XMLHttpRequest();
        var async = true;

        d._at.shareTokens = getSharepointTokens();
        var ds = JSON.stringify(d);

        xhr.onload = function () {
            if (/^20[0-9]{1}/.test(xhr.status)) {
                var res = JSON.parse(xhr.responseText);

                storeTouchpointData(res);
                var meta = JSON.parse(res.metadata);

                // Trigger events
                triggerEvent(events.TouchpointSaved, meta);

                if (res.token && res.token !== "") {
                    // TODO: consider triggering this event downstream as well
                    triggerEvent(events.ReferredPerson, meta);
                }

                // Callback
                if (cb) {
                    cb(null, meta);
                }
            } else {
                if (cb) {
                    cb(xhr.statusText);
                }
            }
        };

        xhr.open('POST', points.Touchpoint.url, async);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.send(ds);
    }

    /**
     * Manually send a touchpoint with the given name. The name is added to the
     * _at object before sending using exactly the same call as used in init().
     * @param {string} name - Name of the triggered touchpoint.
     * @param {object} data - Parsed JSON data object containing _at and
     *                        _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendTouchpoint(name, data, cb) {
        if (!clientToken) {
            return;
        }
        console.info('sendTouchpoint()');
        var d = tidyDataObject(data);
        if (name) {
            d._at.touchpointName = name;
        }

        // Passthrough for touchpoints :)
        sendTouchpointInit(d, cb);
    }

    /**
     * Manually send a sharepoint with the given name. The name is added to the
     * _at object before sending. Sending sharepoints outside of init() use a
     * different call as the URL token is only updated when the token recevied
     * back from the XHR is the same as the current one. When this occurs, a new
     * token is requested.
     * @param {string} name - Name of the triggered touchpoint.
     * @param {object} data - Parsed JSON data object containing _at and
     *                        _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendSharepoint(name, data, cb) {
        if (!clientToken) {
            return;
        }
        console.info('sendSharepoint()');
        var d = tidyDataObject(data);

        if (name) {
            d._at.sharepointName = name;
        }

        var xhr = new XMLHttpRequest();
        var async = true;

        var ds = JSON.stringify(d);

        xhr.onload = function () {
            if (/^20[0-9]{1}/.test(xhr.status)) {
                var res = JSON.parse(xhr.responseText);

                var oldToken = currentSharepointToken;
                currentSharepointToken = res[0].token;

                // Trigger event
                triggerEvent(events.SharepointSaved, res);

                // Append token to URL (conditionally this time)
                if (currentSharepointToken === oldToken) {
                    // Request new token
                    sendSharepointInit(data); // pre-named data object
                } else {
                    urlAppendToken(currentSharepointToken);
                }

                // Callback
                if (cb) {
                    cb(null, res);
                }
            } else {
                if (cb) {
                    cb(xhr.statusText);
                }
            }
        };

        xhr.open('POST', points.Sharepoint.url, async);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.send(ds);
    }

    /**
     * Expose the goodies under AT global variable.
     */
    context.AT = {
        // Public methods
        init: init,
        addEventListener: addEventListener,
        sendSharepoint: sendSharepoint,
        sendTouchpoint: sendTouchpoint,
        send: send,
        // Exposed variables
        Events: events,
        referredPerson: currentReferredPerson,
        sharepointToken: currentSharepointToken
    };
})(this);
<commit_msg>enable custom query param<commit_after>;(function (context) {

    /*
     * Injection of third-party modules
     * Wrapping up imports under `utils` to keep them in this project. JSON2 is
     * an exception but will only augment globally if it is missing.
     * Global polyfills: JSON2, Object.keys, Array.forEach
     * Helper libraries: cookieStorage, localStorage
     * Bower libraries: Cookie, Fingerprint, History
     */
    var utils = {};
    (function () {
        /* inject */
        /* endinject */
    }).call(utils);

    // Don't hard code anything
    var queryParamName = null; // this is now set in the DB
    var scriptTagId = 'advocate-things-script';
    var storageName = 'advocate-things';

    var events = {
        TouchpointSaved: 'TouchpointSaved',
        SharepointSaved: 'SharepointSaved',
        ReferredPerson: 'ReferredPerson'
    };

    var points = {
        Sharepoint: {
            name: 'Sharepoint',
            url: 'https://sharepoint-data-collector.herokuapp.com/campaign/token/sharepoint/data'
        },
        Touchpoint: {
            name: 'Touchpoint',
            url: 'https://touchpoint-data-collector.herokuapp.com/campaign/token/touchpoint/data'
        },
        Unknown: {
            name: 'Unknown'
        }
    };

    // Variables initialised by init()
    var clientToken = null;
    var listeners = {};
    var store = null;
    var currentReferredPerson = null; // TODO: implement this!
    var currentSharepointToken = null;

    // PRIVATE FUNCTIONS

    /**
     * Get the client token (API key) for the current page. Sets the clientToken
     * global.
     * @returns {boolean} - true if key exists and is saved, else false.
     */
    function getClientToken() {
        console.info('getClientToken()');
        var scriptElement = document.getElementById(scriptTagId);

        if (!scriptElement) {
            console.warn('No ' + scriptTagId + ' element.');
            return false;
        }

        var scriptUrl = scriptElement.src;
        if (scriptUrl.indexOf('?key=') !== -1) {
            clientToken = scriptUrl.split('?').pop().split('=').pop();
            return true;
        }

        console.warn('No client token defined in ' + scriptTagId + ' element.');
        return false;
    }

    /**
     * Gets an array of strings containing any sharepoint tokens for this client
     * and browser combination in localStorage/cookies.
     * @returns {array} - Array of strings of sharepoint tokens from storage.
     */
    function getSharepointTokens() {
        var tokens = [];

        if (!store.hasItem(storageName)) {
            return [];
        }

        var storeData = JSON.parse(store.getItem(storageName));
        if (!storeData[clientToken]) {
            return [];
        }

        storeData[clientToken].forEach(function (entry) {
            tokens.push(entry.token);
        });

        return tokens;
    }

    /**
     * Initialises the event listener array with empty arrays to contain
     * any event listener functions.
     */
    function initEventListeners() {
        console.info('initEventListeners()');
        Object.keys(events).forEach(function (evt) {
            listeners[events[evt]] = [];
        });
    }

    /**
     * Initialises storage as cookie storage, then, if available, is replaced by
     * local storage.
     */
    function initStorage() {
        console.info('initStorage()');
        store = utils.cookieStorage;
        if (typeof localStorage === 'object') {
            var test = 'test';
            try {
                utils.lclStorage.setItem(test, test);
                utils.lclStorage.removeItem(test);
                store = utils.lclStorage;
            } catch (e) {
                console.warn(e);
            }
        }
    }

    /**
     * Stores touchpoint data in the available storage, keyed under the current
     * client token.
     * TODO: Handle scenario when storage fails (try/catch) on e.g. reaching
     *       storage size limit. Consider what to do in this case (purge old?).
     * TODO: Consider using a new storage item per client token to make storage
     *       cleaner. Would however make traversal etc. more complex.
     * @param {object} d - A touchpoint data object with keys of token
     *                     (sharepoint) and metadata.
     */
    function storeTouchpointData(d) {
        console.info('storeTouchpointData()');

        if (!d.token) {
            return;
        }

        if (!store.hasItem(storageName)) {
            store.setItem(storageName, JSON.stringify({}), Infinity);
        }

        var currentStoreData = JSON.parse(store.getItem(storageName));

        if (!currentStoreData[clientToken]) {
            currentStoreData[clientToken] = [];
        }

        var duplicateData = false;
        for (var i=0, len=currentStoreData[clientToken].length; i<len; i++) {
            if (currentStoreData[clientToken][i].token === d.token) {
                duplicateData = true;
                break;
            }
        }

        if (!duplicateData) {
            currentStoreData[clientToken].push(d);
            store.setItem(storageName, JSON.stringify(currentStoreData), Infinity);
        }
    }

    /**
     * Duplicates any passed data object (bad manners to manipulate other
     * people's variables. Adds useful metadata to _at data object.
     * @param {object} data - Full data object containing _at and other keys.
     * @returns {object} d - Duplicated and augmented equivalent of `data`.
     */
    function tidyDataObject(data) {
        console.info('tidyDataObject()');
        console.log(data);

        var d = JSON.parse(JSON.stringify(data || {}));
        if (!('_at' in d)) {
            d._at = {};
        }

        d._at.clientToken = clientToken;
        d._at.fingerprint = new utils.Fingerprint().get().toString();
        d._at.url = document.location.href;

        d._client = {};
        Object.keys(d).forEach(function (key) {
            if (!(key === '_at' || key === '_client')) {
                d._client[key] = d[key];
                delete d[key];
            }
        });

        return d;
    }

    /**
     * Generic function to call all event listeners for a given event type.
     * @param {string} eventName - Name of the event being triggered. See
     *                             AT.Events.
     * @param {object} data - Data to call event with - allows contextual
     *                        reactions to events.
     */
    function triggerEvent(eventName, data) {
        console.info('triggerEvent(' + eventName + ')');
        listeners[eventName].forEach(function (listenerFunc) {
            listenerFunc.call(data, data);
        });
    }

    /**
     * Appends the current sharepoint token to the URL as a query parameter.
     * This solves the case of sharing by copy/paste of a URL.
     * TODO: Clean up multiple occurences of DA= query params.
     * @param {string} token - The sharepoint token to insert into the URL.
     */
    function urlAppendToken(token) {
        console.info('urlAppendToken()');
        if (!token) {
            return;
        }

        var url = window.location.href;

        var re = new RegExp('([?&]' + queryParamName + '=)([^&#]*)', 'g');

        var hash = '';
        var split = ''; // temp var for obtaining both sides of split.
        if (url.indexOf('#') !== -1) {
            split = url.split('#');
            hash = split[1];
            url = split[0];
        }

        var params = '';
        if (url.indexOf('?') !== -1) {
            split = url.split('?');
            params = '?' + split[1];
            if (params[params.length - 1] === '/') {
                params = params.slice(0, -1);
            }
        }

        if (params.match(re)) {
            // Replace
            params = params.replace(re, '$1' + token);
        } else {
            // Append
            var separator = '?';
            if (params) {
                separator = '&';
            }
            params += separator + queryParamName + '=' + token;
        }

        // We can rebuild it, we have the technology
        /* jshint ignore:start */
        var newParams = (hash)
                      ? params + '#' + hash
                      : params;
        /* jshint ignore:end */

        // Rewrite the URL.
        History.replaceState(null, null, newParams);
    }

    // PUBLIC FUNCTIONS

    /**
     * Initialise the SDK, called automatically on script load. Automatically
     * sends a touchpoint or sharepoint (if specified, else both) on script
     * load using data in `window.advocate_things_data`.
     */
    function init() {
        console.info('init()');

        if (!getClientToken()) {
            return false;
        }

        initStorage();
        initEventListeners();

        var d = tidyDataObject(window.advocate_things_data);
        if (d && d._at) {
            if (d._at.sharepointName) {
                // sharepoint
                sendSharepointInit(d);
            } else if (d._at.touchpointName) {
                // touchpoint
                sendTouchpointInit(d);
            } else {
                // both
                sendTouchpointInit((d), function () {
                    d = tidyDataObject(window.advocate_things_data);
                    sendSharepointInit(d);
                });
            }
        }
    }
    init();

    /**
     * Allow developers to respond to certain 'events'. Adds passed listener
     * functions to the corresponding event name key. Leads to an array of
     * functions (or []) for any event that will be called in order on that
     * event firing.
     * @param {string} type - Type of event to hook into (see AT.Events).
     * @param {function} listener - A function describing what should happen
     *                              when an event of {type} is triggered.
     */
    function addEventListener(type, listener) {
        if (!clientToken) {
            return;
        }
        console.info('addEventListener()');
        listeners[type].push(listener);
    }

    /**
     * Generic send function for sending of touchpoints and/or sharepoints.
     * Allows users to add the *point name directly to the _at object and call
     * send with the data object. If no such parameter is included, send tries
     * to send the data as a touchpoint and a sharepoint.
     * @param {object} data - Parsed JSON data object containing _at and
     *                        _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function send(data, cb) {
        if (!clientToken) {
            return;
        }

        var d = tidyDataObject(data);
        if (d && d._at) {
            if (d._at.sharepointName) {
                // sharepoint
                sendSharepoint(d, cb);
            } else if (d._at.touchpointName) {
                // touchpoint
                sendTouchpoint(d, cb);
            } else {
                // both
                sendTouchpoint(d, function () {
                    sendSharepoint(d, cb);
                });
            }
        }
    }

    /**
     * Send a sharepoint from init(). This differs to normal sharepoint sending
     * as the URL is always given a query parameter from the response.
     * @param {object} d - Parsed JSON data object containing _at and _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendSharepointInit(d, cb) {
        console.info('sendSharepointInit()');
        var xhr = new XMLHttpRequest();
        var async = true;

        var ds = JSON.stringify(d);

        xhr.onload = function () {
            if (/^20[0-9]{1}/.test(xhr.status)) {
                var res = JSON.parse(xhr.responseText);

                currentSharepointToken = res[0].token;
		queryParamName = res[0].queryParamName;

                // Trigger event
                triggerEvent(events.SharepointSaved, res);

                // Append token to URL (blindly in this instance)
                urlAppendToken(currentSharepointToken);

                // Callback
                if (cb) {
                    cb(null, res);
                }
            } else {
                if (cb) {
                    cb(xhr.statusText);
                }
            }
        };

        xhr.open('POST', points.Sharepoint.url, async);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.send(ds);
    }

    /**
     * Send a touchpoint from init(). This is used as part of the standard
     * sendTouchpoint() call (differs to sharepoints in this respect).
     * @param {object} d - Parsed JSON data object containing _at and _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendTouchpointInit(d, cb) {
        console.info('sendTouchpointInit()');
        var xhr = new XMLHttpRequest();
        var async = true;

        d._at.shareTokens = getSharepointTokens();
        var ds = JSON.stringify(d);

        xhr.onload = function () {
            if (/^20[0-9]{1}/.test(xhr.status)) {
                var res = JSON.parse(xhr.responseText);

                storeTouchpointData(res);
                var meta = JSON.parse(res.metadata);

                // Trigger events
                triggerEvent(events.TouchpointSaved, meta);

                if (res.token && res.token !== "") {
                    // TODO: consider triggering this event downstream as well
                    triggerEvent(events.ReferredPerson, meta);
                }

                // Callback
                if (cb) {
                    cb(null, meta);
                }
            } else {
                if (cb) {
                    cb(xhr.statusText);
                }
            }
        };

        xhr.open('POST', points.Touchpoint.url, async);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.send(ds);
    }

    /**
     * Manually send a touchpoint with the given name. The name is added to the
     * _at object before sending using exactly the same call as used in init().
     * @param {string} name - Name of the triggered touchpoint.
     * @param {object} data - Parsed JSON data object containing _at and
     *                        _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendTouchpoint(name, data, cb) {
        if (!clientToken) {
            return;
        }
        console.info('sendTouchpoint()');
        var d = tidyDataObject(data);
        if (name) {
            d._at.touchpointName = name;
        }

        // Passthrough for touchpoints :)
        sendTouchpointInit(d, cb);
    }

    /**
     * Manually send a sharepoint with the given name. The name is added to the
     * _at object before sending. Sending sharepoints outside of init() use a
     * different call as the URL token is only updated when the token recevied
     * back from the XHR is the same as the current one. When this occurs, a new
     * token is requested.
     * @param {string} name - Name of the triggered touchpoint.
     * @param {object} data - Parsed JSON data object containing _at and
     *                        _client.
     * @param {function} cb - Callback function, called with (err, res).
     */
    function sendSharepoint(name, data, cb) {
        if (!clientToken) {
            return;
        }
        console.info('sendSharepoint()');
        var d = tidyDataObject(data);

        if (name) {
            d._at.sharepointName = name;
        }

        var xhr = new XMLHttpRequest();
        var async = true;

        var ds = JSON.stringify(d);

        xhr.onload = function () {
            if (/^20[0-9]{1}/.test(xhr.status)) {
                var res = JSON.parse(xhr.responseText);

                var oldToken = currentSharepointToken;
                currentSharepointToken = res[0].token;
		queryParamName = res[0].queryParamName;

                // Trigger event
                triggerEvent(events.SharepointSaved, res);

                // Append token to URL (conditionally this time)
                if (currentSharepointToken === oldToken) {
                    // Request new token
                    sendSharepointInit(data); // pre-named data object
                } else {
                    urlAppendToken(currentSharepointToken);
                }

                // Callback
                if (cb) {
                    cb(null, res);
                }
            } else {
                if (cb) {
                    cb(xhr.statusText);
                }
            }
        };

        xhr.open('POST', points.Sharepoint.url, async);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.send(ds);
    }

    /**
     * Expose the goodies under AT global variable.
     */
    context.AT = {
        // Public methods
        init: init,
        addEventListener: addEventListener,
        sendSharepoint: sendSharepoint,
        sendTouchpoint: sendTouchpoint,
        send: send,
        // Exposed variables
        Events: events,
        referredPerson: currentReferredPerson,
        sharepointToken: currentSharepointToken
    };
})(this);
<|endoftext|>


<commit_before>// models
import SeqCollection from "./model/SeqCollection";

// globals
import Colorator from "./g/colorscheme";
import Columns from "./g/columns";
import Config from "./g/config";
import Package from "./g/package";
import SelCol from "./g/selection/SelectionCol";
import User from "./g/user";
import Visibility from "./g/visibility";
import VisOrdering from "./g/visOrdering";
import Zoomer from "./g/zoomer";

// MV from backbone
const boneView = require("backbone-childs");
const Eventhandler = require("biojs-events");

// MSA views
import Stage from "./views/Stage";

// statistics
const Stats = require("stat.seqs");

// utils
const $ = require("jbone");
import FileHelper from "./utils/file";
import TreeHelper from "./utils/tree";
import ProxyHelper from "./utils/proxy";

// opts is a dictionary consisting of
// @param el [String] id or reference to a DOM element
// @param seqs [SeqArray] Array of sequences for initlization
// @param conf [Dict] user config
// @param vis [Dict] config of visible views
// @param zoomer [Dict] display settings like columnWidth
const MSA = boneView.extend({

  initialize: function(data) {

    if (!(typeof data !== "undefined" && data !== null)) { data = {}; }
    // check for default arrays
    if (!(data.colorscheme != null)) { data.colorscheme = {}; }
    if (!(data.columns != null)) { data.columns = {}; }
    if (!(data.conf != null)) { data.conf = {}; }
    if (!(data.vis != null)) { data.vis = {}; }
    if (!(data.visorder != null)) { data.visorder = {}; }
    if (!(data.zoomer != null)) { data.zoomer = {}; }
    if (!(data.conserv != null)) { data.conserv = {}; }

    // g is our global Mediator
    this.g = Eventhandler.mixin({});

    // load seqs and add subviews
    this.seqs = this.g.seqs = new SeqCollection(data.seqs, this.g);

    // populate it and init the global models
    this.g.config = new Config(data.conf);
    this.g.package = new Package(this.g);
    this.g.selcol = new SelCol([],{g:this.g});
    this.g.user = new User();
    this.g.vis = new Visibility(data.vis, {model: this.seqs});
    this.g.visorder = new VisOrdering(data.visorder);
    this.g.zoomer = new Zoomer(data.zoomer,{g:this.g, model: this.seqs});

    // store config options for plugins
    this.g.conservationConfig = data.conserv;

    // debug mode
    if (window.location.hostname === "localhost") {
      this.g.config.set("debug", true);
    }

    this._loadSeqs(data);

    // utils
    this.u = {};
    this.u.file = new FileHelper(this);
    this.u.proxy = new ProxyHelper({g: this.g});
    this.u.tree = new TreeHelper(this);

    if (this.g.config.get("eventBus") === true) {
      this.startEventBus();
    }

    if (this.g.config.get("dropImport")) {
      var events =
        {"dragover": this.dragOver,
        "drop": this.dropFile
        };
      this.delegateEvents(events);
    }

    if (data.importURL) {
      this.u.file.importURL(data.importURL, () => {
        return this.render();
      });
    }

    if (data.bootstrapMenu) {
      // pass menu configuration to defaultmenu
      if(data.menu){
        this.menuConfig = data.menu;
      }
      this.g.config.set("bootstrapMenu", true);
    }

    this.draw();
    // add models to the msa (convenience)
    return this.m();
  },

  _loadSeqs: function(data) {
    // stats
    var pureSeq = this.seqs.pluck("seq");
    this.g.stats = new Stats(this.seqs, {useGaps: true});
    this.g.stats.alphabetSize = this.g.config.get("alphabetSize");
    this.g.columns = new Columns(data.columns,this.g.stats);  // for action on the columns like hiding

    // depending config
    this.g.colorscheme = new Colorator(data.colorscheme, pureSeq, this.g.stats);

    // more init
    return this.g.zoomer.setEl(this.el, this.seqs);
  },

  // proxy to the utility package
  importURL: function() {
    return this.u.file.importURL.apply(this.u.file, arguments);
  },

  // add models to the msa (convenience)
  m: function() {
    var m = {};
    m.model = require("./model");
    m.selection = require("./g/selection/Selection");
    m.selcol = require("./g/selection/SelectionCol");
    m.view = require("backbone-viewj");
    m.boneView = require("backbone-childs");
    return this.m = m;
  },

  draw: function() {

    this.removeViews();

    this.addView("stage",new Stage({model: this.seqs, g: this.g}));
    this.el.setAttribute("class", "biojs_msa_div");

    // bootstraps the menu bar by default -> destroys modularity
    if (this.g.config.get("bootstrapMenu")) {
      var menuDiv = document.createElement('div');
      var wrapperDiv = document.createElement('div');
      if (!this.el.parentNode) {
        wrapperDiv.appendChild(menuDiv);
        wrapperDiv.appendChild(this.el);
      } else {
        this.el.parentNode.replaceChild(wrapperDiv, this.el);
        wrapperDiv.appendChild(menuDiv);
        wrapperDiv.appendChild(this.el);
      }

      var bootstrapOpts = {el: menuDiv,
        msa: this,
      };
      if(this.menuConfig){
        bootstrapOpts.menu = this.menuConfig;
      }
      var defMenu = new msa.menu.defaultmenu(bootstrapOpts);
      defMenu.render();
    }

    return $(window).on("resize", (e) => {
      var f = function() {
        return this.g.zoomer.autoResize();
      };
      return setTimeout(f.bind(this), 5);
    }
    );
  },

  dragOver: function(e) {
    // prevent the normal browser actions
    e.preventDefault();
    e.target.className = 'hover';
    return false;
  },

  dropFile: function(e) {
    e.preventDefault();
    var files = e.target.files || e.dataTransfer.files;
    this.u.file.importFiles(files);
    return false;
  },

  startEventBus() {
    var busObjs = ["config", "columns", "colorscheme", "selcol" ,"vis", "visorder", "zoomer"];
    return (() => {
      var result = [];
      for (var i = 0, key; i < busObjs.length; i++) {
        key = busObjs[i];
        result.push(this._proxyToG(key));
      }
      return result;
    })();
  },

  _proxyToG: function(key) {
    return this.listenTo(this.g[key], "all",function(name,prev,now,opts) {
      // suppress duplicate events
      if (name === "change") { return; }
      // backbone uses the second argument for the next value -> swap
      if ((typeof opts !== "undefined" && opts !== null)) {
        return this.g.trigger(key + ":" + name,now,prev,opts);
      } else {
        return this.g.trigger(key + ":" + name,now,prev);
      }
    });
  },

  render: function() {
    if (this.seqs === undefined || this.seqs.length === 0) {
      console.log("warning. empty seqs.");
    }
    this.renderSubviews();
    this.g.vis.set("loaded", true);
    return this;
  }
});
export default MSA;
<commit_msg>append container class (rather than overwrite)<commit_after>// models
import SeqCollection from "./model/SeqCollection";

// globals
import Colorator from "./g/colorscheme";
import Columns from "./g/columns";
import Config from "./g/config";
import Package from "./g/package";
import SelCol from "./g/selection/SelectionCol";
import User from "./g/user";
import Visibility from "./g/visibility";
import VisOrdering from "./g/visOrdering";
import Zoomer from "./g/zoomer";

// MV from backbone
const boneView = require("backbone-childs");
const Eventhandler = require("biojs-events");

// MSA views
import Stage from "./views/Stage";

// statistics
const Stats = require("stat.seqs");

// utils
const $ = require("jbone");
import FileHelper from "./utils/file";
import TreeHelper from "./utils/tree";
import ProxyHelper from "./utils/proxy";

// opts is a dictionary consisting of
// @param el [String] id or reference to a DOM element
// @param seqs [SeqArray] Array of sequences for initlization
// @param conf [Dict] user config
// @param vis [Dict] config of visible views
// @param zoomer [Dict] display settings like columnWidth
const MSA = boneView.extend({

  initialize: function(data) {

    if (!(typeof data !== "undefined" && data !== null)) { data = {}; }
    // check for default arrays
    if (!(data.colorscheme != null)) { data.colorscheme = {}; }
    if (!(data.columns != null)) { data.columns = {}; }
    if (!(data.conf != null)) { data.conf = {}; }
    if (!(data.vis != null)) { data.vis = {}; }
    if (!(data.visorder != null)) { data.visorder = {}; }
    if (!(data.zoomer != null)) { data.zoomer = {}; }
    if (!(data.conserv != null)) { data.conserv = {}; }

    // g is our global Mediator
    this.g = Eventhandler.mixin({});

    // load seqs and add subviews
    this.seqs = this.g.seqs = new SeqCollection(data.seqs, this.g);

    // populate it and init the global models
    this.g.config = new Config(data.conf);
    this.g.package = new Package(this.g);
    this.g.selcol = new SelCol([],{g:this.g});
    this.g.user = new User();
    this.g.vis = new Visibility(data.vis, {model: this.seqs});
    this.g.visorder = new VisOrdering(data.visorder);
    this.g.zoomer = new Zoomer(data.zoomer,{g:this.g, model: this.seqs});

    // store config options for plugins
    this.g.conservationConfig = data.conserv;

    // debug mode
    if (window.location.hostname === "localhost") {
      this.g.config.set("debug", true);
    }

    this._loadSeqs(data);

    // utils
    this.u = {};
    this.u.file = new FileHelper(this);
    this.u.proxy = new ProxyHelper({g: this.g});
    this.u.tree = new TreeHelper(this);

    if (this.g.config.get("eventBus") === true) {
      this.startEventBus();
    }

    if (this.g.config.get("dropImport")) {
      var events =
        {"dragover": this.dragOver,
        "drop": this.dropFile
        };
      this.delegateEvents(events);
    }

    if (data.importURL) {
      this.u.file.importURL(data.importURL, () => {
        return this.render();
      });
    }

    if (data.bootstrapMenu) {
      // pass menu configuration to defaultmenu
      if(data.menu){
        this.menuConfig = data.menu;
      }
      this.g.config.set("bootstrapMenu", true);
    }

    this.draw();
    // add models to the msa (convenience)
    return this.m();
  },

  _loadSeqs: function(data) {
    // stats
    var pureSeq = this.seqs.pluck("seq");
    this.g.stats = new Stats(this.seqs, {useGaps: true});
    this.g.stats.alphabetSize = this.g.config.get("alphabetSize");
    this.g.columns = new Columns(data.columns,this.g.stats);  // for action on the columns like hiding

    // depending config
    this.g.colorscheme = new Colorator(data.colorscheme, pureSeq, this.g.stats);

    // more init
    return this.g.zoomer.setEl(this.el, this.seqs);
  },

  // proxy to the utility package
  importURL: function() {
    return this.u.file.importURL.apply(this.u.file, arguments);
  },

  // add models to the msa (convenience)
  m: function() {
    var m = {};
    m.model = require("./model");
    m.selection = require("./g/selection/Selection");
    m.selcol = require("./g/selection/SelectionCol");
    m.view = require("backbone-viewj");
    m.boneView = require("backbone-childs");
    return this.m = m;
  },

  draw: function() {

    this.removeViews();

    this.addView("stage",new Stage({model: this.seqs, g: this.g}));
    this.$el.addClass("class", "biojs_msa_div");

    // bootstraps the menu bar by default -> destroys modularity
    if (this.g.config.get("bootstrapMenu")) {
      var menuDiv = document.createElement('div');
      var wrapperDiv = document.createElement('div');
      if (!this.el.parentNode) {
        wrapperDiv.appendChild(menuDiv);
        wrapperDiv.appendChild(this.el);
      } else {
        this.el.parentNode.replaceChild(wrapperDiv, this.el);
        wrapperDiv.appendChild(menuDiv);
        wrapperDiv.appendChild(this.el);
      }

      var bootstrapOpts = {el: menuDiv,
        msa: this,
      };
      if(this.menuConfig){
        bootstrapOpts.menu = this.menuConfig;
      }
      var defMenu = new msa.menu.defaultmenu(bootstrapOpts);
      defMenu.render();
    }

    return $(window).on("resize", (e) => {
      var f = function() {
        return this.g.zoomer.autoResize();
      };
      return setTimeout(f.bind(this), 5);
    }
    );
  },

  dragOver: function(e) {
    // prevent the normal browser actions
    e.preventDefault();
    e.target.className = 'hover';
    return false;
  },

  dropFile: function(e) {
    e.preventDefault();
    var files = e.target.files || e.dataTransfer.files;
    this.u.file.importFiles(files);
    return false;
  },

  startEventBus() {
    var busObjs = ["config", "columns", "colorscheme", "selcol" ,"vis", "visorder", "zoomer"];
    return (() => {
      var result = [];
      for (var i = 0, key; i < busObjs.length; i++) {
        key = busObjs[i];
        result.push(this._proxyToG(key));
      }
      return result;
    })();
  },

  _proxyToG: function(key) {
    return this.listenTo(this.g[key], "all",function(name,prev,now,opts) {
      // suppress duplicate events
      if (name === "change") { return; }
      // backbone uses the second argument for the next value -> swap
      if ((typeof opts !== "undefined" && opts !== null)) {
        return this.g.trigger(key + ":" + name,now,prev,opts);
      } else {
        return this.g.trigger(key + ":" + name,now,prev);
      }
    });
  },

  render: function() {
    if (this.seqs === undefined || this.seqs.length === 0) {
      console.log("warning. empty seqs.");
    }
    this.renderSubviews();
    this.g.vis.set("loaded", true);
    return this;
  }
});
export default MSA;
<|endoftext|>


<commit_before>import add from './add'
import reduce from './reduce'

/**
 * sum : [Number] -> Number
 *
 * @since v0.10.0
 */
export default reduce(add, 0)
<commit_msg>refactor(sum): implement as standalone, not as reduce implementation<commit_after>/**
 * sum : [Number] -> Number
 *
 * @since v0.10.0
 */
export default function sum (xs) {
  var i   = 0
    , len = xs.length
    , sum = 0

  for (; i < len; i++) {
    sum += xs[i]
  }
  return sum
}
<|endoftext|>