<commit_before>/*
 the 'searches' modules provides a combination of
 Reflux.actions and Reflux.stores which trigger and store
 searches in different contexts.
*/
import _ from 'underscore';
import Reflux from 'reflux';
import $ from 'jquery';

import stores from './stores';
import actions from './actions';
import {assign} from './utils';
import assetParserUtils from './assetParserUtils';

const emptySearchState = {
  searchState: 'none',
  searchResults: false,
  searchResultsList: [],
  searchFilterParams: {},
  searchResultsDisplayed: false,
  searchResultsFor: false,
  searchResultsSuccess: null,
  searchDebugQuery: false,
  searchResultsCount: 0,
  parentUid: false,
  parentName: false,
  allPublic: false,
};
const initialState = assign({
  cleared: false,

  defaultQueryState: 'none',
  defaultQueryFor: '',
  defaultQueryDebug: '',
  defaultQueryFilterParams: {},
  defaultQueryResults: '',
  defaultQueryResultsList: [],
  defaultQueryCount: 0,
}, emptySearchState);

function SearchContext(opts={}) {
  var ctx = this;
  var debounceTime = opts.debounceTime || 500;
  var jqxhrs = {};

  var search = Reflux.createAction({
    children: [
      'completed',
      'failed',
      'cancel',
      'refresh',
    ]
  });
  var latestSearchData;

  var searchStore = ctx.store = Reflux.createStore({
    init () {
      this.filterParams = {};
      this.state = {
        searchState: 'none',
      };

      this.listenTo(actions.resources.deleteAsset.completed, this.onDeleteAssetCompleted);
    },
    onDeleteAssetCompleted (asset) {
      var filterOutDeletedAsset = ({listName}) => {
        if (this.state[listName] != undefined) {
          let uid = asset.uid;
          let listLength = this.state[listName].length;
          let l = this.state[listName].filter(function(result){
            return result.uid !== uid;
          });
          if (l.length !== listLength) {
            let o = {};
            o[listName] = l;
            this.update(o);
          }
        }
      };
      var filterOutDeletedAssetFromCategorizedList = () => {
        let list = this.state.defaultQueryCategorizedResultsLists;
        if (list) {
          var l = {};
          for (var category in list) {
            l[category] = list[category].filter(function(result){
              return result.uid !== asset.uid;
            });
          }
          let o = {};
          o.defaultQueryCategorizedResultsLists = l;
          this.update(o);
        }
      };
      filterOutDeletedAsset({listName: 'defaultQueryResultsList'});
      filterOutDeletedAssetFromCategorizedList();
      if (this.state.searchResultsList && this.state.searchResultsList.length > 0) {
        filterOutDeletedAsset({listName: 'searchResultsList'});
      }
    },
    update (items) {
      this.quietUpdate(items);
      this.triggerState();
    },
    triggerState () {
      this.trigger(this.state);
    },
    quietUpdate (items) {
      if (!items.cleared) {
        items.cleared = false;
      }
      assign(this.state, items);
    },
    filterTagQueryData () {
      if (this.filterTags) {
        return {
          q: this.filterTags,
        };
      }
    },
    removeItem (key) {
      delete this.state[key];
    },
    toDataObject () {
      var params = {};
      if (this.state.searchTags) {
        params.tags = this.state.searchTags;
      }
      if (this.state.searchString) {
        params.string = this.state.searchString;
      }
      if (this.state.parentUid) {
        params.parentUid = `parent__uid:${this.state.parentUid}`;
      }
      if (this.state.parentName) {
        params.parentName = this.state.parentName;
      }
      if (this.state.allPublic === true) {
        params.allPublic = true;
      }
      return assign({}, this.filterParams, params);
    },
    toQueryData (dataObject) {
      var searchParams = dataObject || this.toDataObject(),
          // _searchParamsClone = assign({}, searchParams),
          paramGroups = [],
          queryData = {};

      if ('tags' in searchParams) {
        if (searchParams.tags && searchParams.tags.length > 0) {
          paramGroups.push(
              searchParams.tags.map(function(t){
                return `tag:"${t.value}"`;
              }).join(' AND ')
            );
        }
        delete searchParams.tags;
      }
      if ('string' in searchParams) {
        if (searchParams.string && searchParams.string.length > 0) {
          paramGroups.push(
              searchParams.string
            );
        }
        delete searchParams.string;
      }
      if ('parent' in searchParams) {
        if (searchParams.parent && searchParams.parent.length > 0) {
          queryData.parent = searchParams.parent;
        }
        delete searchParams.parent;
      }
      if ('allPublic' in searchParams) {
        queryData.all_public = searchParams.allPublic;
        delete searchParams.allPublic;
      }
      if ('parentName' in searchParams) {
        delete searchParams.parentName;
      }
      paramGroups = paramGroups.concat(_.values(searchParams));
      if (paramGroups.length > 1) {
        queryData.q = paramGroups.map(function(s){
          return `(${s})`;
        }).join(' AND ');
      } else if (paramGroups.length === 1) {
        queryData.q = paramGroups[0];
      }
      return queryData;
    },
  });

  search.listen(function(_opts={}){
    /*
    search will query whatever values are in the store
    and will pass the values back to the store to be reflected
    in the components' states.
    */
    var dataObject = searchStore.toDataObject();
    var _dataObjectClone = assign({}, dataObject);
    var qData = searchStore.toQueryData(dataObject);
    var isSearch = !_opts.cacheAsDefaultSearch;

    // we can clean this up later, but right now, if the search query is empty
    // it cancels the search
    /*
    if (((d)=>{
      if (_opts.cacheAsDefaultSearch) {
        return false;
      }

      var hasTags = d.tags && d.tags.length && d.tags.length > 0;
      var hasString = d.string && d.string.length && d.string.length > 0;
      return !hasTags && !hasString;
    })(_dataObjectClone)) {
      return search.cancel();
    }
    */
    if (isSearch) {
      // cancel existing searches
      if (jqxhrs.search) {
        jqxhrs.search.searchAborted = true;
        jqxhrs.search.abort();
        jqxhrs.search = false;
      }
    }
    latestSearchData = {params: qData, dataObject: dataObject};
    var req = actions.search.assets(qData, {
      onComplete: function(searchData, response) {
        search.completed(dataObject, response, {
          cacheAsDefaultSearch: _opts.cacheAsDefaultSearch,
        });
      },
      onFailed: function(searchData, response) {
        search.failed(response, dataObject);
      }
    })

    jqxhrs[ isSearch ? 'search' : 'default' ] = req;

    if (isSearch) {
      searchStore.update({
        searchState: 'loading',
        searchFor: _dataObjectClone,
      });
    } else {
      searchStore.update({
        defaultQueryState: 'loading',
        defaultQueryFor: _dataObjectClone,
      });
    }
  });
  search.refresh.listen(function(){
    actions.search.assets(latestSearchData.params, {
      onComplete: function(searchData, response) {
        search.completed(latestSearchData.dataObject, response, {
          cacheAsDefaultSearch: false,
        });
      },
      onFailed: function(searchData, response) {
        search.failed(response, latestSearchData.dataObject);
      }
    });
  });

  search.completed.listen(function(searchParams, data, _opts){
    data.results = data.results.map(assetParserUtils.parsed);
    data.results.forEach(stores.allAssets.registerAssetOrCollection);

    var count = data.count;

    var newState;

    if (_opts.cacheAsDefaultSearch) {
      newState = {
        defaultQueryState: 'done',
        defaultQueryFor: searchParams,
        defaultQueryDebug: searchParams.__builtQueryString,
        defaultQueryFilterParams: searchStore.filterParams,
        defaultQueryResults: data,
        defaultQueryResultsList: data.results,
        defaultQueryCount: count,
        defaultQueryCategorizedResultsLists: {
          'Deployed': data.results.filter((asset) => {
            return asset.has_deployment && asset.deployment__active;
          }),
          'Draft': data.results.filter((asset) => {
            return !asset.has_deployment;
          }),
          'Archived': data.results.filter((asset) => {
            return asset.has_deployment && !asset.deployment__active;
          }),
          'Deleted': [], // not implemented yet
        }
      };
    } else {
      newState = {
        searchState: 'done',
        searchResultsFor: searchParams,
        searchDebugQuery: searchParams.__builtQueryString,
        searchBaseFilterParams: searchStore.filterParams,
        searchResults: data,
        // toQueryData() deletes searchParams.tags
        //searchTags: searchParams.tags,
        searchResultsList: data.results,
        searchResultsCount: count,

        // when to show search results (as opposed to default query)
        searchResultsDisplayed: true,
        searchResultsSuccess: count > 0,
        searchResultsCategorizedResultsLists: {
          'Deployed': data.results.filter((asset) => {
            return asset.has_deployment && asset.deployment__active;
          }),
          'Draft': data.results.filter((asset) => {
            return !asset.has_deployment;
          }),
          'Archived': data.results.filter((asset) => {
            return asset.has_deployment && !asset.deployment__active;
          }),
          'Deleted': [], // not implemented yet
        }
      };
    }
    searchStore.update(newState);
  });
  search.failed.listen(function(/*searchData, response*/){
    // if (xhr.searchAborted) {
    //   log('search was canceled because a new search came up')
    // }
    // searchStore.update({
    //   searchState: 'failed',
    // })
  });
  search.cancel.listen(function(){
    if (jqxhrs.search) {
      jqxhrs.search.abort();
    }
    searchStore.update(assign({
      cleared: true
    }, emptySearchState));
  });
  this.mixin = {
    debouncedSearch: ( debounceTime ? _.debounce(search, debounceTime) : search ),
    searchValue: search,
    updateStore (vals) {
      searchStore.update(vals);
    },
    quietUpdateStore (vals) {
      searchStore.quietUpdate(vals);
    },
    searchStore: searchStore,
    searchDefault: function () {
      searchStore.quietUpdate(assign({
        cleared: true,
        searchString: false,
      }, emptySearchState));

      search({
        cacheAsDefaultSearch: true
      });
    },
    getSearchActions: function(){
      return {
        search: search,
      };
    },
  };
}

