<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { setSteps } from '../../actions/tutorial_actions';
import { createImportSession, updateImportSession, attemptImportFile } from '../../actions/survey_actions';

class SurveyImportContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      fileValid: false,
      importAttempted: false,
      importSessionId: null,
      importErrors: [],
      importWarnings: ['Attempting Import...'],
      fileFormatCheck: null,
      file: null,
      survey: {},
      fileChosen: false,
      filePromiseReturned: false
    };
    this.onFormSubmit = this.onFormSubmit.bind(this);
    this.onChange = this.onChange.bind(this);
    this.attemptImport = this.attemptImport.bind(this);
    this.cancelImport = this.cancelImport.bind(this);
  }

  componentDidMount() {
    this.props.setSteps([
      {
        title: 'Help',
        text: 'Click next to see a step by step walkthrough for using this page. To see more detailed information about this application and actions you can take click here to view the full Help Documentation. Accessible versions of these steps are also duplicated in the help documentation.',
        selector: '.help-link',
        position: 'bottom',
      }]);
  }

  onFormSubmit(e){
    e.preventDefault(); // Stop form submit
  }

  onChange(e) {
    if (this.state.importSessionId) {
      this.props.updateImportSession(this.state.importSessionId, e.target.files[0], (successResponse) => {
        this.setState({importErrors: successResponse.data.importErrors, importWarnings: successResponse.data.importWarnings, importSessionId: successResponse.data.id, filePromiseReturned: true});
      });
    } else {
      this.props.createImportSession(e.target.files[0], (successResponse) => {
        this.setState({importErrors: successResponse.data.importErrors, importWarnings: successResponse.data.importWarnings, importSessionId: successResponse.data.id, filePromiseReturned: true});
      });
    }
    this.setState({file: e.target.files[0], fileChosen: true});
  }

  fileSelector() {
    if (this.state.file === null) {
      return (

Please select the file you wish to import

        Choose a Microsoft Excel formatted file (.xls, .xlsx, .xslm)
        <input id="file-select" name="file-select" type="file" value={this.state.file} onChange={this.onChange} accept=".xls, .xlsx, .xlsm, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" />

);
    } else {
      return (

Files to be imported

Filename:

{this.state.file.name}

Size:

{this.state.file.size} bytes

Last Updated:

{this.state.file.lastModifiedDate.toString()}

          {this.fileActions()}

14 Duplicates Found Please resolve before continuing

<button className="btn btn-sm btn-default"></button> <button className="btn btn-sm btn-default"></button>

14 questions remaining

Imported from spreadsheet

                Name & Description
                Response Type
                Category

                <text>What was the principal reason for travel</text>
Select the principal reason for travel
                 Travel Reason (Hepatitis A)
                Travel Reason (Hepatitis A)

Potential Duplicate Questions (n)

                Match
                Name & Description
                Response Type
                Category
                Ref. Count

                99
                What was the principal reason for travel
Select the principal reason for travel
                 Travel Reason (Hepatitis A)
                Epidemiological
                14
                <button className="btn btn-default btn-sm">Select</button>

                89
                What was the primary reason for travel
Select the primary reason for travel
                 Travel Reason (Hepatitis A)
                Epidemiological
                5
                <button className="btn btn-success btn-sm">Select</button>

      );
    }
  }

  attemptImport() {
    this.props.attemptImportFile(this.state.importSessionId, (successResponse)=>{
      this.setState({importWarnings: successResponse.data.importWarnings,
        importErrors: successResponse.data.importErrors,
        importSessionId: successResponse.data.id,
        survey: successResponse.data.survey});
    });
    this.setState({importAttempted: true, importErrors: [], importWarnings: []});
  }

  cancelImport() {
    this.setState({file: null, importAttempted: false, importWarnings: [],importErrors: [], fileChosen: false, filePromiseReturned: false, survey: {}});
  }

  fileActions() {
    if (this.state.importErrors && this.state.importErrors.length > 0 && !this.state.importAttempted) {
      return (

            <button className="btn btn-default" onClick={this.cancelImport}> Remove</button>
            File not recognized as MMG Excel spreadsheet

            {this.state.importErrors.map((msg, i) => {
              return (

                  Error: {msg}

              );
            })}

      );
    }else if (this.state.importWarnings && this.state.importWarnings.length > 0 && !this.state.importAttempted){
      return (

            <button className="btn btn-primary" onClick={this.attemptImport}>Import</button>
            <button className="btn btn-default" onClick={this.cancelImport}> Remove</button>
            File format warnings:

            {this.state.importWarnings.map((msg, i) => {
              return (

                  Warning: {msg}

              );
            })}

      );
    }else if (!this.state.importAttempted && this.state.filePromiseReturned){
      return (

          <button className="btn btn-primary" onClick={this.attemptImport}>Import</button>
          <button className="btn btn-default" onClick={this.cancelImport}>Cancel</button>
          File recognized as MMG Excel spreadsheet

      );
    } else if (this.state.fileChosen && !this.state.filePromiseReturned) {
      return
Checking file format... Please wait.
;
    } else {
      return;
    }
  }

  importStatus() {
    if (this.state.importAttempted && this.state.importErrors && this.state.importErrors.length > 0 && this.state.survey === null) {
      return (

            <button className="btn btn-default" onClick={this.cancelImport}> Remove</button>
            File could not be imported. Try to fix as many of the following errors as possible.

            {this.state.importErrors.map((msg, i) => {
              return (

                  Error: {msg}

              );
            })}

      );
    } else if (this.state.importAttempted && this.state.importWarnings && this.state.importWarnings.length > 0 && this.state.survey && this.state.survey.id) {
      return (

            <a className="btn btn-primary" href={`/#/surveys/${this.state.survey.id}`}>View Survey
            File imported with warnings

            {this.state.importWarnings.map((msg, i) => {
              return (

                  Warning: {msg}

              );
            })}

          {this.importResults()}

      );
    } else if (this.state.importAttempted && this.state.survey && this.state.survey.id) {
      return (

            <a className="btn btn-primary" href={`/#/surveys/${this.state.survey.id}`}>View Survey
            File successfully imported

          {this.importResults()}

      );
    } else if (this.state.importAttempted) {
      return
Attempting Import... Please wait.
;
    } else {
      return;
    }
  }

  importResults() {
    let survey = this.state.survey;
    // TODO: Remove the true statement here and sub in logic to populate this table
    if (survey.id) {
      return;
    } else {
      return (

                Data element name
                Description
                Type
                Value Set Name

                Section: EPIDEMIOLOGIC INFORMATION SECTION

                Reason for testing
                Listing of the reason(s) the subject was tested for Hepatitis. If "Other" has been selected as coded value, specify the free text using "Other Reason for Testing"  data element.
                Coded
                Reason for Test (Hepatitis)

                Other Reason for Testing
                Other reason(s) the subject was tested for Hepatitis (Free text).
                Text
                N/A

                Symtompatic
                Was the subject symptomatic for Hepatitis.
                Coded
                Yes No Unknown (YNU)

                Jaundiced (Symptom)
                Was the subject jaundiced.
                Coded
                Yes No Unknown (YNU)

                Due Date
                Subject's pregnancy due date.
                Date
                N/A

                Previously Aware of Condition
                Was the subject aware they had Hepatitis prior to lab testing.
                Coded
                Yes No Unknown (YNU)

                Provider of Care for Condition
                Does the subject have a provider of care for Hepatitis? This is any healthcare provider that monitors or treats the patient for viral Hepatitis.
                Coded
                Yes No Unknown (YNU)

                Diabetes
                Does subject have diabetes?
                Coded
                Yes No Unknown (YNU)

      );
    }
  }

  render() {
    return (

Import MMG Spreadsheet

                  <form onSubmit={this.onFormSubmit}>
                    {this.fileSelector()}
                  </form>
                  {this.importStatus()}

    );
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({setSteps, createImportSession, updateImportSession, attemptImportFile}, dispatch);
}

SurveyImportContainer.propTypes = {
  setSteps: PropTypes.func,
  createImportSession: PropTypes.func,
  updateImportSession: PropTypes.func,
  attemptImportFile: PropTypes.func
};

export default connect(null, mapDispatchToProps)(SurveyImportContainer);
<commit_msg>Removing placeholder code<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { setSteps } from '../../actions/tutorial_actions';
import { createImportSession, updateImportSession, attemptImportFile } from '../../actions/survey_actions';

class SurveyImportContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      fileValid: false,
      importAttempted: false,
      importSessionId: null,
      importErrors: [],
      importWarnings: ['Attempting Import...'],
      fileFormatCheck: null,
      file: null,
      survey: {},
      fileChosen: false,
      filePromiseReturned: false
    };
    this.onFormSubmit = this.onFormSubmit.bind(this);
    this.onChange = this.onChange.bind(this);
    this.attemptImport = this.attemptImport.bind(this);
    this.cancelImport = this.cancelImport.bind(this);
  }

  componentDidMount() {
    this.props.setSteps([
      {
        title: 'Help',
        text: 'Click next to see a step by step walkthrough for using this page. To see more detailed information about this application and actions you can take click here to view the full Help Documentation. Accessible versions of these steps are also duplicated in the help documentation.',
        selector: '.help-link',
        position: 'bottom',
      }]);
  }

  onFormSubmit(e){
    e.preventDefault(); // Stop form submit
  }

  onChange(e) {
    if (this.state.importSessionId) {
      this.props.updateImportSession(this.state.importSessionId, e.target.files[0], (successResponse) => {
        this.setState({importErrors: successResponse.data.importErrors, importWarnings: successResponse.data.importWarnings, importSessionId: successResponse.data.id, filePromiseReturned: true});
      });
    } else {
      this.props.createImportSession(e.target.files[0], (successResponse) => {
        this.setState({importErrors: successResponse.data.importErrors, importWarnings: successResponse.data.importWarnings, importSessionId: successResponse.data.id, filePromiseReturned: true});
      });
    }
    this.setState({file: e.target.files[0], fileChosen: true});
  }

  fileSelector() {
    if (this.state.file === null) {
      return (

Please select the file you wish to import

        Choose a Microsoft Excel formatted file (.xls, .xlsx, .xslm)
        <input id="file-select" name="file-select" type="file" value={this.state.file} onChange={this.onChange} accept=".xls, .xlsx, .xlsm, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" />

);
    } else {
      return (

Files to be imported

Filename:

{this.state.file.name}

Size:

{this.state.file.size} bytes

Last Updated:

{this.state.file.lastModifiedDate.toString()}

          {this.fileActions()}

      );
    }
  }

  attemptImport() {
    this.props.attemptImportFile(this.state.importSessionId, (successResponse)=>{
      this.setState({importWarnings: successResponse.data.importWarnings,
        importErrors: successResponse.data.importErrors,
        importSessionId: successResponse.data.id,
        survey: successResponse.data.survey});
    });
    this.setState({importAttempted: true, importErrors: [], importWarnings: []});
  }

  cancelImport() {
    this.setState({file: null, importAttempted: false, importWarnings: [],importErrors: [], fileChosen: false, filePromiseReturned: false, survey: {}});
  }

  fileActions() {
    if (this.state.importErrors && this.state.importErrors.length > 0 && !this.state.importAttempted) {
      return (

            <button className="btn btn-default" onClick={this.cancelImport}> Remove</button>
            File not recognized as MMG Excel spreadsheet

            {this.state.importErrors.map((msg, i) => {
              return (

                  Error: {msg}

              );
            })}

      );
    }else if (this.state.importWarnings && this.state.importWarnings.length > 0 && !this.state.importAttempted){
      return (

            <button className="btn btn-primary" onClick={this.attemptImport}>Import</button>
            <button className="btn btn-default" onClick={this.cancelImport}> Remove</button>
            File format warnings:

            {this.state.importWarnings.map((msg, i) => {
              return (

                  Warning: {msg}

              );
            })}

      );
    }else if (!this.state.importAttempted && this.state.filePromiseReturned){
      return (

          <button className="btn btn-primary" onClick={this.attemptImport}>Import</button>
          <button className="btn btn-default" onClick={this.cancelImport}>Cancel</button>
          File recognized as MMG Excel spreadsheet

      );
    } else if (this.state.fileChosen && !this.state.filePromiseReturned) {
      return
Checking file format... Please wait.
;
    } else {
      return;
    }
  }

  importStatus() {
    if (this.state.importAttempted && this.state.importErrors && this.state.importErrors.length > 0 && this.state.survey === null) {
      return (

            <button className="btn btn-default" onClick={this.cancelImport}> Remove</button>
            File could not be imported. Try to fix as many of the following errors as possible.

            {this.state.importErrors.map((msg, i) => {
              return (

                  Error: {msg}

              );
            })}

      );
    } else if (this.state.importAttempted && this.state.importWarnings && this.state.importWarnings.length > 0 && this.state.survey && this.state.survey.id) {
      return (

            <a className="btn btn-primary" href={`/#/surveys/${this.state.survey.id}`}>View Survey
            File imported with warnings

            {this.state.importWarnings.map((msg, i) => {
              return (

                  Warning: {msg}

              );
            })}

          {this.importResults()}

      );
    } else if (this.state.importAttempted && this.state.survey && this.state.survey.id) {
      return (

            <a className="btn btn-primary" href={`/#/surveys/${this.state.survey.id}`}>View Survey
            File successfully imported

          {this.importResults()}

      );
    } else if (this.state.importAttempted) {
      return
Attempting Import... Please wait.
;
    } else {
      return;
    }
  }

  importResults() {
    let survey = this.state.survey;
    // TODO: Remove the true statement here and sub in logic to populate this table
    if (survey.id) {
      return;
    } else {
      return (

                Data element name
                Description
                Type
                Value Set Name

                Section: EPIDEMIOLOGIC INFORMATION SECTION

                Reason for testing
                Listing of the reason(s) the subject was tested for Hepatitis. If "Other" has been selected as coded value, specify the free text using "Other Reason for Testing"  data element.
                Coded
                Reason for Test (Hepatitis)

                Other Reason for Testing
                Other reason(s) the subject was tested for Hepatitis (Free text).
                Text
                N/A

                Symtompatic
                Was the subject symptomatic for Hepatitis.
                Coded
                Yes No Unknown (YNU)

                Jaundiced (Symptom)
                Was the subject jaundiced.
                Coded
                Yes No Unknown (YNU)

                Due Date
                Subject's pregnancy due date.
                Date
                N/A

                Previously Aware of Condition
                Was the subject aware they had Hepatitis prior to lab testing.
                Coded
                Yes No Unknown (YNU)

                Provider of Care for Condition
                Does the subject have a provider of care for Hepatitis? This is any healthcare provider that monitors or treats the patient for viral Hepatitis.
                Coded
                Yes No Unknown (YNU)

                Diabetes
                Does subject have diabetes?
                Coded
                Yes No Unknown (YNU)

      );
    }
  }

  render() {
    return (

Import MMG Spreadsheet

                  <form onSubmit={this.onFormSubmit}>
                    {this.fileSelector()}
                  </form>
                  {this.importStatus()}

    );
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({setSteps, createImportSession, updateImportSession, attemptImportFile}, dispatch);
}

SurveyImportContainer.propTypes = {
  setSteps: PropTypes.func,
  createImportSession: PropTypes.func,
  updateImportSession: PropTypes.func,
  attemptImportFile: PropTypes.func
};

export default connect(null, mapDispatchToProps)(SurveyImportContainer);
<|endoftext|>


<commit_before>/**
 * @author centsent
 */
import directivesModule from '../_index';

/**
 * @ngInject
 */
function hyFullscreen($hyMedia) {
  return {
    restrict: 'A',
    link($scope, elem) {
      elem.bind('click', () => {
        $hyMedia.toggleFullScreen();
      });
    }
  };
}

directivesModule.directive('hyFullscreen', hyFullscreen);
<commit_msg>refactor: add toglle fullscreen class<commit_after>/**
 * @author centsent
 */
import directivesModule from '../_index';

class HyFullscreenDirective {
  constructor($hyMedia) {
    this.restrict = 'A';
    this.$hyMedia = $hyMedia;
  }

  compile() {
    return this.link.bind(this);
  }

  link($scope, elem) {
    const $hyMedia = this.$hyMedia;

    elem.addClass($hyMedia.isFullscreen ? 'exit' : 'enter')
      .bind('click', () => {
        $hyMedia.toggleFullScreen();
      });

    $hyMedia.onFullScreenChange((isFullscreen) => {
      if (isFullscreen) {
        elem.addClass('exit').removeClass('enter');
      } else {
        elem.addClass('enter').removeClass('exit');
      }
    });
  }

  static factory($hyMedia) {
    return new HyFullscreenDirective($hyMedia);
  }
}

HyFullscreenDirective.factory.$inject = ['$hyMedia'];

directivesModule.directive('hyFullscreen', HyFullscreenDirective.factory);
<|endoftext|>


<commit_before>(function(window, document, JSON){
  'use strict';
  // TODO(dcramer): expose API for building a new error embed so things like
  // JS applications can render this on demand
  /**
    window.sentryConfig = {
      dsn: 'http://public@example.com/1',
      eventId: '...',
      attachOnLoad: true,
      parent: document.body
    };
   */

  var GENERIC_ERROR = '
An unknown error occurred while submitting your report. Please try again.
';
  var FORM_ERROR = '
Some fields were invalid. Please correct the errors and try again.
';

  // XMLHttpRequest.DONE does not exist in all browsers
  var XHR_DONE = 4;

  var template = {{ template }};
  var endpoint = {{ endpoint }};
  var encode = window.encodeURIComponent;

  var escape = function(str) {
    return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
  };

  var serialize = function(form) {
    var q = [];
    for (var i = 0; i < form.elements.length; i++) {
      q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].value));
    }
    return q.join("&");
  };

  var onReady = function(f) {
    /in/.test(document.readyState)
      ? setTimeout(function() { onReady(f); }, 9)
      : f();
  };

  var SentryErrorEmbed = function(options) {
    this.build();
  };

  SentryErrorEmbed.prototype.build = function() {
    var self = this;
    this.element = document.createElement('div');
    this.element.className = 'sentry-error-embed-wrapper';
    this.element.innerHTML = template;
    self.element.onclick = function(e){
      if (e.target !== self.element) return;
      self.close();
    };

    this._form = this.element.getElementsByTagName('form')[0];
    this._form.onsumbit = function(e) {
      e.preventDefault();
      self.submit(self.serialize());
    };

    this._submitBtn = this.element.getElementsByTagName('button')[0]
    this._submitBtn.onclick = function(e) {
      e.preventDefault();
      self.submit(self.serialize());
    };

    var divTags = this._form.getElementsByTagName('div');
    for (var i = 0; i < divTags.length; i++) {
      if (divTags[i].className === 'error-wrapper') {
        this._errorWrapper = divTags[i];
      }
      if (divTags[i].className === 'form-content') {
        this._formContent = divTags[i];
      }
    }

    var linkTags = this.element.getElementsByTagName('a');
    for (var i = 0; i < linkTags.length; i++) {
      if (linkTags[i].className === 'close') {
        linkTags[i].onclick = function(e) {
          e.preventDefault();
          self.close();
        };
      }
    }

    this._formMap = {};
    var node;
    for (var i = 0; i < this._form.elements.length; i++) {
      node = this._form.elements[i];
      this._formMap[node.name] = node.parentNode;
    }
  };

  SentryErrorEmbed.prototype.serialize = function() {
    return serialize(this._form);
  };

  SentryErrorEmbed.prototype.close = function() {
    this.element.parentNode.removeChild(this.element);
  };

  SentryErrorEmbed.prototype.submit = function(body) {
    var self = this;
    if (this._submitInProgress)
      return;
    this._submitInProgress = true;

    var xhr new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === XHR_DONE) {
        if (xhr.status === 200) {
          self._errorWrapper.innerHTML = '';
          self._formContent.innerHTML = '
Your feedback has been sent. Thank you!
';
          self._submitBtn.parentNode.removeChild(self._submitBtn);
        } else if (xhr.status == 400) {
          var data = JSON.parse(xhr.responseText);
          var node;
          for (var key in self._formMap) {
            node = self._formMap[key]
            if (data.errors[key]) {
              if (!/form-errors/.test(node.className)) {
                node.className += " form-errors";
              }
            } else if (/form-errors/.test(node.className)) {
              node.className = node.className.replace(/form-errors/, "");
            }
          }
          self._errorWrapper.innerHTML = FORM_ERROR;
        } else {
          self._errorWrapper.innerHTML = GENERIC_ERROR;
        }
        self._submitInProgress = false;
      }
    };
    xhr.open("POST", endpoint, true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.send(body);
  };

  SentryErrorEmbed.prototype.attach = function(parent) {
    parent.appendChild(this.element);
  };

  var options = window.sentryConfig || {};
  var embed = new SentryErrorEmbed(options);
  if (options.attachOnLoad !== false) {
    onReady(function(){
      embed.attach(options.parent || document.body);
    });
  }
}(window, document, JSON));
<commit_msg>Fix syntax error (broken user report embed)<commit_after>(function(window, document, JSON){
  'use strict';
  // TODO(dcramer): expose API for building a new error embed so things like
  // JS applications can render this on demand
  /**
    window.sentryConfig = {
      dsn: 'http://public@example.com/1',
      eventId: '...',
      attachOnLoad: true,
      parent: document.body
    };
   */

  var GENERIC_ERROR = '
An unknown error occurred while submitting your report. Please try again.
';
  var FORM_ERROR = '
Some fields were invalid. Please correct the errors and try again.
';

  // XMLHttpRequest.DONE does not exist in all browsers
  var XHR_DONE = 4;

  var template = {{ template }};
  var endpoint = {{ endpoint }};
  var encode = window.encodeURIComponent;

  var escape = function(str) {
    return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
  };

  var serialize = function(form) {
    var q = [];
    for (var i = 0; i < form.elements.length; i++) {
      q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].value));
    }
    return q.join("&");
  };

  var onReady = function(f) {
    /in/.test(document.readyState)
      ? setTimeout(function() { onReady(f); }, 9)
      : f();
  };

  var SentryErrorEmbed = function(options) {
    this.build();
  };

  SentryErrorEmbed.prototype.build = function() {
    var self = this;
    this.element = document.createElement('div');
    this.element.className = 'sentry-error-embed-wrapper';
    this.element.innerHTML = template;
    self.element.onclick = function(e){
      if (e.target !== self.element) return;
      self.close();
    };

    this._form = this.element.getElementsByTagName('form')[0];
    this._form.onsumbit = function(e) {
      e.preventDefault();
      self.submit(self.serialize());
    };

    this._submitBtn = this.element.getElementsByTagName('button')[0]
    this._submitBtn.onclick = function(e) {
      e.preventDefault();
      self.submit(self.serialize());
    };

    var divTags = this._form.getElementsByTagName('div');
    for (var i = 0; i < divTags.length; i++) {
      if (divTags[i].className === 'error-wrapper') {
        this._errorWrapper = divTags[i];
      }
      if (divTags[i].className === 'form-content') {
        this._formContent = divTags[i];
      }
    }

    var linkTags = this.element.getElementsByTagName('a');
    for (var i = 0; i < linkTags.length; i++) {
      if (linkTags[i].className === 'close') {
        linkTags[i].onclick = function(e) {
          e.preventDefault();
          self.close();
        };
      }
    }

    this._formMap = {};
    var node;
    for (var i = 0; i < this._form.elements.length; i++) {
      node = this._form.elements[i];
      this._formMap[node.name] = node.parentNode;
    }
  };

  SentryErrorEmbed.prototype.serialize = function() {
    return serialize(this._form);
  };

  SentryErrorEmbed.prototype.close = function() {
    this.element.parentNode.removeChild(this.element);
  };

  SentryErrorEmbed.prototype.submit = function(body) {
    var self = this;
    if (this._submitInProgress)
      return;
    this._submitInProgress = true;

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === XHR_DONE) {
        if (xhr.status === 200) {
          self._errorWrapper.innerHTML = '';
          self._formContent.innerHTML = '
Your feedback has been sent. Thank you!
';
          self._submitBtn.parentNode.removeChild(self._submitBtn);
        } else if (xhr.status == 400) {
          var data = JSON.parse(xhr.responseText);
          var node;
          for (var key in self._formMap) {
            node = self._formMap[key]
            if (data.errors[key]) {
              if (!/form-errors/.test(node.className)) {
                node.className += " form-errors";
              }
            } else if (/form-errors/.test(node.className)) {
              node.className = node.className.replace(/form-errors/, "");
            }
          }
          self._errorWrapper.innerHTML = FORM_ERROR;
        } else {
          self._errorWrapper.innerHTML = GENERIC_ERROR;
        }
        self._submitInProgress = false;
      }
    };
    xhr.open("POST", endpoint, true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.send(body);
  };

  SentryErrorEmbed.prototype.attach = function(parent) {
    parent.appendChild(this.element);
  };

  var options = window.sentryConfig || {};
  var embed = new SentryErrorEmbed(options);
  if (options.attachOnLoad !== false) {
    onReady(function(){
      embed.attach(options.parent || document.body);
    });
  }
}(window, document, JSON));
<|endoftext|>


