<commit_before>const { Configuration } = require('@schul-cloud/commons');
const { authenticate } = require('@feathersjs/authentication');
const { disallow } = require('feathers-hooks-common');
const { NotFound } = require('@feathersjs/errors');

const validateReference = (hook) => {
    if (!hook || !hook.params || !hook.params.query || !hook.params.query['merlinReference']) {
        throw new NotFound(`Missing query params: {merlinReference: fooBar}`)
    }
    return hook
}

exports.before = {
    all: [authenticate('jwt')],
    find: [validateReference],
    get: [disallow()],
    create: [disallow()],
    update: [disallow()],
    patch: [disallow()],
    remove: [disallow()],
};

exports.after = {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
};
<commit_msg>Add indentationToTabs<commit_after>const { authenticate } = require('@feathersjs/authentication');
const { disallow } = require('feathers-hooks-common');
const { NotFound } = require('@feathersjs/errors');

const validateReference = (hook) => {
	if (!hook || !hook.params || !hook.params.query || !hook.params.query.merlinReference) {
		throw new NotFound(`Missing query params: {merlinReference: fooBar}`)
	}
	return hook
}

exports.before = {
	all: [authenticate('jwt')],
	find: [validateReference],
	get: [disallow()],
	create: [disallow()],
	update: [disallow()],
	patch: [disallow()],
	remove: [disallow()],
};

exports.after = {
	all: [],
	find: [],
	get: [],
	create: [],
	update: [],
	patch: [],
	remove: [],
};
<|endoftext|>


<commit_before>// LICENSE : MIT
"use strict";
/*
    this module work on Main(Node.js) Context.
 */
import Consumer from "./TwitterConsumer";
import NodeTwitterApi from "node-twitter-api";
import { BrowserWindow, session } from "electron";
import storage from "../../node/storage/accounts";

const twitter = new NodeTwitterApi({
    callback: Consumer.callbackURL,
    consumerKey: Consumer.key,
    consumerSecret: Consumer.secret
});
exports.canAccess = function canAccess() {
    return storage.has("twitter");
};
exports.getCredential = function getCredential() {
    return storage.get("twitter");
};
exports.requireAccess = function requireAccess(callback) {
    console.log(Consumer);
    twitter.getRequestToken((_error, reqToken, reqTokenSecret) => {
        const authUrl = twitter.getAuthUrl(reqToken);
        const loginWindow = new BrowserWindow({ width: 800, height: 600, "node-integration": false });

        const filter = {
            urls: ["*://*/*oauth_token"]
        };
        session.defaultSession.webRequest.onCompleted(filter, details => {
            const url = details.url;
            handleURL(url);
        });
        const handleURL = (url, preventDefault) => {
            const closeWindow = () => setTimeout(() => loginWindow.close(), 0);
            let matched;
            if ((matched = url.match(/\?oauth_token=([^&]*)&oauth_verifier=([^&]*)/))) {
                twitter.getAccessToken(
                    reqToken,
                    reqTokenSecret,
                    matched[2],
                    (__error, accessToken, accessTokenSecret) => {
                        var credential = {
                            accessKey: accessToken,
                            accessSecret: accessTokenSecret
                        };
                        storage.set("twitter", credential);
                        closeWindow();
                        if (callback) {
                            callback(null, credential);
                        }
                    }
                );
                if (preventDefault) {
                    preventDefault();
                }
            }
        };
        loginWindow.webContents.on("will-navigate", (event, url) => {
            handleURL(url, () => event.preventDefault());
        });
        loginWindow.loadURL(authUrl);
    });
};
<commit_msg>fix: use default session after loading<commit_after>// LICENSE : MIT
"use strict";
/*
    this module work on Main(Node.js) Context.
 */
import Consumer from "./TwitterConsumer";
import NodeTwitterApi from "node-twitter-api";
import { BrowserWindow, session } from "electron";
import storage from "../../node/storage/accounts";

const twitter = new NodeTwitterApi({
    callback: Consumer.callbackURL,
    consumerKey: Consumer.key,
    consumerSecret: Consumer.secret
});
exports.canAccess = function canAccess() {
    return storage.has("twitter");
};
exports.getCredential = function getCredential() {
    return storage.get("twitter");
};
exports.requireAccess = function requireAccess(callback) {
    console.log(Consumer);
    twitter.getRequestToken((_error, reqToken, reqTokenSecret) => {
        const authUrl = twitter.getAuthUrl(reqToken);
        const loginWindow = new BrowserWindow({ width: 800, height: 600, "node-integration": false });

        const handleURL = (url, preventDefault) => {
            const closeWindow = () => setTimeout(() => loginWindow.close(), 0);
            let matched;
            if ((matched = url.match(/\?oauth_token=([^&]*)&oauth_verifier=([^&]*)/))) {
                twitter.getAccessToken(
                    reqToken,
                    reqTokenSecret,
                    matched[2],
                    (__error, accessToken, accessTokenSecret) => {
                        var credential = {
                            accessKey: accessToken,
                            accessSecret: accessTokenSecret
                        };
                        storage.set("twitter", credential);
                        closeWindow();
                        if (callback) {
                            callback(null, credential);
                        }
                    }
                );
                if (preventDefault) {
                    preventDefault();
                }
            }
        };
        loginWindow.webContents.on("will-navigate", (event, url) => {
            handleURL(url, () => event.preventDefault());
        });
        loginWindow.loadURL(authUrl);

        const filter = {
            urls: ["*://*/*oauth_token"]
        };
        session.defaultSession.webRequest.onCompleted(filter, details => {
            const url = details.url;
            handleURL(url);
        });
    });
};
<|endoftext|>


<commit_before>import './SearchBox.css';
import React, { Component, PropTypes } from 'react';
import Autosuggest from 'react-autosuggest';
import { connect } from 'react-redux';
import MaterialIcon from 'shared/components/icon/MaterialIcon';
import { updateParams, navigate } from 'shared/state/search/actions';
import { fetch as fetchSuggestions, reset as resetSuggestions } from 'shared/state/search-suggestions/actions';
import SearchBoxHelp from './SearchBoxHelp';

const placeholderSuggestions = [
    'test',
    'couchdb',
    'test framework',
    'socket',
    'promises',
];

class SearchBox extends Component {
    constructor(props) {
        super(props);
        this._handleWindowKeyDown = this._handleWindowKeyDown.bind(this);
    }

    componentWillMount() {
        this._placeholder = `Search packages, like "${placeholderSuggestions[Math.floor(Math.random() * placeholderSuggestions.length)]}"`;
        this._inputValue = this.props.initiallyEmpty ? '' : this.props.q;
    }

    componentDidMount() {
        window.addEventListener('keydown', this._handleWindowKeyDown);

        // Focus on desktop devices automatically
        // The non-mobile device detecting below is pretty naive but works for most cases
        // Adding a 20kb library to detect mobile devices is just not worth it
        if (this.props.initiallyFocused && !('onorientationchange' in window)) {
            this._inputEl.focus();
        }
    }

    componentWillUpdate(nextProps) {
        if (nextProps.q !== this._inputValue) {
            this._inputValue = nextProps.q;
        }
    }

    componentWillUnmount() {
        window.removeEventListener('keydown', this._handleWindowKeyDown);
    }

    render() {
        return (
            <form className="search-box-component" onSubmit={ (e) => this._handleSubmit(e) }>

                    <Autosuggest
                        id="search-box-autosuggest"
                        suggestions={ this.props.suggestions || [] }
                        getSuggestionValue={ (suggestion) => suggestion.package.name }
                        renderSuggestion={ (suggestion) => this._renderSuggestion(suggestion) }
                        ref={ (ref) => { this._inputEl = ref && ref.input; } }
                        onSuggestionsFetchRequested={ (request) => this._handleSuggestionsFetchRequested(request) }
                        onSuggestionsClearRequested={ () => this._handleSuggestionsClearRequested() }
                        onSuggestionSelected={ (e, selected) => this._handleSuggestionSelected(e, selected) }
                        focusInputOnSuggestionClick={ false }
                        inputProps={ {
                            value: this._inputValue,
                            placeholder: this._placeholder,
                            onChange: (e, { newValue }) => this._handleInputChange(newValue),
                        } }
                        theme={ {
                            container: 'autosuggest-component-container',
                            containerOpen: 'is-open',
                            input: 'autosuggest-component-input',
                            suggestionsContainer: 'autosuggest-component-suggestions-container',
                            suggestionsList: 'autosuggest-component-suggestions-list',
                            suggestion: 'autosuggest-component-suggestion',
                            suggestionFocused: 'is-focused',
                            sectionContainer: 'autosuggest-component-section-container',
                            sectionTitle: 'autosuggest-component-section-title',
                            sectionSuggestionsContainer: 'autosuggest-component-section-suggestions-container',
                        } } />

                    <SearchBoxHelp />

                    <button ref={ (ref) => { this._buttonEl = ref; } }>
                        <MaterialIcon id="search" />
                    </button>

            </form>
        );
    }

    // ---------------------------------------------

    _renderSuggestion(suggestion) {
        return (

                { suggestion.highlight ?

 :

{ suggestion.package.name }

                }

{ suggestion.package.description }

        );
    }

    _handleInputChange(newValue) {
        this.props.dispatch(updateParams({ q: newValue }));
    }

    _handleSubmit(e) {
        e.preventDefault();
        this._inputEl.blur();  // Ensure keyboard is hidden in mobile
        this.props.dispatch(navigate());
    }

    _handleWindowKeyDown(e) {
        // Skip if `focusOnKeyDown` is not enabled or if something else is already focused
        // Also ctrl & meta keys are ignored to allow copying to clipboard
        if (this.props.focusOnKeyDown && !e.ctrlKey && !e.metaKey && !e.target.matches('input,textarea,select')) {
            this._inputEl.focus();
        }
    }

    _handleSuggestionsFetchRequested(request) {
        // TODO: Should we throttle this?
        this.props.dispatch(fetchSuggestions(request.value));
    }

    _handleSuggestionsClearRequested() {
        this.props.dispatch(resetSuggestions());
    }

    _handleSuggestionSelected(e, selected) {
        this.props.dispatch(updateParams({ q: selected.suggestionValue }));
        this._buttonEl.click();  // Submit the form (calling submit() was not working with react)
    }
}

SearchBox.propTypes = {
    dispatch: PropTypes.func.isRequired,
    q: PropTypes.string.isRequired,
    suggestions: PropTypes.array,

    initiallyEmpty: PropTypes.bool,
    initiallyFocused: PropTypes.bool,
    focusOnKeyDown: PropTypes.bool,
};

SearchBox.defaultProps = {
    initiallyEmpty: false,
    initiallyFocused: false,
    focusOnKeyDown: false,
};

