<commit_before>'use strict';

var _i18n = require('i18n');
var _ejs = require('ejs');
var _fs = require('fs');

// Custom
var _appConfig = require(__dirname + '/../../config.js');
var _appError = require(_appConfig.commonsPath + 'app-error.js');

_i18n.configure(_appConfig.i18nConfiguration);

var Utility = function() {
  var loadPartial = function(partialName, data, callback) {
    loadTemplateFile(partialName, false, data, callback);
  };

  var loadDialog = function(dialogName, data, callback) {
    loadTemplateFile(dialogName, true, data, callback);
  };

  function loadTemplateFile(fileName, isDialog, data, callback) {
    try {
      if (typeof data === 'undefined' || !data) {
        data = {
          AppUtil: new Utility(),
          i18n: _i18n,
          basePath: _appConfig.basePath
        };
      } else {
        data.AppUtil = new Utility();
        data.i18n = _i18n;
        data.basePath = _appConfig.basePath;
      }
      var fileToLoad = '';
      if (isDialog) {
        fileToLoad = _appConfig.dialogsPath + fileName;
      } else {
        fileToLoad = _appConfig.partialsPath + fileName;
      }
      _fs.readFile(fileToLoad, 'utf-8', function(err, htmlFile) {
        if (err) {
          data.AppUtil = null;
          return callback(new _appError(err, _i18n.__('error.partial_load_error', fileToLoad)));
        }
        var tmpl = _ejs.compile(htmlFile);
        var str = tmpl(data);
        data.AppUtil = null;

        return callback(null, str);
      });
    } catch (e) {
      return callback(new _appError(e, _i18n.__('error.partial_load_error', fileName)));
    }
  }

  var mvFile = function(oldPath, newPath, cbMain) {
    var source = _fs.createReadStream(oldPath);
    var dest = _fs.createWriteStream(newPath);

    source.pipe(dest);
    source.on('end', function() {
      _fs.unlink(oldPath, function(err) {
        if (err) {
          return cbMain(new _appError(err, 'There was an error while moving the file.'));
        }
        return cbMain();
      });
    });

    source.on('error', function(err) {
      return cbMain(new _appError(err, 'There was an error while moving the file.'));
    });
  };

  function checkAndInsertDialog(err, html, errMsg) {
    if (err) {
      var errParse = new _appError(err, errMsg, false, true);
      errParse.display();
      return false;
    }
    document.querySelector('body').insertAdjacentHTML('beforeend', html);
    return true;
  }

  function addCloseEvent($dlg, cbOnClose) {
    $dlg.on('hidden.bs.modal', function() {
      $dlg.off('hidden.bs.modal');
      if (cbOnClose) {
        cbOnClose();
      }
      $dlg = null;
      this.remove();
    });
  }

  function readFormData(formElements) {
    var formData = {};
    // TODO Perform validation based on the classes / data attributes.
    for (var i = 0, len = formElements.length; i !== len; ++i) {
      var elem = formElements[i];
      if (elem.type === 'button' || elem.name === "") {
        continue;
      }
      var ctrlName = elem.name;
      var ctrlVal = elem.value;
      if (ctrlVal) {
        formData[ctrlName] = ctrlVal;
      }
    }
    return formData;
  }

  function checkDates(dtFirst, dtSecond) {
    var dtFirstString = getDateString(dtFirst);
    var dtSecondString = getDateString(dtSecond);

    if (dtFirstString < dtSecondString) {
      return -1;
    }

    if (dtFirstString === dtSecondString) {
      return 0;
    }

    if (dtFirstString > dtSecondString) {
      return 1;
    }

    return false;
  }

  function getDateString(dt) {
    var month = dt.getMonth() + 1;
    month = month < 10 ?  '0' + month : month;

    var date = dt.getDate();
    date = date < 10 ? '0' + date : date;

    var year = dt.getFullYear();

    return '' + year + date + month;
  }

  return {
    loadPartial: loadPartial,
    loadDialog: loadDialog,
    mvFile: mvFile,
    checkAndInsertDialog: checkAndInsertDialog,
    addCloseEvent: addCloseEvent,
    readFormData: readFormData,
    checkDates: checkDates
  };
};

module.exports = new Utility();
<commit_msg>Fixes #48, fixing the incorrect concatenation of strings<commit_after>'use strict';

var _i18n = require('i18n');
var _ejs = require('ejs');
var _fs = require('fs');

// Custom
var _appConfig = require(__dirname + '/../../config.js');
var _appError = require(_appConfig.commonsPath + 'app-error.js');

_i18n.configure(_appConfig.i18nConfiguration);

var Utility = function() {
  var loadPartial = function(partialName, data, callback) {
    loadTemplateFile(partialName, false, data, callback);
  };

  var loadDialog = function(dialogName, data, callback) {
    loadTemplateFile(dialogName, true, data, callback);
  };

  function loadTemplateFile(fileName, isDialog, data, callback) {
    try {
      if (typeof data === 'undefined' || !data) {
        data = {
          AppUtil: new Utility(),
          i18n: _i18n,
          basePath: _appConfig.basePath
        };
      } else {
        data.AppUtil = new Utility();
        data.i18n = _i18n;
        data.basePath = _appConfig.basePath;
      }
      var fileToLoad = '';
      if (isDialog) {
        fileToLoad = _appConfig.dialogsPath + fileName;
      } else {
        fileToLoad = _appConfig.partialsPath + fileName;
      }
      _fs.readFile(fileToLoad, 'utf-8', function(err, htmlFile) {
        if (err) {
          data.AppUtil = null;
          return callback(new _appError(err, _i18n.__('error.partial_load_error', fileToLoad)));
        }
        var tmpl = _ejs.compile(htmlFile);
        var str = tmpl(data);
        data.AppUtil = null;

        return callback(null, str);
      });
    } catch (e) {
      return callback(new _appError(e, _i18n.__('error.partial_load_error', fileName)));
    }
  }

  var mvFile = function(oldPath, newPath, cbMain) {
    var source = _fs.createReadStream(oldPath);
    var dest = _fs.createWriteStream(newPath);

    source.pipe(dest);
    source.on('end', function() {
      _fs.unlink(oldPath, function(err) {
        if (err) {
          return cbMain(new _appError(err, 'There was an error while moving the file.'));
        }
        return cbMain();
      });
    });

    source.on('error', function(err) {
      return cbMain(new _appError(err, 'There was an error while moving the file.'));
    });
  };

  function checkAndInsertDialog(err, html, errMsg) {
    if (err) {
      var errParse = new _appError(err, errMsg, false, true);
      errParse.display();
      return false;
    }
    document.querySelector('body').insertAdjacentHTML('beforeend', html);
    return true;
  }

  function addCloseEvent($dlg, cbOnClose) {
    $dlg.on('hidden.bs.modal', function() {
      $dlg.off('hidden.bs.modal');
      if (cbOnClose) {
        cbOnClose();
      }
      $dlg = null;
      this.remove();
    });
  }

  function readFormData(formElements) {
    var formData = {};
    // TODO Perform validation based on the classes / data attributes.
    for (var i = 0, len = formElements.length; i !== len; ++i) {
      var elem = formElements[i];
      if (elem.type === 'button' || elem.name === "") {
        continue;
      }
      var ctrlName = elem.name;
      var ctrlVal = elem.value;
      if (ctrlVal) {
        formData[ctrlName] = ctrlVal;
      }
    }
    return formData;
  }

  function checkDates(dtFirst, dtSecond) {
    var dtFirstString = getDateString(dtFirst);
    var dtSecondString = getDateString(dtSecond);

    if (dtFirstString < dtSecondString) {
      return -1;
    }

    if (dtFirstString === dtSecondString) {
      return 0;
    }

    if (dtFirstString > dtSecondString) {
      return 1;
    }

    return false;
  }

  function getDateString(dt) {
    var month = dt.getMonth() + 1;
    month = month < 10 ?  '0' + month : month;

    var date = dt.getDate();
    date = date < 10 ? '0' + date : date;

    var year = dt.getFullYear();

    return '' + year + month + date;
  }

  return {
    loadPartial: loadPartial,
    loadDialog: loadDialog,
    mvFile: mvFile,
    checkAndInsertDialog: checkAndInsertDialog,
    addCloseEvent: addCloseEvent,
    readFormData: readFormData,
    checkDates: checkDates
  };
};

module.exports = new Utility();
<|endoftext|>


<commit_before>/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
/* global Vue */
/* global Issuable */
/* global ListMilestone */