<commit_before>/**
 *
 * Sortable table component for dragonfly.
 *
 * Component is configured by a tabledef object. A tabledef
 * contains a map describing all the columns, and optional
 * functions used for getting values, sorting and rendering.
 * An example looks like this:
 *
 * var tabledef = {
 *         columns: {
 *
 */
function SortableTable(tabledef, data, cols, sortby, reversed)
{
  this._init = function()
  {
    if (!cols || !cols.length) {
      cols = [];
      for (var key in tabledef.columns) {
        cols.push(key);
        if (!sortby && tabledef.columns[key].sorter != "unsortable")
        {
          sortby = key;
        }
      }
    }

    for (var key in tabledef.columns)
    {
      var col = tabledef.columns[key];
      if (!col.getter)
      {
        col.getter = this._prop_getter(key);
      }

      if (!col.renderer)
      {
        col.renderer = col.getter;
      }

      if (col.sorter === undefined)
      {
        col.sorter = this._prop_sorter(col.getter);
      }
      else if (col.sorter === "unsortable")
      {
        col.sorter = null;
      }
    }

    this.sortby = sortby;
    this.tabledef = tabledef;
    this.data = data;
    this.columns = cols;
    this.reversed = !!reversed;
    this.groupby = null;
    this._elem = null;
    this.objectid = ObjectRegistry.get_instance().set_object(this);
    this._init_handlers();
  }

  this._init_handlers = function()
  {
    if (!eventHandlers.click["sortable-table-sort"])
    {
      eventHandlers.click["sortable-table-sort"] = this._sort_handler;
    }

    // should be conditional, but doesn't matter as you don't get
    // dupes in the context menu registry anyhow.
    var contextmenu = ContextMenu.get_instance();
    contextmenu.register("sortable-table-grouper", [
      { callback: this._make_context_menu }
    ]);
  }

  this._make_context_menu = function(evt)
  {
    var table = evt.target;
    while (table.nodeName.toLowerCase() != "table") { table = table.parentNode };
    var obj = ObjectRegistry.get_instance().get_object(table.getAttribute("data-object-id"));
    if (!obj.tabledef.groups) { return [] }

    var menuitems = [{
      label: "No grouping",
      handler: obj._make_group_handler(null)
    }];

    for (var group in obj.tabledef.groups)
    {
      menuitems.push({
        label: "Group by " + (obj.tabledef.groups[group].label || group),
        selected: obj.groupby == group,
        handler: obj._make_group_handler(group)
      });
    }

    // visible column selection stuff
    menuitems.push(ContextMenu.separator);

    var allcols = obj.tabledef.column_order;
    if (!allcols)
    {
      allcols = [];
      for (var key in obj.tabledef.columns)
      {
        allcols.push(key);
      }
    }

    for (var n=0, colname; colname=allcols[n]; n++)
    {
      coldef = obj.tabledef.columns[colname];
      menuitems.push({
        label: coldef.label,
        checked: obj.columns.indexOf(colname) != -1,
        handler: obj._make_colselect_handler(colname)
      });
    }
    return menuitems;
  }

  this._find_col_insertion_point = function(col)
  {
    var colpoint = this.tabledef.column_order.indexOf(col);
    for (var n=colpoint+1, current; current=this.tabledef.column_order[n]; n++)
    {
      var point = this.columns.indexOf(current);
      if (point != -1) {
        return point;
      }
    }
    return this.columns.length;
  }

  this._make_group_handler = function(group)
  {
    return function(evt) {
      var target = evt.target;
      while (target.nodeName.toLowerCase() != "table") { target = target.parentNode };
      var obj = ObjectRegistry.get_instance().get_object(target.getAttribute("data-object-id"));
      obj.group(group);
      target.re_render(obj.render());
    }
  }

  this._make_colselect_handler = function(col)
  {
    return function(evt) {
      var target = evt.target;
      while (target.nodeName.toLowerCase() != "table") { target = target.parentNode };
      var obj = ObjectRegistry.get_instance().get_object(target.getAttribute("data-object-id"));
      obj.togglecol(col);
      target.re_render(obj.render());
    }
  }

  this._sort_handler = function(evt, target)
  {
    var table = target.parentNode.parentNode;
    var obj = ObjectRegistry.get_instance().get_object(table.getAttribute("data-object-id"));
    obj.sort(target.getAttribute("data-column-id"));
    table.re_render(obj.render());
  }

  this._default_sorters = {
    "number": function(getter) { return function(a, b) { return b-a; } }
  }

  this.render = function()
  {
    return templates.sortable_table(this.tabledef, this.data, this.objectid,
                                    this.columns, this.groupby, this.sortby,
                                    this.reversed);
  };

  this._prop_getter = function(name)
  {
    return function(obj) { return obj[name]; }
  }

  this._prop_sorter = function(getter)
  {
    return function(a, b)
    {
      a = getter(a);
      b = getter(b);
      if (a > b) { return 1 }
      else if (a < b) { return -1 }
      else { return 0 }
    }
  }

  this.sort = function(col)
  {
    if (col == this.sortby) {
      this.reversed = !this.reversed;
    }
    else
    {
      this.sortby = col;
    }
  };

  this.group = function(group)
  {
    if (!group || !this.tabledef.groups || !(group in this.tabledef.groups))
    {
      this.groupby = null;
    }
    else
    {
      this.groupby = group;
    }
  }

  this.togglecol = function(col)
  {
    var index = this.columns.indexOf(col);
    if (index == -1)
    {
      var point = this._find_col_insertion_point(col);
      this.columns.splice(point, 0, col);
    }
    else
    {
      this.columns.splice(index, 1);
    }
  }

  this._init();
}

window.templates = window.templates || {};

templates.sortable_table = function(tabledef, data, objectid, cols, groupby, sortby, reversed)
{
  return ["table",
          templates.sortable_table_header(tabledef, cols, sortby, reversed),
          templates.sortable_table_body(tabledef, data, cols, groupby, sortby, reversed),
          "class", "sortable-table",
          "data-object-id", objectid,
          "data-menu", "sortable-table-grouper"
         ]
}

templates.sortable_table_header = function(tabledef, cols, sortby, reversed)
{
  return ["tr",
          cols.map(function(c) {
            var coldef = tabledef.columns[c];
            var tdclass = "";

            if (!coldef.sorter)
            {
              tdclass = "unsortable";
            }
            else
            {
              if (sortby==c)
              {
                tdclass = "sort-column";
              }

              if (reversed)
              {
                tdclass += " reversed";
              }
            }
            return ["th",
                    tabledef.columns[c].label,
                    "class", tdclass,
                    "data-column-id", c,
                   ].concat(tabledef.columns[c].sorter ? ["handler", "sortable-table-sort"] : [])
          }),
          "class", "header"
         ];
}

templates.sortable_table_body = function(tabledef, data, cols, groupby, sortby, reversed)
{
  var groups = {};

  if (groupby)
  {
    var grouper = tabledef.groups[groupby].grouper;
    data.forEach(function(e) {
      var g = grouper(e);
      if (g in groups) { groups[g].push(e) }
      else { groups[g] = [e] }
    });
  }
  else
  {
    groups = {nogroup:data};
  }

  var groupnames = [];
  for (var key in groups) { groupnames.push(key) }

  if (groupby && tabledef.groups[groupby].sorter)
  {
    groupnames.sort(tabledef.groups[groupby].sorter);
  }
  else
  {
    groupnames.sort()
  }

  var render_group_headers = groupnames.length > 1;
  return groupnames.map(function(g) {
      return templates.sortable_table_group(tabledef, g,
                                            render_group_headers,
                                            groups[g], cols, groupby, sortby,
                                            reversed)
  });
}