export default connect((state, ownProps) => ({
    ...ownProps,
    q: state.search.params.q,
    suggestions: state.searchSuggestions.results,
}))(SearchBox);
<commit_msg>Disable autocapitalize & autocorrect for mobile users.<commit_after>import './SearchBox.css';
import React, { Component, PropTypes } from 'react';
import Autosuggest from 'react-autosuggest';
import { connect } from 'react-redux';
import MaterialIcon from 'shared/components/icon/MaterialIcon';
import { updateParams, navigate } from 'shared/state/search/actions';
import { fetch as fetchSuggestions, reset as resetSuggestions } from 'shared/state/search-suggestions/actions';
import SearchBoxHelp from './SearchBoxHelp';

const placeholderSuggestions = [
    'test',
    'couchdb',
    'test framework',
    'socket',
    'promises',
];

class SearchBox extends Component {
    constructor(props) {
        super(props);
        this._handleWindowKeyDown = this._handleWindowKeyDown.bind(this);
    }

    componentWillMount() {
        this._placeholder = `Search packages, like "${placeholderSuggestions[Math.floor(Math.random() * placeholderSuggestions.length)]}"`;
        this._inputValue = this.props.initiallyEmpty ? '' : this.props.q;
    }

    componentDidMount() {
        window.addEventListener('keydown', this._handleWindowKeyDown);

        // Focus on desktop devices automatically
        // The non-mobile device detecting below is pretty naive but works for most cases
        // Adding a 20kb library to detect mobile devices is just not worth it
        if (this.props.initiallyFocused && !('onorientationchange' in window)) {
            this._inputEl.focus();
        }
    }

    componentWillUpdate(nextProps) {
        if (nextProps.q !== this._inputValue) {
            this._inputValue = nextProps.q;
        }
    }

    componentWillUnmount() {
        window.removeEventListener('keydown', this._handleWindowKeyDown);
    }

    render() {
        return (
            <form className="search-box-component" onSubmit={ (e) => this._handleSubmit(e) }>

                    <Autosuggest
                        id="search-box-autosuggest"
                        suggestions={ this.props.suggestions || [] }
                        getSuggestionValue={ (suggestion) => suggestion.package.name }
                        renderSuggestion={ (suggestion) => this._renderSuggestion(suggestion) }
                        ref={ (ref) => { this._inputEl = ref && ref.input; } }
                        onSuggestionsFetchRequested={ (request) => this._handleSuggestionsFetchRequested(request) }
                        onSuggestionsClearRequested={ () => this._handleSuggestionsClearRequested() }
                        onSuggestionSelected={ (e, selected) => this._handleSuggestionSelected(e, selected) }
                        focusInputOnSuggestionClick={ false }
                        inputProps={ {
                            value: this._inputValue,
                            placeholder: this._placeholder,
                            onChange: (e, { newValue }) => this._handleInputChange(newValue),
                            autoCapitalize: 'none',
                            autoCorrect: 'off',
                        } }
                        theme={ {
                            container: 'autosuggest-component-container',
                            containerOpen: 'is-open',
                            input: 'autosuggest-component-input',
                            suggestionsContainer: 'autosuggest-component-suggestions-container',
                            suggestionsList: 'autosuggest-component-suggestions-list',
                            suggestion: 'autosuggest-component-suggestion',
                            suggestionFocused: 'is-focused',
                            sectionContainer: 'autosuggest-component-section-container',
                            sectionTitle: 'autosuggest-component-section-title',
                            sectionSuggestionsContainer: 'autosuggest-component-section-suggestions-container',
                        } } />

                    <SearchBoxHelp />

                    <button ref={ (ref) => { this._buttonEl = ref; } }>
                        <MaterialIcon id="search" />
                    </button>

            </form>
        );
    }

    // ---------------------------------------------

    _renderSuggestion(suggestion) {
        return (

                { suggestion.highlight ?

 :

{ suggestion.package.name }

                }

{ suggestion.package.description }

        );
    }

    _handleInputChange(newValue) {
        this.props.dispatch(updateParams({ q: newValue }));
    }

    _handleSubmit(e) {
        e.preventDefault();
        this._inputEl.blur();  // Ensure keyboard is hidden in mobile
        this.props.dispatch(navigate());
    }

    _handleWindowKeyDown(e) {
        // Skip if `focusOnKeyDown` is not enabled or if something else is already focused
        // Also ctrl & meta keys are ignored to allow copying to clipboard
        if (this.props.focusOnKeyDown && !e.ctrlKey && !e.metaKey && !e.target.matches('input,textarea,select')) {
            this._inputEl.focus();
        }
    }

    _handleSuggestionsFetchRequested(request) {
        // TODO: Should we throttle this?
        this.props.dispatch(fetchSuggestions(request.value));
    }

    _handleSuggestionsClearRequested() {
        this.props.dispatch(resetSuggestions());
    }

    _handleSuggestionSelected(e, selected) {
        this.props.dispatch(updateParams({ q: selected.suggestionValue }));
        this._buttonEl.click();  // Submit the form (calling submit() was not working with react)
    }
}

SearchBox.propTypes = {
    dispatch: PropTypes.func.isRequired,
    q: PropTypes.string.isRequired,
    suggestions: PropTypes.array,

    initiallyEmpty: PropTypes.bool,
    initiallyFocused: PropTypes.bool,
    focusOnKeyDown: PropTypes.bool,
};

SearchBox.defaultProps = {
    initiallyEmpty: false,
    initiallyFocused: false,
    focusOnKeyDown: false,
};

export default connect((state, ownProps) => ({
    ...ownProps,
    q: state.search.params.q,
    suggestions: state.searchSuggestions.results,
}))(SearchBox);
<|endoftext|>


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