(function() {
  this.MilestoneSelect = (function() {
    function MilestoneSelect(currentProject, els) {
      var _this, $els;
      if (currentProject != null) {
        _this = this;
        this.currentProject = JSON.parse(currentProject);
      }

      $els = $(els);

      if (!els) {
        $els = $('.js-label-select');
      }

      $els.each(function(i, dropdown) {
        var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove;
        $dropdown = $(dropdown);
        projectId = $dropdown.data('project-id');
        milestonesUrl = $dropdown.data('milestones');
        issueUpdateURL = $dropdown.data('issueUpdate');
        selectedMilestone = $dropdown.data('selected');
        showNo = $dropdown.data('show-no');
        showAny = $dropdown.data('show-any');
        showMenuAbove = $dropdown.data('showMenuAbove');
        showUpcoming = $dropdown.data('show-upcoming');
        useId = $dropdown.data('use-id');
        defaultLabel = $dropdown.data('default-label');
        issuableId = $dropdown.data('issuable-id');
        abilityName = $dropdown.data('ability-name');
        $selectbox = $dropdown.closest('.selectbox');
        $block = $selectbox.closest('.block');
        $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
        $value = $block.find('.value');
        $loading = $block.find('.block-loading').fadeOut();
        if (issueUpdateURL) {
          milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %>');
          milestoneLinkNoneTemplate = 'None';
          collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> ');
        }
        return $dropdown.glDropdown({
          showMenuAbove: showMenuAbove,
          data: function(term, callback) {
            return $.ajax({
              url: milestonesUrl
            }).done(function(data) {
              var extraOptions = [];
              if (showAny) {
                extraOptions.push({
                  id: 0,
                  name: '',
                  title: 'Any Milestone'
                });
              }
              if (showNo) {
                extraOptions.push({
                  id: -1,
                  name: 'No Milestone',
                  title: 'No Milestone'
                });
              }
              if (showUpcoming) {
                extraOptions.push({
                  id: -2,
                  name: '#upcoming',
                  title: 'Upcoming'
                });
              }
              if (extraOptions.length) {
                extraOptions.push('divider');
              }

              callback(extraOptions.concat(data));
              if (showMenuAbove) {
                $dropdown.data('glDropdown').positionMenuAbove();
              }
            });
          },
          filterable: true,
          search: {
            fields: ['title']
          },
          selectable: true,
          toggleLabel: function(selected, el, e) {
            if (selected && 'id' in selected && $(el).hasClass('is-active')) {
              return selected.title;
            } else {
              return defaultLabel;
            }
          },
          defaultLabel: defaultLabel,
          fieldName: $dropdown.data('field-name'),
          text: function(milestone) {
            return _.escape(milestone.title);
          },
          id: function(milestone) {
            if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
              return milestone.name;
            } else {
              return milestone.id;
            }
          },
          isSelected: function(milestone) {
            return milestone.name === selectedMilestone;
          },
          hidden: function() {
            $selectbox.hide();
            // display:block overrides the hide-collapse rule
            return $value.css('display', '');
          },
          vue: $dropdown.hasClass('js-issue-board-sidebar'),
          clicked: function(selected, $el, e) {
            var data, isIssueIndex, isMRIndex, page, boardsStore;
            page = $('body').data('page');
            isIssueIndex = page === 'projects:issues:index';
            isMRIndex = (page === page && page === 'projects:merge_requests:index');
            if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
              e.preventDefault();
              return;
            }

            if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
              !$dropdown.closest('.add-issues-modal').length) {
              boardsStore = gl.issueBoards.BoardsStore.state.filters;
            } else if ($dropdown.closest('.add-issues-modal').length) {
              boardsStore = gl.issueBoards.ModalStore.store.filter;
            }

            if (boardsStore) {
              boardsStore[$dropdown.data('field-name')] = selected.name;
              if (!$dropdown.closest('.add-issues-modal').length) {
                gl.issueBoards.BoardsStore.updateFiltersUrl();
              }
              e.preventDefault();
            } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
              if (selected.name != null) {
                selectedMilestone = selected.name;
              } else {
                selectedMilestone = '';
              }
              return Issuable.filterResults($dropdown.closest('form'));
            } else if ($dropdown.hasClass('js-filter-submit')) {
              return $dropdown.closest('form').submit();
            } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
              if (selected.id !== -1) {
                Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({
                  id: selected.id,
                  title: selected.name
                }));
              } else {
                Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone');
              }

              $dropdown.trigger('loading.gl.dropdown');
              $loading.fadeIn();

              gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
                .then(function () {
                  $dropdown.trigger('loaded.gl.dropdown');
                  $loading.fadeOut();
                });
            } else {
              selected = $selectbox.find('input[type="hidden"]').val();
              data = {};
              data[abilityName] = {};
              data[abilityName].milestone_id = selected != null ? selected : null;
              $loading.fadeIn();
              $dropdown.trigger('loading.gl.dropdown');
              return $.ajax({
                type: 'PUT',
                url: issueUpdateURL,
                data: data
              }).done(function(data) {
                $dropdown.trigger('loaded.gl.dropdown');
                $loading.fadeOut();
                $selectbox.hide();
                $value.css('display', '');
                if (data.milestone != null) {
                  data.milestone.namespace = _this.currentProject.namespace;
                  data.milestone.path = _this.currentProject.path;
                  data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
                  $value.html(milestoneLinkTemplate(data.milestone));
                  return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
                } else {
                  $value.html(milestoneLinkNoneTemplate);
                  return $sidebarCollapsedValue.find('span').text('No');
                }
              });
            }
          }
        });
      });
    }

    return MilestoneSelect;
  })();
}).call(this);
<commit_msg>Fixed milestone select failing tests<commit_after>/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
/* global Vue */
/* global Issuable */
/* global ListMilestone */

(function() {
  this.MilestoneSelect = (function() {
    function MilestoneSelect(currentProject, els) {
      var _this, $els;
      if (currentProject != null) {
        _this = this;
        this.currentProject = JSON.parse(currentProject);
      }

      $els = $(els);

      if (!els) {
        $els = $('.js-milestone-select');
      }

      $els.each(function(i, dropdown) {
        var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove;
        $dropdown = $(dropdown);
        projectId = $dropdown.data('project-id');
        milestonesUrl = $dropdown.data('milestones');
        issueUpdateURL = $dropdown.data('issueUpdate');
        selectedMilestone = $dropdown.data('selected');
        showNo = $dropdown.data('show-no');
        showAny = $dropdown.data('show-any');
        showMenuAbove = $dropdown.data('showMenuAbove');
        showUpcoming = $dropdown.data('show-upcoming');
        useId = $dropdown.data('use-id');
        defaultLabel = $dropdown.data('default-label');
        issuableId = $dropdown.data('issuable-id');
        abilityName = $dropdown.data('ability-name');
        $selectbox = $dropdown.closest('.selectbox');
        $block = $selectbox.closest('.block');
        $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
        $value = $block.find('.value');
        $loading = $block.find('.block-loading').fadeOut();
        if (issueUpdateURL) {
          milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %>');
          milestoneLinkNoneTemplate = 'None';
          collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> ');
        }
        return $dropdown.glDropdown({
          showMenuAbove: showMenuAbove,
          data: function(term, callback) {
            return $.ajax({
              url: milestonesUrl
            }).done(function(data) {
              var extraOptions = [];
              if (showAny) {
                extraOptions.push({
                  id: 0,
                  name: '',
                  title: 'Any Milestone'
                });
              }
              if (showNo) {
                extraOptions.push({
                  id: -1,
                  name: 'No Milestone',
                  title: 'No Milestone'
                });
              }
              if (showUpcoming) {
                extraOptions.push({
                  id: -2,
                  name: '#upcoming',
                  title: 'Upcoming'
                });
              }
              if (extraOptions.length) {
                extraOptions.push('divider');
              }

              callback(extraOptions.concat(data));
              if (showMenuAbove) {
                $dropdown.data('glDropdown').positionMenuAbove();
              }
            });
          },
          filterable: true,
          search: {
            fields: ['title']
          },
          selectable: true,
          toggleLabel: function(selected, el, e) {
            if (selected && 'id' in selected && $(el).hasClass('is-active')) {
              return selected.title;
            } else {
              return defaultLabel;
            }
          },
          defaultLabel: defaultLabel,
          fieldName: $dropdown.data('field-name'),
          text: function(milestone) {
            return _.escape(milestone.title);
          },
          id: function(milestone) {
            if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
              return milestone.name;
            } else {
              return milestone.id;
            }
          },
          isSelected: function(milestone) {
            return milestone.name === selectedMilestone;
          },
          hidden: function() {
            $selectbox.hide();
            // display:block overrides the hide-collapse rule
            return $value.css('display', '');
          },
          vue: $dropdown.hasClass('js-issue-board-sidebar'),
          clicked: function(selected, $el, e) {
            var data, isIssueIndex, isMRIndex, page, boardsStore;
            page = $('body').data('page');
            isIssueIndex = page === 'projects:issues:index';
            isMRIndex = (page === page && page === 'projects:merge_requests:index');
            if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
              e.preventDefault();
              return;
            }

            if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
              !$dropdown.closest('.add-issues-modal').length) {
              boardsStore = gl.issueBoards.BoardsStore.state.filters;
            } else if ($dropdown.closest('.add-issues-modal').length) {
              boardsStore = gl.issueBoards.ModalStore.store.filter;
            }

            if (boardsStore) {
              boardsStore[$dropdown.data('field-name')] = selected.name;
              if (!$dropdown.closest('.add-issues-modal').length) {
                gl.issueBoards.BoardsStore.updateFiltersUrl();
              }
              e.preventDefault();
            } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
              if (selected.name != null) {
                selectedMilestone = selected.name;
              } else {
                selectedMilestone = '';
              }
              return Issuable.filterResults($dropdown.closest('form'));
            } else if ($dropdown.hasClass('js-filter-submit')) {
              return $dropdown.closest('form').submit();
            } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
              if (selected.id !== -1) {
                Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({
                  id: selected.id,
                  title: selected.name
                }));
              } else {
                Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone');
              }

              $dropdown.trigger('loading.gl.dropdown');
              $loading.fadeIn();

              gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
                .then(function () {
                  $dropdown.trigger('loaded.gl.dropdown');
                  $loading.fadeOut();
                });
            } else {
              selected = $selectbox.find('input[type="hidden"]').val();
              data = {};
              data[abilityName] = {};
              data[abilityName].milestone_id = selected != null ? selected : null;
              $loading.fadeIn();
              $dropdown.trigger('loading.gl.dropdown');
              return $.ajax({
                type: 'PUT',
                url: issueUpdateURL,
                data: data
              }).done(function(data) {
                $dropdown.trigger('loaded.gl.dropdown');
                $loading.fadeOut();
                $selectbox.hide();
                $value.css('display', '');
                if (data.milestone != null) {
                  data.milestone.namespace = _this.currentProject.namespace;
                  data.milestone.path = _this.currentProject.path;
                  data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
                  $value.html(milestoneLinkTemplate(data.milestone));
                  return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
                } else {
                  $value.html(milestoneLinkNoneTemplate);
                  return $sidebarCollapsedValue.find('span').text('No');
                }
              });
            }
          }
        });
      });
    }

    return MilestoneSelect;
  })();
}).call(this);
<|endoftext|>


<commit_before>import React from 'react'
import { connect } from 'react-redux'
import { Route, Redirect } from 'react-router-dom'
import Home from './Home'
import Login from './Login'
import Header from './header/Header'
import UserState from '../reducers/models/UserState'

const App = ({ user }) => (

    {user.isLoggedIn() ? <Route path="/" component={Home} /> : <Redirect to="/login" />}
    <Route path="/login" component={Login} />

)

App.propTypes = {
  user: React.PropTypes.instanceOf(UserState).isRequired,
}

const stateToProps = state => ({ user: state.user })
const mergeProps = stateProps => ({ user: new UserState(stateProps.user) })

export default connect(stateToProps, null, mergeProps)(App)
<commit_msg>easier to follow logic, and only allow /login for logged out users<commit_after>import React from 'react'
import { connect } from 'react-redux'
import { Route, Redirect } from 'react-router-dom'
import Home from './Home'
import Login from './Login'
import Header from './header/Header'
import UserState from '../reducers/models/UserState'

const App = ({ user }) => {
  const paths = []

  if (user.isLoggedIn()) {
    paths.push(
      <Route path="/" component={Home} />,
    )
  } else {
    paths.push(<Redirect to="/login" />)
  }

  paths.push(<Route path="/login" component={Login} />)

  return (

      {paths}

  )
}

App.propTypes = {
  user: React.PropTypes.instanceOf(UserState).isRequired,
}

const stateToProps = state => ({ user: state.user })
const mergeProps = stateProps => ({ user: new UserState(stateProps.user) })

export default connect(stateToProps, null, mergeProps)(App)
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import Controls from './Controls'
import Grid from './Grid'
import {
  getColor,
  getNeighborsCoord,
  getCenterCoordinates,
  drawCells,
  clearCells
} from '../utils/helpers'
import PropTypes from 'prop-types'