var commonMethods = {
  getInitialState () {
    return initialState;
  },
  componentDidMount () {
    this.extendSearchContext();
  },
  extendSearchContext () {
    var ctx,
        passedCtx = this.props.searchContext || this.state.searchContext;
    if (passedCtx instanceof SearchContext) {
      ctx = passedCtx;
    } else {
      ctx = getSearchContext(passedCtx);
    }
    assign(this, ctx.mixin);
  },
  searchTagsChange (tags) {
    this.quietUpdateStore({
      searchTags: tags
    });
    this.searchValue();
  },
  searchCollectionChange (collectionUid) {
    this.quietUpdateStore({
      parentUid: collectionUid
    });
    this.searchValue();
  },
  searchChangeEvent (evt) {
    this.quietUpdateStore({
      searchString: evt.target.value,
    });
    this.debouncedSearch();
  },
  refreshSearch () {
    this.debouncedSearch();
  },
  searchClear () {
    this.searchStore.removeItem('searchString');
    this.searchStore.removeItem('searchTags');
    this.getSearchActions().search.cancel();
  },
};

function isSearchContext (ctx) {
  return (ctx instanceof SearchContext);
}

var contexts = {};

function getSearchContext(name, opts={}) {
  if (!contexts[name]) {
    contexts[name] = new SearchContext();
  }
  if (opts.filterParams) {
    contexts[name].store.filterParams = opts.filterParams;
  }
  if (opts.showDefault) {
    contexts[name].store.showDefault = opts.showDefault;
  }
  if (opts.filterTags) {
    contexts[name].store.filterTags = opts.filterTags;
  }
  return contexts[name];
}

module.exports = {
  getSearchContext: getSearchContext,
  common: commonMethods,
  isSearchContext: isSearchContext,
};
<commit_msg>safer searchString handling<commit_after>/*
 the 'searches' modules provides a combination of
 Reflux.actions and Reflux.stores which trigger and store
 searches in different contexts.
*/
import _ from 'underscore';
import Reflux from 'reflux';
import $ from 'jquery';

import stores from './stores';
import actions from './actions';
import {assign} from './utils';
import assetParserUtils from './assetParserUtils';

const emptySearchState = {
  searchState: 'none',
  searchResults: false,
  searchResultsList: [],
  searchFilterParams: {},
  searchResultsDisplayed: false,
  searchResultsFor: false,
  searchResultsSuccess: null,
  searchDebugQuery: false,
  searchResultsCount: 0,
  parentUid: false,
  parentName: false,
  allPublic: false,
};
const initialState = assign({
  cleared: false,

  defaultQueryState: 'none',
  defaultQueryFor: '',
  defaultQueryDebug: '',
  defaultQueryFilterParams: {},
  defaultQueryResults: '',
  defaultQueryResultsList: [],
  defaultQueryCount: 0,
}, emptySearchState);

function SearchContext(opts={}) {
  var ctx = this;
  var debounceTime = opts.debounceTime || 500;
  var jqxhrs = {};

  var search = Reflux.createAction({
    children: [
      'completed',
      'failed',
      'cancel',
      'refresh',
    ]
  });
  var latestSearchData;

  var searchStore = ctx.store = Reflux.createStore({
    init () {
      this.filterParams = {};
      this.state = {
        searchState: 'none',
      };

      this.listenTo(actions.resources.deleteAsset.completed, this.onDeleteAssetCompleted);
    },
    onDeleteAssetCompleted (asset) {
      var filterOutDeletedAsset = ({listName}) => {
        if (this.state[listName] != undefined) {
          let uid = asset.uid;
          let listLength = this.state[listName].length;
          let l = this.state[listName].filter(function(result){
            return result.uid !== uid;
          });
          if (l.length !== listLength) {
            let o = {};
            o[listName] = l;
            this.update(o);
          }
        }
      };
      var filterOutDeletedAssetFromCategorizedList = () => {
        let list = this.state.defaultQueryCategorizedResultsLists;
        if (list) {
          var l = {};
          for (var category in list) {
            l[category] = list[category].filter(function(result){
              return result.uid !== asset.uid;
            });
          }
          let o = {};
          o.defaultQueryCategorizedResultsLists = l;
          this.update(o);
        }
      };
      filterOutDeletedAsset({listName: 'defaultQueryResultsList'});
      filterOutDeletedAssetFromCategorizedList();
      if (this.state.searchResultsList && this.state.searchResultsList.length > 0) {
        filterOutDeletedAsset({listName: 'searchResultsList'});
      }
    },
    update (items) {
      this.quietUpdate(items);
      this.triggerState();
    },
    triggerState () {
      this.trigger(this.state);
    },
    quietUpdate (items) {
      if (!items.cleared) {
        items.cleared = false;
      }
      assign(this.state, items);
    },
    filterTagQueryData () {
      if (this.filterTags) {
        return {
          q: this.filterTags,
        };
      }
    },
    removeItem (key) {
      delete this.state[key];
    },
    toDataObject () {
      var params = {};
      if (this.state.searchTags) {
        params.tags = this.state.searchTags;
      }
      if (this.state.searchString) {
        params.string = this.state.searchString;
      }
      if (this.state.parentUid) {
        params.parentUid = `parent__uid:${this.state.parentUid}`;
      }
      if (this.state.parentName) {
        params.parentName = this.state.parentName;
      }
      if (this.state.allPublic === true) {
        params.allPublic = true;
      }
      return assign({}, this.filterParams, params);
    },
    toQueryData (dataObject) {
      var searchParams = dataObject || this.toDataObject(),
          // _searchParamsClone = assign({}, searchParams),
          paramGroups = [],
          queryData = {};

      if ('tags' in searchParams) {
        if (searchParams.tags && searchParams.tags.length > 0) {
          paramGroups.push(
              searchParams.tags.map(function(t){
                return `tag:"${t.value}"`;
              }).join(' AND ')
            );
        }
        delete searchParams.tags;
      }
      if ('string' in searchParams) {
        if (searchParams.string && searchParams.string.length > 0) {
          paramGroups.push(
              searchParams.string
            );
        }
        delete searchParams.string;
      }
      if ('parent' in searchParams) {
        if (searchParams.parent && searchParams.parent.length > 0) {
          queryData.parent = searchParams.parent;
        }
        delete searchParams.parent;
      }
      if ('allPublic' in searchParams) {
        queryData.all_public = searchParams.allPublic;
        delete searchParams.allPublic;
      }
      if ('parentName' in searchParams) {
        delete searchParams.parentName;
      }
      paramGroups = paramGroups.concat(_.values(searchParams));
      if (paramGroups.length > 1) {
        queryData.q = paramGroups.map(function(s){
          return `(${s})`;
        }).join(' AND ');
      } else if (paramGroups.length === 1) {
        queryData.q = paramGroups[0];
      }
      return queryData;
    },
  });

  search.listen(function(_opts={}){
    /*
    search will query whatever values are in the store
    and will pass the values back to the store to be reflected
    in the components' states.
    */
    var dataObject = searchStore.toDataObject();
    var _dataObjectClone = assign({}, dataObject);
    var qData = searchStore.toQueryData(dataObject);
    var isSearch = !_opts.cacheAsDefaultSearch;

    // we can clean this up later, but right now, if the search query is empty
    // it cancels the search
    /*
    if (((d)=>{
      if (_opts.cacheAsDefaultSearch) {
        return false;
      }

      var hasTags = d.tags && d.tags.length && d.tags.length > 0;
      var hasString = d.string && d.string.length && d.string.length > 0;
      return !hasTags && !hasString;
    })(_dataObjectClone)) {
      return search.cancel();
    }
    */
    if (isSearch) {
      // cancel existing searches
      if (jqxhrs.search) {
        jqxhrs.search.searchAborted = true;
        jqxhrs.search.abort();
        jqxhrs.search = false;
      }
    }
    latestSearchData = {params: qData, dataObject: dataObject};
    var req = actions.search.assets(qData, {
      onComplete: function(searchData, response) {
        search.completed(dataObject, response, {
          cacheAsDefaultSearch: _opts.cacheAsDefaultSearch,
        });
      },
      onFailed: function(searchData, response) {
        search.failed(response, dataObject);
      }
    })

    jqxhrs[ isSearch ? 'search' : 'default' ] = req;

    if (isSearch) {
      searchStore.update({
        searchState: 'loading',
        searchFor: _dataObjectClone,
      });
    } else {
      searchStore.update({
        defaultQueryState: 'loading',
        defaultQueryFor: _dataObjectClone,
      });
    }
  });
  search.refresh.listen(function(){
    actions.search.assets(latestSearchData.params, {
      onComplete: function(searchData, response) {
        search.completed(latestSearchData.dataObject, response, {
          cacheAsDefaultSearch: false,
        });
      },
      onFailed: function(searchData, response) {
        search.failed(response, latestSearchData.dataObject);
      }
    });
  });

  search.completed.listen(function(searchParams, data, _opts){
    data.results = data.results.map(assetParserUtils.parsed);
    data.results.forEach(stores.allAssets.registerAssetOrCollection);

    var count = data.count;

    var newState;

    if (_opts.cacheAsDefaultSearch) {
      newState = {
        defaultQueryState: 'done',
        defaultQueryFor: searchParams,
        defaultQueryDebug: searchParams.__builtQueryString,
        defaultQueryFilterParams: searchStore.filterParams,
        defaultQueryResults: data,
        defaultQueryResultsList: data.results,
        defaultQueryCount: count,
        defaultQueryCategorizedResultsLists: {
          'Deployed': data.results.filter((asset) => {
            return asset.has_deployment && asset.deployment__active;
          }),
          'Draft': data.results.filter((asset) => {
            return !asset.has_deployment;
          }),
          'Archived': data.results.filter((asset) => {
            return asset.has_deployment && !asset.deployment__active;
          }),
          'Deleted': [], // not implemented yet
        }
      };
    } else {
      newState = {
        searchState: 'done',
        searchResultsFor: searchParams,
        searchDebugQuery: searchParams.__builtQueryString,
        searchBaseFilterParams: searchStore.filterParams,
        searchResults: data,
        // toQueryData() deletes searchParams.tags
        //searchTags: searchParams.tags,
        searchResultsList: data.results,
        searchResultsCount: count,

        // when to show search results (as opposed to default query)
        searchResultsDisplayed: true,
        searchResultsSuccess: count > 0,
        searchResultsCategorizedResultsLists: {
          'Deployed': data.results.filter((asset) => {
            return asset.has_deployment && asset.deployment__active;
          }),
          'Draft': data.results.filter((asset) => {
            return !asset.has_deployment;
          }),
          'Archived': data.results.filter((asset) => {
            return asset.has_deployment && !asset.deployment__active;
          }),
          'Deleted': [], // not implemented yet
        }
      };
    }
    searchStore.update(newState);
  });
  search.failed.listen(function(/*searchData, response*/){
    // if (xhr.searchAborted) {
    //   log('search was canceled because a new search came up')
    // }
    // searchStore.update({
    //   searchState: 'failed',
    // })
  });
  search.cancel.listen(function(){
    if (jqxhrs.search) {
      jqxhrs.search.abort();
    }
    searchStore.update(assign({
      cleared: true
    }, emptySearchState));
  });
  this.mixin = {
    debouncedSearch: ( debounceTime ? _.debounce(search, debounceTime) : search ),
    searchValue: search,
    updateStore (vals) {
      searchStore.update(vals);
    },
    quietUpdateStore (vals) {
      searchStore.quietUpdate(vals);
    },
    searchStore: searchStore,
    searchDefault: function () {
      searchStore.quietUpdate(assign({
        cleared: true,
        searchString: false,
      }, emptySearchState));

      search({
        cacheAsDefaultSearch: true
      });
    },
    getSearchActions: function(){
      return {
        search: search,
      };
    },
  };
}

