<commit_before>// React
import { assign } from 'lodash';
import classNames from 'classnames';
import Highlight from 'react-highlight';
import { List } from 'immutable';
import ImmutableHelper from 'utils/helpers/immutable';
import React from 'react';

// Carbon
import Checkbox from 'components/checkbox';
import Dropdown from 'components/dropdown';
import Form from 'components/form';
import Textarea from 'components/textarea';
import Textbox from 'components/textbox';

import OptionsHelper from 'utils/helpers/options-helper';

// App Components
import ComponentCodeBuilder from './../../../utils/component-code-builder';
import PageContentArea from '../../page-sections/page-content-area';

// Flux
import ComponentActions from '../../../actions/component';

/**
 * Simple, site wrapped content area that loads a heading
 *
 * @param {object} props
 * @param {String} props.title
 * @return {PageContentArea}
 */
export default props => (
  <PageContentArea
    title='Preview'
    link={ `https://github.com/Sage/carbon/tree/master/src/components/${props.definition.get('key')}` }
  >
    <div className= { `component-preview component-preview--${props.definition.get('key')}` }>

        { buildPreview(props) }

        <form className='component-preview__controls'>
          { buildFields(props) }
        </form>

          { buildCode(props) }

  </PageContentArea>
);

function getProps(props) {
  props = props.toJS();
  for (let key in props) {
    let prop = props[key];
    if (prop.immutable) {
      props[key] = ImmutableHelper.parseJSON(prop.value);
    }
  }
  return props;
}

/**
 * builds code output
 *
 * @private
 * @method buildCode
 * @param {Object} def - definition
 * @return {String} code string
 */
const buildCode = (props) => {
  let count = getCount(props.definition.get('demoRenderCount')),
      codeString = '',
      i = 0;

  for (; i < count; i ++) {
    let children = null,
        codeObj = new ComponentCodeBuilder(props.definition.getIn(['text', 'name']));

    props.definition.get('demoProps').forEach((prop, key) => {
      if (key === "children") {
        children = prop;
      } else {
        let value;

        if (typeof prop === "function") {
          value = `{ Actions.${key} }`;
        } else {
          value = prop.toJS ? prop.toJS() : prop;
        }

        codeObj.addProp(key, value);
      }
    });

    if (children) {
      children.toJS
        ? codeObj.addChild(children.toJS())
        : codeObj.addChild(children);
    }

    codeString += codeObj.toString();

    if (i < count-1) {
      codeString += '\n\n';
    }
  }

  return codeString;
}

/**
 * builds fields for dynamically editing props
 *
 * @private
 * @method buildCode
 * @param {Object} def - definition
 * @return {String} code string
 */
const buildFields = (props) => {
  let fieldObj = [];

  // get the props
  let demoProps = props.definition.get('demoProps');

  demoProps.forEach((demoPropData, propKey) => {
    let propOptions = props.definition.get('propOptions')
      ? props.definition.getIn(['propOptions', propKey])
      : null;

    if (showProp(propKey, demoPropData)) {
      if (List.isList(demoPropData)) {
        demoPropData.map((field, i) => {
          fieldObj.push(fieldComponent(null, propKey, field, props.name, i));
        })
      } else {
        fieldObj.push(fieldComponent(propOptions, propKey, demoPropData, props.name));
      }
    }
  });

  return fieldObj;
}

/**
 * creates a simple field
 *
 * @private
 * @method fieldComponent
 * @param {Object} propOptions
 * @param {string} propKey
 * @param {Object} demoPropData
 * @param {string} name
 * @return {Component}
 */
const fieldComponent = (propOptions, propKey, demoPropData, name, arrayPos = -1) => {
  let key =   arrayPos >= 0 ? `${propKey}-${arrayPos}`   : propKey,
      label = arrayPos >= 0 ? `${propKey} [${arrayPos}]` : propKey;

  let commonfieldProps = {
        key: key,
        label: label,
        onChange: ComponentActions.updateDefinition.bind(this, propKey, name, arrayPos),
        value: demoPropData
      },
      field = chooseField(propOptions, propKey, demoPropData);

  if (field === Dropdown) {
    commonfieldProps = assign(commonfieldProps, { options: getOptions(propOptions) });
  }

  return React.createElement(field, commonfieldProps);
}

/**
 * gets options
 *
 * @private
 * @method getOptions
 * @param {Array} options
 * @return {Array} parsed options for dropdown
 */
const getOptions = (options) => {
  return options.map((option) => {
    return ImmutableHelper.parseJSON({ id: option, name: option });
  });
}

/**
 * uses the propKey to fiugure out what sort of fiewld should be used
 *
 * @private
 * @method chooseField
 * @param {Object} props - full set of props
 * @param {String} propKey - key of the prop for making the choice
 * @return {String} name of the field type to load
 */
const chooseField = (propOptions, propKey, demoPropData) => {
  if (propOptions) {
    return Dropdown;
  }

  if (showChildren(propKey, demoPropData)) {
    return Textarea;
  }

  if (OptionsHelper.commonBooleans().indexOf(propKey) >= 0) {
    return Checkbox;
  }

  if (propKey !== 'children') {
    return Textbox;
  }
}

/**
 * decides whether to show children as a field
 *
 * @private
 * @method showChildren
 * @param {String} propKey -
 * @param {String} demoPropData -
 * @return {Boolean}
 */
const showChildren = (propKey, demoPropData) => {
  return propKey === 'children'
         && ['object','array'].indexOf(typeof demoPropData) < 0;
}

/**
 * returns true if this prop should be shown - functions and some awkward props can be skipped this way from the form
 *
 * @private
 * @method showProp
 * @param {String} propKey - key of the prop for searching the Options array
 * @return {Boolean}
 */
const showProp = (propKey, demoPropData) => {
  return OptionsHelper.nonDemoFormProps().indexOf(propKey) === -1
         && OptionsHelper.commonEvents().indexOf(propKey) === -1;
}

/**
 * builds the preview components, looping if needed
 *
 * @private
 * @method buildPreview
 * @param {Object} props - formatted as an object for processing, will contain various props
 * @return {Array} of components
 */
const buildPreview = (props) => {
  let components = [],
      count = getCount(props.definition.get('demoRenderCount')),
      i = 0;

  for (; i < count; i ++) {
    let def = props.definition;
    def = def.setIn(['demoProps', 'key'], `child-${i}`);
    components.push(React.createElement(props.definition.get('component'), getProps(def.get('demoProps'))));
  }

  return components;
}

/**
 * returns 1 or the count
 *
 * @private
 * @method getCount
 * @param {Number} count - could be undefined
 * @return {Number} guaranteed integer
 */
const getCount = (count) => {
  return typeof count === 'undefined' ? 1 : count;
}
<commit_msg>demo site - fixes the child components after array change<commit_after>// React
import { assign } from 'lodash';
import classNames from 'classnames';
import Highlight from 'react-highlight';
import { List } from 'immutable';
import ImmutableHelper from 'utils/helpers/immutable';
import React from 'react';

// Carbon
import Checkbox from 'components/checkbox';
import Dropdown from 'components/dropdown';
import Form from 'components/form';
import Textarea from 'components/textarea';
import Textbox from 'components/textbox';

import OptionsHelper from 'utils/helpers/options-helper';

// App Components
import ComponentCodeBuilder from './../../../utils/component-code-builder';
import PageContentArea from '../../page-sections/page-content-area';

// Flux
import ComponentActions from '../../../actions/component';

/**
 * Simple, site wrapped content area that loads a heading
 *
 * @param {object} props
 * @param {String} props.title
 * @return {PageContentArea}
 */
export default props => (
  <PageContentArea
    title='Preview'
    link={ `https://github.com/Sage/carbon/tree/master/src/components/${props.definition.get('key')}` }
  >
    <div className= { `component-preview component-preview--${props.definition.get('key')}` }>

        { buildPreview(props) }

        <form className='component-preview__controls'>
          { buildFields(props) }
        </form>

          { buildCode(props) }

  </PageContentArea>
);

function getProps(props) {
  props = props.toJS();
  for (let key in props) {
    let prop = props[key];
    if (prop.immutable) {
      props[key] = ImmutableHelper.parseJSON(prop.value);
    }
  }
  return props;
}

/**
 * builds code output
 *
 * @private
 * @method buildCode
 * @param {Object} def - definition
 * @return {String} code string
 */
const buildCode = (props) => {
  let count = getCount(props.definition.get('demoRenderCount')),
      codeString = '',
      i = 0;

  for (; i < count; i ++) {
    let children = null,
        codeObj = new ComponentCodeBuilder(props.definition.getIn(['text', 'name']));

    props.definition.get('demoProps').forEach((prop, key) => {
      if (key === "children") {
        children = prop;
      } else {
        let value;

        if (typeof prop === "function") {
          value = `{ Actions.${key} }`;
        } else {
          value = prop.toJS ? prop.toJS() : prop;
        }

        codeObj.addProp(key, value);
      }
    });

    if (children) {
      children.toJS
        ? codeObj.addChild(children.toJS())
        : codeObj.addChild(children);
    }

    codeString += codeObj.toString();

    if (i < count-1) {
      codeString += '\n\n';
    }
  }

  return codeString;
}

/**
 * builds fields for dynamically editing props
 *
 * @private
 * @method buildCode
 * @param {Object} def - definition
 * @return {String} code string
 */
const buildFields = (props) => {
  let fieldObj = [];

  // get the props
  let demoProps = props.definition.get('demoProps');

  demoProps.forEach((demoPropData, propKey) => {
    let propOptions = props.definition.get('propOptions')
      ? props.definition.getIn(['propOptions', propKey])
      : null;

    if (showProp(propKey, demoPropData)) {
      if (List.isList(demoPropData)) {
        demoPropData.map((field, i) => {
          fieldObj.push(fieldComponent(null, propKey, field, props.name, i));
        })
      } else {
        fieldObj.push(fieldComponent(propOptions, propKey, demoPropData, props.name));
      }
    }
  });

  return fieldObj;
}