class App extends Component {
  state = {
    gridWidth: '501px',
    gridHeight: '381px',
    cellSize: 9
  }

  static propTypes = {
    /**
     * Game grid length
     */
    gridWidth: PropTypes.string,
    /**
     * Game grid height
     */
    gridHeight: PropTypes.string,
    /**
     * Gets called when game grid updates: at initial run and Reset
     */
    onUpdate: PropTypes.func,
    /**
     * Gets called when user resets game state
     */
    handleReset: PropTypes.func,
    /**
     * Gets called when user clicks game start buttons
     */
    handleStart: PropTypes.func
  }

  /**
   * Draws cell grid on update
   * @param {number} length Canvas length
   * @param {number} height Canvas height
   */
  handleUpdateCanvas = (length, height) => {
    var canvas = document.getElementById('game-grid')
    var context = canvas.getContext('2d')
    for (let x = 0.5; x < length; x += 10) {
      context.moveTo(x, 0)
      context.lineTo(x, height)
    }

    for (let y = 0.5; y < height; y += 10) {
      context.moveTo(0, y)
      context.lineTo(length, y)
    }

    context.strokeStyle = '#ddd'
    context.stroke()
  }

  handleStart = () => {
    const centerCoordinates = getCenterCoordinates()
    const canvas = document.getElementById('game-grid')
    const ctx = canvas.getContext('2d')

    const makeTopLeftCoord = coordinate => coordinate - 4

    let cellsToDie = []
    let cellsToBeBorn = []

    for (const coordinates of centerCoordinates) {
      const cellColor = getColor(ctx, ...coordinates)
      const neighbors = getNeighborsCoord(...coordinates)

      let liveNeigborsCount = 0

      for (const neighbor of neighbors) {
        liveNeigborsCount =
          getColor(ctx, ...neighbor) !== 0
            ? liveNeigborsCount + 1
            : liveNeigborsCount
      }
      if ([0, 1, 4].includes(liveNeigborsCount) && cellColor) {
        // only live cell can die
        cellsToDie.push(coordinates.map(makeTopLeftCoord))
      }
      if (liveNeigborsCount === 3) {
        cellsToBeBorn.push(coordinates.map(makeTopLeftCoord))
      }
    }
    console.log(`cellsToBeBorn: ${cellsToBeBorn}\ncellsToDie: ${cellsToDie}`)
    clearCells(cellsToDie)
    drawCells(cellsToBeBorn)
  }

  handleReset = () => {
    let canvas = document.getElementById('game-grid')
    let ctx = canvas.getContext('2d')

    let width = parseInt(this.state.gridWidth, 10)
    let height = parseInt(this.state.gridHeight, 10)

    ctx.clearRect(0, 0, canvas.width, canvas.height)
    this.handleUpdateCanvas(width, height)
  }

  render () {
    let width = this.state.gridWidth
    let height = this.state.gridHeight

    return (

Conway's Game of Life

        <Controls
          handleReset={this.handleReset}
          handleStart={this.handleStart}
        />
        <Grid
          gridWidth={width}
          gridHeight={height}
          onUpdate={this.handleUpdateCanvas}
        />

    )
  }
}

export default App
<commit_msg>Keep track of round's generation<commit_after>import React, { Component } from 'react'
import Controls from './Controls'
import Grid from './Grid'
import {
  getColor,
  getNeighborsCoord,
  getCenterCoordinates,
  drawCells,
  clearCells
} from '../utils/helpers'
import PropTypes from 'prop-types'

class App extends Component {
  state = {
    gridWidth: '501px',
    gridHeight: '381px',
    cellSize: 9,
    generation: 0
  }

  static propTypes = {
    /**
     * Game grid length
     */
    gridWidth: PropTypes.string,
    /**
     * Game grid height
     */
    gridHeight: PropTypes.string,
    /**
     * Gets called when game grid updates: at initial run and Reset
     */
    onUpdate: PropTypes.func,
    /**
     * Gets called when user resets game state
     */
    handleReset: PropTypes.func,
    /**
     * Gets called when user clicks game start buttons
     */
    handleStart: PropTypes.func
  }

  /**
   * Draws cell grid on update
   * @param {number} length Canvas length
   * @param {number} height Canvas height
   */
  handleUpdateCanvas = (length, height) => {
    var canvas = document.getElementById('game-grid')
    var context = canvas.getContext('2d')
    for (let x = 0.5; x < length; x += 10) {
      context.moveTo(x, 0)
      context.lineTo(x, height)
    }

    for (let y = 0.5; y < height; y += 10) {
      context.moveTo(0, y)
      context.lineTo(length, y)
    }

    context.strokeStyle = '#ddd'
    context.stroke()
  }

  handleStart = () => {
    const centerCoordinates = getCenterCoordinates()
    const canvas = document.getElementById('game-grid')
    const ctx = canvas.getContext('2d')

    const makeTopLeftCoord = coordinate => coordinate - 4

    let cellsToDie = []
    let cellsToBeBorn = []

    for (const coordinates of centerCoordinates) {
      const cellColor = getColor(ctx, ...coordinates)
      const neighbors = getNeighborsCoord(...coordinates)

      let liveNeigborsCount = 0

      for (const neighbor of neighbors) {
        liveNeigborsCount =
          getColor(ctx, ...neighbor) !== 0
            ? liveNeigborsCount + 1
            : liveNeigborsCount
      }
      if ([0, 1, 4].includes(liveNeigborsCount) && cellColor) {
        // only live cell can die
        cellsToDie.push(coordinates.map(makeTopLeftCoord))
      }
      if (liveNeigborsCount === 3) {
        cellsToBeBorn.push(coordinates.map(makeTopLeftCoord))
      }
    }
    console.log(`cellsToBeBorn: ${cellsToBeBorn}\ncellsToDie: ${cellsToDie}`)
    clearCells(cellsToDie)
    drawCells(cellsToBeBorn)

    this.setState(prevState => ({ generation: prevState.generation + 1 }))
  }

  handleReset = () => {
    let canvas = document.getElementById('game-grid')
    let ctx = canvas.getContext('2d')

    let width = parseInt(this.state.gridWidth, 10)
    let height = parseInt(this.state.gridHeight, 10)

    ctx.clearRect(0, 0, canvas.width, canvas.height)
    this.handleUpdateCanvas(width, height)
  }

  render () {
    let width = this.state.gridWidth
    let height = this.state.gridHeight

    return (

Conway's Game of Life

        <Controls
          handleReset={this.handleReset}
          handleStart={this.handleStart}
        />
        <Grid
          gridWidth={width}
          gridHeight={height}
          onUpdate={this.handleUpdateCanvas}
        />

    )
  }
}

export default App
<|endoftext|>


<commit_before>NailPolish.View = Backbone.View.extend(_.extend(_.clone(NailPolish.SubviewManager), {
  addListeners: {},
  presenterClass: function () {
    return NailPolish.Presenter;
  },

  initialize: function (opts) {
    opts = opts || {};
    this.parent = (this.parent && $(this.parent)) || opts.parent;
    this.repository = opts.repository;
    this.attachmentMethod = opts.attachmentMethod || this.attachmentMethod || 'append';
    this.parentSelector = opts.parentSelector || this.parentSelector;
    this.templateName = opts.templateName || this.templateName;
    if (opts.presenterClass) {
      this.presenterClass = function() {
        return opts.presenterClass;
      };
    }
    this.init(opts);
  },

  init: function (opts) {
    // override in classes
  },

  render: function () {
    this.renderTemplate();
    this.renderSubviews();
    this.attachToParent();
    this.afterRender();

    $(window).trigger('NPView::Rendered');
  },

  subviews: function () {
    return [];
  },

  renderSubviews: function() {
    this.renderEach(this.subviews());
  },

  attachToParent: function () {
    var $parent = new NailPolish.View.ParentFinder(
      this.parent, this.parentSelector, this.attachmentMethod
    ).perform();
    $parent && $parent[this.attachmentMethod](this.el);
  },

  afterRender: function () { /* template method hook ! */
  },

  reRender: function (opts) {
    opts = opts || {};
    if (opts.full === true) {
      this.render();
    }else {
      this.renderTemplate();
      this.renderSubviews();
    }

    this.delegateEvents();

    if(opts.afterRender === true) {
      this.afterRender();
    }
  },

  events: function () {
    return this.addListeners;
  },

  renderTemplate: function () {
    var templateName = _.isFunction(this.templateName) ? this.templateName() : this.templateName;

    var template = HoganTemplates[templateName];
    if (!template) {
      return;
    }

    var rendered = template.render(this.presenter(), this.partials());
    this.$el.html(rendered);
  },

  presenter: function () {
    var presented = this.model || this.collection || {};
    var presenter = new (this.presenterClass())(presented);
    presenter.repository = presenter.repository || this.repository;
    return presenter.toJSON();
  },

  partials: function () {
    // override with own partials if necessary
    return {};
  },

  removeSelf: function() {
    Backbone.View.prototype.remove.apply(this);
  }
}));
<commit_msg>reTemplate, cleanup to View<commit_after>NailPolish.View = Backbone.View.extend(_.extend(_.clone(NailPolish.SubviewManager), {
  addListeners: {},
  presenterClass: function () {
    return NailPolish.Presenter;
  },

  initialize: function (opts) {
    opts = opts || {};
    this.parent = (this.parent && $(this.parent)) || opts.parent;
    this.repository = opts.repository;
    this.attachmentMethod = opts.attachmentMethod || this.attachmentMethod || 'append';
    this.parentSelector = opts.parentSelector || this.parentSelector;
    this.templateName = opts.templateName || this.templateName;
    if (opts.presenterClass) {
      this.presenterClass = function() {
        return opts.presenterClass;
      };
    }
    this.init(opts);
  },

  init: function (opts) {
    // override in classes
  },

  render: function () {
    this.renderTemplate();
    this.renderSubviews();
    this.attachToParent();
    this.afterRender();

    $(window).trigger('NPView::Rendered');
  },

  subviews: function () {
    return [];
  },

  renderSubviews: function() {
    this.renderEach(this.subviews());
  },

  attachToParent: function () {
    var $parent = new NailPolish.View.ParentFinder(
      this.parent, this.parentSelector, this.attachmentMethod
    ).perform();
    if ($parent) {
      $parent[this.attachmentMethod](this.el);
    }
  },

  afterRender: function () {
    /* template method hook ! */
  },

  reRender: function (opts) {
    opts = opts || {};
    if (opts.full === true) {
      this.render();
    } else {
      this.renderTemplate();
      this.renderSubviews();
    }

    this.delegateEvents();

    if (opts.afterRender === true) {
      this.afterRender();
    }
  },

  reTemplate: function(opts) {
    opts = _.extend({
      template: true,
      subviews: true,
      events: true,
      afterRender: true,
      resetClass: true
    }, opts);

    if (opts.resetClass) {
      this.resetClass();
    }
    if (opts.template) {
      this.renderTemplate();
    }
    if (opts.subviews) {
      this.renderSubviews();
    }
    if (opts.events) {
      this.delegateEvents();
    }
    if (opts.afterRender) {
      this.afterRender();
    }
  },

  resetClass: function() {
    this.$el.removeClass();
    this.$el.addClass(_.result(this, 'className'));
  },

  events: function () {
    return this.addListeners;
  },

  renderTemplate: function () {
    var templateName = _.result(this, 'templateName');

    var template = HoganTemplates[templateName];
    if (!template) {
      return;
    }

    var rendered = template.render(this.presenter(), this.partials());
    this.$el.html(rendered);
  },

  presenter: function () {
    var presented = this.model || this.collection || {};
    var presenter = new (this.presenterClass())(presented);
    presenter.repository = presenter.repository || this.repository;
    return presenter.toJSON();
  },

  partials: function () {
    // override with own partials if necessary
    return {};
  },

  removeSelf: function() {
    Backbone.View.prototype.remove.apply(this);
  }
}));
<|endoftext|>