var commonMethods = {
  getInitialState () {
    return initialState;
  },
  componentDidMount () {
    this.extendSearchContext();
  },
  extendSearchContext () {
    var ctx,
        passedCtx = this.props.searchContext || this.state.searchContext;
    if (passedCtx instanceof SearchContext) {
      ctx = passedCtx;
    } else {
      ctx = getSearchContext(passedCtx);
    }
    assign(this, ctx.mixin);
  },
  searchTagsChange (tags) {
    this.quietUpdateStore({
      searchTags: tags
    });
    this.searchValue();
  },
  searchCollectionChange (collectionUid) {
    this.quietUpdateStore({
      parentUid: collectionUid
    });
    this.searchValue();
  },
  searchChangeEvent (evt) {
    let searchString = evt.target.value.trim();
    // don't trigger search on identical strings (e.g. multiple spaces)
    if (this.searchStore.state.searchString !== searchString) {
      this.quietUpdateStore({
        searchString: searchString,
      });
      this.debouncedSearch();
    }
  },
  refreshSearch () {
    this.debouncedSearch();
  },
  searchClear () {
    this.searchStore.removeItem('searchString');
    this.searchStore.removeItem('searchTags');
    this.getSearchActions().search.cancel();
  },
};

function isSearchContext (ctx) {
  return (ctx instanceof SearchContext);
}

var contexts = {};

function getSearchContext(name, opts={}) {
  if (!contexts[name]) {
    contexts[name] = new SearchContext();
  }
  if (opts.filterParams) {
    contexts[name].store.filterParams = opts.filterParams;
  }
  if (opts.showDefault) {
    contexts[name].store.showDefault = opts.showDefault;
  }
  if (opts.filterTags) {
    contexts[name].store.filterTags = opts.filterTags;
  }
  return contexts[name];
}

module.exports = {
  getSearchContext: getSearchContext,
  common: commonMethods,
  isSearchContext: isSearchContext,
};
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import { propTypes } from './react-props-decorators.js'; //tnrtodo: update this once the actual npm module updates its dependencies
var Combokeys = require("combokeys");
var combokeys;
var bindGlobalPlugin = require('combokeys/plugins/global-bind');

var RowView = require('./RowView');
var MapView = require('./MapView');
var CircularView = require('./CircularView');

var Clipboard = require('./Clipboard');
import {Decorator as Cerebral} from 'cerebral-react';

import ToolBar from './ToolBar';
import StatusBar from './StatusBar';
import AnnotationTable from './AnnotationTable';

import styles from './sequence-editor.css';