templates.sortable_table_group = function(tabledef, groupname, render_header, data, cols, groupby, sortby, reversed)
{
  var sorter = tabledef.columns[sortby].sorter;
  if (sorter) { data.sort(sorter) }
  if (reversed) { data.reverse() }
  var tpl = [];
  if (render_header) {
    var renderer = tabledef.groups[groupby].renderer || function(g) { return g };
    tpl.push(["tr",
              ["th", renderer(groupname, data),
               "colspan", String(cols.length),
               "class", "sortable-table-group-header"],
              "class", "header"
             ]);
  }

  var ret =  tpl.concat(data.map(function(item) {
    return templates.sortable_table_row(tabledef, item, cols)
  }));

  for (var n=0, col; col = cols[n]; n++)
  {
    if (tabledef.columns[col].summer)
    {
      ret.push(templates.sortable_table_sumrow(tabledef, groupname, data, cols));
      break;
    }
  }
  return ret;
}

templates.sortable_table_sumrow = function(tabledef, groupname, data, cols)
{
  return ["tr",
          cols.map(function(e) {
            var val = "";
            var summer = tabledef.columns[e].summer;
            if (summer) { val = summer(data, groupname) }
            return ["td", val];
          }),
          "class", "sortable-table-summation-row"
         ];
}

templates.sortable_table_row = function(tabledef, item, cols)
{
  return ["tr",
          cols.map(function(col) {
            var content = tabledef.columns[col].renderer(item, tabledef.columns[col].getter);

            if (typeof content == "string")
            {
              var title = content; // fixme: use custom title renderer.
            }
            else
            {
              title = "";
            }

            if (typeof content == "string" &&
                tabledef.columns[col].maxlength &&
                tabledef.columns[col].maxlength < content.length)
            {
              if (tabledef.columns[col].ellipsis=="start")
              {
                content = "…" + content.slice(-tabledef.columns[col].maxlength);
              }
              else
              {
                content = content.slice(0, tabledef.columns[col].maxlength) + "…";
              }
            }

            return ["td", content,
                    "title", title];
          }).concat(tabledef.handler ? ["handler", tabledef.handler] : [])
            .concat(tabledef.idgetter ? ["data-object-id", tabledef.idgetter(item) ] : [])
         ];
}
<commit_msg>Use 'selected' radio box thingy for 'no grouping' option in sortabletables<commit_after>/**
 *
 * Sortable table component for dragonfly.
 *
 * Component is configured by a tabledef object. A tabledef
 * contains a map describing all the columns, and optional
 * functions used for getting values, sorting and rendering.
 * An example looks like this:
 *
 * var tabledef = {
 *         columns: {
 *
 */
function SortableTable(tabledef, data, cols, sortby, reversed)
{
  this._init = function()
  {
    if (!cols || !cols.length) {
      cols = [];
      for (var key in tabledef.columns) {
        cols.push(key);
        if (!sortby && tabledef.columns[key].sorter != "unsortable")
        {
          sortby = key;
        }
      }
    }

    for (var key in tabledef.columns)
    {
      var col = tabledef.columns[key];
      if (!col.getter)
      {
        col.getter = this._prop_getter(key);
      }

      if (!col.renderer)
      {
        col.renderer = col.getter;
      }

      if (col.sorter === undefined)
      {
        col.sorter = this._prop_sorter(col.getter);
      }
      else if (col.sorter === "unsortable")
      {
        col.sorter = null;
      }
    }

    this.sortby = sortby;
    this.tabledef = tabledef;
    this.data = data;
    this.columns = cols;
    this.reversed = !!reversed;
    this.groupby = null;
    this._elem = null;
    this.objectid = ObjectRegistry.get_instance().set_object(this);
    this._init_handlers();
  }

  this._init_handlers = function()
  {
    if (!eventHandlers.click["sortable-table-sort"])
    {
      eventHandlers.click["sortable-table-sort"] = this._sort_handler;
    }

    // should be conditional, but doesn't matter as you don't get
    // dupes in the context menu registry anyhow.
    var contextmenu = ContextMenu.get_instance();
    contextmenu.register("sortable-table-grouper", [
      { callback: this._make_context_menu }
    ]);
  }

  this._make_context_menu = function(evt)
  {
    var table = evt.target;
    while (table.nodeName.toLowerCase() != "table") { table = table.parentNode };
    var obj = ObjectRegistry.get_instance().get_object(table.getAttribute("data-object-id"));
    if (!obj.tabledef.groups) { return [] }

    var menuitems = [{
      label: "No grouping",
      selected: !obj.groupby,
      handler: obj._make_group_handler(null)
    }];

    for (var group in obj.tabledef.groups)
    {
      menuitems.push({
        label: "Group by " + (obj.tabledef.groups[group].label || group),
        selected: obj.groupby == group,
        handler: obj._make_group_handler(group)
      });
    }

    // visible column selection stuff
    menuitems.push(ContextMenu.separator);

    var allcols = obj.tabledef.column_order;
    if (!allcols)
    {
      allcols = [];
      for (var key in obj.tabledef.columns)
      {
        allcols.push(key);
      }
    }

    for (var n=0, colname; colname=allcols[n]; n++)
    {
      coldef = obj.tabledef.columns[colname];
      menuitems.push({
        label: coldef.label,
        checked: obj.columns.indexOf(colname) != -1,
        handler: obj._make_colselect_handler(colname)
      });
    }
    return menuitems;
  }

  this._find_col_insertion_point = function(col)
  {
    var colpoint = this.tabledef.column_order.indexOf(col);
    for (var n=colpoint+1, current; current=this.tabledef.column_order[n]; n++)
    {
      var point = this.columns.indexOf(current);
      if (point != -1) {
        return point;
      }
    }
    return this.columns.length;
  }

  this._make_group_handler = function(group)
  {
    return function(evt) {
      var target = evt.target;
      while (target.nodeName.toLowerCase() != "table") { target = target.parentNode };
      var obj = ObjectRegistry.get_instance().get_object(target.getAttribute("data-object-id"));
      obj.group(group);
      target.re_render(obj.render());
    }
  }

  this._make_colselect_handler = function(col)
  {
    return function(evt) {
      var target = evt.target;
      while (target.nodeName.toLowerCase() != "table") { target = target.parentNode };
      var obj = ObjectRegistry.get_instance().get_object(target.getAttribute("data-object-id"));
      obj.togglecol(col);
      target.re_render(obj.render());
    }
  }

  this._sort_handler = function(evt, target)
  {
    var table = target.parentNode.parentNode;
    var obj = ObjectRegistry.get_instance().get_object(table.getAttribute("data-object-id"));
    obj.sort(target.getAttribute("data-column-id"));
    table.re_render(obj.render());
  }

  this._default_sorters = {
    "number": function(getter) { return function(a, b) { return b-a; } }
  }

  this.render = function()
  {
    return templates.sortable_table(this.tabledef, this.data, this.objectid,
                                    this.columns, this.groupby, this.sortby,
                                    this.reversed);
  };

  this._prop_getter = function(name)
  {
    return function(obj) { return obj[name]; }
  }

  this._prop_sorter = function(getter)
  {
    return function(a, b)
    {
      a = getter(a);
      b = getter(b);
      if (a > b) { return 1 }
      else if (a < b) { return -1 }
      else { return 0 }
    }
  }

  this.sort = function(col)
  {
    if (col == this.sortby) {
      this.reversed = !this.reversed;
    }
    else
    {
      this.sortby = col;
    }
  };

  this.group = function(group)
  {
    if (!group || !this.tabledef.groups || !(group in this.tabledef.groups))
    {
      this.groupby = null;
    }
    else
    {
      this.groupby = group;
    }
  }

  this.togglecol = function(col)
  {
    var index = this.columns.indexOf(col);
    if (index == -1)
    {
      var point = this._find_col_insertion_point(col);
      this.columns.splice(point, 0, col);
    }
    else
    {
      this.columns.splice(index, 1);
    }
  }

  this._init();
}

window.templates = window.templates || {};

templates.sortable_table = function(tabledef, data, objectid, cols, groupby, sortby, reversed)
{
  return ["table",
          templates.sortable_table_header(tabledef, cols, sortby, reversed),
          templates.sortable_table_body(tabledef, data, cols, groupby, sortby, reversed),
          "class", "sortable-table",
          "data-object-id", objectid,
          "data-menu", "sortable-table-grouper"
         ]
}

templates.sortable_table_header = function(tabledef, cols, sortby, reversed)
{
  return ["tr",
          cols.map(function(c) {
            var coldef = tabledef.columns[c];
            var tdclass = "";

            if (!coldef.sorter)
            {
              tdclass = "unsortable";
            }
            else
            {
              if (sortby==c)
              {
                tdclass = "sort-column";
              }

              if (reversed)
              {
                tdclass += " reversed";
              }
            }
            return ["th",
                    tabledef.columns[c].label,
                    "class", tdclass,
                    "data-column-id", c,
                   ].concat(tabledef.columns[c].sorter ? ["handler", "sortable-table-sort"] : [])
          }),
          "class", "header"
         ];
}

templates.sortable_table_body = function(tabledef, data, cols, groupby, sortby, reversed)
{
  var groups = {};

  if (groupby)
  {
    var grouper = tabledef.groups[groupby].grouper;
    data.forEach(function(e) {
      var g = grouper(e);
      if (g in groups) { groups[g].push(e) }
      else { groups[g] = [e] }
    });
  }
  else
  {
    groups = {nogroup:data};
  }

  var groupnames = [];
  for (var key in groups) { groupnames.push(key) }

  if (groupby && tabledef.groups[groupby].sorter)
  {
    groupnames.sort(tabledef.groups[groupby].sorter);
  }
  else
  {
    groupnames.sort()
  }

  var render_group_headers = groupnames.length > 1;
  return groupnames.map(function(g) {
      return templates.sortable_table_group(tabledef, g,
                                            render_group_headers,
                                            groups[g], cols, groupby, sortby,
                                            reversed)
  });
}

templates.sortable_table_group = function(tabledef, groupname, render_header, data, cols, groupby, sortby, reversed)
{
  var sorter = tabledef.columns[sortby].sorter;
  if (sorter) { data.sort(sorter) }
  if (reversed) { data.reverse() }
  var tpl = [];
  if (render_header) {
    var renderer = tabledef.groups[groupby].renderer || function(g) { return g };
    tpl.push(["tr",
              ["th", renderer(groupname, data),
               "colspan", String(cols.length),
               "class", "sortable-table-group-header"],
              "class", "header"
             ]);
  }

  var ret =  tpl.concat(data.map(function(item) {
    return templates.sortable_table_row(tabledef, item, cols)
  }));

  for (var n=0, col; col = cols[n]; n++)
  {
    if (tabledef.columns[col].summer)
    {
      ret.push(templates.sortable_table_sumrow(tabledef, groupname, data, cols));
      break;
    }
  }
  return ret;
}

templates.sortable_table_sumrow = function(tabledef, groupname, data, cols)
{
  return ["tr",
          cols.map(function(e) {
            var val = "";
            var summer = tabledef.columns[e].summer;
            if (summer) { val = summer(data, groupname) }
            return ["td", val];
          }),
          "class", "sortable-table-summation-row"
         ];
}