<commit_before>//= require codemirror
//= require simple-hint
//= require select2.min
//= require jquery.faviconNotify
//= require rgbcolor
//= require jquery-ui.core.min
//= require jquery-ui.autocomplete
//= require jquery.caret
//= require ZeroClipboard
//= require tag-it
//= require jquery.tipsy
//= require ../../../lib/assets/javascripts/utils/postgres.codemirror
//= require ../../../lib/assets/javascripts/utils/carto.codemirror
//= require models
//= require ../../../lib/assets/javascripts/cartodb/common/dropdown_menu
//= require ../../../lib/assets/javascripts/cartodb/common/forms/string_field
//= require ../../../lib/assets/javascripts/cartodb/common/import_pane.js
//= require_tree ../../../lib/assets/javascripts/cartodb/common
//= require ../../../lib/assets/javascripts/cartodb/common/export_table_dialog
//= require ../../../lib/assets/javascripts/cartodb/table/column_type_dropdown
//= require ../../../lib/assets/javascripts/cartodb/table/editor_small_dialog
//= require ../../../lib/assets/javascripts/cartodb/table/header_dropdown
//= require ../../../lib/assets/javascripts/cartodb/table/header_view
//= require ../../../lib/assets/javascripts/cartodb/table/infowindow
//= require ../../../lib/assets/javascripts/cartodb/table/row_view
//= require ../../../lib/assets/javascripts/cartodb/table/header_view
//= require ../../../lib/assets/javascripts/cartodb/table/tableview
//= require ../../../lib/assets/javascripts/cartodb/table/mapview
////= require ../../../lib/assets/javascripts/cartodb/table/filter_column_dialog
//= require_tree ../../../lib/assets/javascripts/cartodb/table/views
//= require_tree ../../../lib/assets/javascripts/cartodb/table_public
//= require ../../../lib/assets/javascripts/cartodb/table_public/table_public
//= require_tree ../../../lib/assets/javascripts/cartodb/table/views
//= require_tree ../../../lib/assets/javascripts/cartodb/dashboard/views
//= require_tree ../../../lib/assets/javascripts/cartodb/table_public/views
<commit_msg>removed unnecessary library load<commit_after>//= require codemirror
//= require select2.min
//= require jquery.faviconNotify
//= require rgbcolor
//= require jquery-ui.core.min
//= require jquery-ui.autocomplete
//= require jquery.caret
//= require ZeroClipboard
//= require tag-it
//= require jquery.tipsy
//= require ../../../lib/assets/javascripts/utils/postgres.codemirror
//= require ../../../lib/assets/javascripts/utils/carto.codemirror
//= require models
//= require ../../../lib/assets/javascripts/cartodb/common/dropdown_menu
//= require ../../../lib/assets/javascripts/cartodb/common/forms/string_field
//= require ../../../lib/assets/javascripts/cartodb/common/import_pane.js
//= require_tree ../../../lib/assets/javascripts/cartodb/common
//= require ../../../lib/assets/javascripts/cartodb/common/export_table_dialog
//= require ../../../lib/assets/javascripts/cartodb/table/column_type_dropdown
//= require ../../../lib/assets/javascripts/cartodb/table/editor_small_dialog
//= require ../../../lib/assets/javascripts/cartodb/table/header_dropdown
//= require ../../../lib/assets/javascripts/cartodb/table/header_view
//= require ../../../lib/assets/javascripts/cartodb/table/infowindow
//= require ../../../lib/assets/javascripts/cartodb/table/row_view
//= require ../../../lib/assets/javascripts/cartodb/table/header_view
//= require ../../../lib/assets/javascripts/cartodb/table/tableview
//= require ../../../lib/assets/javascripts/cartodb/table/mapview
////= require ../../../lib/assets/javascripts/cartodb/table/filter_column_dialog
//= require_tree ../../../lib/assets/javascripts/cartodb/table/views
//= require_tree ../../../lib/assets/javascripts/cartodb/table_public
//= require ../../../lib/assets/javascripts/cartodb/table_public/table_public
//= require_tree ../../../lib/assets/javascripts/cartodb/table/views
//= require_tree ../../../lib/assets/javascripts/cartodb/dashboard/views
//= require_tree ../../../lib/assets/javascripts/cartodb/table_public/views
<|endoftext|>


<commit_before>// @flow
import React from 'react';
import ClassNames from 'classnames';

import Display from './Display';
import ViewLayerList from './ViewLayerList';
import ActionLayer from './actionLayer/ActionLayer';

import {generateImageList, SPECIFY_PROPERTY} from '../state/generateImageList';
import initialState from '../state/initialState';

import type {Image} from '../image';
import type {ActionLayerName} from './actionLayer/ActionLayer';
import type {ActionLayerSettings} from '../state/generateActionLayerSettings';

type Props = {||};
type State = {
  isDragOver: boolean,
  imageList: Image[],
  display: {
    width: number,
    height: number,
    magnificationPercent: number,
  },
  activeImageId: number,
  activeActionLayer: ActionLayerName,
  actionLayerSettings: ActionLayerSettings,
};

export default class App extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = initialState;
  }
  getActiveImage(): Image {
    const {imageList, activeImageId} = this.state;
    return imageList.filter(image => image.id === activeImageId)[0];
  }
  render() {
    const {
      isDragOver,
      imageList,
      display,
      activeImageId,
      activeActionLayer,
      actionLayerSettings,
    } = this.state;
    const classNames = ClassNames({
      app: true,
      'file-drag-over-area': isDragOver,
    });
    return (

 {
          e.preventDefault();
          this.setState({isDragOver: false});
        }}
        onDragOver={e => {
          e.preventDefault();
          e.dataTransfer.dropEffect = 'copy';
        }}
        onDragEnter={e => {
          e.preventDefault();
          this.setState({isDragOver: true});
        }}
        onDragLeave={e => {
          e.preventDefault();
          if (e.clientX === 0) this.setState({isDragOver: false});
        }}
      >
        <button
          onClick={() => {
            this.setState({
              activeActionLayer:
                this.state.activeActionLayer === 'drawLine'
                  ? 'eraser'
                  : 'drawLine',
            });
          }}
        >
          動作確認用のボタン
        </button>
        <Display {...display}>
          <ViewLayerList viewLayerDataList={imageList} />
          <ActionLayer
            activeActionLayer={activeActionLayer}
            imageData={this.getActiveImage().imageData}
            setting={actionLayerSettings[activeActionLayer]}
            updateImageData={imageData => {
              const updatedState = generateImageList({
                type: SPECIFY_PROPERTY,
                data: {imageData},
                currentState: imageList,
                target: activeImageId,
              });
              this.setState({imageList: updatedState});
            }}
          />
        </Display>
        {isDragOver && (

画像をドロップすると新しくレイヤーが作られます

        )}

    );
  }
}
<commit_msg>動作確認用のボタンを追加。<commit_after>// @flow
import React from 'react';
import ClassNames from 'classnames';

import Display from './Display';
import ViewLayerList from './ViewLayerList';
import ActionLayer from './actionLayer/ActionLayer';

import {generateImageList, SPECIFY_PROPERTY} from '../state/generateImageList';
import {generateActionLayerSettings, SPECIFY_CONTEXT_PROPERTY} from '../state/generateActionLayerSettings';
import initialState from '../state/initialState';

import type {Image} from '../image';
import type {ActionLayerName} from './actionLayer/ActionLayer';
import type {ActionLayerSettings} from '../state/generateActionLayerSettings';

type Props = {||};
type State = {
  isDragOver: boolean,
  imageList: Image[],
  display: {
    width: number,
    height: number,
    magnificationPercent: number,
  },
  activeImageId: number,
  activeActionLayer: ActionLayerName,
  actionLayerSettings: ActionLayerSettings,
};

export default class App extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = initialState;
  }
  getActiveImage(): Image {
    const {imageList, activeImageId} = this.state;
    return imageList.filter(image => image.id === activeImageId)[0];
  }
  render() {
    const {
      isDragOver,
      imageList,
      display,
      activeImageId,
      activeActionLayer,
      actionLayerSettings,
    } = this.state;
    const classNames = ClassNames({
      app: true,
      'file-drag-over-area': isDragOver,
    });
    return (

 {
          e.preventDefault();
          this.setState({isDragOver: false});
        }}
        onDragOver={e => {
          e.preventDefault();
          e.dataTransfer.dropEffect = 'copy';
        }}
        onDragEnter={e => {
          e.preventDefault();
          this.setState({isDragOver: true});
        }}
        onDragLeave={e => {
          e.preventDefault();
          if (e.clientX === 0) this.setState({isDragOver: false});
        }}
      >
        <button
          onClick={() => {
            this.setState({
              activeActionLayer:
                this.state.activeActionLayer === 'drawLine'
                  ? 'eraser'
                  : 'drawLine',
            });
          }}
        >
          ペン ⇔ 消しゴム
        </button>
        <button
          onClick={() => {
            const newValue =
              actionLayerSettings.drawLine.ctx.lineWidth === 1 ? 15 : 1;
            const newSetting = generateActionLayerSettings({
              type: SPECIFY_CONTEXT_PROPERTY,
              currentState: actionLayerSettings,
              data: {lineWidth: newValue},
              target: 'drawLine',
            });
            this.setState({actionLayerSettings: newSetting});
          }}
        >
          ペン（太） ⇔　ペン（細）
        </button>
        <Display {...display}>
          <ViewLayerList viewLayerDataList={imageList} />
          <ActionLayer
            activeActionLayer={activeActionLayer}
            imageData={this.getActiveImage().imageData}
            setting={actionLayerSettings[activeActionLayer]}
            updateImageData={imageData => {
              const updatedState = generateImageList({
                type: SPECIFY_PROPERTY,
                data: {imageData},
                currentState: imageList,
                target: activeImageId,
              });
              this.setState({imageList: updatedState});
            }}
          />
        </Display>
        {isDragOver && (

画像をドロップすると新しくレイヤーが作られます

        )}

    );
  }
}
<|endoftext|>