@Cerebral({
    sequenceLength: ['sequenceLength'],
    bpsPerRow: ['bpsPerRow'],
    totalRows: ['totalRows'],
    newRandomRowToJumpTo: ['newRandomRowToJumpTo'],
    selectedSequenceString: ['selectedSequenceString'],
    caretPosition: ['caretPosition'],
    sequenceData: ['sequenceData'],
    selectionLayer: ['selectionLayer'],
    clipboardData: ['clipboardData'],
    showCircular: ['showCircular'],
    showLinear: ['showLinear'],
    showRow: ['showRow'],
    showSidebar: ['showSidebar'],
    cutsites: ['cutsites'],
    orfData: ['orfData']
})
@propTypes({
    sequenceLength: PropTypes.number.isRequired,
    bpsPerRow: PropTypes.number.isRequired,
    totalRows: PropTypes.number.isRequired,
    newRandomRowToJumpTo: PropTypes.object,
    selectedSequenceString: PropTypes.string.isRequired,
    caretPosition: PropTypes.number.isRequired,
    sequenceData: PropTypes.object.isRequired,
    selectionLayer: PropTypes.object.isRequired,
    clipboardData: PropTypes.object.isRequired,
    showCircular: PropTypes.bool.isRequired,
    showLinear: PropTypes.bool.isRequired,
    showRow: PropTypes.bool.isRequired
})
class SequenceEditor extends React.Component {
    componentDidMount() {
        var {
            sequenceDataInserted,
            backspacePressed,
            selectAll,
            selectInverse,
        } = this.props.signals;
        var self = this;
        combokeys = new Combokeys(document.documentElement);
        // combokeys = new Combokeys(React.findDOMNode(this.refs.sequenceEditor));
        bindGlobalPlugin(combokeys);

        //bind a bunch of keyboard shortcuts we're interested in catching
        //we're using the "mousetrap" library (available thru npm: https://www.npmjs.com/package/br-mousetrap)
        //documentation: https://craig.is/killing/mice
        combokeys.bind(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ], function(event) { // Handle shortcut
            sequenceDataInserted({newSequenceData: {sequence: String.fromCharCode(event.charCode)}});
        });
        combokeys.bind(['left','shift+left'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretLeftOne'});
        });
        combokeys.bind(['right','shift+right'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretRightOne'});
        });
        combokeys.bind(['up','shift+up'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretUpARow'});
        });
        combokeys.bindGlobal(['down','shift+down'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretDownARow'});
        });
        combokeys.bindGlobal(['mod+right','mod+shift+right'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToEndOfRow'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal(['mod+left','mod+shift+left'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToStartOfRow'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal(['mod+up','mod+shift+up'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToStartOfSequence'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal(['mod+down','mod+shift+down'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToEndOfSequence'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bind('backspace', function(event) { // Handle shortcut
            backspacePressed();
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal('command+a', function(event) { // Handle shortcut
            selectAll();
            event.stopPropagation();
        });
        combokeys.bindGlobal('command+ctrl+i', function(event) { // Handle shortcut
            selectInverse();
            event.stopPropagation();
        });
    }

    handlePaste(event) {
        var {
            pasteSequenceString,
        } = this.props.signals;
        event.clipboardData.items[0].getAsString(function(string) {
            pasteSequenceString(string);
        });
    }

    handleCopy() {
        var {
            copySelection,
        } = this.props.signals;
        copySelection();
        // this.props.selectedSequenceString
    }

    componentWillUnmount() {

        // Remove any Mousetrap bindings before unmounting.detach()
        combokeys.detach()
    }
    render() {
        var {
            selectedSequenceString,
            sequenceData,
            showCircular,
            showRow,
            showSidebar,
            cutsites,
            orfData
        } = this.props;

        var table;

        if (showSidebar === 'features') {
            table = (
                <AnnotationTable
                   data={sequenceData.features}
                   />
            );
        } else if (showSidebar === 'cutsites') {
            table = (
                <AnnotationTable
                   data={cutsites}
                   />
            );
        } else if (showSidebar === 'orfs') {
            table = (
                <AnnotationTable
                   data={orfData}
                   />
            );
        }

        return (

                <Clipboard
                    value={selectedSequenceString}
                    onCopy={this.handleCopy.bind(this)}
                    onPaste={this.handlePaste.bind(this)}
                />

                    <ToolBar />

                      {table}

                        <CircularView />

                        <RowView />

                    <StatusBar />

        );
    }
}

module.exports = SequenceEditor;
<commit_msg>restricting characters<commit_after>import React, {PropTypes} from 'react';
import { propTypes } from './react-props-decorators.js'; //tnrtodo: update this once the actual npm module updates its dependencies
var Combokeys = require("combokeys");
var combokeys;
var bindGlobalPlugin = require('combokeys/plugins/global-bind');

var RowView = require('./RowView');
var MapView = require('./MapView');
var CircularView = require('./CircularView');

var Clipboard = require('./Clipboard');
import {Decorator as Cerebral} from 'cerebral-react';

import ToolBar from './ToolBar';
import StatusBar from './StatusBar';
import AnnotationTable from './AnnotationTable';

import styles from './sequence-editor.css';

@Cerebral({
    sequenceLength: ['sequenceLength'],
    bpsPerRow: ['bpsPerRow'],
    totalRows: ['totalRows'],
    newRandomRowToJumpTo: ['newRandomRowToJumpTo'],
    selectedSequenceString: ['selectedSequenceString'],
    caretPosition: ['caretPosition'],
    sequenceData: ['sequenceData'],
    selectionLayer: ['selectionLayer'],
    clipboardData: ['clipboardData'],
    showCircular: ['showCircular'],
    showLinear: ['showLinear'],
    showRow: ['showRow'],
    showSidebar: ['showSidebar'],
    cutsites: ['cutsites'],
    orfData: ['orfData']
})
@propTypes({
    sequenceLength: PropTypes.number.isRequired,
    bpsPerRow: PropTypes.number.isRequired,
    totalRows: PropTypes.number.isRequired,
    newRandomRowToJumpTo: PropTypes.object,
    selectedSequenceString: PropTypes.string.isRequired,
    caretPosition: PropTypes.number.isRequired,
    sequenceData: PropTypes.object.isRequired,
    selectionLayer: PropTypes.object.isRequired,
    clipboardData: PropTypes.object.isRequired,
    showCircular: PropTypes.bool.isRequired,
    showLinear: PropTypes.bool.isRequired,
    showRow: PropTypes.bool.isRequired
})
class SequenceEditor extends React.Component {
    componentDidMount() {
        var {
            sequenceDataInserted,
            backspacePressed,
            selectAll,
            selectInverse,
        } = this.props.signals;
        var self = this;
        combokeys = new Combokeys(document.documentElement);
        // combokeys = new Combokeys(React.findDOMNode(this.refs.sequenceEditor));
        bindGlobalPlugin(combokeys);

        //bind a bunch of keyboard shortcuts we're interested in catching
        //we're using the "mousetrap" library (available thru npm: https://www.npmjs.com/package/br-mousetrap)
        //documentation: https://craig.is/killing/mice
        combokeys.bind(['a', 'c', 'g', 't'], function(event) { // type in bases
            sequenceDataInserted({newSequenceData: {sequence: String.fromCharCode(event.charCode)}});
        });
        combokeys.bind(['left','shift+left'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretLeftOne'});
        });
        combokeys.bind(['right','shift+right'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretRightOne'});
        });
        combokeys.bind(['up','shift+up'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretUpARow'});
        });
        combokeys.bindGlobal(['down','shift+down'] , function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretDownARow'});
        });
        combokeys.bindGlobal(['mod+right','mod+shift+right'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToEndOfRow'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal(['mod+left','mod+shift+left'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToStartOfRow'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal(['mod+up','mod+shift+up'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToStartOfSequence'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal(['mod+down','mod+shift+down'], function(event) { // Handle shortcut
            self.props.signals.caretMoved({shiftHeld: event.shiftKey, type: 'moveCaretToEndOfSequence'});
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bind('backspace', function(event) { // Handle shortcut
            backspacePressed();
            event.stopPropagation();
            event.preventDefault();
        });
        combokeys.bindGlobal('command+a', function(event) { // Handle shortcut
            selectAll();
            event.stopPropagation();
        });
        combokeys.bindGlobal('command+ctrl+i', function(event) { // Handle shortcut
            selectInverse();
            event.stopPropagation();
        });
    }

    handlePaste(event) {
        var {
            pasteSequenceString,
        } = this.props.signals;
        event.clipboardData.items[0].getAsString(function(string) {
            pasteSequenceString(string);
        });
    }

    handleCopy() {
        var {
            copySelection,
        } = this.props.signals;
        copySelection();
        // this.props.selectedSequenceString
    }

    componentWillUnmount() {

        // Remove any Mousetrap bindings before unmounting.detach()
        combokeys.detach()
    }
    render() {
        var {
            selectedSequenceString,
            sequenceData,
            showCircular,
            showRow,
            showSidebar,
            cutsites,
            orfData
        } = this.props;

        var table;

        if (showSidebar === 'features') {
            table = (
                <AnnotationTable
                   data={sequenceData.features}
                   />
            );
        } else if (showSidebar === 'cutsites') {
            table = (
                <AnnotationTable
                   data={cutsites}
                   />
            );
        } else if (showSidebar === 'orfs') {
            table = (
                <AnnotationTable
                   data={orfData}
                   />
            );
        }

        return (

                <Clipboard
                    value={selectedSequenceString}
                    onCopy={this.handleCopy.bind(this)}
                    onPaste={this.handlePaste.bind(this)}
                />

                    <ToolBar />

                      {table}

                        <CircularView />

                        <RowView />

                    <StatusBar />

        );
    }
}

module.exports = SequenceEditor;
<|endoftext|>


<commit_before>"use strict";

import React from 'react';
import ReactDOM from 'react-dom';
import {ViewerResponsive, ViewerHelper, TOOL_NONE, TOOL_PAN, TOOL_ZOOM} from '../src/index';
import SnakeSVG from './svg/snake';

export default class Demo extends React.Component {

  constructor(props) {
    super(props);
    let defaultValue = ViewerHelper.getDefaultValue();
    defaultValue = ViewerHelper.fitSVGToViewer(defaultValue, 1440, 1440, 500, 500);
    this.state = {value: defaultValue, tool: TOOL_NONE, x: 0, y: 0};
  }

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

  handleReset(event) {
    let {containerWidth, containerHeight} = this.refs.viewer.state;
    this.setState({value: ViewerHelper.fitSVGToViewer(this.state.value, 1440, 1440, containerWidth, containerHeight)})
  }

  handleClick(event) {
    console.log('click', event);
    console.log('X', event.x);
    console.log('Y', event.y);
    console.log('scaleFactor', event.scaleFactor);
    console.log('translationX', event.translationX);
    console.log('translationY', event.translationY);
  }

  handleMouseMove(event) {
    this.setState({
      x: event.x,
      y: event.y
    });
  }

  handleChangeTool(event) {
    this.setState({tool: event.target.value});
  }

  render() {
    return (

        {/* col-1 */}

          <ViewerResponsive style={{border:'1px solid black'}} ref="viewer"
                            value={this.state.value} tool={this.state.tool}
                            onChange={event => this.handleChange(event)}
                            onClick={event => this.handleClick(event)}
                            onMouseMove={event => this.handleMouseMove(event)}>

            {SnakeSVG}

          </ViewerResponsive>

        {/* col-2 */}

<input
              type="radio"
              value={TOOL_NONE}
              checked={this.state.tool === TOOL_NONE}
              onChange={event => this.handleChangeTool(event)}/>TOOL: NONE

<input
              type="radio"
              value={TOOL_PAN}
              checked={this.state.tool === TOOL_PAN}
              onChange={event => this.handleChangeTool(event)}/>TOOL: PAN

<input
              type="radio"
              value={TOOL_ZOOM}
              checked={this.state.tool === TOOL_ZOOM}
              onChange={event => this.handleChangeTool(event)}/>TOOL: ZOOM

            <button onClick={event => this.handleReset(event)}>Reset view</button>

          Note: Press Alt for zoom out

            Position: {this.state.x},{this.state.y}

 // /cols

    );
  }
}
<commit_msg> enhance demo responsive<commit_after>"use strict";

import React from 'react';
import ReactDOM from 'react-dom';
import {ViewerResponsive, ViewerHelper, TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT} from '../src/index';
import SnakeSVG from './svg/snake';

export default class Demo extends React.Component {

  constructor(props) {
    super(props);
    let defaultValue = ViewerHelper.getDefaultValue();
    defaultValue = ViewerHelper.fitSVGToViewer(defaultValue, 1440, 1440, 500, 500);
    this.state = {value: defaultValue, tool: TOOL_NONE, x: 0, y: 0};
  }

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

  handleReset(event) {
    let {containerWidth, containerHeight} = this.refs.viewer.state;
    this.setState({value: ViewerHelper.fitSVGToViewer(this.state.value, 1440, 1440, containerWidth, containerHeight)})
  }

  handleClick(event) {
    console.log('click', event);
    console.log('X', event.x);
    console.log('Y', event.y);
    console.log('scaleFactor', event.scaleFactor);
    console.log('translationX', event.translationX);
    console.log('translationY', event.translationY);
  }

  handleMouseMove(event) {
    this.setState({
      x: event.x,
      y: event.y
    });
  }

  handleChangeTool(event) {
    this.setState({tool: event.target.value});
  }

  render() {
    return (

        {/* col-1 */}

          <ViewerResponsive style={{border:'1px solid black'}} ref="viewer"
                            value={this.state.value} tool={this.state.tool}
                            onChange={event => this.handleChange(event)}
                            onClick={event => this.handleClick(event)}
                            onMouseMove={event => this.handleMouseMove(event)}>

            {SnakeSVG}

          </ViewerResponsive>

        {/* col-2 */}

<input
              type="radio"
              value={TOOL_NONE}
              checked={this.state.tool === TOOL_NONE}
              onChange={event => this.handleChangeTool(event)}/>TOOL: NONE

<input
              type="radio"
              value={TOOL_PAN}
              checked={this.state.tool === TOOL_PAN}
              onChange={event => this.handleChangeTool(event)}/>TOOL: PAN

<input
              type="radio"
              value={TOOL_ZOOM}
              checked={this.state.tool === TOOL_ZOOM}
              onChange={event => this.handleChangeTool(event)}/>TOOL: ZOOM

<input
              type="radio"
              value={TOOL_ZOOM_IN}
              checked={this.state.tool === TOOL_ZOOM_IN}
              onChange={event => this.handleChangeTool(event)}/>TOOL: ZOOM IN

<input
              type="radio"
              value={TOOL_ZOOM_OUT}
              checked={this.state.tool === TOOL_ZOOM_OUT}
              onChange={event => this.handleChangeTool(event)}/>TOOL: ZOOM OUT

            <button onClick={event => this.handleReset(event)}>Reset view</button>

          Note: Press Alt for zoom out

            Position: {this.state.x},{this.state.y}

 // /cols

    );
  }
}
<|endoftext|>


<commit_before>"use strict";

var base = require("xbase"),
	util = require("util"),
	ansidiff = require("ansidiff"),
	color = require("cli-color");

exports.diff = diff;
function diff(o, n, indent)
{
	indent = (indent || 0) + 1;

	if(Object.isObject(o))
		return diffObjects(o, n, indent);
	else if(Array.isArray(o))
		return diffArray(o, n, indent);
	else
		return diffValues(o, n);
}

function diffObjects(o, n, indent)
{
	var result = "";

	var oKeys = Object.keys(o);
	var nKeys = Object.keys(n);

	var keysAdded = nKeys.subtract(oKeys);
	keysAdded.forEach(function(keyAdded) { result += " ".repeat(indent*4) + color.green(util.format("%s : %j\n", keyAdded, n[keyAdded])); });

	var keysRemoved = oKeys.subtract(nKeys);
	keysRemoved.forEach(function(keyRemoved) { result += " ".repeat(indent*4) + color.red(util.format("%s : %j\n", keyRemoved, o[keyRemoved])); });

	oKeys.subtract(keysAdded).subtract(keysRemoved).forEach(function(key)
	{
		var subResult = diff(o[key], n[key], indent);
		if(subResult)
			result += " ".repeat(indent*4) + color.yellow(key) + color.white(" : ") + subResult;
	});

	return (result.length ? "{\n" : "") + result + (result.length ? "}\n" : "");
}

function diffArray(o, n, indent)
{
	var result = "";

	n.map(function(v) { return JSON.stringify(v); }).subtract(o.map(function(v) { return JSON.stringify(v); })).forEach(function(added) { result += (result.length ? ", " : "") + color.green(added); });
	o.map(function(v) { return JSON.stringify(v); }).subtract(n.map(function(v) { return JSON.stringify(v); })).forEach(function(removed) { result += (result.length ? ", " : "") + color.red(removed); });

	return (result.length ? "[ " : "") + result + (result.length ? " ]\n" : "");
}

function diffValues(o, n)
{
	if(o!==n)
	{
		if(typeof o==="string")
			return ansidiff.words(JSON.stringify(o), JSON.stringify(n), ansidiff.subtle) + "\n";
		else
			return color.whiteBright(JSON.stringify(o)) + color.yellow(" => ") + color.whiteBright(JSON.stringify(n)) + "\n";
	}
}
<commit_msg>Added support for comparing arrays directly<commit_after>"use strict";

var base = require("xbase"),
	util = require("util"),
	ansidiff = require("ansidiff"),
	color = require("cli-color");

exports.diff = diff;
function diff(o, n, options)
{
	options = base.clone((options || {}));
	options.indent = (options.indent || 0) + 1;

	if(Object.isObject(o))
		return diffObjects(o, n, options);
	else if(Array.isArray(o))
		return diffArray(o, n, options);
	else
		return diffValues(o, n, options);
}

function diffObjects(o, n, options)
{
	var result = "";

	var oKeys = Object.keys(o);
	var nKeys = Object.keys(n);

	var keysAdded = nKeys.subtract(oKeys);
	keysAdded.forEach(function(keyAdded) { result += " ".repeat(options.indent*4) + color.green(util.format("%s : %j\n", keyAdded, n[keyAdded])); });

	var keysRemoved = oKeys.subtract(nKeys);
	keysRemoved.forEach(function(keyRemoved) { result += " ".repeat(options.indent*4) + color.red(util.format("%s : %j\n", keyRemoved, o[keyRemoved])); });

	oKeys.subtract(keysAdded).subtract(keysRemoved).forEach(function(key)
	{
		var subResult = diff(o[key], n[key], options);
		if(subResult)
			result += " ".repeat(options.indent*4) + color.yellow(key) + color.white(" : ") + subResult;
	});

	return (result.length ? "{\n" : "") + result + (result.length ? "}\n" : "");
}

function diffArray(o, n, options)
{
	var result = "";

	if(options.compareArraysDirectly)
	{
		if(o.length!==n.length)
		{
			result += "Arrays are not equal length, cannot compare them directly: old [" + o.length + "] vs new [" + n.length + "]";
		}
		else
		{
			o.forEach(function(item, i)
			{
				var subResult = diff(item, n[i], options);
				if(subResult)
					result += " ".repeat(options.indent*4) + "[" + i + "]: " + subResult;
			});
		}
	}
	else
	{
		n.map(function(v) { return JSON.stringify(v); }).subtract(o.map(function(v) { return JSON.stringify(v); })).forEach(function(added) { result += (result.length ? ", " : "") + color.green(added); });
		o.map(function(v) { return JSON.stringify(v); }).subtract(n.map(function(v) { return JSON.stringify(v); })).forEach(function(removed) { result += (result.length ? ", " : "") + color.red(removed); });
	}

	return (result.length ? "[ " : "") + result + (result.length ? " ]\n" : "");
}

function diffValues(o, n, options)
{
	if(o!==n)
	{
		if(typeof o==="string")
			return ansidiff.words(JSON.stringify(o), JSON.stringify(n), ansidiff.subtle) + "\n";
		else
			return color.whiteBright(JSON.stringify(o)) + color.yellow(" => ") + color.whiteBright(JSON.stringify(n)) + "\n";
	}
}
<|endoftext|>


<commit_before>/* eslint-disable no-undef */

import { createElement, PureComponent } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import { getPolygonCenter } from 'utils/map';

import actions from './recent-imagery-actions';
import reducers, { initialState } from './recent-imagery-reducers';
import {
  getAllTiles,
  getTile,
  getBounds,
  getSources,
  getDates
} from './recent-imagery-selectors';
import RecentImageryDrag from './recent-imagery-drag';
import RecentImageryComponent from './recent-imagery-component';

const mapStateToProps = ({ recentImagery }) => {
  const { active, showSettings, data, dataStatus, settings } = recentImagery;
  const selectorData = {
    data: data.tiles,
    bbox: data.bbox,
    dataStatus,
    settings
  };
  return {
    active,
    showSettings,
    dataStatus,
    allTiles: getAllTiles(selectorData),
    tile: getTile(selectorData),
    bounds: getBounds(selectorData),
    sources: getSources(selectorData),
    dates: getDates(selectorData),
    settings
  };
};

class RecentImageryContainer extends PureComponent {
  componentDidMount() {
    this.middleView = window.App.Views.ReactMapMiddleView;
    this.boundsPolygon = null;
    this.boundsPolygonInfowindow = null;
    this.activatedFromUrl = false;
    this.removedFromUrl = false;
    window.addEventListener('isRecentImageryActivated', () => {
      const { active, toogleRecentImagery } = this.props;
      if (!active) {
        this.activatedFromUrl = true;
        toogleRecentImagery();
      }
    });
    window.addEventListener('removeLayer', e => {
      const { active, toogleRecentImagery } = this.props;
      if (e.detail === 'sentinel_tiles' && active) {
        this.removedFromUrl = true;
        toogleRecentImagery();
      }
    });
  }

  componentWillReceiveProps(nextProps) {
    const {
      active,
      showSettings,
      dataStatus,
      tile,
      bounds,
      sources,
      dates,
      settings,
      getData,
      getMoreTiles
    } = nextProps;
    const { map } = this.middleView;
    const isNewTile =
      tile && (!this.props.tile || tile.url !== this.props.tile.url);

    if (
      (active && active !== this.props.active) ||
      !isEqual(settings.date, this.props.settings.date) ||
      !isEqual(settings.weeks, this.props.settings.weeks)
    ) {
      getData({
        latitude: map.getCenter().lng(),
        longitude: map.getCenter().lat(),
        start: dates.start,
        end: dates.end
      });
    }
    if (!active && active !== this.props.active) {
      this.removeLayer();
      this.removeEvents();
      this.removeBoundsPolygon();
    }
    if (isNewTile) {
      if (this.activatedFromUrl && !this.props.tile) {
        this.updateLayer(tile.url);
        this.setEvents();
      } else if (!this.props.tile) {
        this.showLayer(tile.url);
        this.setEvents();
      } else {
        this.updateLayer(tile.url);
      }
      this.addBoundsPolygon(bounds, tile);
    }
    if (
      !dataStatus.haveAllData &&
      showSettings &&
      (showSettings !== this.props.showSettings ||
        dataStatus.requestedTiles !== this.props.dataStatus.requestedTiles ||
        dataStatus.requestFails !== this.props.dataStatus.requestFails ||
        isNewTile)
    ) {
      getMoreTiles({ sources, dataStatus });
    }
  }

  setEvents() {
    const { map } = this.middleView;

    const loadNewTile = () => {
      const { dates, getData } = this.props;
      const zoom = map.getZoom();
      const needNewTile = !google.maps.geometry.poly.containsLocation(
        map.getCenter(),
        this.boundsPolygon
      );
      if (needNewTile) {
        getData({
          latitude: map.getCenter().lng(),
          longitude: map.getCenter().lat(),
          start: dates.start,
          end: dates.end
        });
      }
      if (zoom >= 10) {
        this.boundsPolygon.setOptions({
          fillColor: 'transparent',
          strokeWeight: 0
        });
        this.boundsPolygonInfowindow.close();
      }
    };
    this.mapDragEvent = map.addListener('dragend', loadNewTile);
    this.mapZoomEvent = map.addListener('zoom_changed', loadNewTile);
  }

  removeEvents() {
    google.maps.event.removeListener(this.mapDragEvent);
    google.maps.event.removeListener(this.mapZoomEvent);
  }

  showLayer(url) {
    const { map } = this.middleView;
    const { settings: { layerSlug, minZoom } } = this.props;
    const zoom = map.getZoom();

    this.middleView.toggleLayer(layerSlug, {
      urlTemplate: url
    });
    if (zoom < minZoom) {
      map.setZoom(minZoom);
      this.middleView.showZoomAlert('notification-zoom-go-back', zoom);
    }
  }

  removeLayer() {
    const { settings: { layerSlug }, resetData } = this.props;
    if (!this.removedFromUrl) {
      this.middleView.toggleLayer(layerSlug);
    }
    this.activatedFromUrl = false;
    this.removedFromUrl = false;
    resetData();
  }

  updateLayer(url) {
    const { settings: { layerSlug } } = this.props;
    this.middleView.updateLayer(layerSlug, {
      urlTemplate: url
    });
  }

  addBoundsPolygon(bounds, tile) {
    const { map } = this.middleView;
    const { description } = tile;

    if (this.boundsPolygon !== null) {
      this.removeBoundsPolygon();
    }
    const coords = [];
    bounds.forEach(item => {
      coords.push({
        lat: item[1],
        lng: item[0]
      });
    });

    this.boundsPolygon = new google.maps.Polygon({
      paths: coords,
      fillColor: 'transparent',
      strokeWeight: 0
    });
    const polygonCenter = getPolygonCenter(this.boundsPolygon);

    this.addBoundsPolygonEvents();
    this.boundsPolygon.setMap(map);

    if (this.boundsPolygonInfowindow !== null) {
      this.boundsPolygonInfowindow.close();
    }
    this.boundsPolygonInfowindow = new google.maps.InfoWindow({
      content: `<div class="recent-imagery-infowindow">${description}</div>`,
      position: polygonCenter.top
    });
  }

  addBoundsPolygonEvents() {
    const { setRecentImageryShowSettings } = this.props;
    const { map } = this.middleView;
    let clickTimeout = null;

    google.maps.event.addListener(this.boundsPolygon, 'mouseover', () => {
      const zoom = map.getZoom();
      if (zoom < 10) {
        this.boundsPolygon.setOptions({
          fillColor: '#000000',
          fillOpacity: 0.1,
          strokeColor: '#000000',
          strokeOpacity: 0.5,
          strokeWeight: 1
        });
        this.boundsPolygonInfowindow.open(map);
      }
    });
    google.maps.event.addListener(this.boundsPolygon, 'mouseout', () => {
      const zoom = map.getZoom();
      if (zoom < 10) {
        this.boundsPolygon.setOptions({
          fillColor: 'transparent',
          strokeWeight: 0
        });
        this.boundsPolygonInfowindow.close();
      }
    });
    google.maps.event.addListener(this.boundsPolygon, 'click', () => {
      clickTimeout = setTimeout(() => {
        setRecentImageryShowSettings(true);
      }, 200);
    });
    google.maps.event.addListener(this.boundsPolygon, 'dblclick', () => {
      clearTimeout(clickTimeout);
    });
  }

  removeBoundsPolygon() {
    this.boundsPolygon.setMap(null);
  }

  render() {
    return createElement(RecentImageryComponent, {
      ...this.props
    });
  }
}

RecentImageryContainer.propTypes = {
  active: PropTypes.bool,
  showSettings: PropTypes.bool,
  dataStatus: PropTypes.object,
  tile: PropTypes.object,
  bounds: PropTypes.array,
  sources: PropTypes.array,
  dates: PropTypes.object,
  settings: PropTypes.object,
  toogleRecentImagery: PropTypes.func,
  setRecentImageryShowSettings: PropTypes.func,
  getData: PropTypes.func,
  getMoreTiles: PropTypes.func,
  resetData: PropTypes.func
};

export { actions, reducers, initialState };
export default connect(mapStateToProps, actions)(
  RecentImageryDrag(RecentImageryContainer)
);
<commit_msg>remove polygon hover style<commit_after>/* eslint-disable no-undef */

import { createElement, PureComponent } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import { getPolygonCenter } from 'utils/map';

import actions from './recent-imagery-actions';
import reducers, { initialState } from './recent-imagery-reducers';
import {
  getAllTiles,
  getTile,
  getBounds,
  getSources,
  getDates
} from './recent-imagery-selectors';
import RecentImageryDrag from './recent-imagery-drag';
import RecentImageryComponent from './recent-imagery-component';

const mapStateToProps = ({ recentImagery }) => {
  const { active, showSettings, data, dataStatus, settings } = recentImagery;
  const selectorData = {
    data: data.tiles,
    bbox: data.bbox,
    dataStatus,
    settings
  };
  return {
    active,
    showSettings,
    dataStatus,
    allTiles: getAllTiles(selectorData),
    tile: getTile(selectorData),
    bounds: getBounds(selectorData),
    sources: getSources(selectorData),
    dates: getDates(selectorData),
    settings
  };
};

class RecentImageryContainer extends PureComponent {
  componentDidMount() {
    this.middleView = window.App.Views.ReactMapMiddleView;
    this.boundsPolygon = null;
    this.boundsPolygonInfowindow = null;
    this.activatedFromUrl = false;
    this.removedFromUrl = false;
    window.addEventListener('isRecentImageryActivated', () => {
      const { active, toogleRecentImagery } = this.props;
      if (!active) {
        this.activatedFromUrl = true;
        toogleRecentImagery();
      }
    });
    window.addEventListener('removeLayer', e => {
      const { active, toogleRecentImagery } = this.props;
      if (e.detail === 'sentinel_tiles' && active) {
        this.removedFromUrl = true;
        toogleRecentImagery();
      }
    });
  }

  componentWillReceiveProps(nextProps) {
    const {
      active,
      showSettings,
      dataStatus,
      tile,
      bounds,
      sources,
      dates,
      settings,
      getData,
      getMoreTiles
    } = nextProps;
    const { map } = this.middleView;
    const isNewTile =
      tile && (!this.props.tile || tile.url !== this.props.tile.url);

    if (
      (active && active !== this.props.active) ||
      !isEqual(settings.date, this.props.settings.date) ||
      !isEqual(settings.weeks, this.props.settings.weeks)
    ) {
      getData({
        latitude: map.getCenter().lng(),
        longitude: map.getCenter().lat(),
        start: dates.start,
        end: dates.end
      });
    }
    if (!active && active !== this.props.active) {
      this.removeLayer();
      this.removeEvents();
      this.removeBoundsPolygon();
    }
    if (isNewTile) {
      if (this.activatedFromUrl && !this.props.tile) {
        this.updateLayer(tile.url);
        this.setEvents();
      } else if (!this.props.tile) {
        this.showLayer(tile.url);
        this.setEvents();
      } else {
        this.updateLayer(tile.url);
      }
      this.addBoundsPolygon(bounds, tile);
    }
    if (
      !dataStatus.haveAllData &&
      showSettings &&
      (showSettings !== this.props.showSettings ||
        dataStatus.requestedTiles !== this.props.dataStatus.requestedTiles ||
        dataStatus.requestFails !== this.props.dataStatus.requestFails ||
        isNewTile)
    ) {
      getMoreTiles({ sources, dataStatus });
    }
  }

  setEvents() {
    const { map } = this.middleView;

    const loadNewTile = () => {
      const { dates, getData } = this.props;
      const zoom = map.getZoom();
      const needNewTile = !google.maps.geometry.poly.containsLocation(
        map.getCenter(),
        this.boundsPolygon
      );
      if (needNewTile) {
        getData({
          latitude: map.getCenter().lng(),
          longitude: map.getCenter().lat(),
          start: dates.start,
          end: dates.end
        });
      }
      if (zoom >= 10) {
        this.boundsPolygon.setOptions({
          fillColor: 'transparent',
          strokeWeight: 0
        });
        this.boundsPolygonInfowindow.close();
      }
    };
    this.mapDragEvent = map.addListener('dragend', loadNewTile);
    this.mapZoomEvent = map.addListener('zoom_changed', loadNewTile);
  }

  removeEvents() {
    google.maps.event.removeListener(this.mapDragEvent);
    google.maps.event.removeListener(this.mapZoomEvent);
  }

  showLayer(url) {
    const { map } = this.middleView;
    const { settings: { layerSlug, minZoom } } = this.props;
    const zoom = map.getZoom();

    this.middleView.toggleLayer(layerSlug, {
      urlTemplate: url
    });
    if (zoom < minZoom) {
      map.setZoom(minZoom);
      this.middleView.showZoomAlert('notification-zoom-go-back', zoom);
    }
  }

  removeLayer() {
    const { settings: { layerSlug }, resetData } = this.props;
    if (!this.removedFromUrl) {
      this.middleView.toggleLayer(layerSlug);
    }
    this.activatedFromUrl = false;
    this.removedFromUrl = false;
    resetData();
  }

  updateLayer(url) {
    const { settings: { layerSlug } } = this.props;
    this.middleView.updateLayer(layerSlug, {
      urlTemplate: url
    });
  }

  addBoundsPolygon(bounds, tile) {
    const { map } = this.middleView;
    const { description } = tile;

    if (this.boundsPolygon !== null) {
      this.removeBoundsPolygon();
    }
    const coords = [];
    bounds.forEach(item => {
      coords.push({
        lat: item[1],
        lng: item[0]
      });
    });

    this.boundsPolygon = new google.maps.Polygon({
      paths: coords,
      fillColor: 'transparent',
      strokeWeight: 0
    });
    const polygonCenter = getPolygonCenter(this.boundsPolygon);

    this.addBoundsPolygonEvents();
    this.boundsPolygon.setMap(map);

    if (this.boundsPolygonInfowindow !== null) {
      this.boundsPolygonInfowindow.close();
    }
    this.boundsPolygonInfowindow = new google.maps.InfoWindow({
      content: `<div class="recent-imagery-infowindow">${description}</div>`,
      position: polygonCenter.top
    });
  }

  addBoundsPolygonEvents() {
    const { setRecentImageryShowSettings } = this.props;
    const { map } = this.middleView;
    let clickTimeout = null;

    google.maps.event.addListener(this.boundsPolygon, 'mouseover', () => {
      this.boundsPolygon.setOptions({
        strokeColor: '#000000',
        strokeOpacity: 0.5,
        strokeWeight: 1
      });
      this.boundsPolygonInfowindow.open(map);
    });
    google.maps.event.addListener(this.boundsPolygon, 'mouseout', () => {
      this.boundsPolygon.setOptions({
        strokeWeight: 0
      });
      this.boundsPolygonInfowindow.close();
    });
    google.maps.event.addListener(this.boundsPolygon, 'click', () => {
      clickTimeout = setTimeout(() => {
        setRecentImageryShowSettings(true);
      }, 200);
    });
    google.maps.event.addListener(this.boundsPolygon, 'dblclick', () => {
      clearTimeout(clickTimeout);
    });
  }

  removeBoundsPolygon() {
    this.boundsPolygon.setMap(null);
  }

  render() {
    return createElement(RecentImageryComponent, {
      ...this.props
    });
  }
}

RecentImageryContainer.propTypes = {
  active: PropTypes.bool,
  showSettings: PropTypes.bool,
  dataStatus: PropTypes.object,
  tile: PropTypes.object,
  bounds: PropTypes.array,
  sources: PropTypes.array,
  dates: PropTypes.object,
  settings: PropTypes.object,
  toogleRecentImagery: PropTypes.func,
  setRecentImageryShowSettings: PropTypes.func,
  getData: PropTypes.func,
  getMoreTiles: PropTypes.func,
  resetData: PropTypes.func
};

export { actions, reducers, initialState };
export default connect(mapStateToProps, actions)(
  RecentImageryDrag(RecentImageryContainer)
);
<|endoftext|>


<commit_before>'use strict';

const should = require('should');

const LeaseManager = require('../lib/lease-manager');

/**
 * A mock SecretProvider that fails to initialize N times before succeeding
 */
class CountingInitializeProvider {

  constructor(count) {
    this.count = count;
  }

  initialize() {
    return new Promise((resolve, reject) => {
      if (this.count > 1) {
        this.count -= 1;
        reject(new Error(`Need ${this.count} more calls to initialize`));
      }
      else {
        resolve({
          data: 'SECRET',
          lease_duration: 1
        });
      }
    });
  }

  renew() {
    return Promise.resolve(true);
  }
}

/**
 * A mock SecretProvider that fails to renew N times before succeeding
 */
class CountingRenewProvider {

  constructor(count) {
    this.count = count;
  }

  initialize() {
    return Promise.resolve({
      data: 'SECRET',
      lease_duration: 1
    });
  }

  renew() {
    return new Promise((resolve, reject) => {
      if (this.count > 1) {
        this.count -= 1;
        reject(new Error(`Need ${this.count} more calls to renew`));
      }
      else {
        resolve({
          lease_duration: 2
        });
      }
    });
  }
}

describe('LeaseManager#constructor', function () {
  it('has a default status of PENDING', function () {
    should(new LeaseManager().status).eql('PENDING');
  });

  it('has default data that is null', function () {
    should(new LeaseManager().data).be.null();
  });

  it('has a default lease duration that is zero', function () {
    should(new LeaseManager().lease_duration).eql(0);
  });

  it('changes status to READY when the provider immediately succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(0));

    return manager.initialize().then(() => {
      manager.status.should.eql('READY');
    });
  });

  it('changes data to a secret when the provider immediately succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(0));

    return manager.initialize().then(() => {
      manager.data.should.eql('SECRET');
    });
  });

  it('changes lease duration to non-zero when the provider immediately succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(0));

    return manager.initialize().then(() => {
      manager.lease_duration.should.eql(1);
    });
  });

  it('change status to ready when the provider eventually succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    return manager.initialize().catch(() => manager.initialize().then(() => {
      manager.status.should.eql('READY');
    }));
  });

  it('change data to a secret when the provider eventually succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    return manager.initialize().catch(() => manager.initialize().then(() => {
      manager.data.should.eql('SECRET');
    }));
  });

  it('change lease duration to non-zero when the provider eventually succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    return manager.initialize().catch(() => manager.initialize().then(() => {
      manager.lease_duration.should.eql(1);
    }));
  });

  it('emits ready event when the provider succeeds', function (done) {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    manager.on('ready', function () {
      should(manager.data).eql('SECRET');
      done();
    });

    manager.initialize().catch(() => manager.initialize());
  });
});