/**
 * creates a simple field
 *
 * @private
 * @method fieldComponent
 * @param {Object} propOptions
 * @param {string} propKey
 * @param {Object} demoPropData
 * @param {string} name
 * @return {Component}
 */
const fieldComponent = (propOptions, propKey, demoPropData, name, arrayPos = -1) => {
  let key =   arrayPos >= 0 ? `${propKey}-${arrayPos}`   : propKey,
      label = arrayPos >= 0 ? `${propKey} [${arrayPos}]` : propKey;

  let commonfieldProps = {
        key: key,
        label: label,
        onChange: ComponentActions.updateDefinition.bind(this, propKey, name, arrayPos),
        value: demoPropData
      },
      field = chooseField(propOptions, propKey, demoPropData);

  if (field === Dropdown) {
    commonfieldProps = assign(commonfieldProps, { options: getOptions(propOptions) });
  }

  if (field) {
    return React.createElement(field, commonfieldProps);
  }
}

/**
 * gets options
 *
 * @private
 * @method getOptions
 * @param {Array} options
 * @return {Array} parsed options for dropdown
 */
const getOptions = (options) => {
  return options.map((option) => {
    return ImmutableHelper.parseJSON({ id: option, name: option });
  });
}

/**
 * uses the propKey to fiugure out what sort of fiewld should be used
 *
 * @private
 * @method chooseField
 * @param {Object} props - full set of props
 * @param {String} propKey - key of the prop for making the choice
 * @return {String} name of the field type to load
 */
const chooseField = (propOptions, propKey, demoPropData) => {
  if (propOptions) {
    return Dropdown;
  }

  if (showChildren(propKey, demoPropData)) {
    return Textarea;
  }

  if (OptionsHelper.commonBooleans().indexOf(propKey) >= 0) {
    return Checkbox;
  }

  if (propKey !== 'children') {
    return Textbox;
  }
}

/**
 * decides whether to show children as a field
 *
 * @private
 * @method showChildren
 * @param {String} propKey -
 * @param {String} demoPropData -
 * @return {Boolean}
 */
const showChildren = (propKey, demoPropData) => {
  return propKey === 'children'
         && ['object','array'].indexOf(typeof demoPropData) < 0;
}

/**
 * returns true if this prop should be shown - functions and some awkward props can be skipped this way from the form
 *
 * @private
 * @method showProp
 * @param {String} propKey - key of the prop for searching the Options array
 * @return {Boolean}
 */
const showProp = (propKey, demoPropData) => {
  return OptionsHelper.nonDemoFormProps().indexOf(propKey) === -1
         && OptionsHelper.commonEvents().indexOf(propKey) === -1;
}

/**
 * builds the preview components, looping if needed
 *
 * @private
 * @method buildPreview
 * @param {Object} props - formatted as an object for processing, will contain various props
 * @return {Array} of components
 */
const buildPreview = (props) => {
  let components = [],
      count = getCount(props.definition.get('demoRenderCount')),
      i = 0;

  for (; i < count; i ++) {
    let def = props.definition;
    def = def.setIn(['demoProps', 'key'], `child-${i}`);
    components.push(React.createElement(props.definition.get('component'), getProps(def.get('demoProps'))));
  }

  return components;
}

/**
 * returns 1 or the count
 *
 * @private
 * @method getCount
 * @param {Number} count - could be undefined
 * @return {Number} guaranteed integer
 */
const getCount = (count) => {
  return typeof count === 'undefined' ? 1 : count;
}
<|endoftext|>


<commit_before>import React from 'react';
import classNames from 'classnames';

import SyncIcon from './icons/SyncIcon';
import FeedbackIcon from './icons/FeedbackIcon';

import { formatFooterTime } from '../utils';

import { SURVEY_PATH } from '../constants';

class Footer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      savingIndicatorText: '',
      syncingIndicatorText: '',
      waitingToReconnect: false,
      isAuthenticated: false,
      lastModified: Date.now(),
      isKintoLoaded: false
    };
    this.loginTimeout = null;

    this.events = eventData => {
      // let content;
      switch (eventData.action) {
        case 'sync-authenticated':
          clearTimeout(this.loginTimeout);

          this.setState({
            isAuthenticated: true,
            waitingToReconnect: false,
            syncingInProcess: true,
            syncingIndicatorText: browser.i18n.getMessage('syncProgress')
          });

          // set title attr of footer to the currently logged in account
          this.footerbuttons.title = eventData.profile && eventData.profile.email;

          browser.runtime.sendMessage({
            action: 'kinto-sync'
          });
          break;
        case 'kinto-loaded':
          clearTimeout(this.loginTimeout);
          // Switch to Date.now() to show when we pulled notes instead of 'eventData.last_modified'
          this.setState({
            lastModified: Date.now(),
            syncingInProcess: false,
            isKintoLoaded: true
          });

          // Force refresh on Material Design Lite library to activate mdl-menu
          componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef

          this.getLastSyncedTime();
          break;
        case 'text-change':
          browser.runtime.sendMessage({
            action: 'kinto-load'
          });
          break;
        case 'text-syncing':
          this.setState({
            syncingIndicatorText: browser.i18n.getMessage('syncProgress'),
            syncingInProcess: true
          });
          // Disable sync-action
          break;
        case 'text-editing':
          if (this.state.isAuthenticated) {
            this.setState({
              syncingInProcess: true
            });
          }
          if (!this.state.waitingToReconnect) {
            this.setState({
              savingIndicatorText: browser.i18n.getMessage('savingChanges')
            });
          }
          // Disable sync-action
          this.setState({
            editingInProcess: true
          });
          break;
        case 'text-synced':
          // Enable sync-action
          this.setState({
            lastModified: eventData.last_modified,
            syncingInProcess: false,
            isLoggingIn: false
          });
          this.getLastSyncedTime();
          break;
        case 'text-saved':
          if (!this.state.waitingToReconnect) {
            // persist reconnect warning, do not override with the 'saved at'
            this.setState({
              savingIndicatorText: browser.i18n.getMessage(
                'savedComplete2',
                formatFooterTime()
              )
            });
          }
          // Enable sync-action
          this.setState({
            editingInProcess: false,
            syncingInProcess: false
          });
          break;
        case 'reconnect':
          clearTimeout(this.loginTimeout);

          this.setState({
            waitingToReconnect: true,
            isAuthenticated: false,
            syncingIndicatorText: browser.i18n.getMessage('reconnectSync'),
            syncingInProcess: false
          });

          chrome.runtime.sendMessage({
            action: 'metrics-reconnect-sync'
          });
          break;
        case 'disconnected':
          this.setState({
            isAuthenticated: false
          });
          this.getLastSyncedTime();
          break;
      }
    };

    this.getLastSyncedTime = () => {
      if (this.state.waitingToReconnect) {
        // persist reconnect warning, do not override with the 'saved at'
        return;
      }

      if (this.state.isAuthenticated) {
        this.setState({
          syncingIndicatorText: browser.i18n.getMessage(
            'syncComplete2',
            formatFooterTime(this.state.lastModified)
          ),
          isAuthenticated: true
        });

      } else {
        this.setState({
          savingIndicatorText: browser.i18n.getMessage(
            'savedComplete2',
            formatFooterTime(this.state.lastModified)
          )
        });
      }
    };

    this.disconnectFromSync = () => {
      this.setState({
        waitingToReconnect: false,
        isAuthenticated: false,
        isLoggingIn: false,
        syncingIndicatorText: browser.i18n.getMessage('disconnected')
      });

      setTimeout(() => {
        this.getLastSyncedTime();
      }, 2000);

      browser.runtime.sendMessage('notes@mozilla.com', {
        action: 'disconnected'
      });
    };

    this.enableSyncAction = () => {
      if (this.state.editingInProcess || this.state.syncingInProcess) {
        return;
      }

      if (this.state.isAuthenticated) {
        // Trigger manual sync
        this.setState({
          syncingInProcess: true
        });
        browser.runtime.sendMessage({
          action: 'kinto-sync'
        });

      } else if (!this.state.isAuthenticated || this.state.waitingToReconnect) {
        // Login
        this.setState({
          isLoggingIn: true,
          syncingIndicatorText: browser.i18n.getMessage('openingLogin')
        });

        const that = this;
        this.loginTimeout = setTimeout(() => {
          that.setState({
            syncingIndicatorText: browser.i18n.getMessage('pleaseLogin'),
            waitingToReconnect: true,
            isLoggingIn: false
          });
        }, 5000);

        // Problem not having editor in Footer Component
        browser.runtime.sendMessage({
          action: 'authenticate'
        });
      }

      this.setState({
        waitingToReconnect: false
      });
    };

    this.giveFeedbackCallback = (e) => {
      e.preventDefault();
      browser.tabs.create({
        url: SURVEY_PATH
      });
    };
  }

  componentDidMount() {
    browser.storage.local.get('credentials').then(data => {
      if (data.hasOwnProperty('credentials')) {
        this.setState({
          isAuthenticated: true
        });
      }
    });

    this.getLastSyncedTime();
    chrome.runtime.onMessage.addListener(this.events);
  }

  componentWillUnmount() {
    chrome.runtime.onMessage.removeListener(this.events);
  }

  render() {

    if (!this.state.isKintoLoaded) {
      return '';
    }

    const isSyncing = this.state.waitingToReconnect || this.state.isAuthenticated || this.state.isLoggingIn;
    // Those classes define animation state on #footer-buttons
    const footerClass = classNames({
       savingLayout: !isSyncing,
       syncingLayout: isSyncing,
       warning: this.state.waitingToReconnect,
       animateSyncIcon: (this.state.isLoggingIn || this.state.syncingInProcess) && !this.state.waitingToReconnect
    });

    return (

 this.footerbuttons = footerbuttons}
          className={footerClass}>

{this.state.savingIndicatorText}

            <button
              id="enable-sync"
              onClick={() => this.enableSyncAction()}
              className="notsyncing">
              <SyncIcon />
            </button>
            <button
              id="syncing-indicator"
              onClick={() => this.enableSyncAction()}>
              {this.state.syncingIndicatorText}
            </button>

            <FeedbackIcon />

            <button id="context-menu-button"
              className="mdl-js-button" />

                <button
                  className="mdl-menu__item context-menu-item"
                  style={{ width: '100%' }}
                  onClick={() => this.disconnectFromSync()}
                >
                  {browser.i18n.getMessage('disableSync')}
                </button>

                  { browser.i18n.getMessage('feedback') }

    );
  }
}