templates.sortable_table_row = function(tabledef, item, cols)
{
  return ["tr",
          cols.map(function(col) {
            var content = tabledef.columns[col].renderer(item, tabledef.columns[col].getter);

            if (typeof content == "string")
            {
              var title = content; // fixme: use custom title renderer.
            }
            else
            {
              title = "";
            }

            if (typeof content == "string" &&
                tabledef.columns[col].maxlength &&
                tabledef.columns[col].maxlength < content.length)
            {
              if (tabledef.columns[col].ellipsis=="start")
              {
                content = "…" + content.slice(-tabledef.columns[col].maxlength);
              }
              else
              {
                content = content.slice(0, tabledef.columns[col].maxlength) + "…";
              }
            }

            return ["td", content,
                    "title", title];
          }).concat(tabledef.handler ? ["handler", tabledef.handler] : [])
            .concat(tabledef.idgetter ? ["data-object-id", tabledef.idgetter(item) ] : [])
         ];
}
<|endoftext|>


<commit_before>import getCorrectUrls from './getCorrectUrls';
import composeQuery from '../composeQuery/composeQuery.js';

it('returns correct urls for 1 page word "deliberately"', () => {
  return getCorrectUrls("deliberately")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word:'deliberately' })
    ]))
});

it('returns correct urls for 2 pages word "crank"', () => {
  return getCorrectUrls("crank")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word:'crank_1' }),
      composeQuery({ type: 'entry', word:'crank_2' })
    ]))
});

it('returns correct urls for 3 pages word "front"', () => {
  return getCorrectUrls("front")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word:'front_1' }),
      composeQuery({ type: 'entry', word:'front_2' }),
      composeQuery({ type: 'entry', word:'front_3' })
    ]))
});

it('returns correct urls for 4 pages word "cool"', () => {
  return getCorrectUrls("cool")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word:'cool_1' }),
      composeQuery({ type: 'entry', word:'cool_2' }),
      composeQuery({ type: 'entry', word:'cool_3' }),
      composeQuery({ type: 'entry', word:'cool_4' }),
    ]))
});
<commit_msg>beatify: add spaces in getCorrectUrls test<commit_after>import getCorrectUrls from './getCorrectUrls';
import composeQuery from '../composeQuery/composeQuery.js';

it('returns correct urls for 1 page word "deliberately"', () => {
  return getCorrectUrls("deliberately")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word: 'deliberately' })
    ]))
});

it('returns correct urls for 2 pages word "crank"', () => {
  return getCorrectUrls("crank")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word: 'crank_1' }),
      composeQuery({ type: 'entry', word: 'crank_2' })
    ]))
});

it('returns correct urls for 3 pages word "front"', () => {
  return getCorrectUrls("front")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word: 'front_1' }),
      composeQuery({ type: 'entry', word: 'front_2' }),
      composeQuery({ type: 'entry', word: 'front_3' })
    ]))
});

it('returns correct urls for 4 pages word "cool"', () => {
  return getCorrectUrls("cool")
    .then(urls => expect(urls).toEqual([
      composeQuery({ type: 'entry', word: 'cool_1' }),
      composeQuery({ type: 'entry', word: 'cool_2' }),
      composeQuery({ type: 'entry', word: 'cool_3' }),
      composeQuery({ type: 'entry', word: 'cool_4' }),
    ]))
});
<|endoftext|>


<commit_before>const RequestRepository = require('src/plex/requestRepository.js');
const requestRepository = new RequestRepository();

function searchRequestController(req, res) {
	const { query, page, type } = req.query;
	console.log('searchReq: ' + query, page, type);

	requestRepository.searchRequest(query, page, type)
	.then((movies) => {
		res.send(movies);
	})
	.catch((error) => {
		res.status(500).send({success: false, error: error.message });
	})
}

module.exports = searchRequestController;<commit_msg>Verify that respond has content, if so send the content back. If no content was found, send 404 status and error message<commit_after>const RequestRepository = require('src/plex/requestRepository.js');
const requestRepository = new RequestRepository();

function searchRequestController(req, res) {
	const { query, page, type } = req.query;
	console.log('searchReq: ' + query, page, type);

	requestRepository.searchRequest(query, page, type)
	.then((movies) => {
		// Verify that respond has content, if so send the content back
		if (movies.length > 0 && movies != null) {
			res.send(movies);
		}
		// If no content was found, send 404 status and error message
		else {
			res.status(404).send({success: false, error: 'Search query did not return any results.'})
		}
	})
	.catch((error) => {
		res.status(500).send({success: false, error: error.message });
	})
}

module.exports = searchRequestController;<|endoftext|>


<commit_before>/* 作者: dailc
 * 时间: 2017-07-30
 * 描述: Longest-Increasing-Subsequence
 *
 * 来自: https://leetcode.com/problems/longest-increasing-subsequence
 */
(function(exports) {

    /**
     * @param {number[]} nums
     * @return {number}
     */
    var lengthOfLIS = function(nums) {
        if (!nums || nums.length == 0) {
            return 0;
        }

        var list = [],
            max = 0,
            len = nums.length;

        for (var i = 0; i < len; i++) {
            var localMax = 0;
            // 找出当前点之前的最大上升序列长度
            for (var j = 0; j < i; j++) {
                if (list[j] > localMax && nums[j] < nums[i]) {
                    localMax = list[j];
                }
            }
            // 当前点则是该局部最大上升长度加1
            list[i] = localMax + 1;
            // 用当前点的长度更新全局最大长度
            max = Math.max(max, list[i]);
        }

        return max;
    };

    exports.lengthOfLIS = lengthOfLIS;

})(window.LeetCode = window.LeetCode || {});<commit_msg>update<commit_after>/* 作者: dailc
 * 时间: 2017-07-30
 * 描述: Longest-Increasing-Subsequence
 *
 * 来自: https://leetcode.com/problems/longest-increasing-subsequence
 */
(function(exports) {

    /**
     * @param {number[]} nums
     * @return {number}
     */
    var lengthOfLIS = function(nums) {
        if (!nums || nums.length == 0) {
            return 0;
        }

        var dp = [],
            max = 0,
            len = nums.length;

        for (var i = 0; i < len; i++) {
            var localMax = 0;
            // 找出当前点之前的最大上升序列长度，因为是非连续，所以要连续找
            for (var j = 0; j < i; j++) {
                if (dp[j] > localMax && nums[j] < nums[i]) {
                    localMax = dp[j];
                }
            }
            // 当前点则是该局部最大上升长度加1
            dp[i] = localMax + 1;
            // 用当前点的长度更新全局最大长度
            max = Math.max(max, dp[i]);
        }

        return max;
    };

    exports.lengthOfLIS = lengthOfLIS;

})(window.LeetCode = window.LeetCode || {});<|endoftext|>


<commit_before>/**
 * skeletontracing_reducer_helpers.js
 * @flow
 *
 * THESE HELPERFUNCTIONS MUST ONLY BE CALLED FROM A REDUCER
 *
 */

import _ from "lodash";
import Maybe from "data.maybe";
import app from "app";
import scaleInfo from "oxalis/model/scaleinfo";
import ColorGenerator from "libs/color_generator";
import update from "immutability-helper";
import Utils from "libs/utils";
import type { Vector3 } from "oxalis/constants";
import type { OxalisState, SkeletonTracingType, EdgeType, NodeType, TreeType, BranchPointType, TreeMapType, CommentType } from "oxalis/store";

function moveNodesToNewTree(trees: TreeMapType, nodeId: number): TreeMapType {
  // TODO
  return trees;
}

export function findTree(trees: TreeMapType, nodeId: number): ?TreeType {
  return _.values(trees)
    .filter(tree => _.map(tree.nodes, "id").includes(nodeId))[0];
}

function generateTreeNamePrefix(state: OxalisState, timestamp) {
  let user = `${app.currentUser.firstName}_${app.currentUser.lastName}`;

  // Replace spaces in user names
  user = user.replace(/ /g, "_");
  if (state.skeletonTracing.tracingType === "Explorational" || !state.task) {
    // Get YYYY-MM-DD string
    const creationDate = new Date(timestamp).toJSON().slice(0, 10);
    return `explorative_${creationDate}_${user}_`;
  } else {
    return `task_${state.task.taskId}_${user}_`;
  }
}

export function createNode(state: OxalisState, position: Vector3, rotation: Vector3, viewport: number, resolution: number, timestamp: number): Maybe<[NodeType, Array<EdgeType>]> {
  const { allowUpdate } = state.skeletonTracing.restrictions;
  const { activeTreeId, activeNodeId } = state.skeletonTracing;

  if (allowUpdate && activeTreeId != null) {
    // Use the same radius as current active node or revert to default value
    const defaultRadius = 10 * scaleInfo.baseVoxel;
    const radius = activeNodeId ? state.skeletonTracing.trees[activeTreeId].nodes[activeNodeId].radius : defaultRadius;

    // Find new node id by increasing the max node id.
    // Default to 0 if there are no nodes yet
    const maxNodeId = _.max(_.flatMap(state.skeletonTracing.trees, tree => _.map(tree.nodes, node => node.id)));
    const nextNewId = _.isNumber(maxNodeId) ? maxNodeId + 1 : 0;

    // Create the new node
    const node: NodeType = {
      position,
      radius,
      rotation,
      viewport,
      resolution,
      id: nextNewId,
      timestamp,
      bitdepth: 8, // datasetConfig.fourBit ? 4 : 8,
      interpolation: true, // datasetConfig.interpolation,
    };

    // Create a new edge
    const newEdges = [];
    if (activeNodeId != null) {
      const newEdge: EdgeType = {
        source: activeNodeId,
        target: nextNewId,
      };
      newEdges.push(newEdge);
    }
    const edges = state.skeletonTracing.trees[activeTreeId].edges.concat(newEdges);

    return Maybe.Just([node, edges]);
  }
  return Maybe.Nothing();
}