describe('LeaseManager#_renew', function () {
  it('changes lease duration when the provider immediately renews', function (done) {
    const manager = new LeaseManager(new CountingRenewProvider(0));

    manager.once('renewed', () => {
      should(manager.lease_duration).eql(2);
      done();
    });

    manager.initialize();
  });

  it('changes lease duration when the provider eventually renews', function (done) {
    const manager = new LeaseManager(new CountingRenewProvider(2));

    manager.once('renewed', () => {
      should(manager.lease_duration).eql(2);
      done();
    });

    manager.initialize();
  });
});
<commit_msg>Added test to cover LeaseManager#_timer changing only when #_renew() is called with a different lease_duration.<commit_after>'use strict';

const should = require('should');

const LeaseManager = require('../lib/lease-manager');

/**
 * A mock SecretProvider that fails to initialize N times before succeeding
 */
class CountingInitializeProvider {

  constructor(count) {
    this.count = count;
  }

  initialize() {
    return new Promise((resolve, reject) => {
      if (this.count > 1) {
        this.count -= 1;
        reject(new Error(`Need ${this.count} more calls to initialize`));
      }
      else {
        resolve({
          data: 'SECRET',
          lease_duration: 1
        });
      }
    });
  }

  renew() {
    return Promise.resolve(true);
  }
}