export default Footer;
<commit_msg>Fix the wording of Synced at changed too<commit_after>import React from 'react';
import classNames from 'classnames';

import SyncIcon from './icons/SyncIcon';
import FeedbackIcon from './icons/FeedbackIcon';

import { formatFooterTime } from '../utils';

import { SURVEY_PATH } from '../constants';

class Footer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      savingIndicatorText: '',
      syncingIndicatorText: '',
      waitingToReconnect: false,
      isAuthenticated: false,
      lastModified: Date.now(),
      isKintoLoaded: false
    };
    this.loginTimeout = null;

    this.events = eventData => {
      // let content;
      switch (eventData.action) {
        case 'sync-authenticated':
          clearTimeout(this.loginTimeout);

          this.setState({
            isAuthenticated: true,
            waitingToReconnect: false,
            syncingInProcess: true,
            syncingIndicatorText: browser.i18n.getMessage('syncProgress')
          });

          // set title attr of footer to the currently logged in account
          this.footerbuttons.title = eventData.profile && eventData.profile.email;

          browser.runtime.sendMessage({
            action: 'kinto-sync'
          });
          break;
        case 'kinto-loaded':
          clearTimeout(this.loginTimeout);
          // Switch to Date.now() to show when we pulled notes instead of 'eventData.last_modified'
          this.setState({
            lastModified: Date.now(),
            syncingInProcess: false,
            isKintoLoaded: true
          });

          // Force refresh on Material Design Lite library to activate mdl-menu
          componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef

          this.getLastSyncedTime();
          break;
        case 'text-change':
          browser.runtime.sendMessage({
            action: 'kinto-load'
          });
          break;
        case 'text-syncing':
          this.setState({
            syncingIndicatorText: browser.i18n.getMessage('syncProgress'),
            syncingInProcess: true
          });
          // Disable sync-action
          break;
        case 'text-editing':
          if (this.state.isAuthenticated) {
            this.setState({
              syncingInProcess: true
            });
          }
          if (!this.state.waitingToReconnect) {
            this.setState({
              savingIndicatorText: browser.i18n.getMessage('savingChanges')
            });
          }
          // Disable sync-action
          this.setState({
            editingInProcess: true
          });
          break;
        case 'text-synced':
          // Enable sync-action
          this.setState({
            lastModified: eventData.last_modified,
            syncingInProcess: false,
            isLoggingIn: false
          });
          this.getLastSyncedTime();
          break;
        case 'text-saved':
          if (!this.state.waitingToReconnect) {
            // persist reconnect warning, do not override with the 'saved at'
            this.setState({
              savingIndicatorText: browser.i18n.getMessage(
                'savedComplete2',
                formatFooterTime()
              )
            });
          }
          // Enable sync-action
          this.setState({
            editingInProcess: false,
            syncingInProcess: false
          });
          break;
        case 'reconnect':
          clearTimeout(this.loginTimeout);

          this.setState({
            waitingToReconnect: true,
            isAuthenticated: false,
            syncingIndicatorText: browser.i18n.getMessage('reconnectSync'),
            syncingInProcess: false
          });

          chrome.runtime.sendMessage({
            action: 'metrics-reconnect-sync'
          });
          break;
        case 'disconnected':
          this.setState({
            isAuthenticated: false
          });
          this.getLastSyncedTime();
          break;
      }
    };

    this.getLastSyncedTime = () => {
      if (this.state.waitingToReconnect) {
        // persist reconnect warning, do not override with the 'saved at'
        return;
      }

      if (this.state.isAuthenticated) {
        this.setState({
          syncingIndicatorText: browser.i18n.getMessage(
            'syncComplete3',
            formatFooterTime(this.state.lastModified)
          ),
          isAuthenticated: true
        });

      } else {
        this.setState({
          savingIndicatorText: browser.i18n.getMessage(
            'changesSaved',
            formatFooterTime(this.state.lastModified)
          )
        });
      }
    };

    this.disconnectFromSync = () => {
      this.setState({
        waitingToReconnect: false,
        isAuthenticated: false,
        isLoggingIn: false,
        syncingIndicatorText: browser.i18n.getMessage('disconnected')
      });

      setTimeout(() => {
        this.getLastSyncedTime();
      }, 2000);

      browser.runtime.sendMessage('notes@mozilla.com', {
        action: 'disconnected'
      });
    };

    this.enableSyncAction = () => {
      if (this.state.editingInProcess || this.state.syncingInProcess) {
        return;
      }

      if (this.state.isAuthenticated) {
        // Trigger manual sync
        this.setState({
          syncingInProcess: true
        });
        browser.runtime.sendMessage({
          action: 'kinto-sync'
        });

      } else if (!this.state.isAuthenticated || this.state.waitingToReconnect) {
        // Login
        this.setState({
          isLoggingIn: true,
          syncingIndicatorText: browser.i18n.getMessage('openingLogin')
        });

        const that = this;
        this.loginTimeout = setTimeout(() => {
          that.setState({
            syncingIndicatorText: browser.i18n.getMessage('pleaseLogin'),
            waitingToReconnect: true,
            isLoggingIn: false
          });
        }, 5000);

        // Problem not having editor in Footer Component
        browser.runtime.sendMessage({
          action: 'authenticate'
        });
      }

      this.setState({
        waitingToReconnect: false
      });
    };

    this.giveFeedbackCallback = (e) => {
      e.preventDefault();
      browser.tabs.create({
        url: SURVEY_PATH
      });
    };
  }

  componentDidMount() {
    browser.storage.local.get('credentials').then(data => {
      if (data.hasOwnProperty('credentials')) {
        this.setState({
          isAuthenticated: true
        });
      }
    });

    this.getLastSyncedTime();
    chrome.runtime.onMessage.addListener(this.events);
  }

  componentWillUnmount() {
    chrome.runtime.onMessage.removeListener(this.events);
  }

  render() {

    if (!this.state.isKintoLoaded) {
      return '';
    }

    const isSyncing = this.state.waitingToReconnect || this.state.isAuthenticated || this.state.isLoggingIn;
    // Those classes define animation state on #footer-buttons
    const footerClass = classNames({
       savingLayout: !isSyncing,
       syncingLayout: isSyncing,
       warning: this.state.waitingToReconnect,
       animateSyncIcon: (this.state.isLoggingIn || this.state.syncingInProcess) && !this.state.waitingToReconnect
    });

    return (

 this.footerbuttons = footerbuttons}
          className={footerClass}>

{this.state.savingIndicatorText}

            <button
              id="enable-sync"
              onClick={() => this.enableSyncAction()}
              className="notsyncing">
              <SyncIcon />
            </button>
            <button
              id="syncing-indicator"
              onClick={() => this.enableSyncAction()}>
              {this.state.syncingIndicatorText}
            </button>

            <FeedbackIcon />

            <button id="context-menu-button"
              className="mdl-js-button" />

                <button
                  className="mdl-menu__item context-menu-item"
                  style={{ width: '100%' }}
                  onClick={() => this.disconnectFromSync()}
                >
                  {browser.i18n.getMessage('disableSync')}
                </button>

                  { browser.i18n.getMessage('feedback') }

    );
  }
}

export default Footer;
<|endoftext|>


<commit_before>'use strict'

const webDriver = require('selenium-webdriver')
let driver

const defaults  = Object.freeze({
  defaultTimeout: 10000,
  getDriver: () => driver || (driver = new webDriver.Builder()
      .withCapabilities(webDriver.Capabilities.firefox())
      .build())
})

/** --------------------- module exports ------------------------ **/

module.exports = defaults
<commit_msg>Clean-up for standard<commit_after>'use strict'

const webDriver = require('selenium-webdriver')
let driver

const defaults = Object.freeze({
  defaultTimeout: 10000,
  getDriver: () => driver || (driver = new webDriver.Builder()
      .withCapabilities(webDriver.Capabilities.firefox())
      .build())
})

/** --------------------- module exports ------------------------ **/

module.exports = defaults
<|endoftext|>


<commit_before>'use strict';
var nyg = require('nyg');
console.log('poopy pants');

var prompts = [
  {
    'type': 'confirm',
    'name': 'useDefaultDir',
    'message': 'Would you like to specify a default directory for generated modules? You can override this later. (y/n)',
    'default': true,
    'filter': function(input){
      if(input === 'y') return 'yes';
      if(input === 'n') return 'no';
    },
    'validate': function(input){
      return true
    }
  }
];

var gen = nyg(prompts,[])
  .on('postprompt',function() {
    var done = gen.async();

    /*gen.config.set('name', dequote(gen.config.get('name')));
    gen.config.set('testDescription', escape(gen.config.get('description')).replace(/\\"+/g, '\"'));
    gen.config.set('description', dequote(gen.config.get('description')));
    gen.config.set('varName', varName(gen.config.get('name')));
    gen.config.set('tags', JSON.stringify(gen.config.get('tags').split(' ').map(function(str) {
      return dequote(str).trim();
    }).filter(Boolean), null, 2));

    gen.config.set('devDependencies', '{\n    "tape": "*"\n  }');*/
  })
  .run();<commit_msg>stuff<commit_after>'use strict';
var nyg = require('nyg');
console.log('poopy pants');