<commit_before>(function(Modules) {
  "use strict";

  // Constructor
  Modules.TaxonTagger = function(taxons) {
    this.selectors = {
      tag_input_element: '.js_bulk_tagger_input',
      bulk_tagger_form: '.js-bulk-tagger-form',
      content_item_forms: '.js-content-item-form',
      select_all_toggle: '.js-select-all',
      content_item_checkboxes: '.js-content-selector',
      selected_count: '.js-selected-count',
      done_form_selector: '.js-mark-as-done'
    }

    this.form_error_class = 'error-saving';
    this.color_of_success = "#d8f3d8";

    this.options_for_select2 = {
      allowClear: true,
      multiple: true,
      data: this.taxons_for_select2(taxons),
      formatSelection: this.format_selected_taxon
    }
  };

  Modules.TaxonTagger.prototype = {

    /*
     * Initializes each content-item tagging form with Select2 and auto-save.
    **/
    start_individual_taggers: function($element) {
      var self = this;

      /*
       * Initialise tag input selects, but only once they become
       * visible aka on-screen.
       */
      $element.find(self.selectors.tag_input_element).waypoint({
        handler: function(_) {
          var $this = $(this.element),
              taxons = $this.data('taxons'),
              options = self.options_for_select2;

          // remove 'disabled' attribute from input element
          $this.prop('disabled', false);

          // populate with previously selected taxons, and initialize select2
          $this.val(taxons).select2(options);

          // remove waypoint
          this.destroy();
        },
        offset: 'bottom-in-view'
      });

      // Contains all the content-item tagging forms
      var $content_item_forms = $element.find(self.selectors.content_item_forms);

      // Individual content-item forms save on change
      $content_item_forms.find('.select2').on(
        'change',
        function(event) {
          $(this).parents('form').trigger('submit.rails');
        }
      );

      // Ajax response handlers for individual content-item form submissions
      $content_item_forms.on(
        'ajax:success',
        function() {
          self.mark_form_as_successfully_updated($(this));
        }
      ).on(
        'ajax:error',
        function() {
          self.mark_form_as_failed_to_update($(this));
        }
      );

      $element.find(self.selectors.done_form_selector).on(
        'ajax:success',
        function() {
          $(this).parents('.content-item').fadeOut('fast', function() {
            $(this).remove();
            Waypoint.refreshAll();
          });
        }
      ).on(
        'ajax:error',
        function() {
          self.mark_form_as_failed_to_update($(this).parents('.content-item'));
        }
      );
    },

    /*
     * Initializes the Bulk Tagger interface
     * Where multiple content-items can be selected and tagged at the same time.
    **/
    start_bulk_tagger: function($element) {
      var self = this;

      var $bulk_tagger_form = $element.find(self.selectors.bulk_tagger_form);

      // Ajax response handlers for bulk tag form submission
      $bulk_tagger_form.on(
        "ajax:success",
        function(event, result) {
          result.forEach(function(data_item) {
            var $form = $("form[data-ref='" + data_item.content_id + "']");
            self.mark_form_as_successfully_updated($form, data_item.taxons);
          });
        }
      ).on(
        "ajax:error",
        function(event, data) {
          self.mark_form_as_failed_to_update($(this));
        }
      );

      // Contains all the content-item tagging forms
      var $content_item_forms = $element.find(self.selectors.content_item_forms);

      // Aligns the content-item selection checkboxes
      $content_item_forms.each(function(index, form) {
        var ref = $(form).attr('data-ref');
        var top_pos = $(form).position().top - 80 + "px";
        $('.content-selector[value="'+ ref +'"]').css("top", top_pos).css("display", "inherit");
      });

      // Contains a reference to every content-item selector
      var $content_item_checkboxes = $element.find(self.selectors.content_item_checkboxes);

      // This is the display element for the count of selected items
      var $selected_count = $element.find(self.selectors.selected_count);

      // Select All toggle
      $element.find(self.selectors.select_all_toggle).on(
        'change',
        function() {
          $content_item_checkboxes.prop('checked', this.checked);
          self.update_selected_count($selected_count, $content_item_checkboxes);
        }
      );

      // Handler to keep track of changes to the count of selected items
      $content_item_checkboxes.on(
        'change',
        function() {
          self.update_selected_count($selected_count, $content_item_checkboxes);
        }
      )
    },

    /**
     * Given:
     * {
     *   "206b7f3a-49b5-476f-af0f-fd27e2a68473": "Parenting, childcare and children's services"
     * }
     *
     * Returns an array of objects suitable for initializing a select2 element:
     * [{
     *   "id": "206b7f3a-49b5-476f-af0f-fd27e2a68473",
     *   "text": "Parenting, childcare and children's services"
     * }]
    **/
    taxons_for_select2: function(taxons) {
      var self = this;
      return Object.keys(taxons).reduce(function(acc, taxon_id) {
        var ancestors = self.taxon_ancestors(taxon_id, taxons);
        ancestors.shift(); // lose the first ancestor, it's common to all taxons

        acc.push({
          "id": taxon_id,
          "text": ancestors.join(' > ')
        });
        return acc;
      }, []);
    },

    // Returns the ancestors array of taxon names
    taxon_ancestors: function(taxon_id, taxons) {
      var taxon = taxons[taxon_id];

      if(taxon.parent_id !== null) {
        return this.taxon_ancestors(taxon.parent_id, taxons).concat(taxon.name);
      }
      else {
        return [taxon.name];
      }
    },

    // renders the selected tag
    format_selected_taxon: function(data) {
      return data.text.split(' > ').pop();
    },

    // display a brief message next to a form
    display_response_state: function($formEl, message) {
      $formEl.siblings(".js-save-state")
        .text(message)
        .delay(700)
        .animate({ opacity: 0 }, 300, 'linear', function() {
          var $textEl = $(this)
          $textEl.html(" ");
          $textEl.css({ opacity: 1 })
        });
    },

    // Green flash of success
    mark_form_as_successfully_updated: function($form, taxons) {
      $form.removeClass(this.form_error_class);
      $form.effect("highlight", { color: this.color_of_success }, 1000);

      this.display_response_state($form, "Saved");

      if(typeof taxons !== 'undefined') {
        var $select2 = $form.find('.select2');
        $select2.data('taxons', taxons);
        this.update_select2_with_new_taxons($select2);
      }
    },

    update_select2_with_new_taxons: function($select2) {
      var taxons = $select2.data('taxons');
      $select2.select2('val', taxons);
    },

    // Red background of failure
    mark_form_as_failed_to_update: function($form) {
      this.display_response_state($form, "Failed to save");
      $form.addClass(this.form_error_class);
    },

    // Tally of the number of content-items selected for bulk-tagging
    update_selected_count: function($selected_count, $content_item_checkboxes) {
      var count = this.count_of_selected_content_items($content_item_checkboxes);
      if(count == 0) {
        $selected_count.text('None');
      } else {
        $selected_count.text(count);
      }
    },

    // Given a $() of checkboxes, returns a count of the number of selected
    count_of_selected_content_items: function($content_item_checkboxes) {
      var checkbox_to_scalar = function($checkbox) {
        return $checkbox.checked ? 1 : 0;
      };

      var sum = function(total, increment) {
        return total + increment;
      };

      return $content_item_checkboxes
        .toArray()
        .map(checkbox_to_scalar)
        .reduce(sum, 0);
    }
  };
})(window.GOVUKAdmin.Modules);
<commit_msg>Allow content to be tagged to the branch's root taxon<commit_after>(function(Modules) {
  "use strict";

  // Constructor
  Modules.TaxonTagger = function(taxons) {
    this.selectors = {
      tag_input_element: '.js_bulk_tagger_input',
      bulk_tagger_form: '.js-bulk-tagger-form',
      content_item_forms: '.js-content-item-form',
      select_all_toggle: '.js-select-all',
      content_item_checkboxes: '.js-content-selector',
      selected_count: '.js-selected-count',
      done_form_selector: '.js-mark-as-done'
    }

    this.form_error_class = 'error-saving';
    this.color_of_success = "#d8f3d8";

    this.options_for_select2 = {
      allowClear: true,
      multiple: true,
      data: this.taxons_for_select2(taxons),
      formatSelection: this.format_selected_taxon
    }
  };

  Modules.TaxonTagger.prototype = {

    /*
     * Initializes each content-item tagging form with Select2 and auto-save.
    **/
    start_individual_taggers: function($element) {
      var self = this;

      /*
       * Initialise tag input selects, but only once they become
       * visible aka on-screen.
       */
      $element.find(self.selectors.tag_input_element).waypoint({
        handler: function(_) {
          var $this = $(this.element),
              taxons = $this.data('taxons'),
              options = self.options_for_select2;

          // remove 'disabled' attribute from input element
          $this.prop('disabled', false);

          // populate with previously selected taxons, and initialize select2
          $this.val(taxons).select2(options);

          // remove waypoint
          this.destroy();
        },
        offset: 'bottom-in-view'
      });

      // Contains all the content-item tagging forms
      var $content_item_forms = $element.find(self.selectors.content_item_forms);

      // Individual content-item forms save on change
      $content_item_forms.find('.select2').on(
        'change',
        function(event) {
          $(this).parents('form').trigger('submit.rails');
        }
      );

      // Ajax response handlers for individual content-item form submissions
      $content_item_forms.on(
        'ajax:success',
        function() {
          self.mark_form_as_successfully_updated($(this));
        }
      ).on(
        'ajax:error',
        function() {
          self.mark_form_as_failed_to_update($(this));
        }
      );

      $element.find(self.selectors.done_form_selector).on(
        'ajax:success',
        function() {
          $(this).parents('.content-item').fadeOut('fast', function() {
            $(this).remove();
            Waypoint.refreshAll();
          });
        }
      ).on(
        'ajax:error',
        function() {
          self.mark_form_as_failed_to_update($(this).parents('.content-item'));
        }
      );
    },

    /*
     * Initializes the Bulk Tagger interface
     * Where multiple content-items can be selected and tagged at the same time.
    **/
    start_bulk_tagger: function($element) {
      var self = this;

      var $bulk_tagger_form = $element.find(self.selectors.bulk_tagger_form);

      // Ajax response handlers for bulk tag form submission
      $bulk_tagger_form.on(
        "ajax:success",
        function(event, result) {
          result.forEach(function(data_item) {
            var $form = $("form[data-ref='" + data_item.content_id + "']");
            self.mark_form_as_successfully_updated($form, data_item.taxons);
          });
        }
      ).on(
        "ajax:error",
        function(event, data) {
          self.mark_form_as_failed_to_update($(this));
        }
      );

      // Contains all the content-item tagging forms
      var $content_item_forms = $element.find(self.selectors.content_item_forms);

      // Aligns the content-item selection checkboxes
      $content_item_forms.each(function(index, form) {
        var ref = $(form).attr('data-ref');
        var top_pos = $(form).position().top - 80 + "px";
        $('.content-selector[value="'+ ref +'"]').css("top", top_pos).css("display", "inherit");
      });

      // Contains a reference to every content-item selector
      var $content_item_checkboxes = $element.find(self.selectors.content_item_checkboxes);

      // This is the display element for the count of selected items
      var $selected_count = $element.find(self.selectors.selected_count);

      // Select All toggle
      $element.find(self.selectors.select_all_toggle).on(
        'change',
        function() {
          $content_item_checkboxes.prop('checked', this.checked);
          self.update_selected_count($selected_count, $content_item_checkboxes);
        }
      );

      // Handler to keep track of changes to the count of selected items
      $content_item_checkboxes.on(
        'change',
        function() {
          self.update_selected_count($selected_count, $content_item_checkboxes);
        }
      )
    },

    /**
     * Given:
     * {
     *   "206b7f3a-49b5-476f-af0f-fd27e2a68473": "Parenting, childcare and children's services"
     * }
     *
     * Returns an array of objects suitable for initializing a select2 element:
     * [{
     *   "id": "206b7f3a-49b5-476f-af0f-fd27e2a68473",
     *   "text": "Parenting, childcare and children's services"
     * }]
    **/
    taxons_for_select2: function(taxons) {
      var self = this;
      var taxon_content_ids = Object.keys(taxons);
      var root_content_id = taxon_content_ids.shift();

      return taxon_content_ids.reduce(function(acc, taxon_id) {
        var ancestors = self.taxon_ancestors(taxon_id, taxons);
        ancestors.shift(); // lose the first ancestor, it's common to all taxons

        acc.push({
          "id": taxon_id,
          "text": ancestors.join(' > ')
        });
        return acc;
      }, [{
        "id": root_content_id,
        "text": taxons[root_content_id].name
      }]);
    },

    // Returns the ancestors array of taxon names
    taxon_ancestors: function(taxon_id, taxons) {
      var taxon = taxons[taxon_id];

      if(taxon.parent_id !== null) {
        return this.taxon_ancestors(taxon.parent_id, taxons).concat(taxon.name);
      }
      else {
        return [taxon.name];
      }
    },

    // renders the selected tag
    format_selected_taxon: function(data) {
      return data.text.split(' > ').pop();
    },

    // display a brief message next to a form
    display_response_state: function($formEl, message) {
      $formEl.siblings(".js-save-state")
        .text(message)
        .delay(700)
        .animate({ opacity: 0 }, 300, 'linear', function() {
          var $textEl = $(this)
          $textEl.html(" ");
          $textEl.css({ opacity: 1 })
        });
    },

    // Green flash of success
    mark_form_as_successfully_updated: function($form, taxons) {
      $form.removeClass(this.form_error_class);
      $form.effect("highlight", { color: this.color_of_success }, 1000);

      this.display_response_state($form, "Saved");

      if(typeof taxons !== 'undefined') {
        var $select2 = $form.find('.select2');
        $select2.data('taxons', taxons);
        this.update_select2_with_new_taxons($select2);
      }
    },

    update_select2_with_new_taxons: function($select2) {
      var taxons = $select2.data('taxons');
      $select2.select2('val', taxons);
    },

    // Red background of failure
    mark_form_as_failed_to_update: function($form) {
      this.display_response_state($form, "Failed to save");
      $form.addClass(this.form_error_class);
    },

    // Tally of the number of content-items selected for bulk-tagging
    update_selected_count: function($selected_count, $content_item_checkboxes) {
      var count = this.count_of_selected_content_items($content_item_checkboxes);
      if(count == 0) {
        $selected_count.text('None');
      } else {
        $selected_count.text(count);
      }
    },

    // Given a $() of checkboxes, returns a count of the number of selected
    count_of_selected_content_items: function($content_item_checkboxes) {
      var checkbox_to_scalar = function($checkbox) {
        return $checkbox.checked ? 1 : 0;
      };

      var sum = function(total, increment) {
        return total + increment;
      };

      return $content_item_checkboxes
        .toArray()
        .map(checkbox_to_scalar)
        .reduce(sum, 0);
    }
  };
})(window.GOVUKAdmin.Modules);
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
 * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
 * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
 * Code distributed by Google as part of the polymer project is also
 * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
 */