/**
 * A mock SecretProvider that fails to renew N times before succeeding
 */
class CountingRenewProvider {

  constructor(count) {
    this.count = count;
  }

  initialize() {
    return Promise.resolve({
      data: 'SECRET',
      lease_duration: 1
    });
  }

  renew() {
    return new Promise((resolve, reject) => {
      if (this.count > 1) {
        this.count -= 1;
        reject(new Error(`Need ${this.count} more calls to renew`));
      }
      else {
        resolve({
          lease_duration: 2
        });
      }
    });
  }
}

class ChangingTimeoutProvider {
  constructor() {
    this.renewed = false;
  }

  initialize() {
    return Promise.resolve({
      data: 'SECRET',
      lease_duration: 1
    });
  }

  renew() {
    if (this.renewed) {
      return Promise.resolve({
        data: 'SECRET',
        lease_duration: 1
      });
    }
    this.renewed = true;
    return Promise.resolve({
      data: 'SECRET',
      lease_duration: 2
    });
  }
}

describe('LeaseManager#constructor', function () {
  it('has a default status of PENDING', function () {
    should(new LeaseManager().status).eql('PENDING');
  });

  it('has default data that is null', function () {
    should(new LeaseManager().data).be.null();
  });

  it('has a default lease duration that is zero', function () {
    should(new LeaseManager().lease_duration).eql(0);
  });

  it('changes status to READY when the provider immediately succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(0));

    return manager.initialize().then(() => {
      manager.status.should.eql('READY');
    });
  });

  it('changes data to a secret when the provider immediately succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(0));

    return manager.initialize().then(() => {
      manager.data.should.eql('SECRET');
    });
  });

  it('changes lease duration to non-zero when the provider immediately succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(0));

    return manager.initialize().then(() => {
      manager.lease_duration.should.eql(1);
    });
  });

  it('change status to ready when the provider eventually succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    return manager.initialize().catch(() => manager.initialize().then(() => {
      manager.status.should.eql('READY');
    }));
  });

  it('change data to a secret when the provider eventually succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    return manager.initialize().catch(() => manager.initialize().then(() => {
      manager.data.should.eql('SECRET');
    }));
  });

  it('change lease duration to non-zero when the provider eventually succeeds', function () {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    return manager.initialize().catch(() => manager.initialize().then(() => {
      manager.lease_duration.should.eql(1);
    }));
  });

  it('emits ready event when the provider succeeds', function (done) {
    const manager = new LeaseManager(new CountingInitializeProvider(2));

    manager.on('ready', function () {
      should(manager.data).eql('SECRET');
      done();
    });

    manager.initialize().catch(() => manager.initialize());
  });
});