define(function(require) {
  /* DEPENDENCIES */

  var Tree = require('./tree');
  var Sunstone = require('sunstone');
  var OpenNebula = require('opennebula');
  var Locale = require('utils/locale');
  var Notifier = require('utils/notifier');

  var LABELS_ATTR = 'LABELS';

  return {
    'labelsStr': _labelsStr,
    'deserializeLabels': _deserializeLabels,
    'makeTree': _makeTree,
    'insertLabelsMenu': _insertLabelsMenu,
    'insertLabelsDropdown': _insertLabelsDropdown,
    'clearLabelsFilter': _clearLabelsFilter,
    'setLabelsFilter': _setLabelsFilter,
    'getLabels': _getLabels,
    'getLabel': _getLabel
  };

  /* FUNCTION DEFINITIONS */

  /*
    Add labels tree to the left menu
   */
  function _insertLabelsMenu(context, dataTable, labelsColumn, labelsPath) {
    var labels = _getLabels(dataTable, labelsColumn, labelsPath);
    $('.labels-tree', context).remove();
    context.append(Tree.html(_makeTree(labels), true));
    Tree.setup($('.labels-tree', context));

    /*
      Filter datatable when a label in the left menu is clicked
     */
    context.off('click', '.one-label');
    context.on('click', '.one-label', function() {
      var regExp = [];
      var label = $(this).attr('one-label-full-name');
      regExp.push('^' + label + '$');
      regExp.push(',' + label + '$');
      regExp.push('^' + label + ',');
      regExp.push(',' + label + ',');
      _setLabelsFilter(dataTable, labelsColumn, regExp.join('|'));
      return false;
    });
  }

  /*
    Generate labels dropdown
   */
  function _insertLabelsDropdown(tabName) {
    var tabTable = Sunstone.getDataTable(tabName);
    var dataTable = tabTable.dataTable;
    var labelsColumn = tabTable.labelsColumn;

    var labels = _getLabels(dataTable, labelsColumn);
    $('#' + tabName + 'LabelsDropdown').html(
      '
' +
      '
' + Locale.tr('Edit Labels') + '
' +
      Tree.html(_makeTree(labels), false) +
      '
' +
        '<input type="text" class="newLabelInput" placeholder="' + Locale.tr("Add Label") + '"/>' +
      '
' +
      '
');

    /*
      Update Dropdown with selected items
      [v] If all the selected items has a label
      [-] If any of the selected items has a label
      [ ] If no selected item has an existing label
     */
    function recountLabels() {
      // Generate Hash with labels and number of items
      var labelsStr, labelsIndexed = {};

      var selectedItems = tabTable.elements();
      $.each(selectedItems, function(index, resourceId) {
        labelsStr = _getLabel(dataTable, labelsColumn, resourceId);
        if (labelsStr != '') {
          $.each(labelsStr.split(','), function(){
            if (labelsIndexed[this]) {
              labelsIndexed[this] += 1
            } else {
              labelsIndexed[this] = 1
            }
          })
        }
      });

      // Set checkboxes (check|minus) depending on the number of items
      var labelsDropdown = $('#' + tabName + 'LabelsDropdown');

      // Reset label checkboxes
      $('.labelsCheckbox', labelsDropdown)
        .removeClass('fa-minus-square-o')
        .removeClass('fa-check-square-o')
        .addClass('fa-square-o');

      var labelsCheckbox;
      $.each(labelsIndexed, function(labelName, numberOfItems) {
        labelsCheckbox = $('.labelsCheckbox',
          $('[one-label-full-name="' + labelName + '"]', labelsDropdown).closest('li'));
        if (labelsCheckbox.length > 0) {
          if (numberOfItems == selectedItems.length) {
            $(labelsCheckbox[0])
              .removeClass('fa-square-o')
              .addClass('fa-check-square-o');
          } else {
            $(labelsCheckbox[0])
              .removeClass('fa-square-o')
              .addClass('fa-minus-square-o');
          }
        }
      });

      $('.newLabelInput', labelsDropdown).focus();
    }

    recountLabels();
    $('[data-dropdown="' + tabName + 'LabelsDropdown"]').off('click');
    $('[data-dropdown="' + tabName + 'LabelsDropdown"]').on('click', function(){
      recountLabels();
    });

    /*
      Check/Uncheck label & Update Templates
     */
    $('#' + tabName + 'LabelsDropdown').off('click', '.labelsCheckbox');
    $('#' + tabName + 'LabelsDropdown').on('click', '.labelsCheckbox', function() {
      var action;
      if ($(this).hasClass('fa-square-o')) {
        action = 'add';
        $(this).removeClass('fa-square-o').addClass('fa-check-square-o');
      } else {
        action = 'remove';
        $(this).removeClass('fa-check-square-o fa-minus-square-o').addClass('fa-square-o');
      }

      var labelName = $('.one-label', $(this).parent('li')).attr('one-label-full-name');
      var labelsArray, labelIndex;
      var selectedItems = tabTable.elements();
      $.each(selectedItems, function(index, resourceId) {
        labelsStr = _getLabel(dataTable, labelsColumn, resourceId);
        if (labelsStr != '') {
          labelsArray = labelsStr.split(',')
        } else {
          labelsArray = []
        }

        labelIndex = $.inArray(labelName, labelsArray);
        if (action == 'add' && labelIndex == -1) {
          labelsArray.push(labelName)
          _updateResouceLabels(tabName, resourceId, labelsArray);
        } else if (action == 'remove' && labelIndex != -1) {
          labelsArray.splice(labelIndex, 1);
          _updateResouceLabels(tabName, resourceId, labelsArray);
        }
      });
    });

    /*
      Add a new label when ENTER is presed in the input
     */
    $('#' + tabName + 'LabelsDropdown').off('keypress', '.newLabelInput');
    $('#' + tabName + 'LabelsDropdown').on('keypress', '.newLabelInput', function(e) {
      var ev = e || window.event;
      var key = ev.keyCode;

      if (key == 13 && !ev.altKey) {
        var labelName = $(this).val();
        var labelsArray, labelIndex;
        var selectedItems = tabTable.elements();
        $.each(selectedItems, function(index, resourceId) {
          labelsStr = _getLabel(dataTable, labelsColumn, resourceId);
          if (labelsStr != '') {
            labelsArray = labelsStr.split(',')
          } else {
            labelsArray = []
          }

          labelIndex = $.inArray(labelName, labelsArray);
          if (labelIndex == -1) {
            labelsArray.push(labelName);
            _updateResouceLabels(tabName, resourceId, labelsArray);
          }
        });

        ev.preventDefault();
      }
    });
  }

  function _updateResouceLabels(tabName, resourceId, labelsArray) {
    var templateStr = LABELS_ATTR + '="' + labelsArray.join(',') + '"';
    var resource = Sunstone.getResource(tabName);
    var tabTable = Sunstone.getDataTable(tabName);

    OpenNebula[resource].append({
      timeout: true,
      data : {
          id: resourceId,
          extra_param: templateStr
      },
      success: function(request) {
        OpenNebula[resource].show({
          timeout: true,
          data : {
              id: resourceId
          },
          success: function(request, response) {
            tabTable.updateElement(request, response);
            if (Sunstone.rightInfoVisible($('#' + tabName))) {
              Sunstone.insertPanels(tabName, response);
            }

            _insertLabelsMenu(tabName);
            _insertLabelsDropdown(tabName);
          },
          error: Notifier.onError
        });
      },
      error: Notifier.onError
    })
  }

  function _labelsStr(elementTemplate) {
    return elementTemplate[LABELS_ATTR];
  }

  function _deserializeLabels(labelsStr) {
    var indexedLabels = {};

    if (labelsStr) {
      var parent;
      $.each(labelsStr.split(','), function() {
        parent = indexedLabels;
        $.each(this.split('/'), function() {
          if (parent[this] == undefined) {
            parent[this] = {};
          }
          parent = parent[this];
        });
      });
    }

    return indexedLabels;
  }

  function _makeTree(indexedLabels) {
    var treeRoot = {
      htmlStr : '',
      subTree : []
    };

    $.each(indexedLabels, function(folderName, childs) {
      treeRoot.subTree.push(_makeSubTree('', folderName, childs));
    });

    return treeRoot;
  }

  function _makeSubTree(parentName, folderName, childs) {
    var fullName = parentName + folderName;
    var htmlStr =
      '' +
        folderName +
      '';

    var tree = {
      htmlStr: htmlStr,
      subTree: []
    };

    $.each(childs, function(subFolderName, subChilds) {
      tree.subTree.push(_makeSubTree(fullName + '/', subFolderName, subChilds));
    });

    return tree;
  }

  /*
    dataTable Filters
   */

  function _setLabelsFilter(dataTable, labelsColumn, regExp) {
    dataTable.fnFilter(regExp, labelsColumn, true, false);
  }

  function _clearLabelsFilter(dataTable, labelsColumn) {
    dataTable.fnFilter('', labelsColumn, true, false);
  }

  function _getLabels(dataTable, labelsColumn, labelsPath) {
    var labels = [];
    var tmp;
    $.each(dataTable.fnGetData(), function() {
      if (labelsPath) {
        tmp = this;
        $.each(labelsPath.split('.'), function() {
          if (tmp) {
            tmp = tmp[this];
          }
        });
        if (tmp && tmp != '') {
          labels.push(tmp);
        }
      } else {
        if (this[labelsColumn] != '') {
          labels.push(this[labelsColumn]);
        }
      }
    })
    return _deserializeLabels(labels.join(','));
  }

  function _getLabel(dataTable, labelsColumn, resourceId) {
    var nodes = dataTable.fnGetNodes();
    var tr = $('.check_item[value="' + resourceId + '"]', nodes).closest('tr');
    var aData = dataTable.fnGetData(tr);
    return aData[labelsColumn];
  }
});
<commit_msg>feature #4184: Update drowpdown after adding a label<commit_after>/* -------------------------------------------------------------------------- */
/* Copyright 2002-2015, OpenNebula Project, OpenNebula Systems                */
/*                                                                            */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may    */
/* not use this file except in compliance with the License. You may obtain    */
/* a copy of the License at                                                   */
/*                                                                            */
/* http://www.apache.org/licenses/LICENSE-2.0                                 */
/*                                                                            */
/* Unless required by applicable law or agreed to in writing, software        */
/* distributed under the License is distributed on an "AS IS" BASIS,          */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   */
/* See the License for the specific language governing permissions and        */
/* limitations under the License.                                             */
/* -------------------------------------------------------------------------- */

define(function(require) {
  /* DEPENDENCIES */

  var Tree = require('./tree');
  var Sunstone = require('sunstone');
  var OpenNebula = require('opennebula');
  var Locale = require('utils/locale');
  var Notifier = require('utils/notifier');

  var LABELS_ATTR = 'LABELS';

  return {
    'labelsStr': _labelsStr,
    'deserializeLabels': _deserializeLabels,
    'makeTree': _makeTree,
    'insertLabelsMenu': _insertLabelsMenu,
    'insertLabelsDropdown': _insertLabelsDropdown,
    'clearLabelsFilter': _clearLabelsFilter,
    'setLabelsFilter': _setLabelsFilter,
    'getLabels': _getLabels,
    'getLabel': _getLabel
  };

  /* FUNCTION DEFINITIONS */

  /*
    Add labels tree to the left menu
   */
  function _insertLabelsMenu(context, dataTable, labelsColumn, labelsPath) {
    var labels = _getLabels(dataTable, labelsColumn, labelsPath);
    $('.labels-tree', context).remove();
    context.append(Tree.html(_makeTree(labels), true));
    Tree.setup($('.labels-tree', context));

    /*
      Filter datatable when a label in the left menu is clicked
     */
    context.off('click', '.one-label');
    context.on('click', '.one-label', function() {
      var regExp = [];
      var label = $(this).attr('one-label-full-name');
      regExp.push('^' + label + '$');
      regExp.push(',' + label + '$');
      regExp.push('^' + label + ',');
      regExp.push(',' + label + ',');
      _setLabelsFilter(dataTable, labelsColumn, regExp.join('|'));
      return false;
    });
  }

  /*
    Generate labels dropdown
   */
  function _insertLabelsDropdown(tabName) {
    var tabTable = Sunstone.getDataTable(tabName);
    var dataTable = tabTable.dataTable;
    var labelsColumn = tabTable.labelsColumn;

    var labels = _getLabels(dataTable, labelsColumn);
    $('#' + tabName + 'LabelsDropdown').html(
      '
' +
      '
' + Locale.tr('Edit Labels') + '
' +
      Tree.html(_makeTree(labels), false) +
      '
' +
        '<input type="text" class="newLabelInput" placeholder="' + Locale.tr("Add Label") + '"/>' +
      '
' +
      '
');

    /*
      Update Dropdown with selected items
      [v] If all the selected items has a label
      [-] If any of the selected items has a label
      [ ] If no selected item has an existing label
     */
    function recountLabels() {
      // Generate Hash with labels and number of items
      var labelsStr, labelsIndexed = {};

      var selectedItems = tabTable.elements();
      $.each(selectedItems, function(index, resourceId) {
        labelsStr = _getLabel(dataTable, labelsColumn, resourceId);
        if (labelsStr != '') {
          $.each(labelsStr.split(','), function(){
            if (labelsIndexed[this]) {
              labelsIndexed[this] += 1
            } else {
              labelsIndexed[this] = 1
            }
          })
        }
      });

      // Set checkboxes (check|minus) depending on the number of items
      var labelsDropdown = $('#' + tabName + 'LabelsDropdown');

      // Reset label checkboxes
      $('.labelsCheckbox', labelsDropdown)
        .removeClass('fa-minus-square-o')
        .removeClass('fa-check-square-o')
        .addClass('fa-square-o');

      var labelsCheckbox;
      $.each(labelsIndexed, function(labelName, numberOfItems) {
        labelsCheckbox = $('.labelsCheckbox',
          $('[one-label-full-name="' + labelName + '"]', labelsDropdown).closest('li'));
        if (labelsCheckbox.length > 0) {
          if (numberOfItems == selectedItems.length) {
            $(labelsCheckbox[0])
              .removeClass('fa-square-o')
              .addClass('fa-check-square-o');
          } else {
            $(labelsCheckbox[0])
              .removeClass('fa-square-o')
              .addClass('fa-minus-square-o');
          }
        }
      });

      $('.newLabelInput', labelsDropdown).focus();
    }

    recountLabels();
    $('[data-dropdown="' + tabName + 'LabelsDropdown"]').off('click');
    $('[data-dropdown="' + tabName + 'LabelsDropdown"]').on('click', function(){
      recountLabels();
    });

    /*
      Check/Uncheck label & Update Templates
     */
    $('#' + tabName + 'LabelsDropdown').off('click', '.labelsCheckbox');
    $('#' + tabName + 'LabelsDropdown').on('click', '.labelsCheckbox', function() {
      var action;
      if ($(this).hasClass('fa-square-o')) {
        action = 'add';
        $(this).removeClass('fa-square-o').addClass('fa-check-square-o');
      } else {
        action = 'remove';
        $(this).removeClass('fa-check-square-o fa-minus-square-o').addClass('fa-square-o');
      }

      var labelName = $('.one-label', $(this).parent('li')).attr('one-label-full-name');
      var labelsArray, labelIndex;
      var selectedItems = tabTable.elements();
      $.each(selectedItems, function(index, resourceId) {
        labelsStr = _getLabel(dataTable, labelsColumn, resourceId);
        if (labelsStr != '') {
          labelsArray = labelsStr.split(',')
        } else {
          labelsArray = []
        }

        labelIndex = $.inArray(labelName, labelsArray);
        if (action == 'add' && labelIndex == -1) {
          labelsArray.push(labelName)
          _updateResouceLabels(tabName, resourceId, labelsArray);
        } else if (action == 'remove' && labelIndex != -1) {
          labelsArray.splice(labelIndex, 1);
          _updateResouceLabels(tabName, resourceId, labelsArray);
        }
      });
    });

    /*
      Add a new label when ENTER is presed in the input
     */
    $('#' + tabName + 'LabelsDropdown').off('keypress', '.newLabelInput');
    $('#' + tabName + 'LabelsDropdown').on('keypress', '.newLabelInput', function(e) {
      var ev = e || window.event;
      var key = ev.keyCode;

      if (key == 13 && !ev.altKey) {
        var labelName = $(this).val();
        var labelsArray, labelIndex;
        var selectedItems = tabTable.elements();
        $.each(selectedItems, function(index, resourceId) {
          labelsStr = _getLabel(dataTable, labelsColumn, resourceId);
          if (labelsStr != '') {
            labelsArray = labelsStr.split(',')
          } else {
            labelsArray = []
          }

          labelIndex = $.inArray(labelName, labelsArray);
          if (labelIndex == -1) {
            labelsArray.push(labelName);
            _updateResouceLabels(tabName, resourceId, labelsArray);
          }
        });

        ev.preventDefault();
      }
    });
  }

  function _updateResouceLabels(tabName, resourceId, labelsArray) {
    var templateStr = LABELS_ATTR + '="' + labelsArray.join(',') + '"';
    var resource = Sunstone.getResource(tabName);
    var tabTable = Sunstone.getDataTable(tabName);

    OpenNebula[resource].append({
      timeout: true,
      data : {
          id: resourceId,
          extra_param: templateStr
      },
      success: function(request) {
        OpenNebula[resource].show({
          timeout: true,
          data : {
              id: resourceId
          },
          success: function(request, response) {
            tabTable.updateElement(request, response);
            if (Sunstone.rightInfoVisible($('#' + tabName))) {
              Sunstone.insertPanels(tabName, response);
            }

            _insertLabelsMenu($('#li_' + tabName), tabTable.dataTable, tabTable.labelsColumn);
            _insertLabelsDropdown(tabName);
          },
          error: Notifier.onError
        });
      },
      error: Notifier.onError
    })
  }

  function _labelsStr(elementTemplate) {
    return elementTemplate[LABELS_ATTR];
  }

  function _deserializeLabels(labelsStr) {
    var indexedLabels = {};

    if (labelsStr) {
      var parent;
      $.each(labelsStr.split(','), function() {
        parent = indexedLabels;
        $.each(this.split('/'), function() {
          if (parent[this] == undefined) {
            parent[this] = {};
          }
          parent = parent[this];
        });
      });
    }

    return indexedLabels;
  }

  function _makeTree(indexedLabels) {
    var treeRoot = {
      htmlStr : '',
      subTree : []
    };

    $.each(indexedLabels, function(folderName, childs) {
      treeRoot.subTree.push(_makeSubTree('', folderName, childs));
    });

    return treeRoot;
  }

  function _makeSubTree(parentName, folderName, childs) {
    var fullName = parentName + folderName;
    var htmlStr =
      '' +
        folderName +
      '';

    var tree = {
      htmlStr: htmlStr,
      subTree: []
    };

    $.each(childs, function(subFolderName, subChilds) {
      tree.subTree.push(_makeSubTree(fullName + '/', subFolderName, subChilds));
    });

    return tree;
  }

  /*
    dataTable Filters
   */

  function _setLabelsFilter(dataTable, labelsColumn, regExp) {
    dataTable.fnFilter(regExp, labelsColumn, true, false);
  }

  function _clearLabelsFilter(dataTable, labelsColumn) {
    dataTable.fnFilter('', labelsColumn, true, false);
  }

  function _getLabels(dataTable, labelsColumn, labelsPath) {
    var labels = [];
    var tmp;
    $.each(dataTable.fnGetData(), function() {
      if (labelsPath) {
        tmp = this;
        $.each(labelsPath.split('.'), function() {
          if (tmp) {
            tmp = tmp[this];
          }
        });
        if (tmp && tmp != '') {
          labels.push(tmp);
        }
      } else {
        if (this[labelsColumn] != '') {
          labels.push(this[labelsColumn]);
        }
      }
    })
    return _deserializeLabels(labels.join(','));
  }

  function _getLabel(dataTable, labelsColumn, resourceId) {
    var nodes = dataTable.fnGetNodes();
    var tr = $('.check_item[value="' + resourceId + '"]', nodes).closest('tr');
    var aData = dataTable.fnGetData(tr);
    return aData[labelsColumn];
  }
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import * as React from 'react';
import ReactDOM from 'react-dom';
import Timeline from '../../components/timeline';
import ActiveTabGlobalTrack from '../../components/timeline/ActiveTabGlobalTrack';
import ActiveTabResourcesPanel from '../../components/timeline/ActiveTabResourcesPanel';
import ActiveTabResourceTrack from '../../components/timeline/ActiveTabResourceTrack';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { storeWithProfile } from '../fixtures/stores';
import { getProfileWithNiceTracks } from '../fixtures/profiles/tracks';
import { changeTimelineTrackOrganization } from '../../actions/receive-profile';
import { getBoundingBox, fireFullClick } from '../fixtures/utils';
import { addActiveTabInformationToProfile } from '../fixtures/profiles/processed-profile';
import mockCanvasContext from '../fixtures/mocks/canvas-context';
import {
  getActiveTabGlobalTracks,
  getActiveTabResourceTracks,
} from '../../selectors/profile';
import { getFirstSelectedThreadIndex } from '../../selectors/url-state';
import { changeSelectedThreads } from '../../actions/profile-view';
import { ensureExists } from '../../utils/flow';

describe('ActiveTabTimeline', function() {
  beforeEach(() => {
    jest.spyOn(ReactDOM, 'findDOMNode').mockImplementation(() => {
      // findDOMNode uses nominal typing instead of structural (null | Element | Text), so
      // opt out of the type checker for this mock by returning `any`.
      const mockEl = ({
        getBoundingClientRect: () => getBoundingBox(300, 300),
      }: any);
      return mockEl;
    });

    jest
      .spyOn(HTMLElement.prototype, 'getBoundingClientRect')
      .mockImplementation(() => getBoundingBox(200, 300));

    const ctx = mockCanvasContext();
    jest
      .spyOn(HTMLCanvasElement.prototype, 'getContext')
      .mockImplementation(() => ctx);
  });

  it('should be rendered properly from the Timeline component', () => {
    const {
      profile,
      parentInnerWindowIDsWithChildren,
      firstTabBrowsingContextID,
    } = addActiveTabInformationToProfile(getProfileWithNiceTracks());
    profile.threads[0].frameTable.innerWindowID[0] = parentInnerWindowIDsWithChildren;
    const store = storeWithProfile(profile);
    store.dispatch(
      changeTimelineTrackOrganization({
        type: 'active-tab',
        browsingContextID: firstTabBrowsingContextID,
      })
    );

    const { container } = render(
      <Provider store={store}>
        <Timeline />
      </Provider>
    );
    expect(container.firstChild).toMatchSnapshot();
  });

  describe('ActiveTabGlobalTrack', function() {
    function setup() {
      const { profile, ...pageInfo } = addActiveTabInformationToProfile(
        getProfileWithNiceTracks()
      );
      profile.threads[0].frameTable.innerWindowID[0] =
        pageInfo.parentInnerWindowIDsWithChildren;
      const store = storeWithProfile(profile);
      store.dispatch(
        changeTimelineTrackOrganization({
          type: 'active-tab',
          browsingContextID: pageInfo.firstTabBrowsingContextID,
        })
      );
      const trackIndex = 0;
      const { getState, dispatch } = store;
      const trackReference = { type: 'global', trackIndex };
      const tracks = getActiveTabGlobalTracks(getState());
      const track = tracks[trackIndex];
      const setInitialSelected = () => {};
      if (track.type !== 'tab') {
        throw new Error('Expected a tab track.');
      }
      const threadIndex = track.mainThreadIndex;

      if (threadIndex !== null) {
        // The assertions are simpler if the GeckoMain tab thread is not already selected.
        dispatch(changeSelectedThreads(new Set([threadIndex + 1])));
      }

      const renderResult = render(
        <Provider store={store}>
          <ActiveTabGlobalTrack
            trackIndex={trackIndex}
            trackReference={trackReference}
            setInitialSelected={setInitialSelected}
          />
        </Provider>
      );
      const { container } = renderResult;

      const getGlobalTrackRow = () =>
        ensureExists(
          container.querySelector('.timelineTrackGlobalRow'),
          `Couldn't find the track global row with selector .timelineTrackGlobalRow`
        );

      return {
        ...renderResult,
        ...pageInfo,
        dispatch,
        getState,
        profile,
        store,
        trackReference,
        trackIndex,
        threadIndex,
        getGlobalTrackRow,
      };
    }

    it('matches the snapshot of a global tab track', () => {
      const { container } = setup();
      expect(container.firstChild).toMatchSnapshot();
    });

    it('has useful parts of the component', function() {
      const { getGlobalTrackRow } = setup();
      expect(getGlobalTrackRow()).toBeTruthy();
    });

    it('starts out not being selected', function() {
      const { getState, getGlobalTrackRow, threadIndex } = setup();
      expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
      expect(getGlobalTrackRow().classList.contains('selected')).toBe(false);
    });

    it('can select a thread by clicking the row', () => {
      const { getState, getGlobalTrackRow, threadIndex } = setup();
      expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
      fireFullClick(getGlobalTrackRow());
      expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
    });

    it('does not display the resources panel if there are no resource tracks', () => {
      const { getState, queryByText } = setup();
      expect(getActiveTabResourceTracks(getState()).length).toBe(0);
      expect(queryByText(/Resources/)).toBe(null);
    });
  });

  describe('ActiveTabResourcesPanel', function() {
    function setup() {
      const { profile, ...pageInfo } = addActiveTabInformationToProfile(
        getProfileWithNiceTracks()
      );
      const mainThreadIndex = 0;
      const resourceThreadIndex = 1;
      // Setting the first thread as parent track and the second as the iframe track.
      profile.threads[mainThreadIndex].frameTable.innerWindowID[0] =
        pageInfo.parentInnerWindowIDsWithChildren;
      profile.threads[resourceThreadIndex].frameTable.innerWindowID[0] =
        pageInfo.iframeInnerWindowIDsWithChild;
      const store = storeWithProfile(profile);
      store.dispatch(
        changeTimelineTrackOrganization({
          type: 'active-tab',
          browsingContextID: pageInfo.firstTabBrowsingContextID,
        })
      );
      const { getState, dispatch } = store;
      const resourceTracks = getActiveTabResourceTracks(getState());

      const renderResult = render(
        <Provider store={store}>
          <ActiveTabResourcesPanel
            resourceTracks={resourceTracks}
            setInitialSelected={() => {}}
          />
        </Provider>
      );

      const { getByText, queryByText } = renderResult;
      const getResourcesPanelHeader = () => getByText(/Resources/);
      const getResourceFrameTrack = () => queryByText(/IFrame:/);

      return {
        ...renderResult,
        ...pageInfo,
        dispatch,
        getState,
        profile,
        store,
        getResourcesPanelHeader,
        getResourceFrameTrack,
        mainThreadIndex,
        resourceThreadIndex,
      };
    }

    it('matches the snapshot of a resources panel when closed', () => {
      const { container } = setup();
      expect(container.firstChild).toMatchSnapshot();
    });

    it('matches the snapshot of a resources panel when opened', () => {
      const { container, getResourcesPanelHeader } = setup();
      fireFullClick(getResourcesPanelHeader());
      expect(container.firstChild).toMatchSnapshot();
    });

    it('is closed by default', () => {
      const { getResourceFrameTrack } = setup();
      expect(getResourceFrameTrack()).toBeFalsy();
    });

    it('clicking on the header opens the resources panel', () => {
      const { getResourcesPanelHeader, getResourceFrameTrack } = setup();
      const resourcesPanelHeader = getResourcesPanelHeader();
      expect(getResourceFrameTrack()).toBeFalsy();

      fireFullClick(resourcesPanelHeader);
      expect(getResourceFrameTrack()).toBeTruthy();
    });

    it('selects the main track when panel is being closed', () => {
      const {
        getResourcesPanelHeader,
        getResourceFrameTrack,
        getState,
        mainThreadIndex,
        resourceThreadIndex,
      } = setup();
      // At first, make sure the main thread is selected.
      expect(getFirstSelectedThreadIndex(getState())).toBe(mainThreadIndex);

      // 1. Open the panel.
      fireFullClick(getResourcesPanelHeader());
      // 2. Select the reource track.
      fireFullClick(ensureExists(getResourceFrameTrack()));
      // Selected thread should be the resource now.
      expect(getFirstSelectedThreadIndex(getState())).toBe(resourceThreadIndex);

      // 3. Close the panel.
      fireFullClick(getResourcesPanelHeader());
      // Now the main thread should be selected again.
      expect(getFirstSelectedThreadIndex(getState())).toBe(mainThreadIndex);
    });
  });

  describe('ActiveTabResourceTrack', function() {
    function setup() {
      const { profile, ...pageInfo } = addActiveTabInformationToProfile(
        getProfileWithNiceTracks()
      );
      // Setting the threads with the following relationship:
      // Page #1 (thread #0)
      // |- Page #2 (thread #1)
      //    |- Page #3 (thread #2)
      const threadIndex = 2;
      profile.threads[0].frameTable.innerWindowID[0] =
        pageInfo.parentInnerWindowIDsWithChildren;
      profile.threads[1].frameTable.innerWindowID[0] =
        pageInfo.iframeInnerWindowIDsWithChild;
      profile.threads[threadIndex].frameTable.innerWindowID[0] =
        pageInfo.fistTabInnerWindowIDs[2];
      profile.threads[threadIndex].name = 'GeckoMain';
      const store = storeWithProfile(profile);
      store.dispatch(
        changeTimelineTrackOrganization({
          type: 'active-tab',
          browsingContextID: pageInfo.firstTabBrowsingContextID,
        })
      );
      const { getState, dispatch } = store;
      const resourceTracks = getActiveTabResourceTracks(getState());
      const trackIndex = 1;
      const renderResult = render(
        <Provider store={store}>
          <ActiveTabResourceTrack
            resourceTrack={resourceTracks[1]}
            trackIndex={trackIndex}
            setInitialSelected={() => {}}
          />
        </Provider>
      );

      const { getByText, container } = renderResult;
      const resourcePage = ensureExists(profile.pages)[2];
      const getResourceFrameTrackLabel = () => getByText(resourcePage.url);
      const getResourceTrackRow = () =>
        ensureExists(
          container.querySelector('.timelineTrackResourceRow'),
          `Couldn't find the track resource row with selector .timelineTrackResourceRow`
        );

      return {
        ...renderResult,
        ...pageInfo,
        dispatch,
        getState,
        profile,
        store,
        threadIndex,
        getResourceFrameTrackLabel,
        getResourceTrackRow,
        resourcePage,
      };
    }

    describe('with a thread/sub-frame track', function() {
      it('matches the snapshot of a resource track', () => {
        const { container } = setup();
        expect(container.firstChild).toMatchSnapshot();
      });

      it('has the correct track name', function() {
        const { queryByText, resourcePage } = setup();
        expect(queryByText(resourcePage.url)).toBeTruthy();
      });

      it('starts out not being selected', function() {
        const { getState, threadIndex } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
      });

      it('can select a thread by clicking the label', () => {
        const { getState, getResourceFrameTrackLabel, threadIndex } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        fireFullClick(getResourceFrameTrackLabel());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
      });

      it('can select a thread by clicking the row', () => {
        const { getState, getResourceTrackRow, threadIndex } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        fireFullClick(getResourceTrackRow());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
      });

      it('can toggle a selected track by clicking the label', () => {
        const {
          getState,
          getResourceFrameTrackLabel,
          getResourceTrackRow,
          threadIndex,
        } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        expect(getResourceTrackRow().classList.contains('opened')).toBe(false);
        fireFullClick(getResourceFrameTrackLabel());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
        expect(getResourceTrackRow().classList.contains('opened')).toBe(true);
      });

      it('does not toggle a selected track by clicking other part of the track except label', () => {
        const { getState, getResourceTrackRow, threadIndex } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        expect(getResourceTrackRow().classList.contains('opened')).toBe(false);
        fireFullClick(getResourceTrackRow());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
        expect(getResourceTrackRow().classList.contains('opened')).toBe(false);
      });
    });
  });
});
<commit_msg>Add a `isResourceTrackOpen` function and use it for two tests<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import * as React from 'react';
import ReactDOM from 'react-dom';
import Timeline from '../../components/timeline';
import ActiveTabGlobalTrack from '../../components/timeline/ActiveTabGlobalTrack';
import ActiveTabResourcesPanel from '../../components/timeline/ActiveTabResourcesPanel';
import ActiveTabResourceTrack from '../../components/timeline/ActiveTabResourceTrack';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { storeWithProfile } from '../fixtures/stores';
import { getProfileWithNiceTracks } from '../fixtures/profiles/tracks';
import { changeTimelineTrackOrganization } from '../../actions/receive-profile';
import { getBoundingBox, fireFullClick } from '../fixtures/utils';
import { addActiveTabInformationToProfile } from '../fixtures/profiles/processed-profile';
import mockCanvasContext from '../fixtures/mocks/canvas-context';
import {
  getActiveTabGlobalTracks,
  getActiveTabResourceTracks,
} from '../../selectors/profile';
import { getFirstSelectedThreadIndex } from '../../selectors/url-state';
import { changeSelectedThreads } from '../../actions/profile-view';
import { ensureExists } from '../../utils/flow';

describe('ActiveTabTimeline', function() {
  beforeEach(() => {
    jest.spyOn(ReactDOM, 'findDOMNode').mockImplementation(() => {
      // findDOMNode uses nominal typing instead of structural (null | Element | Text), so
      // opt out of the type checker for this mock by returning `any`.
      const mockEl = ({
        getBoundingClientRect: () => getBoundingBox(300, 300),
      }: any);
      return mockEl;
    });

    jest
      .spyOn(HTMLElement.prototype, 'getBoundingClientRect')
      .mockImplementation(() => getBoundingBox(200, 300));

    const ctx = mockCanvasContext();
    jest
      .spyOn(HTMLCanvasElement.prototype, 'getContext')
      .mockImplementation(() => ctx);
  });

  it('should be rendered properly from the Timeline component', () => {
    const {
      profile,
      parentInnerWindowIDsWithChildren,
      firstTabBrowsingContextID,
    } = addActiveTabInformationToProfile(getProfileWithNiceTracks());
    profile.threads[0].frameTable.innerWindowID[0] = parentInnerWindowIDsWithChildren;
    const store = storeWithProfile(profile);
    store.dispatch(
      changeTimelineTrackOrganization({
        type: 'active-tab',
        browsingContextID: firstTabBrowsingContextID,
      })
    );

    const { container } = render(
      <Provider store={store}>
        <Timeline />
      </Provider>
    );
    expect(container.firstChild).toMatchSnapshot();
  });

  describe('ActiveTabGlobalTrack', function() {
    function setup() {
      const { profile, ...pageInfo } = addActiveTabInformationToProfile(
        getProfileWithNiceTracks()
      );
      profile.threads[0].frameTable.innerWindowID[0] =
        pageInfo.parentInnerWindowIDsWithChildren;
      const store = storeWithProfile(profile);
      store.dispatch(
        changeTimelineTrackOrganization({
          type: 'active-tab',
          browsingContextID: pageInfo.firstTabBrowsingContextID,
        })
      );
      const trackIndex = 0;
      const { getState, dispatch } = store;
      const trackReference = { type: 'global', trackIndex };
      const tracks = getActiveTabGlobalTracks(getState());
      const track = tracks[trackIndex];
      const setInitialSelected = () => {};
      if (track.type !== 'tab') {
        throw new Error('Expected a tab track.');
      }
      const threadIndex = track.mainThreadIndex;

      if (threadIndex !== null) {
        // The assertions are simpler if the GeckoMain tab thread is not already selected.
        dispatch(changeSelectedThreads(new Set([threadIndex + 1])));
      }

      const renderResult = render(
        <Provider store={store}>
          <ActiveTabGlobalTrack
            trackIndex={trackIndex}
            trackReference={trackReference}
            setInitialSelected={setInitialSelected}
          />
        </Provider>
      );
      const { container } = renderResult;

      const getGlobalTrackRow = () =>
        ensureExists(
          container.querySelector('.timelineTrackGlobalRow'),
          `Couldn't find the track global row with selector .timelineTrackGlobalRow`
        );

      return {
        ...renderResult,
        ...pageInfo,
        dispatch,
        getState,
        profile,
        store,
        trackReference,
        trackIndex,
        threadIndex,
        getGlobalTrackRow,
      };
    }

    it('matches the snapshot of a global tab track', () => {
      const { container } = setup();
      expect(container.firstChild).toMatchSnapshot();
    });

    it('has useful parts of the component', function() {
      const { getGlobalTrackRow } = setup();
      expect(getGlobalTrackRow()).toBeTruthy();
    });

    it('starts out not being selected', function() {
      const { getState, getGlobalTrackRow, threadIndex } = setup();
      expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
      expect(getGlobalTrackRow().classList.contains('selected')).toBe(false);
    });

    it('can select a thread by clicking the row', () => {
      const { getState, getGlobalTrackRow, threadIndex } = setup();
      expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
      fireFullClick(getGlobalTrackRow());
      expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
    });

    it('does not display the resources panel if there are no resource tracks', () => {
      const { getState, queryByText } = setup();
      expect(getActiveTabResourceTracks(getState()).length).toBe(0);
      expect(queryByText(/Resources/)).toBe(null);
    });
  });

  describe('ActiveTabResourcesPanel', function() {
    function setup() {
      const { profile, ...pageInfo } = addActiveTabInformationToProfile(
        getProfileWithNiceTracks()
      );
      const mainThreadIndex = 0;
      const resourceThreadIndex = 1;
      // Setting the first thread as parent track and the second as the iframe track.
      profile.threads[mainThreadIndex].frameTable.innerWindowID[0] =
        pageInfo.parentInnerWindowIDsWithChildren;
      profile.threads[resourceThreadIndex].frameTable.innerWindowID[0] =
        pageInfo.iframeInnerWindowIDsWithChild;
      const store = storeWithProfile(profile);
      store.dispatch(
        changeTimelineTrackOrganization({
          type: 'active-tab',
          browsingContextID: pageInfo.firstTabBrowsingContextID,
        })
      );
      const { getState, dispatch } = store;
      const resourceTracks = getActiveTabResourceTracks(getState());

      const renderResult = render(
        <Provider store={store}>
          <ActiveTabResourcesPanel
            resourceTracks={resourceTracks}
            setInitialSelected={() => {}}
          />
        </Provider>
      );

      const { getByText, queryByText } = renderResult;
      const getResourcesPanelHeader = () => getByText(/Resources/);
      const getResourceFrameTrack = () => queryByText(/IFrame:/);

      return {
        ...renderResult,
        ...pageInfo,
        dispatch,
        getState,
        profile,
        store,
        getResourcesPanelHeader,
        getResourceFrameTrack,
        mainThreadIndex,
        resourceThreadIndex,
      };
    }

    it('matches the snapshot of a resources panel when closed', () => {
      const { container } = setup();
      expect(container.firstChild).toMatchSnapshot();
    });

    it('matches the snapshot of a resources panel when opened', () => {
      const { container, getResourcesPanelHeader } = setup();
      fireFullClick(getResourcesPanelHeader());
      expect(container.firstChild).toMatchSnapshot();
    });

    it('is closed by default', () => {
      const { getResourceFrameTrack } = setup();
      expect(getResourceFrameTrack()).toBeFalsy();
    });

    it('clicking on the header opens the resources panel', () => {
      const { getResourcesPanelHeader, getResourceFrameTrack } = setup();
      const resourcesPanelHeader = getResourcesPanelHeader();
      expect(getResourceFrameTrack()).toBeFalsy();

      fireFullClick(resourcesPanelHeader);
      expect(getResourceFrameTrack()).toBeTruthy();
    });

    it('selects the main track when panel is being closed', () => {
      const {
        getResourcesPanelHeader,
        getResourceFrameTrack,
        getState,
        mainThreadIndex,
        resourceThreadIndex,
      } = setup();
      // At first, make sure the main thread is selected.
      expect(getFirstSelectedThreadIndex(getState())).toBe(mainThreadIndex);

      // 1. Open the panel.
      fireFullClick(getResourcesPanelHeader());
      // 2. Select the reource track.
      fireFullClick(ensureExists(getResourceFrameTrack()));
      // Selected thread should be the resource now.
      expect(getFirstSelectedThreadIndex(getState())).toBe(resourceThreadIndex);

      // 3. Close the panel.
      fireFullClick(getResourcesPanelHeader());
      // Now the main thread should be selected again.
      expect(getFirstSelectedThreadIndex(getState())).toBe(mainThreadIndex);
    });
  });

  describe('ActiveTabResourceTrack', function() {
    function setup() {
      const { profile, ...pageInfo } = addActiveTabInformationToProfile(
        getProfileWithNiceTracks()
      );
      // Setting the threads with the following relationship:
      // Page #1 (thread #0)
      // |- Page #2 (thread #1)
      //    |- Page #3 (thread #2)
      const threadIndex = 2;
      profile.threads[0].frameTable.innerWindowID[0] =
        pageInfo.parentInnerWindowIDsWithChildren;
      profile.threads[1].frameTable.innerWindowID[0] =
        pageInfo.iframeInnerWindowIDsWithChild;
      profile.threads[threadIndex].frameTable.innerWindowID[0] =
        pageInfo.fistTabInnerWindowIDs[2];
      profile.threads[threadIndex].name = 'GeckoMain';
      const store = storeWithProfile(profile);
      store.dispatch(
        changeTimelineTrackOrganization({
          type: 'active-tab',
          browsingContextID: pageInfo.firstTabBrowsingContextID,
        })
      );
      const { getState, dispatch } = store;
      const resourceTracks = getActiveTabResourceTracks(getState());
      const trackIndex = 1;
      const renderResult = render(
        <Provider store={store}>
          <ActiveTabResourceTrack
            resourceTrack={resourceTracks[1]}
            trackIndex={trackIndex}
            setInitialSelected={() => {}}
          />
        </Provider>
      );

      const { getByText, container } = renderResult;
      const resourcePage = ensureExists(profile.pages)[2];
      const getResourceFrameTrackLabel = () => getByText(resourcePage.url);
      const getResourceTrackRow = () =>
        ensureExists(
          container.querySelector('.timelineTrackResourceRow'),
          `Couldn't find the track resource row with selector .timelineTrackResourceRow`
        );
      const isResourceTrackOpen = () =>
        getResourceTrackRow().classList.contains('opened');

      return {
        ...renderResult,
        ...pageInfo,
        dispatch,
        getState,
        profile,
        store,
        threadIndex,
        getResourceFrameTrackLabel,
        getResourceTrackRow,
        resourcePage,
        isResourceTrackOpen,
      };
    }

    describe('with a thread/sub-frame track', function() {
      it('matches the snapshot of a resource track', () => {
        const { container } = setup();
        expect(container.firstChild).toMatchSnapshot();
      });

      it('has the correct track name', function() {
        const { queryByText, resourcePage } = setup();
        expect(queryByText(resourcePage.url)).toBeTruthy();
      });

      it('starts out not being selected', function() {
        const { getState, threadIndex } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
      });

      it('can select a thread by clicking the label', () => {
        const { getState, getResourceFrameTrackLabel, threadIndex } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        fireFullClick(getResourceFrameTrackLabel());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
      });

      it('can select a thread by clicking the row', () => {
        const { getState, getResourceTrackRow, threadIndex } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        fireFullClick(getResourceTrackRow());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
      });

      it('can toggle a selected track by clicking the label', () => {
        const {
          getState,
          getResourceFrameTrackLabel,
          threadIndex,
          isResourceTrackOpen,
        } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        expect(isResourceTrackOpen()).toBe(false);
        fireFullClick(getResourceFrameTrackLabel());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
        expect(isResourceTrackOpen()).toBe(true);
      });

      it('does not toggle a selected track by clicking other part of the track except label', () => {
        const {
          getState,
          getResourceTrackRow,
          threadIndex,
          isResourceTrackOpen,
        } = setup();
        expect(getFirstSelectedThreadIndex(getState())).not.toBe(threadIndex);
        expect(isResourceTrackOpen()).toBe(false);
        fireFullClick(getResourceTrackRow());
        expect(getFirstSelectedThreadIndex(getState())).toBe(threadIndex);
        expect(isResourceTrackOpen()).toBe(false);
      });
    });
  });
});
<|endoftext|>


<commit_before>import {bootstrap} from 'aurelia-bootstrapper';
import {View} from 'aurelia-templating';
import {Aurelia} from 'aurelia-framework';

export const StageComponent = {
  withResources(resources): ComponentTester {
    return new ComponentTester().withResources(resources);
  }
};

export class ComponentTester {
  bind: (bindingContext: any) => void;
  attached: () => void;
  unbind: () => void;
  dispose: () => Promise<any>;
  element: Element;
  viewModel: any;
  configure = aurelia => aurelia.use.standardConfiguration();
  _html: string;
  _resources: string | string[] = [];
  _bindingContext: any;
  _rootView: View;

  bootstrap(configure: (aurelia: Aurelia) => void) {
    this.configure = configure;
  }

  withResources(resources: string | string[]): ComponentTester {
    this._resources = resources;
    return this;
  }

  inView(html: string): ComponentTester {
    this._html = html;
    return this;
  }

  boundTo(bindingContext: any): ComponentTester {
    this._bindingContext = bindingContext;
    return this;
  }

  manuallyHandleLifecycle(): ComponentTester {
    this._prepareLifecycle();
    return this;
  }

  create(): Promise<void> {
    return bootstrap(aurelia => {
      return Promise.resolve(this.configure(aurelia)).then(() => {
        aurelia.use.globalResources(this._resources);
        return aurelia.start().then(a => {
          let host = document.createElement('div');
          host.innerHTML = this._html;
          document.body.appendChild(host);
          aurelia.enhance(this._bindingContext, host);
          this._rootView = aurelia.root;
          this.element = host.firstElementChild;
          if (aurelia.root.controllers.length) {
            this.viewModel = aurelia.root.controllers[0].viewModel;
          }
          this.dispose = () => host.parentNode.removeChild(host);
          return new Promise(resolve => setTimeout(() => resolve(), 0));
        });
      });
    });
  }

  _prepareLifecycle() {
    // bind
    const bindPrototype = View.prototype.bind;
    View.prototype.bind = () => {};
    this.bind = bindingContext => new Promise(resolve => {
      View.prototype.bind = bindPrototype;
      if (bindingContext !== undefined) {
        this._bindingContext = bindingContext;
      }
      this._rootView.bind(this._bindingContext);
      setTimeout(() => resolve(), 0);
    });

    // attached
    const attachedPrototype = View.prototype.attached;
    View.prototype.attached = () => {};
    this.attached = () => new Promise(resolve => {
      View.prototype.attached = attachedPrototype;
      this._rootView.attached();
      setTimeout(() => resolve(), 0);
    });

    // detached
    this.detached = () => new Promise(resolve => {
      this._rootView.detached();
      setTimeout(() => resolve(), 0);
    });

    // unbind
    this.unbind = () => new Promise(resolve => {
      this._rootView.unbind();
      setTimeout(() => resolve(), 0);
    });
  }
}
<commit_msg>fix(component-tester): support testing without provided resources<commit_after>import {bootstrap} from 'aurelia-bootstrapper';
import {View} from 'aurelia-templating';
import {Aurelia} from 'aurelia-framework';

export const StageComponent = {
  withResources(resources): ComponentTester {
    return new ComponentTester().withResources(resources);
  }
};

export class ComponentTester {
  bind: (bindingContext: any) => void;
  attached: () => void;
  unbind: () => void;
  dispose: () => Promise<any>;
  element: Element;
  viewModel: any;
  configure = aurelia => aurelia.use.standardConfiguration();
  _html: string;
  _resources: string | string[] = [];
  _bindingContext: any;
  _rootView: View;

  bootstrap(configure: (aurelia: Aurelia) => void) {
    this.configure = configure;
  }

  withResources(resources: string | string[]): ComponentTester {
    this._resources = resources;
    return this;
  }

  inView(html: string): ComponentTester {
    this._html = html;
    return this;
  }

  boundTo(bindingContext: any): ComponentTester {
    this._bindingContext = bindingContext;
    return this;
  }

  manuallyHandleLifecycle(): ComponentTester {
    this._prepareLifecycle();
    return this;
  }

  create(): Promise<void> {
    return bootstrap(aurelia => {
      return Promise.resolve(this.configure(aurelia)).then(() => {
        if (this._resources) {
          aurelia.use.globalResources(this._resources);
        }
        return aurelia.start().then(a => {
          let host = document.createElement('div');
          host.innerHTML = this._html;
          document.body.appendChild(host);
          aurelia.enhance(this._bindingContext, host);
          this._rootView = aurelia.root;
          this.element = host.firstElementChild;
          if (aurelia.root.controllers.length) {
            this.viewModel = aurelia.root.controllers[0].viewModel;
          }
          this.dispose = () => host.parentNode.removeChild(host);
          return new Promise(resolve => setTimeout(() => resolve(), 0));
        });
      });
    });
  }

  _prepareLifecycle() {
    // bind
    const bindPrototype = View.prototype.bind;
    View.prototype.bind = () => {};
    this.bind = bindingContext => new Promise(resolve => {
      View.prototype.bind = bindPrototype;
      if (bindingContext !== undefined) {
        this._bindingContext = bindingContext;
      }
      this._rootView.bind(this._bindingContext);
      setTimeout(() => resolve(), 0);
    });

    // attached
    const attachedPrototype = View.prototype.attached;
    View.prototype.attached = () => {};
    this.attached = () => new Promise(resolve => {
      View.prototype.attached = attachedPrototype;
      this._rootView.attached();
      setTimeout(() => resolve(), 0);
    });

    // detached
    this.detached = () => new Promise(resolve => {
      this._rootView.detached();
      setTimeout(() => resolve(), 0);
    });

    // unbind
    this.unbind = () => new Promise(resolve => {
      this._rootView.unbind();
      setTimeout(() => resolve(), 0);
    });
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';

class About extends Component {
  render() {
    return (

About

            #StartupDetroit is an open source approach to building the Detroit
            Startup community.

            Startup Detroit is for the makers, creators, investors, and
            supporters in the software and tech startup space building and
            investing in innovation in and around metro Detroit.

            Communities thrive when the are they open and inclusive. Open source
            is an excellent model to make this work.

            Our goal of #StartupDetroit is to create a brand owned by the
            community, not owned by anyone one organization.

            To build an inclusive community, we want startups, investors, and
            supporting organziations and companies to be invited to participate.
            We do not want to limit participation by location. Rather,
            #StartupDetroit is for the startups, investors, and supporting
            organizations in Detroit, around metro Detroit, and around the world
            that want to see this region thrive.

Thank you for your support!

    );
  }
}

export default About;
<commit_msg>typo in About<commit_after>import React, { Component } from 'react';

class About extends Component {
  render() {
    return (

About

            #StartupDetroit is an open source approach to building the Detroit
            Startup community.

            Startup Detroit is for the makers, creators, investors, and
            supporters in the software and tech startup space building and
            investing in innovation in and around metro Detroit.

            Communities thrive when the are they open and inclusive. Open source
            is an excellent model to make this work.

            Our goal with #StartupDetroit is to create a brand owned by the
            community, not owned by anyone or any organization.

            To build an inclusive community, we want startups, investors, and
            supporting organziations and companies to be invited to participate.
            We do not want to limit participation by location. Rather,
            #StartupDetroit is for the startups, investors, and supporting
            organizations in Detroit, around metro Detroit, and around the world
            that want to see this region thrive.

Thank you for your support!

    );
  }
}

export default About;
<|endoftext|>


<commit_before>
'use strict';

import template from './category-selection.template.html';
import AttractionsService from './../../services/attractions/attractions.service';
import UserService from './../../services/users/user.service';
import TravelService from './../../services/travel/travel.service';

class CategorySelectionComponent {
    constructor(){
        this.controller = CategorySelectionComponentController;
        this.template = template;
        this.bindings = {
            attractions: '<',
        }
    }

    static get name() {
        return 'categorySelection';
    }
}

class CategorySelectionComponentController {

    constructor($state, $window, AttractionsService, UserService, TravelService){
        this.$state = $state;
        this.$window = $window;
        this.AttractionsService = AttractionsService;
        this.UserService = UserService;
        this.TravelService = TravelService;
        this.mustsees = [];
    }

    // simulates a click on a certain box and adds/removes it to/from the list of selected Must-Sees
    click(index) {
      if(this.isContained(index) == -1) {
        this.mustsees[this.mustsees.length] = index;
      }
      else {
        this.mustsees.splice(this.isContained(index), 1);
      }
      //console.log(this.selection.toString());
    }

    // calculates whether a given index is already contained in the field selection
    isContained(index) {
      for(var i = 0; i < this.mustsees.length; i++) {
        if(this.mustsees[i] == index) {
          return i;
        }
      }
      return -1;
    }

    calculate() {
      // Set up required data
      this.density = 3; // mocked value
      if(this.UserService.isAuthenticated()) {
        console.log("User " + this.UserService.getCurrentUser().username + " is currently logged in");
        this.density = this.userService.getCurrentUser().density;
      }

      var arrival = new Date(JSON.parse(this.$window.localStorage['journey']).arrival);
      var departure = new Date(JSON.parse(this.$window.localStorage['journey']).departure);

      var timeDiff = Math.abs(arrival.getTime() - departure.getTime());
      var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24));

      this.duration = 4;
      if(diffDays > 0) {
        this.duration = diffDays;
      }

      this.sliderSelection = [$('#Monuments').val(), $('#Museums').val(), $('#Parks').val(), $('#Churches').val()];
      var sum = 0;
      for(var i = 0; i < this.sliderSelection.length; i++) {
        sum = sum + parseInt(this.sliderSelection[i]);
      }
      if(sum == 0) {
        this.sliderSelection = [1, 1, 1, 1];
        sum = 4;
      }

      this.attractionWeight = [
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[0])/sum)), // Monuments
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[1])/sum)), // Museums
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[2])/sum)), // Parks
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[3])/sum))  // Churches
      ];

      // Within these arrays all the relevant attractions for the schedule are stored
      this.monuments = [];
      this.museums = [];
      this.parks = [];
      this.churches = [];

      // add all selected must-sees to the arrays
      for(var i = 0; i < this.mustsees.length; i++) {
        for(var j = 0; j < this.attractions.length; j++) {
          if(this.mustsees[i] == this.attractions[j]._id) {
            switch(this.attractions[j].category) {
              case "Monuments": this.monuments.push(this.attractions[j]); break;
              case "Museums": this.museums.push(this.attractions[j]); break;
              case "Parks": this.parks.push(this.attractions[j]); break;
              case "Churches": this.churches.push(this.attractions[j]); break;
              default: console.log("Undefined category"); break;
            }
          }
        }
      }
      // the pool is a copy of the attractions with the already selected must-sees removed
      this.pool = this.attractions.slice(0);
      this.removeAlreadySelected();

      // adding the best attractions of each category to the fields until they required amount is reached
      while(this.monuments.length < this.attractionWeight[0]) {
        this.monuments.push(this.pool[this.getBestOfCategory("Monuments")]);
        this.pool.splice(this.getBestOfCategory("Monuments"), 1);
      }
      while(this.museums.length < this.attractionWeight[1]) {
        this.museums.push(this.pool[this.getBestOfCategory("Museums")]);
        this.pool.splice(this.getBestOfCategory("Museums"), 1);
      }
      while(this.parks.length < this.attractionWeight[2]) {
        this.parks.push(this.pool[this.getBestOfCategory("Parks")]);
        this.pool.splice(this.getBestOfCategory("Parks"), 1);
      }
      while(this.churches.length < this.attractionWeight[3]) {
        this.churches.push(this.pool[this.getBestOfCategory("Churches")]);
        this.pool.splice(this.getBestOfCategory("Churches"), 1);
      }

      // The Selection is the full set of selected attractions for the schedule
      this.selection = this.monuments.concat(this.museums).concat(this.parks).concat(this.churches);

      // create a travel-object
      var schedule = [];
      var current = 0;

      this.printSelection();
      // calculate the schedule
      for(var i = 0; i < this.duration; i++) {
        for(var j = 0; j < this.density; j++) {
          var start = 0;
          if(j == 0) {
            start = new Date(arrival);
            start.setDate(arrival.getDate() + parseInt(i));
          }
          else {
            start = new Date(schedule[i*this.density + j - 1].end);
            start.setHours(start.getHours() + 1);
          }
          var end = new Date(start);

          //console.log("  (" + (current+1) + ") " + this.selection[current]);

          end.setMinutes(end.getMinutes() + this.selection[current].duration);
          var activity = {'attractionID': this.selection[current]._id,
              'start': start,
              'end': end};

          schedule[i*this.density + j] = activity;
          current = current + 1;
        }
      }

      var username = "johndoe";
      if(this.UserService.isAuthenticated()) {
        username = this.UserService.getCurrentUser().username;
      }
      var id = 1;
      var travel = {'_id': id,
          'username': username,
          'arrival': arrival,
          'departure': departure,
          'schedule': schedule
        };

      //console.log(travel);

      // TODO post the created travel-object to the server
      // TODO open the schedule viewer with the created schedule

      //let _id = this.movie['_id'];

      this.TravelService.create(travel).then(data => {
          //this.movie = JSON.parse(JSON.stringify(data));

          this.$state.go('travel',{ travelId: id});
      });
    }

    removeAlreadySelected() {
      var all = this.monuments.concat(this.museums).concat(this.parks).concat(this.churches);
      for(var i = 0; i < all.length; i++) {
        this.pool.splice(this.indexOfDuplicate(all[i]), 1);
      }
    }

    indexOfDuplicate(index) {
      for(var i = 0; i < this.mustsees.length; i++) {
        if(this.mustsees[i] == index) {
          return i;
        }
      }
      return -1;
    }

    getBestOfCategory(category) {
      for(var i = 0; i < this.pool.length; i++) {
        if(this.pool[i].category == category) {
          return i;
        }
      }
      return -1;
    }

    printPool() {
      console.log("Current Pool:");
      for(var i = 0; i < this.pool.length; i++) {
        console.log("(" + (i+1) + ") " + this.pool[i].title);
      }
    }

    printAttractions() {
      console.log("Current Attractions:");
      for(var i = 0; i < this.attractions.length; i++) {
        console.log("(" + (i+1) + ") " + this.attractions[i].title);
      }
    }

    printSelection() {
      console.log("Current Selection:");
      for(var i = 0; i < this.selection.length; i++) {
        console.log("(" + (i+1) + ") " + this.selection[i].title);
      }
    }

    printWeights() {
      console.log('Attractions in ' + this.duration + ' days (density: ' + this.density + '):');
      console.log(' ' + this.attractionWeight[0] + ' Monuments');
      console.log(' ' + this.attractionWeight[1] + ' Museums');
      console.log(' ' + this.attractionWeight[2] + ' Parks');
      console.log(' ' + this.attractionWeight[3] + ' Churches');
    }

    printSelectedMustSees() {
      console.log("Currently selected attractions");
      for(var i = 0; i < this.monuments.length; i++){
        console.log(' ' + (i+1) + '. Monument: ' + this.monuments[i].title);
      }
      for(var i = 0; i < this.museums.length; i++){
        console.log(' ' + (i+1) + '. Museum: ' + this.museums[i].title);
      }
      for(var i = 0; i < this.parks.length; i++){
        console.log(' ' + (i+1) + ' Park: ' + this.parks[i].title);
      }
      for(var i = 0; i < this.churches.length; i++){
        console.log(' ' + (i+1) + ' Church: ' + this.churches[i].title);
      }
    }

    static get $inject(){
        return ['$state', '$window', AttractionsService.name, UserService.name, TravelService.name];
    }
}