var prompts = [
  {
    'type': 'confirm',
    'name': 'useDefaultDir',
    'message': 'Would you like to specify a default directory for generated modules? You can override this later.',
    'default': true,
    'filter': function(input){
      if(input === 'y') return 'yes';
      if(input === 'n') return 'no';
    },
    'validate': function(input){
      input = input.toLowerCase();
      if(input !== 'y' || input !== 'yes') return false;
      if(input !== 'n' || input !== 'no') return false;
      return true
    }
  }
];

var gen = nyg(prompts,[])
  .on('postprompt',function() {
    var done = gen.async();

    /*gen.config.set('name', dequote(gen.config.get('name')));
    gen.config.set('testDescription', escape(gen.config.get('description')).replace(/\\"+/g, '\"'));
    gen.config.set('description', dequote(gen.config.get('description')));
    gen.config.set('varName', varName(gen.config.get('name')));
    gen.config.set('tags', JSON.stringify(gen.config.get('tags').split(' ').map(function(str) {
      return dequote(str).trim();
    }).filter(Boolean), null, 2));

    gen.config.set('devDependencies', '{\n    "tape": "*"\n  }');*/
  })
  .run();<|endoftext|>


<commit_before>/* -*- mode: espresso; espresso-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set softtabstop=2 shiftwidth=2 tabstop=2 expandtab: */

"use strict";

var NeuronAnnotations = function()
{
  this.widgetID = this.registerInstance();
  this.registerSource();

  this.nextFieldID = 1;    // unique ID for annotation fields added by the "+" button
  this.queryResults = [];
  this.pid = project.id;
};

NeuronAnnotations.prototype = {};
$.extend(NeuronAnnotations.prototype, new InstanceRegistry());
$.extend(NeuronAnnotations.prototype, new SkeletonSource());

/* Implement interfaces */

NeuronAnnotations.prototype.getName = function()
{
    return "Neuron Search " + this.widgetID;
};

NeuronAnnotations.prototype.destroy = function()
{
  this.unregisterInstance();
  this.unregisterSource();
};

NeuronAnnotations.prototype.append = function() {};
NeuronAnnotations.prototype.clear = function(source_chain) {};
NeuronAnnotations.prototype.removeSkeletons = function() {};
NeuronAnnotations.prototype.updateModels = function() {};

NeuronAnnotations.prototype.getSelectedSkeletons = function() {
  return this.get_selected_neurons().reduce( function(o, e) {
    if (e.type === 'neuron') {
      o = o.concat(e.skeleton_ids);
    }
    return o;
  }, []);
};

NeuronAnnotations.prototype.hasSkeleton = function(skeleton_id) {
  return this.queryResults.some(function(e) {
    return e.type === 'neuron' && e.skeleton_ids.some(function(id) {
      return id === skeleton_id;
    });
  });
};

NeuronAnnotations.prototype.getSelectedSkeletonModels = function() {
  return this.get_selected_neurons().reduce(function(o, e) {
    if (e.type === 'neuron') {
      e.skeleton_ids.forEach(function(s) {
        o[s] = new SelectionTable.prototype.SkeletonModel(
            s, e.name, new THREE.Color().setRGB(1, 1, 0));
      });
    }
    return o;
  }, {});
};

NeuronAnnotations.prototype.highlight = function(skeleton_id)
{
  // Find neuron containing this skeleton_id
  var neurons = this.queryResults.filter(function(e) {
    if (e.type == 'neuron') {
      return e.skeleton_ids.some(function(s) {
        return s == skeleton_id;
      });
    } else {
      return false;
    }
  });

  if (neurons) {
    // Remove any highlighting
    $('[class^=neuron_annotation_result_row' + this.widgetID + '_]').css(
        'background-color', 'white');
    // Highlight the neuron, containing the requested skeleton, if available.
    // Altough the code works for multiple neurons, it should be normally the
    // case that there is only one neuron, belonging to the skeleton.
    neurons.forEach($.proxy(function(n) {
      $('.neuron_annotation_result_row' + this.widgetID + '_' + n.id).css(
          'background-color', SelectionTable.prototype.highlighting_color);
    }, this));
  }
};

/* Non-interface methods */

/**
 * Create a table row and passes it to add_row_fn which should it add it
 * whereever it wants. The third parameter specifies the number of indentation
 * steps that should be used.
 */
NeuronAnnotations.prototype.add_result_table_row = function(entity, add_row_fn,
    indent)
{
  // Build table row
  var tr = document.createElement('tr');
  tr.setAttribute('class', 'neuron_annotation_result_row' +
          this.widgetID + '_' + entity.id);
  tr.setAttribute('type', entity.type);

  // Checkbox & name column, potentially indented
  var td_cb = document.createElement('td');
  var div_cb = document.createElement('div');
  // Make sure the line isn't wrapped
  div_cb.style.whiteSpace = 'nowrap';
  // Add indentation
  div_cb.style.marginLeft = indent * 1.5 + 'em';
  var cb = document.createElement('input');
  cb.setAttribute('type', 'checkbox');
  cb.setAttribute('class', 'result' + this.widgetID + '_' +
          entity.id);
  div_cb.appendChild(cb);
  var a = document.createElement('a');
  a.setAttribute('href', '#');
  a.appendChild(document.createTextNode(entity.name));
  div_cb.appendChild(a);
  td_cb.appendChild(div_cb);
  tr.appendChild(td_cb);

  // Type column
  var td_type = document.createElement('td');
  td_type.appendChild(document.createTextNode(
          entity.type));
  tr.appendChild(td_type);

  // Annotations column
  var td_ann = document.createElement('td');
  // Build list of annotations and use layout of jQuery tagbox
  var ul = entity.annotations.reduce(
    function(o, e) {
      var li = document.createElement('li');
      li.setAttribute('title', 'Remove annotation');
      li.setAttribute('class', 'remove_annotation');
      li.setAttribute('neuron_id', entity.id);
      li.setAttribute('annotation_id', e.id);
      li.appendChild(document.createTextNode(e.name));
      o.appendChild(li);
      return o;
    }, document.createElement('ul'));
  ul.setAttribute('class', 'tagEditor');
  td_ann.appendChild(ul);
  tr.appendChild(td_ann);

  // Add row to table
  add_row_fn(tr);

  // Wire up handlers
  if (entity.type == 'neuron') {
    var create_handler = function(skid) {
      return function() {
        TracingTool.goToNearestInNeuronOrSkeleton( 'skeleton', skid );
      }
    }
    // Go to nearest
    if (entity.skeleton_ids.length > 0) {
      $(a).click(create_handler(entity.skeleton_ids[0]));
    } else {
      $(a).click(function() { alert("No skeleton found!"); });
    }
  } else if (entity.type == 'annotation') {
    // Add annotation attribute to link
    a.setAttribute('annotation', entity.name);
    // Expand
    var self = this;
    $(a).click(function() {
      // If expanded, collapse it. Expand it otherwise.
      if ($(this).is('[expanded]')) {
        // TODO: Collapse it
      } else {
        // Mark link expanded
        this.setAttribute('expanded', 'true');
        // Request entities that are annotated with this annotation
        // and replace the clicked on annotation with the result.
        var query_data = {
          'neuron_query_by_annotation': $(this).attr('annotation'),
        };
        requestQueue.register(django_url + project.id + '/neuron/query-by-annotations',
            'POST', query_data, function(status, text, xml) {
              if (status === 200) {
                var e = $.parseJSON(text);
                if (e.error) {
                  alert(e.error);
                } else {
                  //Append new content right after the current node
                  var appender = function(new_tr) {
                    $(tr).after(new_tr)
                  };

                  e.forEach((function(entity) {
                    self.add_result_table_row(entity, appender, indent + 1);
                  }).bind(self));

                  // The order of the query result array doesn't matter.
                  // It is therefore possible to just append the new results.
                  self.queryResults = self.queryResults.concat(e);
                }
              }
        });
      }
    });
  }
  // Add click handlers to remove tags from nodes
  var NA = this;
  $(".remove_annotation", $(ul)).click( function() {
      NA.remove_annotation($(this).attr('neuron_id'),
          $(this).attr('annotation_id'));
  });
};

NeuronAnnotations.prototype.query = function()
{
  var form_data = $('#neuron_query_by_annotations' +
      this.widgetID).serializeArray().reduce(function(o, e) {
        o[e.name] = e.value;
        return o;
      }, {});

  // Here, $.proxy is used to bind 'this' to the anonymous function
  requestQueue.register(django_url + this.pid + '/neuron/query-by-annotations',
      'POST', form_data, $.proxy( function(status, text, xml) {
        if (status === 200) {
          var e = $.parseJSON(text);
          if (e.error) {
            alert(e.error);
          } else {
            var $tableBody = $('#neuron_annotations_query_results' +
                this.widgetID).find('tbody');
            $tableBody.empty();
            this.queryResults = e;
            // create appender function which adds rows to table
            var appender = function(tr) {
              $tableBody.append(tr);
            };
            // Create result table rows
            this.queryResults.forEach((function(entity) {
              this.add_result_table_row(entity, appender, 0);
            }).bind(this));

            // If there are results, display the result table
            if (this.queryResults.length > 0) {
              $('#neuron_annotations_query_no_results' + this.widgetID).hide();
              $('#neuron_annotations_query_results' + this.widgetID).show();
            } else {
              $('#neuron_annotations_query_results' + this.widgetID).hide();
              $('#neuron_annotations_query_no_results' + this.widgetID).show();
            }
          }
        }
      }, this));
};

NeuronAnnotations.prototype.add_query_field = function()
{
  // Create a copy of the first row.
  var $newRow = $("#neuron_query_by_annotation" + this.widgetID).clone();
  $newRow.attr({
      id: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID,
      name: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID
  });

  $newRow.children()[0].innerHTML = 'and:'

  // Update the text field attributes.
  var $text = $newRow.find("input[type='text']");
  $text.attr({
      id: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID,
      name: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID,
      value: ''
  });
  // Add autocompletion to it
  this.add_autocomplete_to_input($text);

  // Update the button attributes.
  var $button = $newRow.find("input[type='button']");
  $button.attr('value', '-');
  $button.click(this.remove_query_field.bind(this, this.nextFieldID));
  $("#neuron_query_by_annotator" + this.widgetID).before($newRow);

  this.nextFieldID += 1;
};