describe('LeaseManager#_renew', function () {
  it('changes lease duration when the provider immediately renews', function (done) {
    const manager = new LeaseManager(new CountingRenewProvider(0));

    manager.once('renewed', () => {
      should(manager.lease_duration).eql(2);
      done();
    });

    manager.initialize();
  });

  it('changes lease duration when the provider eventually renews', function (done) {
    const manager = new LeaseManager(new CountingRenewProvider(2));

    manager.once('renewed', () => {
      should(manager.lease_duration).eql(2);
      done();
    });

    manager.initialize();
  });

  it('shouldn\t try to change #_timer unless the token/secret timeout has changed', function (done) {
    const manager = new LeaseManager(new ChangingTimeoutProvider());
    let timer = null,
        renewal = false;

    manager.on('ready', () => {
      timer = manager._timer;
    });

    manager.on('renewed', () => {
      if (!renewal) {
        manager._timer.should.equal(timer);
        timer = manager._timer;
        timer._idleTimeout.should.equal(500); // 1 second / 2
        renewal = true;
      } else {
        manager._timer.should.not.equal(timer);
        manager._timer._idleTimeout.should.equal(1000) // 2 seconds / 2
        done();
      }
    });

    manager.initialize().then(() => {
      manager.initialize();
    })
  });
});
<|endoftext|>