var _              = require('lodash');
var findup         = require('findup-sync');
var fs             = require('fs');
var nomnom         = require('nomnom');
var path           = require('path');
var serveWaterfall = require('serve-waterfall');

var paths = require('./paths');

var HOME_DIR    = path.resolve(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE);
var CONFIG_NAME = 'wct.conf.js';
var WCT_ROOT    = path.resolve(__dirname, '..');

// The full set of options, as a reference.
function defaults() {
  return {
    // The test suites that should be run.
    suites:      ['test/'],
    // Output stream to write log messages to.
    output:      process.stdout,
    // Whether the output stream should be treated as a TTY (and be given more
    // complex output formatting). Defaults to `output.isTTY`.
    ttyOutput:   undefined,
    // Spew all sorts of debugging messages.
    verbose:     false,
    // Display test results in expanded form. Verbose implies expanded.
    expanded:    false,
    // The on-disk path where tests & static files should be served from. Paths
    // (such as `suites`) are evaluated relative to this.
    //
    // Defaults to the project directory.
    root:        undefined,
    // Idle timeout for tests.
    testTimeout: 90 * 1000,
    // Whether the browser should be closed after the tests run.
    persistent:  false,
    // Additional .js files to include in *generated* test indexes.
    extraScripts: [],
    // Configuration options passed to the browser client.
    clientOptions: {
      // Also see `webserver.pathMappings` below.
      root: '/test-components/',
    },
    // Webdriver capabilities objects for each browser that should be run.
    //
    // Capabilities can also contain a `url` value which is either a string URL
    // for the webdriver endpoint, or {hostname:, port:, user:, pwd:}.
    //
    // Most of the time you will want to rely on the WCT browser plugins to fill
    // this in for you (e.g. via `--local`, `--sauce`, etc).
    activeBrowsers: [],
    // Default capabilities to use when constructing webdriver connections (for
    // each browser specified in `activeBrowsers`). A handy place to hang common
    // configuration.
    //
    // Selenium: https://code.google.com/p/selenium/wiki/DesiredCapabilities
    // Sauce:    https://docs.saucelabs.com/reference/test-configuration/
    browserOptions: {},
    // The plugins that should be loaded, and their configuration.
    //
    // When an array, the named plugins will be loaded with their default
    // configuration. When an object, each key maps to a plugin, and values are
    // configuration values to be merged.
    //
    //   plugins: {
    //     local: {browsers: ['firefox', 'chrome']},
    //   }
    //
    plugins: ['local', 'sauce'],
    // Callback that allows you to perform advanced configuration of the WCT
    // runner.
    //
    // The hook is given the WCT context, and can generally be written like a
    // plugin. For example, to serve custom content via the internal webserver:
    //
    //     registerHooks: function(wct) {
    //       wct.hook('prepare:webserver', function(app, done) {
    //         app.use(...);
    //         done();
    //       });
    //     }
    //
    registerHooks: function(wct) {},
    // Configuration options for the webserver that serves up your test files
    // and dependencies.
    //
    // Typically, you will not need to modify these values.
    webserver: {
      // The port that the webserver should run on. A port will be determined at
      // runtime if none is provided.
      port: undefined,
      // The hostname used when generating URLs for the webdriver client.
      hostname: 'localhost',
      // mappings of URL prefix to on disk paths that the web server should
      // serve via https://github.com/PolymerLabs/serve-waterfall
      pathMappings: serveWaterfall.mappings.WEB_COMPONENT.concat([
        // WCT loads its client dependencies via a separate components root.
        {'/test-components/': path.join(WCT_ROOT, 'bower_components')},
      ]),
      // The URL prefix that serves contents from the project root.
      urlPrefix: '/components/<basename>',
    },
  };
}

/** nomnom configuration for command line arguments.
 *
 * This might feel like duplication with `defaults()`, and out of place (why not
 * in `cli.js`?). But, not every option matches a configurable value, and it is
 * best to keep the configuration for these together to help keep them in sync.
 */
var ARG_CONFIG = {
  persistent: {
    help:      'Keep browsers active (refresh to rerun tests).',
    abbr:      'p',
    flag:      true,
  },
  root: {
    help:      'The root directory to serve tests from.',
    transform: path.resolve,
  },
  plugins: {
    help:      'Plugins that should be loaded.',
    metavar:   'NAME',
    full:      'plugin',
    list:       true,
  },
  skipPlugins: {
    help:      'Configured plugins that should _not_ be loaded.',
    metavar:   'NAME',
    full:      'skip-plugin',
    list:      true,
  },
  expanded: {
    help:      'Log a status line for each test run.',
    flag:      true,
  },
  verbose: {
    help:      'Log all the things.',
    flag:      true,
  },
  simpleOutput: {
    help:      'Avoid fancy terminal output.',
    flag:      true,
  },
  skipUpdateCheck: {
    help:      'Don\'t check for updates.',
    full:      'skip-update-check',
    flag:      true,
  },
  'webserver.port': {
    help:      'A port to use for the test webserver.',
    full:      'webserver-port',
  },
  'webserver.hostname': {
    full:      'webserver-hostname',
    hidden:    true,
  },
  // Managed by supports-color; let's not freak out if we see it.
  color: {flag: true},

  // Deprecated

  browsers: {
    abbr:   'b',
    hidden: true,
    list:   true,
  },
  remote: {
    abbr:   'r',
    hidden: true,
    flag:   true,
  },
};

// Values that should be extracted when pre-parsing args.
var PREPARSE_ARGS = ['plugins', 'skipPlugins', 'simpleOutput', 'skipUpdateCheck'];

/**
 * Discovers appropriate config files (global, and for the project), merging
 * them, and returning them.
 *
 * @return {!Object} The merged configuration.
 */