NeuronAnnotations.prototype.remove_query_field = function(rowNum)
{
  var $row = $("#neuron_query_by_annotation" + this.widgetID + "_" + rowNum);
  $row.remove();
};

NeuronAnnotations.prototype.toggle_neuron_selections = function()
{
  var newValue = $("#neuron_annotations_toggle_neuron_selections_checkbox" +
      this.widgetID)[0].checked;
  $("#neuron_annotations_query_results_table" + this.widgetID).find(
      'tbody tr td input[class*=result' + this.widgetID + '_]').each(
          function(i, element) {
            element.checked = newValue;
          });
};

NeuronAnnotations.prototype.get_selected_neurons = function()
{
  var visited = {};
  return this.queryResults.reduce((function(o, e) {
      // Test if one of the checkboxes for a particular neuron is checked
      var is_checked = $("#neuron_annotations_query_results_table" +
          this.widgetID).find('tr[type=neuron]').find('input[class=result' +
              this.widgetID + '_' + e.id + ']').is(':checked');
      // Avoid duplicates if the same neuron is checked multiple times and
      // add it only if not yet present.
      if (is_checked && !(e.id in visited)) {
          o.push(e);
          visited[e.id] = true;
      }
      return o;
    }).bind(this), []);
}

NeuronAnnotations.prototype.prompt_for_annotations = function()
{
  // TODO: prompt for annotations
  var annotation = prompt('Annotation:');
  if (!annotation) return;
  annotation = annotation.trim();
  if (0 === annotation.length) return; // can't annotate with nothing
  return [annotation];
}

NeuronAnnotations.prototype.annotate_neurons_of_skeletons = function(skeleton_ids)
{
  this.annotate(null, skeleton_ids);
};

NeuronAnnotations.prototype.annotate_neurons = function(neuron_ids)
{
  this.annotate(neuron_ids, null);
};

NeuronAnnotations.prototype.annotate = function(neuron_ids, skeleton_ids)
{
  // Get annotation terms
  var annotations = this.prompt_for_annotations();
  if (!annotations) return;
  // Build request data structure
  var data = {
    annotations: annotations,
  };
  if (neuron_ids) {
      data.neuron_ids = neuron_ids;
  }
  if (skeleton_ids) {
      data.skeleton_ids = skeleton_ids;
  }
  // Do request
  requestQueue.register(django_url + project.id + '/neuron/annotate',
      'POST', data, function(status, text, xml) {
        if (status === 200) {
          var e = $.parseJSON(text);
          if (e.error) {
            alert(e.error);
          } else {
            if (annotations.length == 1)
              growlAlert('Information', 'Annotation ' + annotations[0] + ' added.');
            else
              growlAlert('Information', 'Annotations ' + annotations.join(', ') + ' added.');
          }
        }
      });
};

NeuronAnnotations.prototype.remove_annotation = function(neuron_id,
    annotation_id)
{
  if (!confirm('Are you sure you want to remove this annotation?')) {
    return;
  }

  requestQueue.register(django_url + this.pid + '/neuron/' + neuron_id +
      '/annotation/' + annotation_id + '/remove',
      'POST', {}, $.proxy(function(status, text, xml) {
        if (status === 200) {
          var e = $.parseJSON(text);
          if (e.error) {
            alert(e.error);
          } else {
            // Display message returned by the server
            growlAlert('Information', e.message);
            // Remove current annotation from displayed list
            var result_tr = $('#neuron_annotations_query_results' +
                this.widgetID).find('.remove_annotation[neuron_id=' +
                neuron_id + '][annotation_id=' + annotation_id + ']');
            result_tr.fadeOut(1000, function() { $(this).remove(); });
          }
        }
      }, this));
};

NeuronAnnotations.prototype.add_autocomplete_to_input = function(input)
{
  // Get a JSON list with all available annotations and initialize
  // autocompletion for the name field.
  requestQueue.register(django_url + project.id + '/annotations/list',
      'GET', {}, function (status, data, text) {
        var e = $.parseJSON(data);
        if (status !== 200) {
            alert("The server returned an unexpected status (" +
              status + ") " + "with error message:\n" + text);
        } else {
          var annotations = e.map(function(aobj) {
            return aobj.aname;
          });
          $(input).autocomplete({
            source: annotations
          });
        }
      });
};
<commit_msg>Annotations: add options dialog to add new annotations<commit_after>/* -*- mode: espresso; espresso-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set softtabstop=2 shiftwidth=2 tabstop=2 expandtab: */

"use strict";

var NeuronAnnotations = function()
{
  this.widgetID = this.registerInstance();
  this.registerSource();

  this.nextFieldID = 1;    // unique ID for annotation fields added by the "+" button
  this.queryResults = [];
  this.pid = project.id;
};

NeuronAnnotations.prototype = {};
$.extend(NeuronAnnotations.prototype, new InstanceRegistry());
$.extend(NeuronAnnotations.prototype, new SkeletonSource());

/* Implement interfaces */

NeuronAnnotations.prototype.getName = function()
{
    return "Neuron Search " + this.widgetID;
};

NeuronAnnotations.prototype.destroy = function()
{
  this.unregisterInstance();
  this.unregisterSource();
};

NeuronAnnotations.prototype.append = function() {};
NeuronAnnotations.prototype.clear = function(source_chain) {};
NeuronAnnotations.prototype.removeSkeletons = function() {};
NeuronAnnotations.prototype.updateModels = function() {};

NeuronAnnotations.prototype.getSelectedSkeletons = function() {
  return this.get_selected_neurons().reduce( function(o, e) {
    if (e.type === 'neuron') {
      o = o.concat(e.skeleton_ids);
    }
    return o;
  }, []);
};

NeuronAnnotations.prototype.hasSkeleton = function(skeleton_id) {
  return this.queryResults.some(function(e) {
    return e.type === 'neuron' && e.skeleton_ids.some(function(id) {
      return id === skeleton_id;
    });
  });
};

NeuronAnnotations.prototype.getSelectedSkeletonModels = function() {
  return this.get_selected_neurons().reduce(function(o, e) {
    if (e.type === 'neuron') {
      e.skeleton_ids.forEach(function(s) {
        o[s] = new SelectionTable.prototype.SkeletonModel(
            s, e.name, new THREE.Color().setRGB(1, 1, 0));
      });
    }
    return o;
  }, {});
};

NeuronAnnotations.prototype.highlight = function(skeleton_id)
{
  // Find neuron containing this skeleton_id
  var neurons = this.queryResults.filter(function(e) {
    if (e.type == 'neuron') {
      return e.skeleton_ids.some(function(s) {
        return s == skeleton_id;
      });
    } else {
      return false;
    }
  });

  if (neurons) {
    // Remove any highlighting
    $('[class^=neuron_annotation_result_row' + this.widgetID + '_]').css(
        'background-color', 'white');
    // Highlight the neuron, containing the requested skeleton, if available.
    // Altough the code works for multiple neurons, it should be normally the
    // case that there is only one neuron, belonging to the skeleton.
    neurons.forEach($.proxy(function(n) {
      $('.neuron_annotation_result_row' + this.widgetID + '_' + n.id).css(
          'background-color', SelectionTable.prototype.highlighting_color);
    }, this));
  }
};

/* Non-interface methods */

/**
 * Create a table row and passes it to add_row_fn which should it add it
 * whereever it wants. The third parameter specifies the number of indentation
 * steps that should be used.
 */
NeuronAnnotations.prototype.add_result_table_row = function(entity, add_row_fn,
    indent)
{
  // Build table row
  var tr = document.createElement('tr');
  tr.setAttribute('class', 'neuron_annotation_result_row' +
          this.widgetID + '_' + entity.id);
  tr.setAttribute('type', entity.type);

  // Checkbox & name column, potentially indented
  var td_cb = document.createElement('td');
  var div_cb = document.createElement('div');
  // Make sure the line isn't wrapped
  div_cb.style.whiteSpace = 'nowrap';
  // Add indentation
  div_cb.style.marginLeft = indent * 1.5 + 'em';
  var cb = document.createElement('input');
  cb.setAttribute('type', 'checkbox');
  cb.setAttribute('class', 'result' + this.widgetID + '_' +
          entity.id);
  div_cb.appendChild(cb);
  var a = document.createElement('a');
  a.setAttribute('href', '#');
  a.appendChild(document.createTextNode(entity.name));
  div_cb.appendChild(a);
  td_cb.appendChild(div_cb);
  tr.appendChild(td_cb);

  // Type column
  var td_type = document.createElement('td');
  td_type.appendChild(document.createTextNode(
          entity.type));
  tr.appendChild(td_type);

  // Annotations column
  var td_ann = document.createElement('td');
  // Build list of annotations and use layout of jQuery tagbox
  var ul = entity.annotations.reduce(
    function(o, e) {
      var li = document.createElement('li');
      li.setAttribute('title', 'Remove annotation');
      li.setAttribute('class', 'remove_annotation');
      li.setAttribute('neuron_id', entity.id);
      li.setAttribute('annotation_id', e.id);
      li.appendChild(document.createTextNode(e.name));
      o.appendChild(li);
      return o;
    }, document.createElement('ul'));
  ul.setAttribute('class', 'tagEditor');
  td_ann.appendChild(ul);
  tr.appendChild(td_ann);

  // Add row to table
  add_row_fn(tr);

  // Wire up handlers
  if (entity.type == 'neuron') {
    var create_handler = function(skid) {
      return function() {
        TracingTool.goToNearestInNeuronOrSkeleton( 'skeleton', skid );
      }
    }
    // Go to nearest
    if (entity.skeleton_ids.length > 0) {
      $(a).click(create_handler(entity.skeleton_ids[0]));
    } else {
      $(a).click(function() { alert("No skeleton found!"); });
    }
  } else if (entity.type == 'annotation') {
    // Add annotation attribute to link
    a.setAttribute('annotation', entity.name);
    // Expand
    var self = this;
    $(a).click(function() {
      // If expanded, collapse it. Expand it otherwise.
      if ($(this).is('[expanded]')) {
        // TODO: Collapse it
      } else {
        // Mark link expanded
        this.setAttribute('expanded', 'true');
        // Request entities that are annotated with this annotation
        // and replace the clicked on annotation with the result.
        var query_data = {
          'neuron_query_by_annotation': $(this).attr('annotation'),
        };
        requestQueue.register(django_url + project.id + '/neuron/query-by-annotations',
            'POST', query_data, function(status, text, xml) {
              if (status === 200) {
                var e = $.parseJSON(text);
                if (e.error) {
                  alert(e.error);
                } else {
                  //Append new content right after the current node
                  var appender = function(new_tr) {
                    $(tr).after(new_tr)
                  };

                  e.forEach((function(entity) {
                    self.add_result_table_row(entity, appender, indent + 1);
                  }).bind(self));

                  // The order of the query result array doesn't matter.
                  // It is therefore possible to just append the new results.
                  self.queryResults = self.queryResults.concat(e);
                }
              }
        });
      }
    });
  }
  // Add click handlers to remove tags from nodes
  var NA = this;
  $(".remove_annotation", $(ul)).click( function() {
      NA.remove_annotation($(this).attr('neuron_id'),
          $(this).attr('annotation_id'));
  });
};