export function deleteNode(state: OxalisState, timestamp: number): Maybe<[TreeMapType, number, ?number]> {
  const skeletonTracing = state.skeletonTracing;
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId } = skeletonTracing;

  if (allowUpdate && activeNodeId != null && activeTreeId != null) {
    let newActiveNodeId = activeNodeId;
    let newActiveTreeId = activeTreeId;
    let newTrees = skeletonTracing.trees;

    // Delete Node
    let activeTree: TreeType = skeletonTracing.trees[activeTreeId];
    activeTree = update(activeTree, { nodes: { $set: _.omit(activeTree.nodes, [activeNodeId.toString()]) } });

    // Do we need to split trees? Are there edges leading to/from it?
    const sourceNodeIds = activeTree.edges.filter(edge => edge.target === activeNodeId).map(edge => edge.source);
    const targetNodeIds = activeTree.edges.filter(edge => edge.source === activeNodeId).map(edge => edge.target);
    const neighborIds = sourceNodeIds.concat(targetNodeIds);

    if (neighborIds.length === 0) {
      return deleteTree(state, timestamp);
    } else {
      // Use split-algorithmus. If we delete a node which is only connected via one edge,
      // this algorithmus will only produce one tree (which reuses the old oen)

      // Build a hashmap which contains for each node all edges leading/leaving into/from the node
      let nodeToEdgesMap: {[number]: Array<EdgeType>} = {};
      activeTree.edges.forEach((edge) => {
        if (nodeToEdgesMap[edge.source]) {
          nodeToEdgesMap[edge.source].push(edge);
        } else {
          nodeToEdgesMap[edge.source] = [edge];
        }
        if (nodeToEdgesMap[edge.target]) {
          nodeToEdgesMap[edge.target].push(edge);
        } else {
          nodeToEdgesMap[edge.target] = [edge];
        }
      });

      // Traverse from active node in all directions (i.e., use each edge) and
      // remember which edges were already visited
      const deletedEdges = nodeToEdgesMap[activeNodeId];
      let visitedEdges = {};
      const getEdgeHash = edge => edge.source + "-" + edge.target;

      // Mark edges of deleted node as visited
      deletedEdges.forEach((deletedEdge) => {
        visitedEdges[getEdgeHash(deletedEdge)] = true;
      })

      const traverseTree = function (nodeId: number, newTree: TreeType) {
        const edges = nodeToEdgesMap[nodeId];

        if (nodeId !== activeNodeId) {
          newTree.nodes[nodeId] = activeTree.nodes[nodeId];
        }
        for (const edge of edges) {
          const edgeHash = getEdgeHash(edge);
          if (visitedEdges[edgeHash]) {
            continue;
          }
          visitedEdges[edgeHash] = true;
          newTree.edges.push(edge);

          traverseTree(edge.source, newTree);
          traverseTree(edge.target, newTree);
        }
      };

      // The intermediateState is used for the createTree function, which takes
      // care of generating non-colliding tree names, ids and colors
      let intermediateState = state;
      const cutTrees = deletedEdges.map((edgeOfActiveNode, edgeIndex) => {
        let newTree;
        if (edgeIndex === 0) {
          // Reuse the first tree
          newTree = {
            branchPoints: [],
            color: activeTree.color,
            comments: [],
            edges: [],
            name: activeTree.name,
            nodes: {},
            timestamp: activeTree.timestamp,
            treeId: activeTree.treeId,
          };
        } else {
          newTree = createTree(intermediateState, timestamp).get();
          intermediateState = update(intermediateState, { skeletonTracing: { trees: { [newTree.treeId]: { $set: newTree }}}});
        }

        const neighborId = activeNodeId != edgeOfActiveNode.source
          ? edgeOfActiveNode.source
          : edgeOfActiveNode.target;

        if (newActiveNodeId != null) {
          newActiveNodeId = neighborId;
        }
        traverseTree(neighborId, newTree);
        return newTree;
      });

      // Write branchpoints into correct trees
      activeTree.branchPoints.forEach((branchpoint) => {
        cutTrees.forEach((newTree) => {
          if (newTree.nodes[branchpoint.id]) {
            newTree.branchPoints.push(branchpoint);
          }
        });
      });

      // Write comments into correct trees
      activeTree.comments.forEach((comment) => {
        cutTrees.forEach((newTree) => {
          if (newTree.nodes[comment.node]) {
            newTree.comments.push(comment);
          }
        });
      });

      // newActiveNodeId was already written to when traversing the tree. Find the
      // corresponding treeId
      const newActiveTree = findTree(newTrees, newActiveNodeId);
      if (!newActiveTree) {
        throw new Error("Could not find tree for active node id")
      };
      newActiveTreeId = newActiveTree.treeId;

      newTrees = skeletonTracing.trees;
      cutTrees.forEach((cutTree) => {
        newTrees = update(newTrees, { [cutTree.treeId]: { $set: cutTree } });
      });
    }

    return Maybe.Just([newTrees, newActiveTreeId, newActiveNodeId]);
  } else {
    return Maybe.Nothing();
  }
}

export function createBranchPoint(skeletonTracing: SkeletonTracingType, timestamp: number): Maybe<BranchPointType> {
  const { branchPointsAllowed, allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId } = skeletonTracing;

  if (branchPointsAllowed && allowUpdate && activeNodeId != null && activeTreeId != null) {
    const doesBranchPointExistAlready = _.some(skeletonTracing.trees[activeTreeId].branchPoints, branchPoint => branchPoint.id === activeNodeId);

    if (!doesBranchPointExistAlready) {
      // create new branchpoint
      return Maybe.Just({
        id: activeNodeId,
        timestamp,
      });
    }
  }
  return Maybe.Nothing();
}

export function deleteBranchPoint(skeletonTracing: SkeletonTracingType): Maybe<[Array<BranchPointType>, number, number]> {
  const { branchPointsAllowed, allowUpdate } = skeletonTracing.restrictions;
  const { trees } = skeletonTracing;
  const hasBranchPoints = _.some(_.map(trees, tree => !_.isEmpty(tree.branchPoints)));

  if (branchPointsAllowed && allowUpdate && hasBranchPoints) {
    // Find most recent branchpoint across all trees
    const treesWithBranchPoints = _.values(trees).filter(tree => !_.isEmpty(tree.branchPoints));
    const treeId = _.maxBy(treesWithBranchPoints, tree => _.last(tree.branchPoints).timestamp).treeId;
    const branchPoint = _.last(trees[treeId].branchPoints);

    if (branchPoint) {
      // Delete branchpoint
      const newBranchPoints = _.without(skeletonTracing.trees[treeId].branchPoints, branchPoint);
      return Maybe.Just([newBranchPoints, treeId, branchPoint.id]);
    }
  }
  return Maybe.Nothing();
}

export function createTree(state: OxalisState, timestamp: number): Maybe<TreeType> {
  const { allowUpdate } = state.skeletonTracing.restrictions;

  if (allowUpdate) {
    // create a new tree id and name
    // tree id can become 0 after deleting all trees
    const maxTreeId = _.max(_.map(state.skeletonTracing.trees, "treeId"));
    const newTreeId = _.isNumber(maxTreeId) ? maxTreeId + 1 : 0;

    const name = generateTreeNamePrefix(state, timestamp) + Utils.zeroPad(newTreeId, 2);

    // Create the new tree
    const tree: TreeType = {
      name,
      treeId: newTreeId,
      nodes: {},
      timestamp,
      color: ColorGenerator.distinctColorForId(newTreeId),
      branchPoints: [],
      edges: [],
      comments: [],
    };
    return Maybe.Just(tree);
  }
  return Maybe.Nothing();
}

export function deleteTree(state: OxalisState, timestamp: number): Maybe<[TreeMapType, number, ?number]> {
  const skeletonTracing = state.skeletonTracing;
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeTreeId } = skeletonTracing;

  if (allowUpdate && activeTreeId != null) {
    // Delete tree
    let newTrees = _.omit(skeletonTracing.trees, activeTreeId.toString());

    // Because we always want an active tree, check if we need
    // to create one.
    let newActiveTreeId;
    let newActiveNodeId;
    if (_.size(newTrees) === 0) {
      const newTree = createTree(state, timestamp).get();
      newTrees = update(newTrees, { [newTree.treeId]: { $set: newTree } });

      newActiveTreeId = newTree.treeId;
      newActiveNodeId = null;
    } else {
      // just set the last tree to be the active one
      const maxTreeId = _.max(_.map(newTrees, "treeId"));
      newActiveTreeId = maxTreeId;
      newActiveNodeId = _.get(newTrees[maxTreeId].nodes, "nodes[0].id", null);
    }

    return Maybe.Just([newTrees, newActiveTreeId, newActiveNodeId]);
  }
  return Maybe.Nothing();
}

export function mergeTrees(skeletonTracing: SkeletonTracingType, sourceNodeId: number, targetNodeId: number): Maybe<[TreeType, number, number]> {
  const { allowUpdate } = skeletonTracing.restrictions;
  const { trees } = skeletonTracing;
  const sourceTree = findTree(trees, sourceNodeId);
  const targetTree = findTree(trees, targetNodeId); // should be activeTree

  if (allowUpdate && sourceTree && targetTree) {
    const newEdge: EdgeType = {
      source: sourceNodeId,
      target: targetNodeId,
    };

    let newTrees = _.omit(trees, sourceTree.treeId.toString());
    newTrees = update(newTrees, { [targetTree.treeId]: {
      nodes: { $set: Object.assign({}, targetTree.nodes, sourceTree.nodes) },
      edges: { $set: targetTree.edges.concat(sourceTree.edges).concat([newEdge]) },
    } });
    return Maybe.Just([newTrees, targetTree.treeId, targetNodeId]);
  }
  return Maybe.Nothing();
}

export function shuffleTreeColor(skeletonTracing: SkeletonTracingType, treeId: number): Maybe<[TreeType, number]> {
  let tree = skeletonTracing.trees[treeId];

  if (_.isNumber(treeId) && tree) {
    const randomId = _.random(0, 10000, false);
    // ColorGenerator fails to produce distinct color for huge ids (Infinity)
    tree = update(tree, { color: { $set: ColorGenerator.distinctColorForId(randomId) } });
    return Maybe.Just([tree, treeId]);
  }

  return Maybe.Nothing();
}

export function createComment(skeletonTracing: SkeletonTracingType, commentText: string): Maybe<Array<CommentType>> {
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId, trees } = skeletonTracing;

  if (allowUpdate && activeNodeId != null && activeTreeId != null) {
    // Gather all comments other than the activeNode's comments
    const comments = trees[activeTreeId].comments;
    const commentsWithoutActiveNodeComment = comments.filter(comment => comment.node !== activeNodeId);

    const newComment: CommentType = {
      node: activeNodeId,
      comment: commentText,
    };

    const newComments = commentsWithoutActiveNodeComment.concat([newComment]);
    return Maybe.Just(newComments);
  }

  return Maybe.Nothing();
}

export function deleteComment(skeletonTracing: SkeletonTracingType): Maybe<Array<CommentType>> {
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId, trees } = skeletonTracing;

  if (allowUpdate && activeNodeId != null && activeTreeId != null) {
    const comments = trees[activeTreeId].comments;
    const commentsWithoutActiveNodeComment = comments.filter(comment => comment.node !== activeNodeId);

    return Maybe.Just(commentsWithoutActiveNodeComment);
  }
  return Maybe.Nothing();
}
<commit_msg>fix setting active node when removing entire tree and fix updating the first tree correctly when deleting node<commit_after>/**
 * skeletontracing_reducer_helpers.js
 * @flow
 *
 * THESE HELPERFUNCTIONS MUST ONLY BE CALLED FROM A REDUCER
 *
 */

import _ from "lodash";
import Maybe from "data.maybe";
import app from "app";
import scaleInfo from "oxalis/model/scaleinfo";
import ColorGenerator from "libs/color_generator";
import update from "immutability-helper";
import Utils from "libs/utils";
import type { Vector3 } from "oxalis/constants";
import type { OxalisState, SkeletonTracingType, EdgeType, NodeType, TreeType, BranchPointType, TreeMapType, CommentType } from "oxalis/store";