function fromDisk() {
  var globalFile  = path.join(HOME_DIR, CONFIG_NAME);
  var projectFile = findup(CONFIG_NAME, {nocase: true});
  // Load a shared config from the user's home dir, if they have one, and then
  // try the project-specific path (starting at the current working directory).
  var paths   = _.unique([globalFile, projectFile]);
  var configs = _.filter(paths, fs.existsSync).map(require);
  var options = merge.apply(null, configs);

  if (!options.root && projectFile && projectFile !== globalFile) {
    options.root = path.dirname(projectFile);
  }

  return options;
}

/**
 * Runs a simplified options parse over the command line arguments, extracting
 * any values that are necessary for a full parse.
 *
 * At the moment, the only values extracted are `--plugin` and `--simpleOutput`.
 *
 * @param {!Array<string>} args
 * @return {!Object}
 */
function preparseArgs(args) {
  // Don't let it short circuit on help.
  args = _.difference(args, ['--help', '-h']);

  var parser = nomnom();
  parser.options(ARG_CONFIG);
  parser.printer(function() {});  // No-op output & errors.
  var options = parser.parse(args);

  return _expandOptionPaths(_.pick(options, PREPARSE_ARGS));
}

/**
 * Runs a complete options parse over the args, respecting plugin options.
 *
 * @param {!Context} context The context, containing plugin state and any base
 *     options to merge into.
 * @param {!Array<string>} args The args to parse.
 * @param {function(*)} done
 */
function parseArgs(context, args, done) {
  var parser = nomnom();
  parser.script('wct');
  parser.options(ARG_CONFIG);

  context.plugins(function(error, plugins) {
    if (error) return done(error);

    plugins.forEach(_configurePluginOptions.bind(null, parser));
    var options = _expandOptionPaths(normalize(parser.parse(args)));
    if (options._ && options._.length > 0) {
      options.suites = options._;
    }

    context.options = merge(context.options, options);
    done();
  });
}

function _configurePluginOptions(parser, plugin) {
  if (!plugin.cliConfig || plugin.cliConfig.length === 0) return;

  // Group options per plugin. It'd be nice to also have a header, but that ends
  // up shifting all the options over.
  parser.option('plugins.' + plugin.name + '.', {string: ' '});

  _.each(plugin.cliConfig, function(config, key) {
    // Make sure that we don't expose the name prefixes.
    if (!config.full) {
      config.full = key;
    }
    parser.option('plugins.' + plugin.name + '.' + key, config);
  });
}

function _expandOptionPaths(options) {
  var result = {};
  _.each(options, function(value, key) {
    var target = result;
    var parts  = key.split('.');
    for (var i = 0; i < parts.length - 1; i++) {
      target = target[parts[i]] = target[parts[i]] || {};
    }
    target[_.last(parts)] = value;
  });
  return result;
}

/**
 * @param {!Object...} configs Configuration objects to merge.
 * @return {!Object} The merged configuration, where configuration objects
 *     specified later in the arguments list are given precedence.
 */
function merge() {
  var result  = {};
  var configs = Array.prototype.map.call(arguments, normalize);
  _.merge.apply(_, [result].concat(configs));

  // false plugin configs are preserved.
  configs.forEach(function(config) {
    _.each(config.plugins, function(value, key) {
      if (value === false) {
        result.plugins[key] = false;
      }
    });
  });

  return result;
}

/**
 * @param {!Object} config Configuration object to normalize.
 * @return {!Object} `config`.
 */
function normalize(config) {
  var i, name;
  if (_.isArray(config.plugins)) {
    var pluginConfigs = {};
    for (i = 0, name; name = config.plugins[i]; i++) {
      // A named plugin is explicitly enabled (e.g. --plugin foo).
      pluginConfigs[name] = {disabled: false};
    }
    config.plugins = pluginConfigs;
  }

  // Always wins.
  if (config.skipPlugins) {
    config.plugins = config.plugins || {};
    for (i = 0, name; name = config.skipPlugins[i]; i++) {
      config.plugins[name] = false;
    }
  }

  return config;
}

/**
 * Expands values within the configuration based on the current environment.
 *
 * @param {!Context} context The context for the current run.
 * @param {function(*)} done
 */
function expand(context, done) {
  var options = context.options;
  var root    = context.options.root || process.cwd();
  context.options.root = root = path.resolve(root);

  options.origSuites = _.clone(options.suites);

  expandDeprecated(context, function(error) {
    if (error) return done(error);

    paths.expand(root, options.suites, function(error, suites) {
      if (error) return done(error);
      options.suites = suites;
      done();
    });
  });
}

/**
 * Expands any options that have been deprecated, and warns about it.
 *
 * @param {!Context} context The context for the current run.
 */
function expandDeprecated(context, done) {
  var options = context.options;
  // We collect configuration fragments to be merged into the options object.
  var fragments = [];

  var browsers = _.isArray(options.browsers) ? options.browsers : [options.browsers];
  browsers = _.compact(browsers);
  if (browsers.length > 0) {
    context.emit('log:warn', 'The --browsers flag/option is deprecated. Please use --local and --sauce instead, or configure via plugins.[local|sauce].browsers.');
    var fragment = {plugins: {sauce: {}, local: {}}};
    fragments.push(fragment);

    for (var i = 0, browser; browser = browsers[i]; i++) {
      var name   = browser.browserName || browser;
      var plugin = browser.platform || name.indexOf('/') !== -1 ? 'sauce' : 'local';
      fragment.plugins[plugin].browsers = fragment.plugins[plugin].browsers || [];
      fragment.plugins[plugin].browsers.push(browser);
    }

    delete options.browsers;
  }

  if (options.sauce) {
    context.emit('log:warn', 'The sauce configuration key is deprecated. Please use plugins.sauce instead.');
    fragments.push({
      plugins: {sauce: options.sauce},
    });
    delete options.sauce;
  }

  if (options.remote) {
    context.emit('log:warn', 'The --remote flag is deprecated. Please use --sauce default instead.');
    fragments.push({
      plugins: {sauce: {browsers: ['default']}},
    });
    delete options.remote;
  }

  if (fragments.length > 0) {
    // We are careful to modify context.options in place.
    _.merge(context.options, merge.apply(null, fragments));
  }

  done();
}

/**
 * @param {!Object} options The configuration to validate.
 * @param {function(*)} Callback indicating whether the configuration is valid.
 */
function validate(options, done) {
  if (options.webRunner) {
    return done('webRunner is no longer a supported configuration option. Please list the files you wish to test as arguments, or as `suites` in a configuration object.');
  }
  if (options.component) {
    return done('component is no longer a supported configuration option. Please list the files you wish to test as arguments, or as `suites` in a configuration object.');
  }

  if (options.activeBrowsers.length === 0) {
    return done('No browsers configured to run');
  }
  if (options.suites.length === 0) {
    var root  = options.root || process.cwd();
    var globs = options.origSuites.join(', ');
    return done(
        'No test suites were found matching your configuration\n' +
        '\n' +
        '  WCT searched for .js and .html files matching: ' + globs + '\n' +
        '\n' +
        '  Relative paths were resolved against: ' + root);
  }

  done(null);
}

module.exports = {
  defaults:     defaults,
  fromDisk:     fromDisk,
  preparseArgs: preparseArgs,
  parseArgs:    parseArgs,
  merge:        merge,
  normalize:    normalize,
  expand:       expand,
  validate:     validate,
};
<commit_msg>Use a shared /components/ root<commit_after>/**
 * @license
 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
 * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
 * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
 * Code distributed by Google as part of the polymer project is also
 * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
 */
var _              = require('lodash');
var findup         = require('findup-sync');
var fs             = require('fs');
var nomnom         = require('nomnom');
var path           = require('path');
var serveWaterfall = require('serve-waterfall');

var paths = require('./paths');

var HOME_DIR    = path.resolve(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE);
var CONFIG_NAME = 'wct.conf.js';
var WCT_ROOT    = path.resolve(__dirname, '..');

// The full set of options, as a reference.
function defaults() {
  return {
    // The test suites that should be run.
    suites:      ['test/'],
    // Output stream to write log messages to.
    output:      process.stdout,
    // Whether the output stream should be treated as a TTY (and be given more
    // complex output formatting). Defaults to `output.isTTY`.
    ttyOutput:   undefined,
    // Spew all sorts of debugging messages.
    verbose:     false,
    // Display test results in expanded form. Verbose implies expanded.
    expanded:    false,
    // The on-disk path where tests & static files should be served from. Paths
    // (such as `suites`) are evaluated relative to this.
    //
    // Defaults to the project directory.
    root:        undefined,
    // Idle timeout for tests.
    testTimeout: 90 * 1000,
    // Whether the browser should be closed after the tests run.
    persistent:  false,
    // Additional .js files to include in *generated* test indexes.
    extraScripts: [],
    // Configuration options passed to the browser client.
    clientOptions: {
      // Also see `webserver.pathMappings` below.
      root: '/components/',
    },
    // Webdriver capabilities objects for each browser that should be run.
    //
    // Capabilities can also contain a `url` value which is either a string URL
    // for the webdriver endpoint, or {hostname:, port:, user:, pwd:}.
    //
    // Most of the time you will want to rely on the WCT browser plugins to fill
    // this in for you (e.g. via `--local`, `--sauce`, etc).
    activeBrowsers: [],
    // Default capabilities to use when constructing webdriver connections (for
    // each browser specified in `activeBrowsers`). A handy place to hang common
    // configuration.
    //
    // Selenium: https://code.google.com/p/selenium/wiki/DesiredCapabilities
    // Sauce:    https://docs.saucelabs.com/reference/test-configuration/
    browserOptions: {},
    // The plugins that should be loaded, and their configuration.
    //
    // When an array, the named plugins will be loaded with their default
    // configuration. When an object, each key maps to a plugin, and values are
    // configuration values to be merged.
    //
    //   plugins: {
    //     local: {browsers: ['firefox', 'chrome']},
    //   }
    //
    plugins: ['local', 'sauce'],
    // Callback that allows you to perform advanced configuration of the WCT
    // runner.
    //
    // The hook is given the WCT context, and can generally be written like a
    // plugin. For example, to serve custom content via the internal webserver:
    //
    //     registerHooks: function(wct) {
    //       wct.hook('prepare:webserver', function(app, done) {
    //         app.use(...);
    //         done();
    //       });
    //     }
    //
    registerHooks: function(wct) {},
    // Configuration options for the webserver that serves up your test files
    // and dependencies.
    //
    // Typically, you will not need to modify these values.
    webserver: {
      // The port that the webserver should run on. A port will be determined at
      // runtime if none is provided.
      port: undefined,
      // The hostname used when generating URLs for the webdriver client.
      hostname: 'localhost',
      // mappings of URL prefix to on disk paths that the web server should
      // serve via https://github.com/PolymerLabs/serve-waterfall
      pathMappings: serveWaterfall.mappings.WEB_COMPONENT.concat([
        // We also expose built in WCT dependencies, but with lower priority
        // than the project's components.
        {'/components/': path.join(WCT_ROOT, 'bower_components')},
      ]),
      // The URL prefix that serves contents from the project root.
      urlPrefix: '/components/<basename>',
    },
  };
}