NeuronAnnotations.prototype.query = function()
{
  var form_data = $('#neuron_query_by_annotations' +
      this.widgetID).serializeArray().reduce(function(o, e) {
        o[e.name] = e.value;
        return o;
      }, {});

  // Here, $.proxy is used to bind 'this' to the anonymous function
  requestQueue.register(django_url + this.pid + '/neuron/query-by-annotations',
      'POST', form_data, $.proxy( function(status, text, xml) {
        if (status === 200) {
          var e = $.parseJSON(text);
          if (e.error) {
            alert(e.error);
          } else {
            var $tableBody = $('#neuron_annotations_query_results' +
                this.widgetID).find('tbody');
            $tableBody.empty();
            this.queryResults = e;
            // create appender function which adds rows to table
            var appender = function(tr) {
              $tableBody.append(tr);
            };
            // Create result table rows
            this.queryResults.forEach((function(entity) {
              this.add_result_table_row(entity, appender, 0);
            }).bind(this));

            // If there are results, display the result table
            if (this.queryResults.length > 0) {
              $('#neuron_annotations_query_no_results' + this.widgetID).hide();
              $('#neuron_annotations_query_results' + this.widgetID).show();
            } else {
              $('#neuron_annotations_query_results' + this.widgetID).hide();
              $('#neuron_annotations_query_no_results' + this.widgetID).show();
            }
          }
        }
      }, this));
};

NeuronAnnotations.prototype.add_query_field = function()
{
  // Create a copy of the first row.
  var $newRow = $("#neuron_query_by_annotation" + this.widgetID).clone();
  $newRow.attr({
      id: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID,
      name: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID
  });

  $newRow.children()[0].innerHTML = 'and:'

  // Update the text field attributes.
  var $text = $newRow.find("input[type='text']");
  $text.attr({
      id: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID,
      name: 'neuron_query_by_annotation' + this.widgetID + '_' +
          this.nextFieldID,
      value: ''
  });
  // Add autocompletion to it
  this.add_autocomplete_to_input($text);

  // Update the button attributes.
  var $button = $newRow.find("input[type='button']");
  $button.attr('value', '-');
  $button.click(this.remove_query_field.bind(this, this.nextFieldID));
  $("#neuron_query_by_annotator" + this.widgetID).before($newRow);

  this.nextFieldID += 1;
};

NeuronAnnotations.prototype.remove_query_field = function(rowNum)
{
  var $row = $("#neuron_query_by_annotation" + this.widgetID + "_" + rowNum);
  $row.remove();
};

NeuronAnnotations.prototype.toggle_neuron_selections = function()
{
  var newValue = $("#neuron_annotations_toggle_neuron_selections_checkbox" +
      this.widgetID)[0].checked;
  $("#neuron_annotations_query_results_table" + this.widgetID).find(
      'tbody tr td input[class*=result' + this.widgetID + '_]').each(
          function(i, element) {
            element.checked = newValue;
          });
};

NeuronAnnotations.prototype.get_selected_neurons = function()
{
  var visited = {};
  return this.queryResults.reduce((function(o, e) {
      // Test if one of the checkboxes for a particular neuron is checked
      var is_checked = $("#neuron_annotations_query_results_table" +
          this.widgetID).find('tr[type=neuron]').find('input[class=result' +
              this.widgetID + '_' + e.id + ']').is(':checked');
      // Avoid duplicates if the same neuron is checked multiple times and
      // add it only if not yet present.
      if (is_checked && !(e.id in visited)) {
          o.push(e);
          visited[e.id] = true;
      }
      return o;
    }).bind(this), []);
}

NeuronAnnotations.prototype.prompt_for_annotations = function(success_fn)
{
  var dialog = new OptionsDialog("Add new annotation");
  dialog.appendMessage("Add a new annotation for the selected objects");
  var annotation_input = dialog.appendField('Annotation: ', 'new-annotation', '');
  dialog.onOK = function() {
    var annotation = annotation_input.value;
    if (!annotation) return;
    annotation = annotation.trim();
    if (0 === annotation.length) return; // can't annotate with nothing
    success_fn([annotation]);
  };

  dialog.show(300, 200, true);
}

NeuronAnnotations.prototype.annotate_neurons_of_skeletons = function(skeleton_ids)
{
  this.annotate(null, skeleton_ids);
};

NeuronAnnotations.prototype.annotate_neurons = function(neuron_ids)
{
  this.annotate(neuron_ids, null);
};

NeuronAnnotations.prototype.annotate = function(neuron_ids, skeleton_ids)
{
  // Get annotation terms
  var annotations = this.prompt_for_annotations(function(annotations) {
    if (!annotations) return;
    // Build request data structure
    var data = {
      annotations: annotations,
    };
    if (neuron_ids) {
        data.neuron_ids = neuron_ids;
    }
    if (skeleton_ids) {
        data.skeleton_ids = skeleton_ids;
    }
    // Do request
    requestQueue.register(django_url + project.id + '/neuron/annotate',
        'POST', data, function(status, text, xml) {
          if (status === 200) {
            var e = $.parseJSON(text);
            if (e.error) {
              alert(e.error);
            } else {
              if (annotations.length == 1)
                growlAlert('Information', 'Annotation ' + annotations[0] + ' added.');
              else
                growlAlert('Information', 'Annotations ' + annotations.join(', ') + ' added.');
            }
          }
        });
  });
};

NeuronAnnotations.prototype.remove_annotation = function(neuron_id,
    annotation_id)
{
  if (!confirm('Are you sure you want to remove this annotation?')) {
    return;
  }

  requestQueue.register(django_url + this.pid + '/neuron/' + neuron_id +
      '/annotation/' + annotation_id + '/remove',
      'POST', {}, $.proxy(function(status, text, xml) {
        if (status === 200) {
          var e = $.parseJSON(text);
          if (e.error) {
            alert(e.error);
          } else {
            // Display message returned by the server
            growlAlert('Information', e.message);
            // Remove current annotation from displayed list
            var result_tr = $('#neuron_annotations_query_results' +
                this.widgetID).find('.remove_annotation[neuron_id=' +
                neuron_id + '][annotation_id=' + annotation_id + ']');
            result_tr.fadeOut(1000, function() { $(this).remove(); });
          }
        }
      }, this));
};

NeuronAnnotations.prototype.add_autocomplete_to_input = function(input)
{
  // Get a JSON list with all available annotations and initialize
  // autocompletion for the name field.
  requestQueue.register(django_url + project.id + '/annotations/list',
      'GET', {}, function (status, data, text) {
        var e = $.parseJSON(data);
        if (status !== 200) {
            alert("The server returned an unexpected status (" +
              status + ") " + "with error message:\n" + text);
        } else {
          var annotations = e.map(function(aobj) {
            return aobj.aname;
          });
          $(input).autocomplete({
            source: annotations
          });
        }
      });
};
<|endoftext|>


<commit_before>var ACTION_STATES = {
  active: {
    icons: {
      19: "images/action-icon-active.png",
      38: "images/action-icon-active@2x.png"
    },
    title: "Disable annotation"
  },
  sleeping: {
    icons: {
      19: "images/action-icon-inactive.png",
      38: "images/action-icon-inactive@2x.png"
    },
    title: "Enable annotation"
  }
}
var CRX_BASE_URL = chrome.extension.getURL('/')
var PDF_VIEWER_URL = chrome.extension.getURL('content/web/viewer.html')

function getPDFViewerURL(url) {
  return PDF_VIEWER_URL + '?file=' + encodeURIComponent(url)
}

function isPDFURL(url) {
  return url.toLowerCase().indexOf('.pdf') > 0
}

function isPDFViewerURL(url) {
  return url.indexOf(getPDFViewerURL('')) == 0
}

function inject(tab) {
  if (isPDFURL(tab.url)) {
      if (!isPDFViewerURL(tab.url)) {
        chrome.tabs.update(tab.id, {
          url: getPDFViewerURL(tab.url)
        })
      }
  } else {
    chrome.tabs.executeScript(tab.id, {
      file: 'public/embed.js'
    }, function () {
      chrome.tabs.executeScript(tab.id, {
        code: 'window.annotator = true;'
      })
    })
  }
}