function moveNodesToNewTree(trees: TreeMapType, nodeId: number): TreeMapType {
  // TODO
  return trees;
}

export function findTree(trees: TreeMapType, nodeId: number): ?TreeType {
  return _.values(trees)
    .filter(tree => _.map(tree.nodes, "id").includes(nodeId))[0];
}

function generateTreeNamePrefix(state: OxalisState, timestamp) {
  let user = `${app.currentUser.firstName}_${app.currentUser.lastName}`;

  // Replace spaces in user names
  user = user.replace(/ /g, "_");
  if (state.skeletonTracing.tracingType === "Explorational" || !state.task) {
    // Get YYYY-MM-DD string
    const creationDate = new Date(timestamp).toJSON().slice(0, 10);
    return `explorative_${creationDate}_${user}_`;
  } else {
    return `task_${state.task.taskId}_${user}_`;
  }
}

export function createNode(state: OxalisState, position: Vector3, rotation: Vector3, viewport: number, resolution: number, timestamp: number): Maybe<[NodeType, Array<EdgeType>]> {
  const { allowUpdate } = state.skeletonTracing.restrictions;
  const { activeTreeId, activeNodeId } = state.skeletonTracing;

  if (allowUpdate && activeTreeId != null) {
    // Use the same radius as current active node or revert to default value
    const defaultRadius = 10 * scaleInfo.baseVoxel;
    const radius = activeNodeId ? state.skeletonTracing.trees[activeTreeId].nodes[activeNodeId].radius : defaultRadius;

    // Find new node id by increasing the max node id.
    // Default to 0 if there are no nodes yet
    const maxNodeId = _.max(_.flatMap(state.skeletonTracing.trees, tree => _.map(tree.nodes, node => node.id)));
    const nextNewId = _.isNumber(maxNodeId) ? maxNodeId + 1 : 0;

    // Create the new node
    const node: NodeType = {
      position,
      radius,
      rotation,
      viewport,
      resolution,
      id: nextNewId,
      timestamp,
      bitdepth: 8, // datasetConfig.fourBit ? 4 : 8,
      interpolation: true, // datasetConfig.interpolation,
    };

    // Create a new edge
    const newEdges = [];
    if (activeNodeId != null) {
      const newEdge: EdgeType = {
        source: activeNodeId,
        target: nextNewId,
      };
      newEdges.push(newEdge);
    }
    const edges = state.skeletonTracing.trees[activeTreeId].edges.concat(newEdges);

    return Maybe.Just([node, edges]);
  }
  return Maybe.Nothing();
}

export function deleteNode(state: OxalisState, timestamp: number): Maybe<[TreeMapType, number, ?number]> {
  const skeletonTracing = state.skeletonTracing;
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId } = skeletonTracing;

  if (allowUpdate && activeNodeId != null && activeTreeId != null) {
    let newActiveNodeId = activeNodeId;
    let newActiveTreeId = activeTreeId;
    let newTrees = skeletonTracing.trees;

    // Delete Node
    let activeTree: TreeType = skeletonTracing.trees[activeTreeId];
    activeTree = update(activeTree, { nodes: { $set: _.omit(activeTree.nodes, [activeNodeId.toString()]) } });

    // Do we need to split trees? Are there edges leading to/from it?
    const sourceNodeIds = activeTree.edges.filter(edge => edge.target === activeNodeId).map(edge => edge.source);
    const targetNodeIds = activeTree.edges.filter(edge => edge.source === activeNodeId).map(edge => edge.target);
    const neighborIds = sourceNodeIds.concat(targetNodeIds);

    if (neighborIds.length === 0) {
      return deleteTree(state, timestamp);
    } else {
      // Use split-algorithmus. If we delete a node which is only connected via one edge,
      // this algorithmus will only produce one tree (which reuses the old oen)

      // Build a hashmap which contains for each node all edges leading/leaving into/from the node
      let nodeToEdgesMap: {[number]: Array<EdgeType>} = {};
      activeTree.edges.forEach((edge) => {
        if (nodeToEdgesMap[edge.source]) {
          nodeToEdgesMap[edge.source].push(edge);
        } else {
          nodeToEdgesMap[edge.source] = [edge];
        }
        if (nodeToEdgesMap[edge.target]) {
          nodeToEdgesMap[edge.target].push(edge);
        } else {
          nodeToEdgesMap[edge.target] = [edge];
        }
      });

      // Traverse from active node in all directions (i.e., use each edge) and
      // remember which edges were already visited
      const deletedEdges = nodeToEdgesMap[activeNodeId];
      let visitedEdges = {};
      const getEdgeHash = edge => edge.source + "-" + edge.target;

      // Mark edges of deleted node as visited
      deletedEdges.forEach((deletedEdge) => {
        visitedEdges[getEdgeHash(deletedEdge)] = true;
      })

      const traverseTree = function (nodeId: number, newTree: TreeType) {
        const edges = nodeToEdgesMap[nodeId];

        if (nodeId !== activeNodeId) {
          newTree.nodes[nodeId] = activeTree.nodes[nodeId];
        }
        for (const edge of edges) {
          const edgeHash = getEdgeHash(edge);
          if (visitedEdges[edgeHash]) {
            continue;
          }
          visitedEdges[edgeHash] = true;
          newTree.edges.push(edge);

          traverseTree(edge.source, newTree);
          traverseTree(edge.target, newTree);
        }
      };

      // The intermediateState is used for the createTree function, which takes
      // care of generating non-colliding tree names, ids and colors
      let intermediateState = state;
      // For each edge of the to-be-deleted node, create a new tree.
      const cutTrees = deletedEdges.map((edgeOfActiveNode, edgeIndex) => {
        let newTree;
        if (edgeIndex === 0) {
          // Reuse the first tree
          newTree = {
            branchPoints: [],
            color: activeTree.color,
            comments: [],
            edges: [],
            name: activeTree.name,
            nodes: {},
            timestamp: activeTree.timestamp,
            treeId: activeTree.treeId,
          };
        } else {
          newTree = createTree(intermediateState, timestamp).get();
          intermediateState = update(intermediateState, { skeletonTracing: { trees: { [newTree.treeId]: { $set: newTree }}}});
        }

        const neighborId = activeNodeId != edgeOfActiveNode.source
          ? edgeOfActiveNode.source
          : edgeOfActiveNode.target;

        if (newActiveNodeId != null) {
          // Use a neighbor of the deleted node as the new active node
          newActiveNodeId = neighborId;
        }
        traverseTree(neighborId, newTree);
        return newTree;
      });

      // Write branchpoints into correct trees
      activeTree.branchPoints.forEach((branchpoint) => {
        cutTrees.forEach((newTree) => {
          if (newTree.nodes[branchpoint.id]) {
            newTree.branchPoints.push(branchpoint);
          }
        });
      });

      // Write comments into correct trees
      activeTree.comments.forEach((comment) => {
        cutTrees.forEach((newTree) => {
          if (newTree.nodes[comment.node]) {
            newTree.comments.push(comment);
          }
        });
      });

      newTrees = skeletonTracing.trees;
      cutTrees.forEach((cutTree) => {
        newTrees = update(newTrees, { [cutTree.treeId]: { $set: cutTree } });
      });
      // newActiveNodeId was already written to when traversing the tree. Find the
      // corresponding treeId
      const newActiveTree = findTree(newTrees, newActiveNodeId);
      if (!newActiveTree) {
        throw new Error("Could not find tree for active node id")
      };
      newActiveTreeId = newActiveTree.treeId;
    }

    return Maybe.Just([newTrees, newActiveTreeId, newActiveNodeId]);
  } else {
    return Maybe.Nothing();
  }
}

export function createBranchPoint(skeletonTracing: SkeletonTracingType, timestamp: number): Maybe<BranchPointType> {
  const { branchPointsAllowed, allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId } = skeletonTracing;

  if (branchPointsAllowed && allowUpdate && activeNodeId != null && activeTreeId != null) {
    const doesBranchPointExistAlready = _.some(skeletonTracing.trees[activeTreeId].branchPoints, branchPoint => branchPoint.id === activeNodeId);

    if (!doesBranchPointExistAlready) {
      // create new branchpoint
      return Maybe.Just({
        id: activeNodeId,
        timestamp,
      });
    }
  }
  return Maybe.Nothing();
}

export function deleteBranchPoint(skeletonTracing: SkeletonTracingType): Maybe<[Array<BranchPointType>, number, number]> {
  const { branchPointsAllowed, allowUpdate } = skeletonTracing.restrictions;
  const { trees } = skeletonTracing;
  const hasBranchPoints = _.some(_.map(trees, tree => !_.isEmpty(tree.branchPoints)));

  if (branchPointsAllowed && allowUpdate && hasBranchPoints) {
    // Find most recent branchpoint across all trees
    const treesWithBranchPoints = _.values(trees).filter(tree => !_.isEmpty(tree.branchPoints));
    const treeId = _.maxBy(treesWithBranchPoints, tree => _.last(tree.branchPoints).timestamp).treeId;
    const branchPoint = _.last(trees[treeId].branchPoints);

    if (branchPoint) {
      // Delete branchpoint
      const newBranchPoints = _.without(skeletonTracing.trees[treeId].branchPoints, branchPoint);
      return Maybe.Just([newBranchPoints, treeId, branchPoint.id]);
    }
  }
  return Maybe.Nothing();
}

export function createTree(state: OxalisState, timestamp: number): Maybe<TreeType> {
  const { allowUpdate } = state.skeletonTracing.restrictions;

  if (allowUpdate) {
    // create a new tree id and name
    // tree id can become 0 after deleting all trees
    const maxTreeId = _.max(_.map(state.skeletonTracing.trees, "treeId"));
    const newTreeId = _.isNumber(maxTreeId) ? maxTreeId + 1 : 0;

    const name = generateTreeNamePrefix(state, timestamp) + Utils.zeroPad(newTreeId, 2);

    // Create the new tree
    const tree: TreeType = {
      name,
      treeId: newTreeId,
      nodes: {},
      timestamp,
      color: ColorGenerator.distinctColorForId(newTreeId),
      branchPoints: [],
      edges: [],
      comments: [],
    };
    return Maybe.Just(tree);
  }
  return Maybe.Nothing();
}

export function deleteTree(state: OxalisState, timestamp: number): Maybe<[TreeMapType, number, ?number]> {
  const skeletonTracing = state.skeletonTracing;
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeTreeId } = skeletonTracing;

  if (allowUpdate && activeTreeId != null) {
    // Delete tree
    let newTrees = _.omit(skeletonTracing.trees, activeTreeId.toString());

    // Because we always want an active tree, check if we need
    // to create one.
    let newActiveTreeId;
    let newActiveNodeId;
    if (_.size(newTrees) === 0) {
      const newTree = createTree(state, timestamp).get();
      newTrees = update(newTrees, { [newTree.treeId]: { $set: newTree } });

      newActiveTreeId = newTree.treeId;
      newActiveNodeId = null;
    } else {
      // just set the last tree to be the active one
      const maxTreeId = _.max(_.map(newTrees, "treeId"));
      newActiveTreeId = maxTreeId;
      newActiveNodeId = _.first(Object.keys(newTrees[maxTreeId].nodes));
    }

    return Maybe.Just([newTrees, newActiveTreeId, newActiveNodeId]);
  }
  return Maybe.Nothing();
}