/** nomnom configuration for command line arguments.
 *
 * This might feel like duplication with `defaults()`, and out of place (why not
 * in `cli.js`?). But, not every option matches a configurable value, and it is
 * best to keep the configuration for these together to help keep them in sync.
 */
var ARG_CONFIG = {
  persistent: {
    help:      'Keep browsers active (refresh to rerun tests).',
    abbr:      'p',
    flag:      true,
  },
  root: {
    help:      'The root directory to serve tests from.',
    transform: path.resolve,
  },
  plugins: {
    help:      'Plugins that should be loaded.',
    metavar:   'NAME',
    full:      'plugin',
    list:       true,
  },
  skipPlugins: {
    help:      'Configured plugins that should _not_ be loaded.',
    metavar:   'NAME',
    full:      'skip-plugin',
    list:      true,
  },
  expanded: {
    help:      'Log a status line for each test run.',
    flag:      true,
  },
  verbose: {
    help:      'Log all the things.',
    flag:      true,
  },
  simpleOutput: {
    help:      'Avoid fancy terminal output.',
    flag:      true,
  },
  skipUpdateCheck: {
    help:      'Don\'t check for updates.',
    full:      'skip-update-check',
    flag:      true,
  },
  'webserver.port': {
    help:      'A port to use for the test webserver.',
    full:      'webserver-port',
  },
  'webserver.hostname': {
    full:      'webserver-hostname',
    hidden:    true,
  },
  // Managed by supports-color; let's not freak out if we see it.
  color: {flag: true},

  // Deprecated

  browsers: {
    abbr:   'b',
    hidden: true,
    list:   true,
  },
  remote: {
    abbr:   'r',
    hidden: true,
    flag:   true,
  },
};

// Values that should be extracted when pre-parsing args.
var PREPARSE_ARGS = ['plugins', 'skipPlugins', 'simpleOutput', 'skipUpdateCheck'];

/**
 * Discovers appropriate config files (global, and for the project), merging
 * them, and returning them.
 *
 * @return {!Object} The merged configuration.
 */
function fromDisk() {
  var globalFile  = path.join(HOME_DIR, CONFIG_NAME);
  var projectFile = findup(CONFIG_NAME, {nocase: true});
  // Load a shared config from the user's home dir, if they have one, and then
  // try the project-specific path (starting at the current working directory).
  var paths   = _.unique([globalFile, projectFile]);
  var configs = _.filter(paths, fs.existsSync).map(require);
  var options = merge.apply(null, configs);

  if (!options.root && projectFile && projectFile !== globalFile) {
    options.root = path.dirname(projectFile);
  }

  return options;
}

/**
 * Runs a simplified options parse over the command line arguments, extracting
 * any values that are necessary for a full parse.
 *
 * At the moment, the only values extracted are `--plugin` and `--simpleOutput`.
 *
 * @param {!Array<string>} args
 * @return {!Object}
 */
function preparseArgs(args) {
  // Don't let it short circuit on help.
  args = _.difference(args, ['--help', '-h']);

  var parser = nomnom();
  parser.options(ARG_CONFIG);
  parser.printer(function() {});  // No-op output & errors.
  var options = parser.parse(args);

  return _expandOptionPaths(_.pick(options, PREPARSE_ARGS));
}

/**
 * Runs a complete options parse over the args, respecting plugin options.
 *
 * @param {!Context} context The context, containing plugin state and any base
 *     options to merge into.
 * @param {!Array<string>} args The args to parse.
 * @param {function(*)} done
 */
function parseArgs(context, args, done) {
  var parser = nomnom();
  parser.script('wct');
  parser.options(ARG_CONFIG);

  context.plugins(function(error, plugins) {
    if (error) return done(error);

    plugins.forEach(_configurePluginOptions.bind(null, parser));
    var options = _expandOptionPaths(normalize(parser.parse(args)));
    if (options._ && options._.length > 0) {
      options.suites = options._;
    }

    context.options = merge(context.options, options);
    done();
  });
}

function _configurePluginOptions(parser, plugin) {
  if (!plugin.cliConfig || plugin.cliConfig.length === 0) return;

  // Group options per plugin. It'd be nice to also have a header, but that ends
  // up shifting all the options over.
  parser.option('plugins.' + plugin.name + '.', {string: ' '});

  _.each(plugin.cliConfig, function(config, key) {
    // Make sure that we don't expose the name prefixes.
    if (!config.full) {
      config.full = key;
    }
    parser.option('plugins.' + plugin.name + '.' + key, config);
  });
}

function _expandOptionPaths(options) {
  var result = {};
  _.each(options, function(value, key) {
    var target = result;
    var parts  = key.split('.');
    for (var i = 0; i < parts.length - 1; i++) {
      target = target[parts[i]] = target[parts[i]] || {};
    }
    target[_.last(parts)] = value;
  });
  return result;
}

/**
 * @param {!Object...} configs Configuration objects to merge.
 * @return {!Object} The merged configuration, where configuration objects
 *     specified later in the arguments list are given precedence.
 */
function merge() {
  var result  = {};
  var configs = Array.prototype.map.call(arguments, normalize);
  _.merge.apply(_, [result].concat(configs));

  // false plugin configs are preserved.
  configs.forEach(function(config) {
    _.each(config.plugins, function(value, key) {
      if (value === false) {
        result.plugins[key] = false;
      }
    });
  });

  return result;
}

/**
 * @param {!Object} config Configuration object to normalize.
 * @return {!Object} `config`.
 */
function normalize(config) {
  var i, name;
  if (_.isArray(config.plugins)) {
    var pluginConfigs = {};
    for (i = 0, name; name = config.plugins[i]; i++) {
      // A named plugin is explicitly enabled (e.g. --plugin foo).
      pluginConfigs[name] = {disabled: false};
    }
    config.plugins = pluginConfigs;
  }

  // Always wins.
  if (config.skipPlugins) {
    config.plugins = config.plugins || {};
    for (i = 0, name; name = config.skipPlugins[i]; i++) {
      config.plugins[name] = false;
    }
  }

  return config;
}

/**
 * Expands values within the configuration based on the current environment.
 *
 * @param {!Context} context The context for the current run.
 * @param {function(*)} done
 */
function expand(context, done) {
  var options = context.options;
  var root    = context.options.root || process.cwd();
  context.options.root = root = path.resolve(root);

  options.origSuites = _.clone(options.suites);

  expandDeprecated(context, function(error) {
    if (error) return done(error);

    paths.expand(root, options.suites, function(error, suites) {
      if (error) return done(error);
      options.suites = suites;
      done();
    });
  });
}

/**
 * Expands any options that have been deprecated, and warns about it.
 *
 * @param {!Context} context The context for the current run.
 */
function expandDeprecated(context, done) {
  var options = context.options;
  // We collect configuration fragments to be merged into the options object.
  var fragments = [];

  var browsers = _.isArray(options.browsers) ? options.browsers : [options.browsers];
  browsers = _.compact(browsers);
  if (browsers.length > 0) {
    context.emit('log:warn', 'The --browsers flag/option is deprecated. Please use --local and --sauce instead, or configure via plugins.[local|sauce].browsers.');
    var fragment = {plugins: {sauce: {}, local: {}}};
    fragments.push(fragment);

    for (var i = 0, browser; browser = browsers[i]; i++) {
      var name   = browser.browserName || browser;
      var plugin = browser.platform || name.indexOf('/') !== -1 ? 'sauce' : 'local';
      fragment.plugins[plugin].browsers = fragment.plugins[plugin].browsers || [];
      fragment.plugins[plugin].browsers.push(browser);
    }

    delete options.browsers;
  }

  if (options.sauce) {
    context.emit('log:warn', 'The sauce configuration key is deprecated. Please use plugins.sauce instead.');
    fragments.push({
      plugins: {sauce: options.sauce},
    });
    delete options.sauce;
  }

  if (options.remote) {
    context.emit('log:warn', 'The --remote flag is deprecated. Please use --sauce default instead.');
    fragments.push({
      plugins: {sauce: {browsers: ['default']}},
    });
    delete options.remote;
  }

  if (fragments.length > 0) {
    // We are careful to modify context.options in place.
    _.merge(context.options, merge.apply(null, fragments));
  }

  done();
}

/**
 * @param {!Object} options The configuration to validate.
 * @param {function(*)} Callback indicating whether the configuration is valid.
 */
function validate(options, done) {
  if (options.webRunner) {
    return done('webRunner is no longer a supported configuration option. Please list the files you wish to test as arguments, or as `suites` in a configuration object.');
  }
  if (options.component) {
    return done('component is no longer a supported configuration option. Please list the files you wish to test as arguments, or as `suites` in a configuration object.');
  }

  if (options.activeBrowsers.length === 0) {
    return done('No browsers configured to run');
  }
  if (options.suites.length === 0) {
    var root  = options.root || process.cwd();
    var globs = options.origSuites.join(', ');
    return done(
        'No test suites were found matching your configuration\n' +
        '\n' +
        '  WCT searched for .js and .html files matching: ' + globs + '\n' +
        '\n' +
        '  Relative paths were resolved against: ' + root);
  }

  done(null);
}

module.exports = {
  defaults:     defaults,
  fromDisk:     fromDisk,
  preparseArgs: preparseArgs,
  parseArgs:    parseArgs,
  merge:        merge,
  normalize:    normalize,
  expand:       expand,
  validate:     validate,
};
<|endoftext|>


<commit_before>import React from 'react';
import { Switch } from 'react-router';
import { Route, Link } from 'react-router-dom';
import About from './About';
import NotFound from './NotFound';
import Posts from '../containers/Posts';

export default () => {
  return (

<Link to="/">Home</Link>

<Link to="/about">About</Link>

      <Switch>
        <Route exact path="/" render={() => <Posts />}/>
        <Route exact path="/about" component={About}/>
        <Route component={NotFound}/>
      </Switch>

  );
};
<commit_msg>Add redux<commit_after>import React from 'react';
import { Switch } from 'react-router';
import { Route, Link } from 'react-router-dom';
import About from './About';
import NotFound from './NotFound';
import Posts from '../containers/Posts';

export default () => {
  return (

<Link to="/">Home</Link>

<Link to="/about">About</Link>

      <Switch>
        <Route exact path="/" components={Posts}/>
        <Route exact path="/about" component={About}/>
        <Route component={NotFound}/>
      </Switch>

  );
};
<|endoftext|>