export default CategorySelectionComponent;
<commit_msg>Fixed Calculation Algorithm<commit_after>
'use strict';

import template from './category-selection.template.html';
import AttractionsService from './../../services/attractions/attractions.service';
import UserService from './../../services/users/user.service';
import TravelService from './../../services/travel/travel.service';

class CategorySelectionComponent {
    constructor(){
        this.controller = CategorySelectionComponentController;
        this.template = template;
        this.bindings = {
            attractions: '<',
        }
    }

    static get name() {
        return 'categorySelection';
    }
}

class CategorySelectionComponentController {

    constructor($state, $window, AttractionsService, UserService, TravelService){
        this.$state = $state;
        this.$window = $window;
        this.AttractionsService = AttractionsService;
        this.UserService = UserService;
        this.TravelService = TravelService;
        this.mustsees = [];
    }

    // simulates a click on a certain box and adds/removes it to/from the list of selected Must-Sees
    click(index) {
      if(this.isContained(index) == -1) {
        this.mustsees[this.mustsees.length] = index;
      }
      else {
        this.mustsees.splice(this.isContained(index), 1);
      }
      //console.log(this.selection.toString());
    }

    // calculates whether a given index is already contained in the field selection
    isContained(index) {
      for(var i = 0; i < this.mustsees.length; i++) {
        if(this.mustsees[i] == index) {
          return i;
        }
      }
      return -1;
    }