function remove(tab) {
  if (isPDFViewerURL(tab.url)) {
    url = tab.url.slice(getPDFViewerURL('').length).split('#')[0];
    chrome.tabs.update(tab.id, {
      url: decodeURIComponent(url)
    })
  } else {
    chrome.tabs.executeScript(tab.id, {
      code: [
        'var script = document.createElement("script");',
        'script.src = "' + CRX_BASE_URL + 'public/destroy.js' + '";',
        'document.body.appendChild(script);',
        'delete window.annotator;',
      ].join('\n')
    })
  }
}

function state(tabId, value) {
  var stateMap = localStorage.getItem('state')
  stateMap = stateMap ? JSON.parse(stateMap) : {}

  if (value === undefined) {
    return stateMap[tabId]
  }

  if (value) {
    stateMap[tabId] = value
  } else {
    delete stateMap[tabId]
  }

  localStorage.setItem('state', JSON.stringify(stateMap))

  return value
}

function setPageAction(tabId, value) {
  chrome.pageAction.setIcon({
    tabId: tabId,
    path: ACTION_STATES[value].icons
  })
  chrome.pageAction.setTitle({
    tabId: tabId,
    title: ACTION_STATES[value].title
  })
  chrome.pageAction.show(tabId)
}

function onInstalled() {
  /* We need this so that 3-rd party cookie blocking does not kill us.
     See https://github.com/hypothesis/h/issues/634 for more info.
     This is intended to be a temporary fix only.
  */
  var details = {
    primaryPattern: 'https://hypothes.is/*',
    setting: 'allow'
  }
  chrome.contentSettings.cookies.set(details)
  chrome.contentSettings.images.set(details)
  chrome.contentSettings.javascript.set(details)

  chrome.tabs.query({}, function (tabs) {
    for (var i in tabs) {
      var tabId = tabs[i].id
        , tabState = state(tabId) || 'sleeping'
      setPageAction(tabId, tabState)
    }
  })
}

function onUpdateAvailable() {
  chrome.runtime.reload()
}

function onPageAction(tab) {
  var newState

  if (state(tab.id) == 'active') {
    newState = state(tab.id, 'sleeping')
    remove(tab)
  } else {
    newState = state(tab.id, 'active')
    inject(tab)
  }

  setPageAction(tab.id, newState)
}

function onTabCreated(tab) {
  state(tab.id, 'sleeping')
}

function onTabRemoved(tab) {
  state(tab.id, null)
}

function onTabUpdated(tabId, info, tab) {
  var currentState = state(tabId) || 'sleeping'

  setPageAction(tabId, currentState)

  if (currentState == 'active' && info.status == 'loading') {
    inject(tab)
  }
}

chrome.runtime.onInstalled.addListener(onInstalled)
chrome.runtime.onUpdateAvailable.addListener(onUpdateAvailable)
chrome.pageAction.onClicked.addListener(onPageAction)
chrome.tabs.onCreated.addListener(onTabCreated)
chrome.tabs.onRemoved.addListener(onTabRemoved)
chrome.tabs.onUpdated.addListener(onTabUpdated)
<commit_msg>Open the onboarding page on first install<commit_after>var ACTION_STATES = {
  active: {
    icons: {
      19: "images/action-icon-active.png",
      38: "images/action-icon-active@2x.png"
    },
    title: "Disable annotation"
  },
  sleeping: {
    icons: {
      19: "images/action-icon-inactive.png",
      38: "images/action-icon-inactive@2x.png"
    },
    title: "Enable annotation"
  }
}
var CRX_BASE_URL = chrome.extension.getURL('/')
var PDF_VIEWER_URL = chrome.extension.getURL('content/web/viewer.html')

function getPDFViewerURL(url) {
  return PDF_VIEWER_URL + '?file=' + encodeURIComponent(url)
}

function isPDFURL(url) {
  return url.toLowerCase().indexOf('.pdf') > 0
}

function isPDFViewerURL(url) {
  return url.indexOf(getPDFViewerURL('')) == 0
}

function inject(tab) {
  if (isPDFURL(tab.url)) {
      if (!isPDFViewerURL(tab.url)) {
        chrome.tabs.update(tab.id, {
          url: getPDFViewerURL(tab.url)
        })
      }
  } else {
    chrome.tabs.executeScript(tab.id, {
      file: 'public/embed.js'
    }, function () {
      chrome.tabs.executeScript(tab.id, {
        code: 'window.annotator = true;'
      })
    })
  }
}

function remove(tab) {
  if (isPDFViewerURL(tab.url)) {
    url = tab.url.slice(getPDFViewerURL('').length).split('#')[0];
    chrome.tabs.update(tab.id, {
      url: decodeURIComponent(url)
    })
  } else {
    chrome.tabs.executeScript(tab.id, {
      code: [
        'var script = document.createElement("script");',
        'script.src = "' + CRX_BASE_URL + 'public/destroy.js' + '";',
        'document.body.appendChild(script);',
        'delete window.annotator;',
      ].join('\n')
    })
  }
}

function state(tabId, value) {
  var stateMap = localStorage.getItem('state')
  stateMap = stateMap ? JSON.parse(stateMap) : {}

  if (value === undefined) {
    return stateMap[tabId]
  }

  if (value) {
    stateMap[tabId] = value
  } else {
    delete stateMap[tabId]
  }

  localStorage.setItem('state', JSON.stringify(stateMap))

  return value
}

function setPageAction(tabId, value) {
  chrome.pageAction.setIcon({
    tabId: tabId,
    path: ACTION_STATES[value].icons
  })
  chrome.pageAction.setTitle({
    tabId: tabId,
    title: ACTION_STATES[value].title
  })
  chrome.pageAction.show(tabId)
}

function onInstalled(installDetails) {
  if (installDetails.reason === 'install') {
    chrome.tabs.create({url: 'http://hypothes.is/welcome'});
  }

  /* We need this so that 3-rd party cookie blocking does not kill us.
     See https://github.com/hypothesis/h/issues/634 for more info.
     This is intended to be a temporary fix only.
  */
  var details = {
    primaryPattern: 'https://hypothes.is/*',
    setting: 'allow'
  }
  chrome.contentSettings.cookies.set(details)
  chrome.contentSettings.images.set(details)
  chrome.contentSettings.javascript.set(details)

  chrome.tabs.query({}, function (tabs) {
    for (var i in tabs) {
      var tabId = tabs[i].id
        , tabState = state(tabId) || 'sleeping'
      setPageAction(tabId, tabState)
    }
  })
}

function onUpdateAvailable() {
  chrome.runtime.reload()
}

function onPageAction(tab) {
  var newState

  if (state(tab.id) == 'active') {
    newState = state(tab.id, 'sleeping')
    remove(tab)
  } else {
    newState = state(tab.id, 'active')
    inject(tab)
  }

  setPageAction(tab.id, newState)
}

function onTabCreated(tab) {
  state(tab.id, 'sleeping')
}

function onTabRemoved(tab) {
  state(tab.id, null)
}

function onTabUpdated(tabId, info, tab) {
  var currentState = state(tabId) || 'sleeping'

  setPageAction(tabId, currentState)

  if (currentState == 'active' && info.status == 'loading') {
    inject(tab)
  }
}

chrome.runtime.onInstalled.addListener(onInstalled)
chrome.runtime.onUpdateAvailable.addListener(onUpdateAvailable)
chrome.pageAction.onClicked.addListener(onPageAction)
chrome.tabs.onCreated.addListener(onTabCreated)
chrome.tabs.onRemoved.addListener(onTabRemoved)
chrome.tabs.onUpdated.addListener(onTabUpdated)
<|endoftext|>


<commit_before>import {
    implementation,
    Element,
    DEFAULT_DOCUMENT_NAME,
    DEFAULT_NAMESPACE_PREFIX,
    DEFAULT_NAMESPACE_URI,
    NAMESPACE_SEPARATOR,
} from './core'
import { ElementAssembler } from './element'
import { DocumentTypeAssembler } from './doctype'
import { ParentNodeAssembler } from './parentnode'

const DEFAULT_DOCTYPE = null

/**
 * Document DOM node assembler
 */
export class DocumentAssembler extends ParentNodeAssembler {
    /**
     * Set the associated document type declaration node
     * @param {*} doctype node or assembler
     */
    set doctype(doctype) {
        const node = this.node
        const ref = this.doctype
        if(ref) {
            const child = doctype instanceof DocumentTypeAssembler?
                doctype.node :
                doctype
            node.replaceChild(child, ref.node)
        }
        else node.prepend(doctype)
    }

    /**
     * The associatied document type declaration node
     * @returns {DocumentType}
     */
    get doctype() {
        return this.getInstance(this.node.doctype)
    }

    /**
     * Set the root document element
     * @param {*} documentElement node or assembler or init dictionary
     */
    set documentElement(documentElement) {
        const node = this.node
        const root = this.documentElement
        if(documentElement instanceof ElementAssembler) {
            if(!documentElement.node) documentElement.assemble()
            if(root) node.replaceChild(documentElement.node, root.node)
            else documentElement.parentNode = node
        }
        else if(documentElement instanceof Element) {
            if(root) node.replaceChild(documentElement, root.node)
            else node.append(documentElement)
        }
        else {
            if(root) root.init(documentElement)
            else node.append(new this.constructor.documentElementClass(documentElement))
        }
    }

    /**
     * The root document element
     * @returns {Element} Element
     */
    get documentElement() {
        return this.getInstance(this.node.documentElement)
    }

    /**
     * Create the specified Document node
     * @param {*} [namespaceURI]
     * @param {*} [qualifiedName]
     * @param {*} [doctype]
     * @returns {Document}
     */
    static create({
        namespaceURI = this.namespaceURI,
        qualifiedName = this.qualifiedName,
        doctype = this.doctype
    } = this) {
        return implementation.createDocument(
            namespaceURI,
            qualifiedName,
            doctype instanceof DocumentTypeAssembler?
                doctype.node :
                doctype)
    }