export function mergeTrees(skeletonTracing: SkeletonTracingType, sourceNodeId: number, targetNodeId: number): Maybe<[TreeType, number, number]> {
  const { allowUpdate } = skeletonTracing.restrictions;
  const { trees } = skeletonTracing;
  const sourceTree = findTree(trees, sourceNodeId);
  const targetTree = findTree(trees, targetNodeId); // should be activeTree

  if (allowUpdate && sourceTree && targetTree) {
    const newEdge: EdgeType = {
      source: sourceNodeId,
      target: targetNodeId,
    };

    let newTrees = _.omit(trees, sourceTree.treeId.toString());
    newTrees = update(newTrees, { [targetTree.treeId]: {
      nodes: { $set: Object.assign({}, targetTree.nodes, sourceTree.nodes) },
      edges: { $set: targetTree.edges.concat(sourceTree.edges).concat([newEdge]) },
    } });
    return Maybe.Just([newTrees, targetTree.treeId, targetNodeId]);
  }
  return Maybe.Nothing();
}

export function shuffleTreeColor(skeletonTracing: SkeletonTracingType, treeId: number): Maybe<[TreeType, number]> {
  let tree = skeletonTracing.trees[treeId];

  if (_.isNumber(treeId) && tree) {
    const randomId = _.random(0, 10000, false);
    // ColorGenerator fails to produce distinct color for huge ids (Infinity)
    tree = update(tree, { color: { $set: ColorGenerator.distinctColorForId(randomId) } });
    return Maybe.Just([tree, treeId]);
  }

  return Maybe.Nothing();
}

export function createComment(skeletonTracing: SkeletonTracingType, commentText: string): Maybe<Array<CommentType>> {
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId, trees } = skeletonTracing;

  if (allowUpdate && activeNodeId != null && activeTreeId != null) {
    // Gather all comments other than the activeNode's comments
    const comments = trees[activeTreeId].comments;
    const commentsWithoutActiveNodeComment = comments.filter(comment => comment.node !== activeNodeId);

    const newComment: CommentType = {
      node: activeNodeId,
      comment: commentText,
    };

    const newComments = commentsWithoutActiveNodeComment.concat([newComment]);
    return Maybe.Just(newComments);
  }

  return Maybe.Nothing();
}

export function deleteComment(skeletonTracing: SkeletonTracingType): Maybe<Array<CommentType>> {
  const { allowUpdate } = skeletonTracing.restrictions;
  const { activeNodeId, activeTreeId, trees } = skeletonTracing;

  if (allowUpdate && activeNodeId != null && activeTreeId != null) {
    const comments = trees[activeTreeId].comments;
    const commentsWithoutActiveNodeComment = comments.filter(comment => comment.node !== activeNodeId);

    return Maybe.Just(commentsWithoutActiveNodeComment);
  }
  return Maybe.Nothing();
}
<|endoftext|>


<commit_before>import { connect } from 'react-redux';
import DetailedStatus from '../components/detailed_status';
import { makeGetStatus } from '../../../selectors';
import {
  replyCompose,
  mentionCompose,
  directCompose,
} from '../../../actions/compose';
import {
  reblog,
  favourite,
  unreblog,
  unfavourite,
  pin,
  unpin,
} from '../../../actions/interactions';
import {
  muteStatus,
  unmuteStatus,
  deleteStatus,
  hideStatus,
  revealStatus,
} from '../../../actions/statuses';
import { initMuteModal } from '../../../actions/mutes';
import { initBlockModal } from '../../../actions/blocks';
import { initReport } from '../../../actions/reports';
import { openModal } from '../../../actions/modal';
import { defineMessages, injectIntl } from 'react-intl';
import { boostModal, deleteModal } from '../../../initial_state';
import { showAlertForError } from '../../../actions/alerts';

const messages = defineMessages({
  deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
  deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
  redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
});

const makeMapStateToProps = () => {
  const getStatus = makeGetStatus();

  const mapStateToProps = (state, props) => ({
    status: getStatus(state, props),
    domain: state.getIn(['meta', 'domain']),
  });

  return mapStateToProps;
};

const mapDispatchToProps = (dispatch, { intl }) => ({

  onReply (status, router) {
    dispatch((_, getState) => {
      let state = getState();
      if (state.getIn(['compose', 'text']).trim().length !== 0) {
        dispatch(openModal('CONFIRM', {
          message: intl.formatMessage(messages.replyMessage),
          confirm: intl.formatMessage(messages.replyConfirm),
          onConfirm: () => dispatch(replyCompose(status, router)),
        }));
      } else {
        dispatch(replyCompose(status, router));
      }
    });
  },

  onModalReblog (status) {
    dispatch(reblog(status));
  },

  onReblog (status, e) {
    if (status.get('reblogged')) {
      dispatch(unreblog(status));
    } else {
      if (e.shiftKey || !boostModal) {
        this.onModalReblog(status);
      } else {
        dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
      }
    }
  },

  onFavourite (status) {
    if (status.get('favourited')) {
      dispatch(unfavourite(status));
    } else {
      dispatch(favourite(status));
    }
  },

  onPin (status) {
    if (status.get('pinned')) {
      dispatch(unpin(status));
    } else {
      dispatch(pin(status));
    }
  },

  onEmbed (status) {
    dispatch(openModal('EMBED', {
      url: status.get('url'),
      onError: error => dispatch(showAlertForError(error)),
    }));
  },

  onDelete (status, history, withRedraft = false) {
    if (!deleteModal) {
      dispatch(deleteStatus(status.get('id'), history, withRedraft));
    } else {
      dispatch(openModal('CONFIRM', {
        message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
        confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
        onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
      }));
    }
  },

  onDirect (account, router) {
    dispatch(directCompose(account, router));
  },

  onMention (account, router) {
    dispatch(mentionCompose(account, router));
  },

  onOpenMedia (media, index) {
    dispatch(openModal('MEDIA', { media, index }));
  },

  onOpenVideo (media, options) {
    dispatch(openModal('VIDEO', { media, options }));
  },

  onBlock (status) {
    const account = status.get('account');
    dispatch(initBlockModal(account));
  },

  onReport (status) {
    dispatch(initReport(status.get('account'), status));
  },

  onMute (account) {
    dispatch(initMuteModal(account));
  },

  onMuteConversation (status) {
    if (status.get('muted')) {
      dispatch(unmuteStatus(status.get('id')));
    } else {
      dispatch(muteStatus(status.get('id')));
    }
  },

  onToggleHidden (status) {
    if (status.get('hidden')) {
      dispatch(revealStatus(status.get('id')));
    } else {
      dispatch(hideStatus(status.get('id')));
    }
  },

});

export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));
<commit_msg>Fix missing pictureInPicture prop in detailed status container (#15309)<commit_after>import { connect } from 'react-redux';
import DetailedStatus from '../components/detailed_status';
import { makeGetStatus, makeGetPictureInPicture } from '../../../selectors';
import {
  replyCompose,
  mentionCompose,
  directCompose,
} from '../../../actions/compose';
import {
  reblog,
  favourite,
  unreblog,
  unfavourite,
  pin,
  unpin,
} from '../../../actions/interactions';
import {
  muteStatus,
  unmuteStatus,
  deleteStatus,
  hideStatus,
  revealStatus,
} from '../../../actions/statuses';
import { initMuteModal } from '../../../actions/mutes';
import { initBlockModal } from '../../../actions/blocks';
import { initReport } from '../../../actions/reports';
import { openModal } from '../../../actions/modal';
import { defineMessages, injectIntl } from 'react-intl';
import { boostModal, deleteModal } from '../../../initial_state';
import { showAlertForError } from '../../../actions/alerts';

const messages = defineMessages({
  deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
  deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
  redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
});

const makeMapStateToProps = () => {
  const getStatus = makeGetStatus();
  const getPictureInPicture = makeGetPictureInPicture();

  const mapStateToProps = (state, props) => ({
    status: getStatus(state, props),
    domain: state.getIn(['meta', 'domain']),
    pictureInPicture: getPictureInPicture(state, props),
  });

  return mapStateToProps;
};

const mapDispatchToProps = (dispatch, { intl }) => ({

  onReply (status, router) {
    dispatch((_, getState) => {
      let state = getState();
      if (state.getIn(['compose', 'text']).trim().length !== 0) {
        dispatch(openModal('CONFIRM', {
          message: intl.formatMessage(messages.replyMessage),
          confirm: intl.formatMessage(messages.replyConfirm),
          onConfirm: () => dispatch(replyCompose(status, router)),
        }));
      } else {
        dispatch(replyCompose(status, router));
      }
    });
  },

  onModalReblog (status) {
    dispatch(reblog(status));
  },

  onReblog (status, e) {
    if (status.get('reblogged')) {
      dispatch(unreblog(status));
    } else {
      if (e.shiftKey || !boostModal) {
        this.onModalReblog(status);
      } else {
        dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
      }
    }
  },

  onFavourite (status) {
    if (status.get('favourited')) {
      dispatch(unfavourite(status));
    } else {
      dispatch(favourite(status));
    }
  },

  onPin (status) {
    if (status.get('pinned')) {
      dispatch(unpin(status));
    } else {
      dispatch(pin(status));
    }
  },

  onEmbed (status) {
    dispatch(openModal('EMBED', {
      url: status.get('url'),
      onError: error => dispatch(showAlertForError(error)),
    }));
  },

  onDelete (status, history, withRedraft = false) {
    if (!deleteModal) {
      dispatch(deleteStatus(status.get('id'), history, withRedraft));
    } else {
      dispatch(openModal('CONFIRM', {
        message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
        confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
        onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
      }));
    }
  },

  onDirect (account, router) {
    dispatch(directCompose(account, router));
  },

  onMention (account, router) {
    dispatch(mentionCompose(account, router));
  },

  onOpenMedia (media, index) {
    dispatch(openModal('MEDIA', { media, index }));
  },

  onOpenVideo (media, options) {
    dispatch(openModal('VIDEO', { media, options }));
  },

  onBlock (status) {
    const account = status.get('account');
    dispatch(initBlockModal(account));
  },

  onReport (status) {
    dispatch(initReport(status.get('account'), status));
  },

  onMute (account) {
    dispatch(initMuteModal(account));
  },

  onMuteConversation (status) {
    if (status.get('muted')) {
      dispatch(unmuteStatus(status.get('id')));
    } else {
      dispatch(muteStatus(status.get('id')));
    }
  },

  onToggleHidden (status) {
    if (status.get('hidden')) {
      dispatch(revealStatus(status.get('id')));
    } else {
      dispatch(hideStatus(status.get('id')));
    }
  },

});

export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));
<|endoftext|>