    calculate() {
      // Set up required data
      this.density = 3; // mocked value
      if(this.UserService.isAuthenticated()) {
        console.log("User " + this.UserService.getCurrentUser().username + " is currently logged in");
        this.density = this.userService.getCurrentUser().density;
      }

      var arrival = new Date(JSON.parse(this.$window.localStorage['journey']).arrival);
      var departure = new Date(JSON.parse(this.$window.localStorage['journey']).departure);

      var timeDiff = Math.abs(arrival.getTime() - departure.getTime());
      var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24));

      this.duration = 4;
      if(diffDays > 0) {
        this.duration = diffDays;
      }

      var requiredAttractions = this.duration * this.density;

      this.sliderSelection = [$('#Monuments').val(), $('#Museums').val(), $('#Parks').val(), $('#Churches').val()];
      var sum = 0;
      for(var i = 0; i < this.sliderSelection.length; i++) {
        sum = sum + parseInt(this.sliderSelection[i]);
      }
      if(sum == 0) {
        this.sliderSelection = [1, 1, 1, 1];
        sum = 4;
      }

      this.attractionWeight = [
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[0])/sum)), // Monuments
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[1])/sum)), // Museums
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[2])/sum)), // Parks
        Math.round(this.density * this.duration * (parseInt(this.sliderSelection[3])/sum))  // Churches
      ];

      while((this.attractionWeight[0] + this.attractionWeight[1] + this.attractionWeight[2] + this.attractionWeight[3]) < requiredAttractions) {
        switch(Math.floor((Math.random() * 4))) {
          case 0: this.attractionWeight[0] = this.attractionWeight[0] + 1; break;
          case 1: this.attractionWeight[1] = this.attractionWeight[1] + 1; break;
          case 2: this.attractionWeight[2] = this.attractionWeight[2] + 1; break;
          case 3: this.attractionWeight[3] = this.attractionWeight[3] + 1; break;
          default: this.attractionWeight[0] = this.attractionWeight[0] + 1; break;
        }
      }

      // Within these arrays all the relevant attractions for the schedule are stored
      this.monuments = [];
      this.museums = [];
      this.parks = [];
      this.churches = [];

      // add all selected must-sees to the arrays
      for(var i = 0; i < this.mustsees.length; i++) {
        for(var j = 0; j < this.attractions.length; j++) {
          if(this.mustsees[i] == this.attractions[j]._id) {
            switch(this.attractions[j].category) {
              case "Monuments": this.monuments.push(this.attractions[j]); break;
              case "Museums": this.museums.push(this.attractions[j]); break;
              case "Parks": this.parks.push(this.attractions[j]); break;
              case "Churches": this.churches.push(this.attractions[j]); break;
              default: console.log("Undefined category"); break;
            }
          }
        }
      }
      // the pool is a copy of the attractions with the already selected must-sees removed
      this.pool = this.attractions.slice(0);
      this.removeAlreadySelected();

      // adding the best attractions of each category to the fields until they required amount is reached
      while(this.monuments.length < this.attractionWeight[0]) {
        this.monuments.push(this.pool[this.getBestOfCategory("Monuments")]);
        this.pool.splice(this.getBestOfCategory("Monuments"), 1);
      }
      while(this.museums.length < this.attractionWeight[1]) {
        this.museums.push(this.pool[this.getBestOfCategory("Museums")]);
        this.pool.splice(this.getBestOfCategory("Museums"), 1);
      }
      while(this.parks.length < this.attractionWeight[2]) {
        this.parks.push(this.pool[this.getBestOfCategory("Parks")]);
        this.pool.splice(this.getBestOfCategory("Parks"), 1);
      }
      while(this.churches.length < this.attractionWeight[3]) {
        this.churches.push(this.pool[this.getBestOfCategory("Churches")]);
        this.pool.splice(this.getBestOfCategory("Churches"), 1);
      }

      // The Selection is the full set of selected attractions for the schedule
      this.selection = this.monuments.concat(this.museums).concat(this.parks).concat(this.churches);

      // create a travel-object
      var schedule = [];
      var current = 0;

      this.printSelection();
      // calculate the schedule
      for(var i = 0; i < this.duration; i++) {
        for(var j = 0; j < this.density; j++) {
          var start = 0;
          if(j == 0) {
            start = new Date(arrival);
            start.setDate(arrival.getDate() + parseInt(i));
          }
          else {
            start = new Date(schedule[i*this.density + j - 1].end);
            start.setHours(start.getHours() + 1);
          }
          var end = new Date(start);

          end.setMinutes(end.getMinutes() + this.selection[current].duration);
          var activity = {'attractionID': this.selection[current]._id,
              'start': start,
              'end': end};

          schedule[i*this.density + j] = activity;
          current = current + 1;
        }
      }

      var username = "johndoe";
      if(this.UserService.isAuthenticated()) {
        username = this.UserService.getCurrentUser().username;
      }
      var id = 1;
      var travel = {'_id': id,
          'username': username,
          'arrival': arrival,
          'departure': departure,
          'schedule': schedule
        };

      // TODO post the created travel-object to the server
      // TODO open the schedule viewer with the created schedule

      this.TravelService.create(travel).then(data => {
          //this.movie = JSON.parse(JSON.stringify(data));

          this.$state.go('travel',{ travelID: id});
      });
    }

    removeAlreadySelected() {
      var all = this.monuments.concat(this.museums).concat(this.parks).concat(this.churches);
      for(var i = 0; i < all.length; i++) {
        this.pool.splice(this.indexOfDuplicate(all[i]), 1);
      }
    }

    indexOfDuplicate(index) {
      for(var i = 0; i < this.mustsees.length; i++) {
        if(this.mustsees[i] == index) {
          return i;
        }
      }
      return -1;
    }

    getBestOfCategory(category) {
      for(var i = 0; i < this.pool.length; i++) {
        if(this.pool[i].category == category) {
          return i;
        }
      }
      return -1;
    }

    printPool() {
      console.log("Current Pool:");
      for(var i = 0; i < this.pool.length; i++) {
        console.log("(" + (i+1) + ") " + this.pool[i].title);
      }
    }

    printAttractions() {
      console.log("Current Attractions:");
      for(var i = 0; i < this.attractions.length; i++) {
        console.log("(" + (i+1) + ") " + this.attractions[i].title);
      }
    }

    printSelection() {
      console.log("Current Selection:");
      for(var i = 0; i < this.selection.length; i++) {
        console.log("(" + (i+1) + ") " + this.selection[i].title);
      }
    }

    printWeights() {
      console.log('Attractions in ' + this.duration + ' days (density: ' + this.density + '):');
      console.log(' ' + this.attractionWeight[0] + ' Monuments');
      console.log(' ' + this.attractionWeight[1] + ' Museums');
      console.log(' ' + this.attractionWeight[2] + ' Parks');
      console.log(' ' + this.attractionWeight[3] + ' Churches');
    }

    printSelectedMustSees() {
      console.log("Currently selected attractions");
      for(var i = 0; i < this.monuments.length; i++){
        console.log(' ' + (i+1) + '. Monument: ' + this.monuments[i].title);
      }
      for(var i = 0; i < this.museums.length; i++){
        console.log(' ' + (i+1) + '. Museum: ' + this.museums[i].title);
      }
      for(var i = 0; i < this.parks.length; i++){
        console.log(' ' + (i+1) + ' Park: ' + this.parks[i].title);
      }
      for(var i = 0; i < this.churches.length; i++){
        console.log(' ' + (i+1) + ' Church: ' + this.churches[i].title);
      }
    }

    static get $inject(){
        return ['$state', '$window', AttractionsService.name, UserService.name, TravelService.name];
    }
}

export default CategorySelectionComponent;
<|endoftext|>