<commit_before>$(document).ready(function() {
	//init Crafty with FPS of 50 and create the canvas element
	Crafty.init();
	Crafty.canvas.init();

	//preload the needed assets
	Crafty.load(["images/sprite.png", "images/bg.png"], function() {
		//splice the spritemap
		Crafty.sprite(64, "images/sprite.png", {
			ship: [0,0],
			big: [1,0],
			medium: [2,0],
			small: [3,0]
		});

		//start the main scene when loaded
		Crafty.scene("main");
	});

	Crafty.scene("main", function() {
		Crafty.background("url('images/bg.png')");

		//score display
		var score = Crafty.e("2D, DOM, Text")
			.text("Score: 0")
			.attr({x: Crafty.viewport.width - 300, y: Crafty.viewport.height - 50, w: 200, h:50})
			.css({color: "#fff"});

		//player entity
		var player = Crafty.e("2D, Canvas, ship, Controls, Collision")
			.attr({move: {left: false, right: false, up: false, down: false}, xspeed: 0, yspeed: 0, decay: 0.9,
				x: Crafty.viewport.width / 2, y: Crafty.viewport.height / 2, score: 0})
			.origin("center")
			.bind("KeyDown", function(e) {
				//on keydown, set the move booleans
				if(e.keyCode === Crafty.keys.RIGHT_ARROW) {
					this.move.right = true;
				} else if(e.keyCode === Crafty.keys.LEFT_ARROW) {
					this.move.left = true;
				} else if(e.keyCode === Crafty.keys.UP_ARROW) {
					this.move.up = true;
				} else if(e.keyCode === Crafty.keys.SPACE) {
					//create a bullet entity
					Crafty.e("2D, DOM, Color, bullet")
						.attr({
							x: this._x,
							y: this._y,
							w: 2,
							h: 5,
							rotation: this._rotation,
							xspeed: 20 * Math.sin(this._rotation / 57.3),
							yspeed: 20 * Math.cos(this._rotation / 57.3)
						})
						.color("rgb(255, 0, 0)")
						.bind("EnterFrame", function() {
							this.x += this.xspeed;
							this.y -= this.yspeed;

							//destroy if it goes out of bounds
							if(this._x > Crafty.viewport.width || this._x < 0 || this._y > Crafty.viewport.height || this._y < 0) {
								this.destroy();
							}
						});
				}
			}).bind("KeyUp", function(e) {
				//on key up, set the move booleans to false
				if(e.keyCode === Crafty.keys.RIGHT_ARROW) {
					this.move.right = false;
				} else if(e.keyCode === Crafty.keys.LEFT_ARROW) {
					this.move.left = false;
				} else if(e.keyCode === Crafty.keys.UP_ARROW) {
					this.move.up = false;
				}
			}).bind("EnterFrame", function() {
				if(this.move.right) this.rotation += 5;
				if(this.move.left) this.rotation -= 5;

				//acceleration and movement vector
				var vx = Math.sin(this._rotation * Math.PI / 180) * 0.3,
					vy = Math.cos(this._rotation * Math.PI / 180) * 0.3;

				//if the move up is true, increment the y/xspeeds
				if(this.move.up) {
					this.yspeed -= vy;
					this.xspeed += vx;
				} else {
					//if released, slow down the ship
					this.xspeed *= this.decay;
					this.yspeed *= this.decay;
				}

				//move the ship by the x and y speeds or movement vector
				this.x += this.xspeed;
				this.y += this.yspeed;

				//if ship goes out of bounds, put him back
				if(this._x > Crafty.viewport.width) {
					this.x = -64;
				}
				if(this._x < -64) {
					this.x =  Crafty.viewport.width;
				}
				if(this._y > Crafty.viewport.height) {
					this.y = -64;
				}
				if(this._y < -64) {
					this.y = Crafty.viewport.height;
				}

				//if all asteroids are gone, start again with more
				if(asteroidCount <= 0) {
					initRocks(lastCount, lastCount * 2);
				}
			}).collision()
			.onHit("asteroid", function() {
				//if player gets hit, restart the game
				Crafty.scene("main");
			});

		//keep a count of asteroids
		var asteroidCount,
			lastCount;

		//Asteroid component
		Crafty.c("asteroid", {
			init: function() {
				this.origin("center");
				this.attr({
					x: Crafty.randRange(0, Crafty.viewport.width), //give it random positions, rotation and speed
					x: Crafty.randRange(0, Crafty.viewport.height),
					xspeed: Crafty.randRange(1, 5),
					yspeed: Crafty.randRange(1, 5),
					rspeed: Crafty.randRange(-5, 5)
				}).bind("EnterFrame", function() {
					this.x += this.xspeed;
					this.y += this.yspeed;
					this.rotation += this.rspeed;

					if(this._x > Crafty.viewport.width) {
						this.x = -64;
					}
					if(this._x < -64) {
						this.x =  Crafty.viewport.width;
					}
					if(this._y > Crafty.viewport.height) {
						this.y = -64;
					}
					if(this._y < -64) {
						this.y = Crafty.viewport.height;
					}
				}).collision()
				.onHit("bullet", function(e) {
					//if hit by a bullet increment the score
					player.score += 5;
					score.text("Score: "+player.score);
					e[0].obj.destroy(); //destroy the bullet

					var size;
					//decide what size to make the asteroid
					if(this.has("big")) {
						this.removeComponent("big").addComponent("medium");
						size = "medium";
					} else if(this.has("medium")) {
						this.removeComponent("medium").addComponent("small");
						size = "small";
					} else if(this.has("small")) { //if the lowest size, delete self
						asteroidCount--;
						this.destroy();
						return;
					}

					var oldxspeed = this.xspeed;
					this.xspeed = -this.yspeed;
					this.yspeed = oldxspeed;

					asteroidCount++;
					//split into two asteroids by creating another asteroid
					Crafty.e("2D, DOM, "+size+", Collision, asteroid").attr({x: this._x, y: this._y});
				});

			}
		});

		//function to fill the screen with asteroids by a random amount
		function initRocks(lower, upper) {
			var rocks = Crafty.randRange(lower, upper);
			asteroidCount = rocks;
			lastCount = rocks;

			for(var i = 0; i < rocks; i++) {
				Crafty.e("2D, DOM, big, Collision, asteroid");
			}
		}
		//first level has between 1 and 10 asteroids
		initRocks(1, 10);
	});

});<commit_msg>minor error. #122<commit_after>$(document).ready(function() {
	//init Crafty with FPS of 50 and create the canvas element
	Crafty.init();
	Crafty.canvas.init();

	//preload the needed assets
	Crafty.load(["images/sprite.png", "images/bg.png"], function() {
		//splice the spritemap
		Crafty.sprite(64, "images/sprite.png", {
			ship: [0,0],
			big: [1,0],
			medium: [2,0],
			small: [3,0]
		});

		//start the main scene when loaded
		Crafty.scene("main");
	});

	Crafty.scene("main", function() {
		Crafty.background("url('images/bg.png')");

		//score display
		var score = Crafty.e("2D, DOM, Text")
			.text("Score: 0")
			.attr({x: Crafty.viewport.width - 300, y: Crafty.viewport.height - 50, w: 200, h:50})
			.css({color: "#fff"});

		//player entity
		var player = Crafty.e("2D, Canvas, ship, Controls, Collision")
			.attr({move: {left: false, right: false, up: false, down: false}, xspeed: 0, yspeed: 0, decay: 0.9,
				x: Crafty.viewport.width / 2, y: Crafty.viewport.height / 2, score: 0})
			.origin("center")
			.bind("KeyDown", function(e) {
				//on keydown, set the move booleans
				if(e.keyCode === Crafty.keys.RIGHT_ARROW) {
					this.move.right = true;
				} else if(e.keyCode === Crafty.keys.LEFT_ARROW) {
					this.move.left = true;
				} else if(e.keyCode === Crafty.keys.UP_ARROW) {
					this.move.up = true;
				} else if(e.keyCode === Crafty.keys.SPACE) {
					//create a bullet entity
					Crafty.e("2D, DOM, Color, bullet")
						.attr({
							x: this._x,
							y: this._y,
							w: 2,
							h: 5,
							rotation: this._rotation,
							xspeed: 20 * Math.sin(this._rotation / 57.3),
							yspeed: 20 * Math.cos(this._rotation / 57.3)
						})
						.color("rgb(255, 0, 0)")
						.bind("EnterFrame", function() {
							this.x += this.xspeed;
							this.y -= this.yspeed;

							//destroy if it goes out of bounds
							if(this._x > Crafty.viewport.width || this._x < 0 || this._y > Crafty.viewport.height || this._y < 0) {
								this.destroy();
							}
						});
				}
			}).bind("KeyUp", function(e) {
				//on key up, set the move booleans to false
				if(e.keyCode === Crafty.keys.RIGHT_ARROW) {
					this.move.right = false;
				} else if(e.keyCode === Crafty.keys.LEFT_ARROW) {
					this.move.left = false;
				} else if(e.keyCode === Crafty.keys.UP_ARROW) {
					this.move.up = false;
				}
			}).bind("EnterFrame", function() {
				if(this.move.right) this.rotation += 5;
				if(this.move.left) this.rotation -= 5;

				//acceleration and movement vector
				var vx = Math.sin(this._rotation * Math.PI / 180) * 0.3,
					vy = Math.cos(this._rotation * Math.PI / 180) * 0.3;

				//if the move up is true, increment the y/xspeeds
				if(this.move.up) {
					this.yspeed -= vy;
					this.xspeed += vx;
				} else {
					//if released, slow down the ship
					this.xspeed *= this.decay;
					this.yspeed *= this.decay;
				}

				//move the ship by the x and y speeds or movement vector
				this.x += this.xspeed;
				this.y += this.yspeed;

				//if ship goes out of bounds, put him back
				if(this._x > Crafty.viewport.width) {
					this.x = -64;
				}
				if(this._x < -64) {
					this.x =  Crafty.viewport.width;
				}
				if(this._y > Crafty.viewport.height) {
					this.y = -64;
				}
				if(this._y < -64) {
					this.y = Crafty.viewport.height;
				}

				//if all asteroids are gone, start again with more
				if(asteroidCount <= 0) {
					initRocks(lastCount, lastCount * 2);
				}
			}).collision()
			.onHit("asteroid", function() {
				//if player gets hit, restart the game
				Crafty.scene("main");
			});

		//keep a count of asteroids
		var asteroidCount,
			lastCount;

		//Asteroid component
		Crafty.c("asteroid", {
			init: function() {
				this.origin("center");
				this.attr({
					x: Crafty.randRange(0, Crafty.viewport.width), //give it random positions, rotation and speed
					y: Crafty.randRange(0, Crafty.viewport.height),
					xspeed: Crafty.randRange(1, 5),
					yspeed: Crafty.randRange(1, 5),
					rspeed: Crafty.randRange(-5, 5)
				}).bind("EnterFrame", function() {
					this.x += this.xspeed;
					this.y += this.yspeed;
					this.rotation += this.rspeed;

					if(this._x > Crafty.viewport.width) {
						this.x = -64;
					}
					if(this._x < -64) {
						this.x =  Crafty.viewport.width;
					}
					if(this._y > Crafty.viewport.height) {
						this.y = -64;
					}
					if(this._y < -64) {
						this.y = Crafty.viewport.height;
					}
				}).collision()
				.onHit("bullet", function(e) {
					//if hit by a bullet increment the score
					player.score += 5;
					score.text("Score: "+player.score);
					e[0].obj.destroy(); //destroy the bullet

					var size;
					//decide what size to make the asteroid
					if(this.has("big")) {
						this.removeComponent("big").addComponent("medium");
						size = "medium";
					} else if(this.has("medium")) {
						this.removeComponent("medium").addComponent("small");
						size = "small";
					} else if(this.has("small")) { //if the lowest size, delete self
						asteroidCount--;
						this.destroy();
						return;
					}

					var oldxspeed = this.xspeed;
					this.xspeed = -this.yspeed;
					this.yspeed = oldxspeed;

					asteroidCount++;
					//split into two asteroids by creating another asteroid
					Crafty.e("2D, DOM, "+size+", Collision, asteroid").attr({x: this._x, y: this._y});
				});

			}
		});

		//function to fill the screen with asteroids by a random amount
		function initRocks(lower, upper) {
			var rocks = Crafty.randRange(lower, upper);
			asteroidCount = rocks;
			lastCount = rocks;

			for(var i = 0; i < rocks; i++) {
				Crafty.e("2D, DOM, big, Collision, asteroid");
			}
		}
		//first level has between 1 and 10 asteroids
		initRocks(1, 10);
	});

});<|endoftext|>


<commit_before>'use strict';

const assert = require('assert');
const Dashycode = require('./../../.lib-dist/dashycode');

describe('Dashycode', function () {
	// Technically we should be testing for values up to 0x10FFFF, but we will
	// never see any above 0xFFFF because of how SockJS works.
	const codepoints = Array.from({length: 0x10000}, (v, k) => k);
	const encoded = new Map();

	const encode = (codepoint) => {
		const character = String.fromCodePoint(codepoint);
		const dashycode = Dashycode.encode(character);
		assert.strictEqual(encoded.has(dashycode), false);
		encoded.set(dashycode, character);
	};

	const decode = (dashycode) => {
		const character = Dashycode.decode(dashycode);
		assert.strictEqual(encoded.get(dashycode), character);
	};

	it('should encode all codepoints uniquely', function () {
		return codepoints.reduce((p, codepoint) => (
			p.then(v => encode(codepoint))
		), Promise.resolve());
	});

	it('should decode all codepoints accurately', function () {
		return [...encoded.keys()].reduce((p, dashycode) => (
			p.then(v => decode(dashycode))
		), Promise.resolve());
	});

	after(function () {
		encoded.clear();
	});
});
<commit_msg>Correct comment about codepoints in Dashycode unit tests<commit_after>'use strict';

const assert = require('assert');
const Dashycode = require('./../../.lib-dist/dashycode');

describe('Dashycode', function () {
	// Technically we should be testing for values up to 0x10FFFF, but since
	// node.js uses UTF-16, codepoints higher than 0xFFFF would be represented
	// as surrogate pairs, making it pointless to test them since we've already
	// tested the surrogate codepoints.
	const codepoints = Array.from({length: 0x10000}, (v, k) => k);
	const encoded = new Map();

	const encode = (codepoint) => {
		const character = String.fromCodePoint(codepoint);
		const dashycode = Dashycode.encode(character);
		assert.strictEqual(encoded.has(dashycode), false);
		encoded.set(dashycode, character);
	};

	const decode = (dashycode) => {
		const character = Dashycode.decode(dashycode);
		assert.strictEqual(encoded.get(dashycode), character);
	};

	it('should encode all codepoints uniquely', function () {
		return codepoints.reduce((p, codepoint) => (
			p.then(v => encode(codepoint))
		), Promise.resolve());
	});

	it('should decode all codepoints accurately', function () {
		return [...encoded.keys()].reduce((p, dashycode) => (
			p.then(v => decode(dashycode))
		), Promise.resolve());
	});

	after(function () {
		encoded.clear();
	});
});
<|endoftext|>