    /**
     * The default namespace URI
     * @returns {String}
     */
    static get namespaceURI() {
        return DEFAULT_NAMESPACE_URI
    }

    /**
     * The qualified name of the document element node
     * @returns {String}
     */
    static get qualifiedName() {
        const { prefix, localName } = this
        return prefix?
            prefix + NAMESPACE_SEPARATOR + localName :
            localName
    }

    /**
     * The namespace prefix of the document element node
     * @returns {String}
     */
    static get prefix() {
        return DEFAULT_NAMESPACE_PREFIX
    }

    /**
     * The local name of the document element node
     * @returns {String}
     */
    static get localName() {
        return this === DocumentAssembler? DEFAULT_DOCUMENT_NAME : this.name
    }

    /**
     * Doctype redefenition facility
     * @returns {*|null}
     */
    static get doctype() {
        return DEFAULT_DOCTYPE
    }

    /**
     * @returns {Function}
     */
    static get documentElementClass() {
        return ElementAssembler
    }
}
<commit_msg>Fix doctype and documentElement setters<commit_after>import {
    implementation,
    Element,
    DEFAULT_DOCUMENT_NAME,
    DEFAULT_NAMESPACE_PREFIX,
    DEFAULT_NAMESPACE_URI,
    NAMESPACE_SEPARATOR,
} from './core'
import { ElementAssembler } from './element'
import { DocumentTypeAssembler } from './doctype'
import { ParentNodeAssembler } from './parentnode'

const DEFAULT_DOCTYPE = null

/**
 * Document DOM node assembler
 */
export class DocumentAssembler extends ParentNodeAssembler {
    /**
     * Set the associated document type declaration node
     * @param {*} doctype node or assembler
     */
    set doctype(doctype) {
        const node = this.node
        const ref = this.doctype
        const child = doctype instanceof DocumentTypeAssembler?
            doctype.node :
            doctype
        if(ref) {
            node.replaceChild(child, ref.node)
        }
        else node.prepend(child)
    }

    /**
     * The associatied document type declaration node
     * @returns {DocumentType}
     */
    get doctype() {
        return this.getInstance(this.node.doctype)
    }

    /**
     * Set the root document element
     * @param {*} documentElement node or assembler or init dictionary
     */
    set documentElement(documentElement) {
        const node = this.node
        const root = this.documentElement
        if(documentElement instanceof ElementAssembler) {
            if(root) node.replaceChild(documentElement.node, root.node)
            else documentElement.parentNode = node
        }
        else if(documentElement instanceof Element) {
            if(root) node.replaceChild(documentElement, root.node)
            else node.appendChild(documentElement)
        }
        else {
            if(root) {
                root.init(documentElement)
            }
            else {
                const { documentElementClass } = this.constructor
                const child = new documentElementClass(documentElement)
                child.parentNode = node
            }
        }
    }

    /**
     * The root document element
     * @returns {Element} Element
     */
    get documentElement() {
        return this.getInstance(this.node.documentElement)
    }

    /**
     * Create the specified Document node
     * @param {*} [namespaceURI]
     * @param {*} [qualifiedName]
     * @param {*} [doctype]
     * @returns {Document}
     */
    static create({
        namespaceURI = this.namespaceURI,
        qualifiedName = this.qualifiedName,
        doctype = this.doctype
    } = this) {
        return implementation.createDocument(
            namespaceURI,
            qualifiedName,
            doctype instanceof DocumentTypeAssembler?
                doctype.node :
                doctype)
    }

    /**
     * The default namespace URI
     * @returns {String}
     */
    static get namespaceURI() {
        return DEFAULT_NAMESPACE_URI
    }

    /**
     * The qualified name of the document element node
     * @returns {String}
     */
    static get qualifiedName() {
        const { prefix, localName } = this
        return prefix?
            prefix + NAMESPACE_SEPARATOR + localName :
            localName
    }

    /**
     * The namespace prefix of the document element node
     * @returns {String}
     */
    static get prefix() {
        return DEFAULT_NAMESPACE_PREFIX
    }

    /**
     * The local name of the document element node
     * @returns {String}
     */
    static get localName() {
        return this === DocumentAssembler? DEFAULT_DOCUMENT_NAME : this.name
    }

    /**
     * Doctype redefenition facility
     * @returns {*|null}
     */
    static get doctype() {
        return DEFAULT_DOCTYPE
    }

    /**
     * @returns {Function}
     */
    static get documentElementClass() {
        return ElementAssembler
    }
}
<|endoftext|>


<commit_before>/*
  Copyright (c) 2017 Wu Bin
*/

'use strict'

const fs = require('fs')
const http = require('http')
const path = require('path')
const Url = require('url')
const https = require('https');

const Download = {};

let RHttp = function(protocol){
  let fn = "";
  if (protocol == "http:") {
    fn = http.get;
  } else if (protocol == "https:") {
    fn = https.get;
  } else {
    throw new Error('Invalid Protocol!')
  }
  return fn;
}

let http_request = function(url, dir, filename) {
  let protocol = Url.parse(url).protocol;

  return new Promise( (resolve, reject) => {
    let fn = RHttp(protocol)
    fn(url, res => {
      let statusCode = res.statusCode;
      let statusMessage = res.statusMessage;
      let chunk = ""
      res.setEncoding('binary')
      res.on('data', data => {
        chunk += data;
      })
        .on('error', err => {
          throw new Error(err)
        })
        .on('end', () => {
          if (parseInt(statusCode) != 200) {
            throw new Error(`${statusCode} ${statusMessage}`)
          }
          fs.writeFile(dir + filename, chunk, { encoding: 'binary' }, (err) => {
            if (err) throw new Error(err);
            resolve({status: statusCode})
          })
        })
    })
  })
}

let request_stream = function(url, dir, filename) {
  let protocol = Url.parse(url).protocol;

  return new Promise( (resolve, reject) => {
    let fn = RHttp(protocol)
    fn(url, res => {
      let statusCode = res.statusCode;
      let statusMessage = res.statusMessage;
      let writeStream = fs.createWriteStream(dir + filename)
      res.on('data', data => {
        writeStream.write(data)
      })
        .on('error', err => {
          throw new Error(err);
        })
        .on('end', function(){
          if (parseInt(statusCode) != 200) {
            throw new Error(`${statusCode} ${statusMessage}`)
          }
          writeStream.end()
          writeStream.on('close', function(){
            resolve({status: statusCode})
          })
        })
    })
  })
}

/**
 * File
 *
 * @param {String} url
 * @param {String} dir
 * @param {String} filename
 * @returns
 */
Download.File = (url, dir, filename) => {

  if(!dir) {
    dir = __dirname + "\\"
  }
  if(typeof filename != "string") {
    filename =  path.basename(url)
  }
  return http_request(url, dir, filename)
}

/**
 * FileStream
 *
 * @param {String} url
 * @param {String} dir
 * @param {String} filename
 * @returns
 */
Download.FileStream = (url, dir, filename) => {

  if(!dir) {
    dir = __dirname + "\\"
  }
  if(typeof filename != "string") {
    filename =  path.basename(url)
  }
  return request_stream(url, dir, filename)
}

module.exports = Download<commit_msg>format<commit_after>/*
  Copyright (c) 2017 Wu Bin
*/

"use strict";

const fs = require("fs");
const http = require("http");
const path = require("path");
const Url = require("url");
const https = require("https");

const Download = {};

let RHttp = function(protocol) {
  let fn = "";
  if (protocol == "http:") {
    fn = http.get;
  } else if (protocol == "https:") {
    fn = https.get;
  } else {
    throw new Error("Invalid Protocol!");
  }
  return fn;
};

let http_request = function(url, dir, filename) {
  let protocol = Url.parse(url).protocol;

  return new Promise((resolve, reject) => {
    let fn = RHttp(protocol);
    fn(url, res => {
      let statusCode = res.statusCode;
      let statusMessage = res.statusMessage;
      let chunk = "";
      res.setEncoding("binary");
      res
        .on("data", data => {
          chunk += data;
        })
        .on("error", err => {
          throw new Error(err);
        })
        .on("end", () => {
          if (parseInt(statusCode) != 200) {
            throw new Error(`${statusCode} ${statusMessage}`);
          }
          fs.writeFile(dir + filename, chunk, { encoding: "binary" }, err => {
            if (err) throw new Error(err);
            resolve({ status: statusCode });
          });
        });
    });
  });
};

let request_stream = function(url, dir, filename) {
  let protocol = Url.parse(url).protocol;

  return new Promise((resolve, reject) => {
    let fn = RHttp(protocol);
    fn(url, res => {
      let statusCode = res.statusCode;
      let statusMessage = res.statusMessage;
      let writeStream = fs.createWriteStream(dir + filename);
      res
        .on("data", data => {
          writeStream.write(data);
        })
        .on("error", err => {
          throw new Error(err);
        })
        .on("end", function() {
          if (parseInt(statusCode) != 200) {
            throw new Error(`${statusCode} ${statusMessage}`);
          }
          writeStream.end();
          writeStream.on("close", function() {
            resolve({ status: statusCode });
          });
        });
    });
  });
};

/**
 * File
 *
 * @param {String} url
 * @param {String} dir
 * @param {String} filename
 * @returns
 */
Download.File = (url, dir, filename) => {
  if (!dir) {
    dir = __dirname + "\\";
  }
  if (typeof filename != "string") {
    filename = path.basename(url);
  }
  return http_request(url, dir, filename);
};

/**
 * FileStream
 *
 * @param {String} url
 * @param {String} dir
 * @param {String} filename
 * @returns
 */
Download.FileStream = (url, dir, filename) => {
  if (!dir) {
    dir = __dirname + "\\";
  }
  if (typeof filename != "string") {
    filename = path.basename(url);
  }
  return request_stream(url, dir, filename);
};

module.exports = Download;
<|endoftext|>