<commit_before>import classNames from 'classnames';
import React, {Component, PropTypes} from 'react';
import {Hooks} from 'PluginSDK';

import Application from '../../structs/Application';
import PodSpec from '../../structs/PodSpec';

import {DEFAULT_APP_SPEC} from '../../constants/DefaultApp';
import {DEFAULT_POD_SPEC} from '../../constants/DefaultPod';

import FullScreenModal from '../../../../../../src/js/components/modals/FullScreenModal';
import FullScreenModalHeader from '../../../../../../src/js/components/modals/FullScreenModalHeader';
import FullScreenModalHeaderActions from '../../../../../../src/js/components/modals/FullScreenModalHeaderActions';
import FullScreenModalHeaderTitle from '../../../../../../src/js/components/modals/FullScreenModalHeaderTitle';
import NewCreateServiceModalServicePicker from './NewCreateServiceModalServicePicker';
import NewCreateServiceModalForm from './NewCreateServiceModalForm';
import CreateServiceJsonOnly from './CreateServiceJsonOnly';
import Service from '../../structs/Service';
import ServiceConfigDisplay from '../../service-configuration/ServiceConfigDisplay';
import ServiceUtil from '../../utils/ServiceUtil';
import ToggleButton from '../../../../../../src/js/components/ToggleButton';
import Util from '../../../../../../src/js/utils/Util';
import ContainerServiceFormSection from '../forms/ContainerServiceFormSection';
import EnvironmentFormSection from '../forms/EnvironmentFormSection';
import GeneralServiceFormSection from '../forms/GeneralServiceFormSection';
import HealthChecksFormSection from '../forms/HealthChecksFormSection';
import NetworkingFormSection from '../forms/NetworkingFormSection';
import MultiContainerNetworkingFormSection from '../forms/MultiContainerNetworkingFormSection';
import MultiContainerVolumesFormSection from '../forms/MultiContianerVolumesFormSection';
import VolumesFormSection from '../forms/VolumesFormSection';
import {combineParsers} from '../../../../../../src/js/utils/ParserUtil';
import {combineReducers} from '../../../../../../src/js/utils/ReducerUtil';
import JSONConfigReducers from '../../reducers/JSONReducers';
import JSONParserReducers from '../../reducers/JSONParser';

const METHODS_TO_BIND = [
  'handleGoBack',
  'handleClose',
  'handleConvertToPod',
  'handleJSONToggle',
  'handleServiceChange',
  'handleServiceErrorChange',
  'handleServiceReview',
  'handleServiceRun',
  'handleServiceSelection'
];

class NewServiceFormModal extends Component {
  constructor() {
    super(...arguments);

    this.state = this.getResetState();

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  /**
   * @override
   */
  componentWillUpdate(nextProps) {
    const requestCompleted = this.props.isPending && !nextProps.isPending;
    const shouldClose = requestCompleted && !nextProps.errors;

    if (shouldClose) {
      this.handleClose();
    }
  }

  /**
   * @override
   */
  componentWillReceiveProps(nextProps) {
    if (!ServiceUtil.isEqual(this.props.service, nextProps.service)) {
      const newState = {serviceConfig: nextProps.service.getSpec()};

      if (nextProps.isEdit) {
        newState.servicePickerActive = false;
        newState.serviceJsonActive = false;
        newState.serviceFormActive = true;

        if (nextProps.service instanceof PodSpec) {
          newState.serviceJsonActive = true;
          newState.serviceFormActive = false;
        }
      }

      this.setState(newState);
    }
  }

  shouldForceSubmit() {
    const {errors} = this.props;

    if (errors && errors.message) {
      return /force=true/.test(errors.message);
    }

    return false;
  }

  handleGoBack({tabViewID}) {
    const {
      serviceFormActive,
      serviceJsonActive,
      servicePickerActive,
      serviceReviewActive
    } = this.state;

    if (serviceReviewActive) {
      // Just hide review screen. Form or JSON mode will be
      // activated automaticaly depending on their last state
      this.setState({
        serviceReviewActive: false,
        activeTab: tabViewID
      });
      return;
    }

    // Close if picker is open, or if editing a service in the form
    if (servicePickerActive || (!serviceReviewActive && this.props.isEdit)) {
      this.handleClose();
      return;
    }

    if (serviceFormActive) {
      // Switch back from form to picker
      this.setState({
        servicePickerActive: true,
        serviceFormActive: false
      });
      return;
    }

    if (serviceJsonActive) {
      // Switch back from JSON to picker
      this.setState({
        servicePickerActive: true,
        serviceJsonActive: false
      });

    }
  }

  handleClose() {
    this.props.onClose();
    this.setState(this.getResetState());
  }

  handleConvertToPod() {
    this.handleServiceSelection({type: 'pod'});
  }

  handleJSONToggle() {
    this.setState({isJSONModeActive: !this.state.isJSONModeActive});
  }

  handleServiceChange(newService) {
    this.setState({serviceConfig: newService});
  }

  handleServiceErrorChange(hasErrors) {
    this.setState({serviceFormHasErrors: hasErrors});
  }

  handleServiceSelection({type}) {
    switch (type) {

      case 'app':
        this.setState({
          servicePickerActive: false,
          serviceFormActive: true,
          serviceConfig: new Application(
            Object.assign(
              {id: this.props.service.getId()},
              DEFAULT_APP_SPEC
            )
          )
        });
        break;

      case 'pod':
        this.setState({
          servicePickerActive: false,
          serviceFormActive: true,
          serviceConfig: new PodSpec(
            Object.assign(
              {id: this.props.service.getId()},
              DEFAULT_POD_SPEC
            )
          )
        });
        break;

      case 'json':
        this.setState({
          servicePickerActive: false,
          serviceJsonActive: true,
          serviceConfig: this.props.service
        });
        break;

    };
  }

  handleServiceReview() {
    if (this.createComponent && this.createComponent.validateCurrentState()) {
      this.handleServiceErrorChange(true);
    } else {
      this.setState({serviceReviewActive: true});
    }
  }

  handleServiceRun() {
    const {marathonAction, service} = this.props;
    marathonAction(
      service,
      this.state.serviceConfig,
      this.shouldForceSubmit()
    );
  }

  getHeader() {

    // NOTE: Always prioritize review screen check
    if (this.state.serviceReviewActive) {
      return (
        <FullScreenModalHeader>
          <FullScreenModalHeaderActions
            actions={this.getSecondaryActions()}
            type="secondary" />
          <FullScreenModalHeaderTitle>
            Review & Run Service
          </FullScreenModalHeaderTitle>
          <FullScreenModalHeaderActions
            actions={this.getPrimaryActions()}
            type="primary" />
        </FullScreenModalHeader>
      );
    }

    let title = 'Run a Service';
    const {isEdit, service} = this.props;
    let serviceName = service.getName();
    if (serviceName) {
      serviceName = `"${serviceName}"`;
    } else {
      serviceName = 'Service';
    }

    if (isEdit) {
      title = `Edit ${serviceName}`;
    }

    return (
      <FullScreenModalHeader>
        <FullScreenModalHeaderActions
          actions={this.getSecondaryActions()}
          type="secondary" />
        <FullScreenModalHeaderTitle>
          {title}
        </FullScreenModalHeaderTitle>
        <FullScreenModalHeaderActions
          actions={this.getPrimaryActions()}
          type="primary" />
      </FullScreenModalHeader>
    );
  }

  getModalContent() {
    let {
      isJSONModeActive,
      serviceConfig,
      serviceFormActive,
      serviceJsonActive,
      servicePickerActive,
      serviceReviewActive
    } = this.state;

    let errorsMap = new Map();
    if (this.props.errors) {
      let message = this.props.errors.message;

      if (this.shouldForceSubmit()) {
        message = `App is currently locked by one or more deployments.
         Press the button again to forcefully change and deploy the
         new configuration.`;
      }
      errorsMap.set('/', [message]);

      if (this.props.errors.details) {
        this.props.errors.details.forEach(function ({errors, path}) {
          const existingMessages = errorsMap.get(path);

          let messages = errors;

          if (existingMessages) {
            messages = messages.concat(existingMessages);
          }

          errorsMap.set(path, messages);
        });
      }
    }

    // NOTE: Always prioritize review screen check
    if (serviceReviewActive) {
      return (

            <ServiceConfigDisplay
              onEditClick={this.handleGoBack}
              appConfig={serviceConfig}
              clearError={this.props.clearError}
              errors={errorsMap} />

      );
    }

    if (servicePickerActive) {
      return (
        <NewCreateServiceModalServicePicker
          onServiceSelect={this.handleServiceSelection} />
      );
    }

    if (serviceFormActive) {
      let {isEdit} = this.props;

      const SECTIONS = [
        ContainerServiceFormSection,
        EnvironmentFormSection,
        GeneralServiceFormSection,
        HealthChecksFormSection,
        NetworkingFormSection,
        VolumesFormSection,
        MultiContainerVolumesFormSection,
        MultiContainerNetworkingFormSection
      ];

      const jsonParserReducers = combineParsers(
        Hooks.applyFilter('serviceCreateJsonParserReducers', JSONParserReducers)
      );

      let isPod = serviceConfig instanceof PodSpec;
      const jsonConfigReducers = combineReducers(
        Hooks.applyFilter('serviceJsonConfigReducers', JSONConfigReducers),
        {
          cmd: !isPod,
          container: !isPod,
          containers: isPod,
          localVolumes: !isPod,
          externalVolumes: !isPod,
          volumes: isPod,
          network: isPod
        }
      );
      const inputConfigReducers = combineReducers(
        Hooks.applyFilter('serviceInputConfigReducers',
          Object.assign({}, ...SECTIONS.map((item) => item.configReducers))
        )
      );

      return (
        <NewCreateServiceModalForm
          activeTab={this.state.activeTab}
          jsonParserReducers={jsonParserReducers}
          jsonConfigReducers={jsonConfigReducers}
          inputConfigReducers={inputConfigReducers}
          isJSONModeActive={isJSONModeActive}
          ref={(ref) => {
            return this.createComponent = ref;
          }}
          service={serviceConfig}
          onChange={this.handleServiceChange}
          onConvertToPod={this.handleConvertToPod}
          onErrorStateChange={this.handleServiceErrorChange}
          isEdit={isEdit} />
      );
    }

    if (serviceJsonActive) {
      return (
        <CreateServiceJsonOnly
          ref={(ref) => {
            return this.createComponent = ref;
          }}
          service={serviceConfig}
          onChange={this.handleServiceChange}
          onErrorStateChange={this.handleServiceErrorChange} />
      );
    }

    return null;
  }

  getPrimaryActions() {
    const {
      serviceFormActive,
      serviceJsonActive,
      servicePickerActive,
      serviceReviewActive
    } = this.state;

    const force = this.shouldForceSubmit();
    const runButtonLabel = force ? 'Force Run Service' : 'Run Service';
    const runButtonClassNames = classNames('flush-vertical', {
      'button-primary': !force,
      'button-danger': force
    });

    // NOTE: Always prioritize review screen check
    if (serviceReviewActive) {
      return [
        {
          className: runButtonClassNames,
          clickHandler: this.handleServiceRun,
          label: runButtonLabel
        }
      ];
    }

    if (servicePickerActive) {
      return null;
    }

    if (serviceFormActive) {
      return [
        {
          node: (
            <ToggleButton
              className="flush"
              checkboxClassName="toggle-button"
              checked={this.state.isJSONModeActive}
              onChange={this.handleJSONToggle}
              key="json-editor">
              JSON Editor
            </ToggleButton>
          )
        },
        {
          className: 'button-primary flush-vertical',
          clickHandler: this.handleServiceReview,
          disabled: this.state.serviceFormHasErrors,
          label: 'Review & Run'
        }
      ];
    }

    if (serviceJsonActive) {
      return [
        {
          className: 'button-primary flush-vertical',
          clickHandler: this.handleServiceReview,
          disabled: this.state.serviceFormHasErrors,
          label: 'Review & Run'
        }
      ];
    }

    return [];
  }

  getResetState(nextProps = this.props) {
    const newState = {
      activeTab: null,
      isJSONModeActive: false,
      serviceConfig: nextProps.service.getSpec(),
      serviceFormActive: false,
      serviceJsonActive: false,
      servicePickerActive: true,
      serviceReviewActive: false,
      serviceFormHasErrors: false
    };

    // Switch directly to form/json if edit
    if (nextProps.isEdit) {
      newState.servicePickerActive = false;

      if (nextProps.service instanceof PodSpec) {
        newState.serviceJsonActive = true;
      } else {
        newState.serviceFormActive = true;
      }
    }

    return newState;
  }

  getSecondaryActions() {
    const {
      servicePickerActive,
      serviceReviewActive
    } = this.state;
    let label = 'Back';

    if (servicePickerActive || (this.props.isEdit && !serviceReviewActive)) {
      label = 'Cancel';
    }

    return [
      {
        className: 'button-stroke',
        clickHandler: this.handleGoBack,
        label
      }
    ];
  }

  render() {
    const {props, state: {servicePickerActive, serviceReviewActive}} = this;
    let useGemini = false;

    if (servicePickerActive || serviceReviewActive) {
      useGemini = true;
    }

    return (

        <FullScreenModal
          header={this.getHeader()}
          onClose={this.handleClose}
          useGemini={useGemini}
          {...Util.omit(props, Object.keys(NewServiceFormModal.propTypes))}>
          {this.getModalContent()}
        </FullScreenModal>

    );
  }
}

NewServiceFormModal.propTypes = {
  clearError: PropTypes.func.isRequired,
  errors: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string
  ]),
  isEdit: PropTypes.bool,
  isPending: PropTypes.bool.isRequired,
  marathonAction: PropTypes.func.isRequired,
  onClose: PropTypes.func,
  service: PropTypes.instanceOf(Service).isRequired
};

module.exports = NewServiceFormModal;
<commit_msg>Add decision for Pod or app reducer<commit_after>import classNames from 'classnames';
import React, {Component, PropTypes} from 'react';
import {Hooks} from 'PluginSDK';

import Application from '../../structs/Application';
import PodSpec from '../../structs/PodSpec';

import {DEFAULT_APP_SPEC} from '../../constants/DefaultApp';
import {DEFAULT_POD_SPEC} from '../../constants/DefaultPod';

import FullScreenModal from '../../../../../../src/js/components/modals/FullScreenModal';
import FullScreenModalHeader from '../../../../../../src/js/components/modals/FullScreenModalHeader';
import FullScreenModalHeaderActions from '../../../../../../src/js/components/modals/FullScreenModalHeaderActions';
import FullScreenModalHeaderTitle from '../../../../../../src/js/components/modals/FullScreenModalHeaderTitle';
import NewCreateServiceModalServicePicker from './NewCreateServiceModalServicePicker';
import NewCreateServiceModalForm from './NewCreateServiceModalForm';
import CreateServiceJsonOnly from './CreateServiceJsonOnly';
import Service from '../../structs/Service';
import ServiceConfigDisplay from '../../service-configuration/ServiceConfigDisplay';
import ServiceUtil from '../../utils/ServiceUtil';
import ToggleButton from '../../../../../../src/js/components/ToggleButton';
import Util from '../../../../../../src/js/utils/Util';
import ContainerServiceFormSection from '../forms/ContainerServiceFormSection';
import EnvironmentFormSection from '../forms/EnvironmentFormSection';
import GeneralServiceFormSection from '../forms/GeneralServiceFormSection';
import HealthChecksFormSection from '../forms/HealthChecksFormSection';
import NetworkingFormSection from '../forms/NetworkingFormSection';
import MultiContainerNetworkingFormSection from '../forms/MultiContainerNetworkingFormSection';
import MultiContainerVolumesFormSection from '../forms/MultiContianerVolumesFormSection';
import VolumesFormSection from '../forms/VolumesFormSection';
import {combineParsers} from '../../../../../../src/js/utils/ParserUtil';
import {combineReducers} from '../../../../../../src/js/utils/ReducerUtil';
import JSONAppReducers from '../../reducers/JSONAppReducers';
import JSONPodReducers from '../../reducers/JSONPodReducers';
import JSONParser from '../../reducers/JSONParser';

const METHODS_TO_BIND = [
  'handleGoBack',
  'handleClose',
  'handleConvertToPod',
  'handleJSONToggle',
  'handleServiceChange',
  'handleServiceErrorChange',
  'handleServiceReview',
  'handleServiceRun',
  'handleServiceSelection'
];

class NewServiceFormModal extends Component {
  constructor() {
    super(...arguments);

    this.state = this.getResetState();

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  /**
   * @override
   */
  componentWillUpdate(nextProps) {
    const requestCompleted = this.props.isPending && !nextProps.isPending;
    const shouldClose = requestCompleted && !nextProps.errors;

    if (shouldClose) {
      this.handleClose();
    }
  }

  /**
   * @override
   */
  componentWillReceiveProps(nextProps) {
    if (!ServiceUtil.isEqual(this.props.service, nextProps.service)) {
      const newState = {serviceConfig: nextProps.service.getSpec()};

      if (nextProps.isEdit) {
        newState.servicePickerActive = false;
        newState.serviceJsonActive = false;
        newState.serviceFormActive = true;

        if (nextProps.service instanceof PodSpec) {
          newState.serviceJsonActive = true;
          newState.serviceFormActive = false;
        }
      }

      this.setState(newState);
    }
  }

  shouldForceSubmit() {
    const {errors} = this.props;

    if (errors && errors.message) {
      return /force=true/.test(errors.message);
    }

    return false;
  }

  handleGoBack({tabViewID}) {
    const {
      serviceFormActive,
      serviceJsonActive,
      servicePickerActive,
      serviceReviewActive
    } = this.state;

    if (serviceReviewActive) {
      // Just hide review screen. Form or JSON mode will be
      // activated automaticaly depending on their last state
      this.setState({
        serviceReviewActive: false,
        activeTab: tabViewID
      });
      return;
    }

    // Close if picker is open, or if editing a service in the form
    if (servicePickerActive || (!serviceReviewActive && this.props.isEdit)) {
      this.handleClose();
      return;
    }

    if (serviceFormActive) {
      // Switch back from form to picker
      this.setState({
        servicePickerActive: true,
        serviceFormActive: false
      });
      return;
    }

    if (serviceJsonActive) {
      // Switch back from JSON to picker
      this.setState({
        servicePickerActive: true,
        serviceJsonActive: false
      });

    }
  }

  handleClose() {
    this.props.onClose();
    this.setState(this.getResetState());
  }

  handleConvertToPod() {
    this.handleServiceSelection({type: 'pod'});
  }

  handleJSONToggle() {
    this.setState({isJSONModeActive: !this.state.isJSONModeActive});
  }

  handleServiceChange(newService) {
    this.setState({serviceConfig: newService});
  }

  handleServiceErrorChange(hasErrors) {
    this.setState({serviceFormHasErrors: hasErrors});
  }

  handleServiceSelection({type}) {
    switch (type) {

      case 'app':
        this.setState({
          servicePickerActive: false,
          serviceFormActive: true,
          serviceConfig: new Application(
            Object.assign(
              {id: this.props.service.getId()},
              DEFAULT_APP_SPEC
            )
          )
        });
        break;

      case 'pod':
        this.setState({
          servicePickerActive: false,
          serviceFormActive: true,
          serviceConfig: new PodSpec(
            Object.assign(
              {id: this.props.service.getId()},
              DEFAULT_POD_SPEC
            )
          )
        });
        break;

      case 'json':
        this.setState({
          servicePickerActive: false,
          serviceJsonActive: true,
          serviceConfig: this.props.service
        });
        break;

    };
  }

  handleServiceReview() {
    if (this.createComponent && this.createComponent.validateCurrentState()) {
      this.handleServiceErrorChange(true);
    } else {
      this.setState({serviceReviewActive: true});
    }
  }

  handleServiceRun() {
    const {marathonAction, service} = this.props;
    marathonAction(
      service,
      this.state.serviceConfig,
      this.shouldForceSubmit()
    );
  }

  getHeader() {

    // NOTE: Always prioritize review screen check
    if (this.state.serviceReviewActive) {
      return (
        <FullScreenModalHeader>
          <FullScreenModalHeaderActions
            actions={this.getSecondaryActions()}
            type="secondary" />
          <FullScreenModalHeaderTitle>
            Review & Run Service
          </FullScreenModalHeaderTitle>
          <FullScreenModalHeaderActions
            actions={this.getPrimaryActions()}
            type="primary" />
        </FullScreenModalHeader>
      );
    }

    let title = 'Run a Service';
    const {isEdit, service} = this.props;
    let serviceName = service.getName();
    if (serviceName) {
      serviceName = `"${serviceName}"`;
    } else {
      serviceName = 'Service';
    }

    if (isEdit) {
      title = `Edit ${serviceName}`;
    }

    return (
      <FullScreenModalHeader>
        <FullScreenModalHeaderActions
          actions={this.getSecondaryActions()}
          type="secondary" />
        <FullScreenModalHeaderTitle>
          {title}
        </FullScreenModalHeaderTitle>
        <FullScreenModalHeaderActions
          actions={this.getPrimaryActions()}
          type="primary" />
      </FullScreenModalHeader>
    );
  }

  getModalContent() {
    let {
      isJSONModeActive,
      serviceConfig,
      serviceFormActive,
      serviceJsonActive,
      servicePickerActive,
      serviceReviewActive
    } = this.state;

    let errorsMap = new Map();
    if (this.props.errors) {
      let message = this.props.errors.message;

      if (this.shouldForceSubmit()) {
        message = `App is currently locked by one or more deployments.
         Press the button again to forcefully change and deploy the
         new configuration.`;
      }
      errorsMap.set('/', [message]);

      if (this.props.errors.details) {
        this.props.errors.details.forEach(function ({errors, path}) {
          const existingMessages = errorsMap.get(path);

          let messages = errors;

          if (existingMessages) {
            messages = messages.concat(existingMessages);
          }

          errorsMap.set(path, messages);
        });
      }
    }

    // NOTE: Always prioritize review screen check
    if (serviceReviewActive) {
      return (

            <ServiceConfigDisplay
              onEditClick={this.handleGoBack}
              appConfig={serviceConfig}
              clearError={this.props.clearError}
              errors={errorsMap} />

      );
    }

    if (servicePickerActive) {
      return (
        <NewCreateServiceModalServicePicker
          onServiceSelect={this.handleServiceSelection} />
      );
    }

    if (serviceFormActive) {
      let {isEdit} = this.props;

      const SECTIONS = [
        ContainerServiceFormSection,
        EnvironmentFormSection,
        GeneralServiceFormSection,
        HealthChecksFormSection,
        NetworkingFormSection,
        VolumesFormSection,
        MultiContainerVolumesFormSection,
        MultiContainerNetworkingFormSection
      ];

      const jsonParserReducers = combineParsers(
        Hooks.applyFilter('serviceCreateJsonParserReducers', JSONParser)
      );

      let isPod = serviceConfig instanceof PodSpec;

      let jsonConfigReducers = combineReducers(
        Hooks.applyFilter('serviceJsonConfigReducers', JSONAppReducers)
      );

      if (isPod) {
        jsonConfigReducers = combineReducers(
          Hooks.applyFilter('serviceJsonConfigReducers', JSONPodReducers)
        );
      }

      const inputConfigReducers = combineReducers(
        Hooks.applyFilter('serviceInputConfigReducers',
          Object.assign({}, ...SECTIONS.map((item) => item.configReducers))
        )
      );

      return (
        <NewCreateServiceModalForm
          activeTab={this.state.activeTab}
          jsonParserReducers={jsonParserReducers}
          jsonConfigReducers={jsonConfigReducers}
          inputConfigReducers={inputConfigReducers}
          isJSONModeActive={isJSONModeActive}
          ref={(ref) => {
            return this.createComponent = ref;
          }}
          service={serviceConfig}
          onChange={this.handleServiceChange}
          onConvertToPod={this.handleConvertToPod}
          onErrorStateChange={this.handleServiceErrorChange}
          isEdit={isEdit} />
      );
    }

    if (serviceJsonActive) {
      return (
        <CreateServiceJsonOnly
          ref={(ref) => {
            return this.createComponent = ref;
          }}
          service={serviceConfig}
          onChange={this.handleServiceChange}
          onErrorStateChange={this.handleServiceErrorChange} />
      );
    }

    return null;
  }

  getPrimaryActions() {
    const {
      serviceFormActive,
      serviceJsonActive,
      servicePickerActive,
      serviceReviewActive
    } = this.state;

    const force = this.shouldForceSubmit();
    const runButtonLabel = force ? 'Force Run Service' : 'Run Service';
    const runButtonClassNames = classNames('flush-vertical', {
      'button-primary': !force,
      'button-danger': force
    });

    // NOTE: Always prioritize review screen check
    if (serviceReviewActive) {
      return [
        {
          className: runButtonClassNames,
          clickHandler: this.handleServiceRun,
          label: runButtonLabel
        }
      ];
    }

    if (servicePickerActive) {
      return null;
    }

    if (serviceFormActive) {
      return [
        {
          node: (
            <ToggleButton
              className="flush"
              checkboxClassName="toggle-button"
              checked={this.state.isJSONModeActive}
              onChange={this.handleJSONToggle}
              key="json-editor">
              JSON Editor
            </ToggleButton>
          )
        },
        {
          className: 'button-primary flush-vertical',
          clickHandler: this.handleServiceReview,
          disabled: this.state.serviceFormHasErrors,
          label: 'Review & Run'
        }
      ];
    }

    if (serviceJsonActive) {
      return [
        {
          className: 'button-primary flush-vertical',
          clickHandler: this.handleServiceReview,
          disabled: this.state.serviceFormHasErrors,
          label: 'Review & Run'
        }
      ];
    }

    return [];
  }

  getResetState(nextProps = this.props) {
    const newState = {
      activeTab: null,
      isJSONModeActive: false,
      serviceConfig: nextProps.service.getSpec(),
      serviceFormActive: false,
      serviceJsonActive: false,
      servicePickerActive: true,
      serviceReviewActive: false,
      serviceFormHasErrors: false
    };

    // Switch directly to form/json if edit
    if (nextProps.isEdit) {
      newState.servicePickerActive = false;

      if (nextProps.service instanceof PodSpec) {
        newState.serviceJsonActive = true;
      } else {
        newState.serviceFormActive = true;
      }
    }

    return newState;
  }

  getSecondaryActions() {
    const {
      servicePickerActive,
      serviceReviewActive
    } = this.state;
    let label = 'Back';

    if (servicePickerActive || (this.props.isEdit && !serviceReviewActive)) {
      label = 'Cancel';
    }

    return [
      {
        className: 'button-stroke',
        clickHandler: this.handleGoBack,
        label
      }
    ];
  }

  render() {
    const {props, state: {servicePickerActive, serviceReviewActive}} = this;
    let useGemini = false;

    if (servicePickerActive || serviceReviewActive) {
      useGemini = true;
    }

    return (

        <FullScreenModal
          header={this.getHeader()}
          onClose={this.handleClose}
          useGemini={useGemini}
          {...Util.omit(props, Object.keys(NewServiceFormModal.propTypes))}>
          {this.getModalContent()}
        </FullScreenModal>

    );
  }
}

NewServiceFormModal.propTypes = {
  clearError: PropTypes.func.isRequired,
  errors: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string
  ]),
  isEdit: PropTypes.bool,
  isPending: PropTypes.bool.isRequired,
  marathonAction: PropTypes.func.isRequired,
  onClose: PropTypes.func,
  service: PropTypes.instanceOf(Service).isRequired
};

module.exports = NewServiceFormModal;
<|endoftext|>


<commit_before>// Requires
var _ = require('lodash');
var glob = require("glob");
var Q = require('q');

function setup(options, imports, register) {
    // Import
    var vfs = imports.vfs;
    var workspace = imports.workspace;

    // Construct
    var filesSearch = function(args) {
        var d = Q.defer();

        args = _.defaults({}, args || {}, {
            'start': 0,
            'limit': 30
        });

        glob("**/*"+args.query+"*", {
            'cwd': workspace.root
        }, function (err, files) {
            if (err) {
                d.reject(err);
            } else {
                var results = _.map(files.slice(args.start, args.start+args.limit), function(path) {
                    return "/"+path;
                });

                d.resolve({
                    'files': results,
                    'n': _.size(files)
                });
            }
        });

        return d.promise;
    };

    // Register
    register(null, {
        "search": {
            files: filesSearch
        }
    });
}

// Exports
module.exports = setup;
<commit_msg>Remove folder from files search<commit_after>// Requires
var _ = require('lodash');
var glob = require("glob");
var Q = require('q');

function setup(options, imports, register) {
    // Import
    var vfs = imports.vfs;
    var workspace = imports.workspace;

    // Construct
    var filesSearch = function(args) {
        var d = Q.defer();

        args = _.defaults({}, args || {}, {
            'start': 0,
            'limit': 30
        });

        glob("**/*"+args.query+"*", {
            'cwd': workspace.root,
            'mark': true
        }, function (err, files) {
            if (err) {
                d.reject(err);
            } else {
                var results = _.chain(files)
                .filter(function(path) {
                    return !(!path.length || path[path.length-1] == "/");
                })
                .map(function(path) {
                    return "/"+path;
                })
                .value();

                d.resolve({
                    'files': results.slice(args.start, args.start+args.limit),
                    'n': _.size(results)
                });
            }
        });

        return d.promise;
    };

    // Register
    register(null, {
        "search": {
            files: filesSearch
        }
    });
}

// Exports
module.exports = setup;
<|endoftext|>


<commit_before>"use strict";

var trycatch = require('trycatch');
var qs = require('querystring');
var url = require('url');
var fs = require('fs');
var ua = require('ua-parser');
var MD = require('mobile-detect');
var ActionMgr = require('./actionmgr');
exports.HttpApi = function (db, converter, prefix, codepath, ref, valuemgr) {
	var code;
	var actionmgr = new ActionMgr.ActionMgr(db, converter, prefix, "mapping", valuemgr, ref);
	var me = this;
	this.onchange = function () { };
	function loadCode(appid, actionid, callback) {
		// cache mtcode in code for minimize disk usage, lazy load
		if (code === undefined) {
			fs.readFile(codepath, 'ascii', function (err, data) {
				code = data;
				replaceParam();
			});
		}
		else replaceParam();

		function replaceParam() {
			callback(code.replace('$APPID$', appid).replace('$ACTIONID$', actionid));
		}
	}

	// purpose get real ip address
	function getRemoteAddress(req) {
		return req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress;
	}

	// purpose: extract basic info from user agent and request parameter
	function trackBasic(request) {
		console.log("=====trace basic");
		var useragent = request.headers['user-agent'];
		var r = ua.parse(useragent);
		var uri = request.params._url || '';
		var md = new MD(useragent);
		var devicetype = 'desktop';
		if (md.tablet() !== null)
			devicetype = 'tablet';
		else if (md.phone() !== null)
			devicetype = 'phone';
		else
			devicetype = 'desktop';
		if (uri === "" || uri.startsWith(request.headers.referer) === false) uri = request.headers.referer || '';
		var res = {
			_url: uri,
			_callback : request.params._callback,
			_ref: request.params._ref,
			_typeid: request.params._typeid,
			_ip: getRemoteAddress(request),
			_deltat: request.params._deltat,
			_os: r.os.family,
			_browser: r.ua.family,
			_browserver: r.ua.major + '.' + r.ua.minor,
			_osver: r.os.major + '.' + r.os.minor,
			_deviceid: r.device.family,
			_scr: request.params._scr,
			_lang: request.headers["accept-language"],
			_devicetype: devicetype
		};

		for (var i in request.params)
			// if (request.params.hasOwnProperty(i))
			if (i.startsWith('_') === false)
				res[i] = isNaN(request.params[i]) ? request.params[i] : parseFloat(request.params[i]);

		// extract campaign
		var query = url.parse(uri, true).query;
		res._utm_source = query.utm_source;
		res._utm_campaign = query.utm_campaign;
		res._utm_term = query.utm_term;
		res._utm_content = query.utm_content;
		res._utm_medium = query.utm_medium;

		return res;
	}

	function getMtid(req, appid, res, callback) {
		console.log("=====get mtid");
		var mtid = getCookie(req, "mtid");
		console.log("mtid: "+mtid);
		if (mtid === undefined || mtid.length != 24) {
			mtid = req.params._mtid;
			if (mtid === undefined) {
				return actionmgr.setupRaw(appid, function (mtid) {
					setCookie(res, "mtid", mtid, appid);
					callback(mtid);
				});
			}
		}

		// check if mtid is valid
		actionmgr.ismtidValid(appid, mtid, function (ret) {
			if (ret) callback(mtid);
			else {
				eraseCookie(res, "mtid", appid);
				actionmgr.setupRaw(appid, function (mtid) {
					setCookie(res, "mtid", mtid, appid);
					callback(mtid);
				});
			}
		});
	}

	function eraseCookie(res, name, path) {
		res.setHeader('Set-Cookie', name + "=x; expires=Wed, 21 Aug 1995 11:11:11 GMT; path=/" + path);
	}

	function eraseCookieDuplicate(res, name, path) {
		res.setHeader('Set-Cookie', name + "=x; expires=Wed, 21 Aug 1995 11:11:11 GMT");
	}

	function clearDuplicate(req, res){
		console.log("clear duplicate");
		eraseCookieDuplicate(res, 'mtid', req.appid);
		res.end();
	}

	function clear(req, res) {
		console.log("=====clear");
		// delete the cookie
		eraseCookie(res, 'mtid', req.appid);
		res.end();
	}

	function track(req, res) {
		console.log("=====track");
		var appid = req.appid;
		var data = trackBasic(req);
		var callback = (data._callback == 'true' || data._callback == true);
		delete data._callback;

		getMtid(req, appid, res, function (mtid) {
			data._mtid = mtid;
			actionmgr.saveRaw(appid, data, function (actionid) {
				me.onchange(appid, "type." + data._typeid);

				if (callback === true)
				{
					res.setHeader('Content-Type', 'application/javascript');
					res.end("mt.actionid = \"" + mtid + "\";");
				}
				else {
					res.setHeader('Content-Type', 'text/plain');
					res.end("\"" + mtid + "\"");
				}

			});
		});
	}

	// identify an user
	// if mtid not exists in the parameter ->create one
	function info(req, res) {
		console.log("=====info");
		var appid = req.appid;
		getMtid(req, appid, res, function (mtid) {
			var data = {};
			for (var i in req.params) if (req.params.hasOwnProperty(i))
				if (i.startsWith('_') === false) data[i] = isNaN(req.params[i]) ? req.params[i] : parseFloat(req.params[i]);

			actionmgr.identifyRaw(appid, { mtid: mtid, user: data }, function (mtid) {
				//set new mtid if need
				setCookie(res, "mtid", mtid, appid);
				res.setHeader('Content-Type', 'text/plain');

				res.end("\"" + mtid + "\"");
			});
		});
	}

	function x(req, res) {
		actionmgr.x(req, res, function () {
		});
	}

	function checkMtid(req,res){
		// check duplicate
		if(checkNotDuplicateMtid(req,res)){
			console.log("true");
			// check worng mtid
		var mtid = getCookie(req,"mtid");
		if(mtid !== undefined && mtid.length != 24){
				console.log("wrong mtid");
				clear(req, res);
			}
		}
			res.setHeader('Content-Type', 'text/plain');
			res.end();

	}

	function checkNotDuplicateMtid(req,res){
		console.log("checkDuplicate");
		var cookieHeaders = req.headers.cookie;
		if(cookieHeaders === undefined) return true;
		var cookie = cookieHeaders.split(';');
		var count = 0;
		var value = "";
		for(var i =0; i< cookie.length;i++){
			var c = cookie[i];
			while (c.charAt(0)==' ') {
				c = c.substring(1);
			}
			if (c.indexOf("mtid") == 0) {
				if(count == 0){
					var cmtid = "mtid=";
					value = c.substring(cmtid.length,c.length);
				}
				count++;
			}
		}
		if(count > 1){
			clearDuplicate(req,res);
			return false;
		}
		return true;
	}

	function getCookie(req, name) {
		var list = {}, rc = req.headers.cookie;
		rc && rc.split(';').forEach(function (cookie) {
			var parts = cookie.split('=');
			list[parts.shift().trim()] = decodeURIComponent(parts.join('='));
		});
		return list[name];
	}

	function setCookie(res, name, value, path) {
		var tenyearlater = new Date().getYear() + 10 + 1900;
		res.setHeader('Set-Cookie', name + '=' + encodeURIComponent(value) + "; expires=Wed, 21 Aug " + tenyearlater + " 11:11:11 GMT; path=/" + path);

	}

	function fix(req, res) {
		console.log("=====fix");
		var appid = req.appid;
		var actionid = req.actionid;
		var lastactionid = req.lastactionid;
		var data = trackBasic(req);
		getMtid(req, appid, res, function (mtid) {
			data._mtid = mtid;
			actionmgr.fixRaw(appid, actionid, lastactionid, data, function () {
				res.end();
			});
		});
	}

	function suggest(req, res) {
		console.log("=====suggest");
		valuemgr.suggest(req.appid + "", req.typeid + "", req.field + "", req.qr + "", function (results) {
			res.setHeader('Content-Type', 'application/json');
			res.setHeader('Access-Control-Allow-Origin', '*');
			//res.setHeader('Access-Control-Allow-Origin', 'https://app.meotrics.com env=HTTPS');

			res.setHeader('Access-Control-Allow-Methods', 'GET');
			res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
			res.setHeader('Access-Control-Allow-Credentials', true);
			res.end(JSON.stringify(results));
		});
	}

	function pageview(req, res) {
		console.log("=====pageview");
		var appid = req.appid;
		// record an new pageview
		var data = trackBasic(req);
		getMtid(req, appid, res, function (mtid) {
			data._mtid = mtid;
			data._typeid = 'pageview';

			actionmgr.saveRaw(appid, data, function (actionid) {
				me.onchange(appid, 'type.pageview');
				// return code
				loadCode(appid, actionid, function (code) {
					res.setHeader('Content-Type', 'text/css');
					res.end(code);
				});
			});
		});
	}

	this.route = function (req, res) {
		var me = this;
		trycatch(function () {
			var url_parts = url.parse(req.url, true);
			if (req.method === 'POST') {
				var body = '';
				req.on('data', function (data) {
					body += data;
				});
				req.on('end', function () {
				req['params'] = qs.parse(body);
				handle(req, res, url_parts.pathname);
			});
		}
			else if (req.method === 'GET') {
				req['params'] = url_parts.query;
				handle(req, res, url_parts.pathname);
			}

			function handle(req, res, path) {
				var parts = path.split('/');
				res.statusCode = 200;
				req['appid'] = parts[1];
				var action = parts[2];
				if (action === 'track') track(req, res);
				else if(action == 'check') checkMtid(req,res);
				else if (action === '' || action === undefined) pageview(req, res);
				else if (action === 'clear') clear(req, res);
				else if (action === 'info') info(req, res);
				else if (action === 'x') {
					req['actionid'] = parts[3];

					x(req, res);
				}
				else if (action === 'suggest') {
					req['typeid'] = parts[3];
					req['field'] = parts[4];
					req['qr'] = parts[5];
					suggest(req, res);
				}
				else if (action === 'fix') {
					var query = url.parse(path, true).query;
					req['actionid'] = req.params.actionid;
					req['lastactionid'] = req.params.lastactionid;
						if(req.params.actionid==null) console.log('errfix: ', path);
					fix(req, res);
				} else {
					res.statusCode = 404;
					res.end('action "' + action + '" not found, action must be one of [x, clear, info, fix, track]');
				}
			}
		}, function (err) {
			res.statusCode = 500;
			res.end();
			console.log(err, err.stack);
		});
	};
};
<commit_msg>debug devicetype<commit_after>"use strict";

var trycatch = require('trycatch');
var qs = require('querystring');
var url = require('url');
var fs = require('fs');
var ua = require('ua-parser');
var MD = require('mobile-detect');
var ActionMgr = require('./actionmgr');
exports.HttpApi = function (db, converter, prefix, codepath, ref, valuemgr) {
	var code;
	var actionmgr = new ActionMgr.ActionMgr(db, converter, prefix, "mapping", valuemgr, ref);
	var me = this;
	this.onchange = function () { };
	function loadCode(appid, actionid, callback) {
		// cache mtcode in code for minimize disk usage, lazy load
		if (code === undefined) {
			fs.readFile(codepath, 'ascii', function (err, data) {
				code = data;
				replaceParam();
			});
		}
		else replaceParam();

		function replaceParam() {
			callback(code.replace('$APPID$', appid).replace('$ACTIONID$', actionid));
		}
	}

	// purpose get real ip address
	function getRemoteAddress(req) {
		return req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress;
	}

	// purpose: extract basic info from user agent and request parameter
	function trackBasic(request) {
		console.log("=====trace basic");
		var useragent = request.headers['user-agent'];
		var r = ua.parse(useragent);
		var uri = request.params._url || '';
		var md = new MD(useragent);
		var devicetype = 'desktop';
		if (md.tablet() !== null)
			devicetype = 'tablet';
		else if (md.phone() !== null)
			devicetype = 'phone';
		else
			devicetype = 'desktop';

		console.log(devicetype);
		if (uri === "" || uri.startsWith(request.headers.referer) === false) uri = request.headers.referer || '';
		var res = {
			_url: uri,
			_callback : request.params._callback,
			_ref: request.params._ref,
			_typeid: request.params._typeid,
			_ip: getRemoteAddress(request),
			_deltat: request.params._deltat,
			_os: r.os.family,
			_browser: r.ua.family,
			_browserver: r.ua.major + '.' + r.ua.minor,
			_osver: r.os.major + '.' + r.os.minor,
			_deviceid: r.device.family,
			_scr: request.params._scr,
			_lang: request.headers["accept-language"],
			_devicetype: devicetype
		};

		for (var i in request.params)
			// if (request.params.hasOwnProperty(i))
			if (i.startsWith('_') === false)
				res[i] = isNaN(request.params[i]) ? request.params[i] : parseFloat(request.params[i]);

		// extract campaign
		var query = url.parse(uri, true).query;
		res._utm_source = query.utm_source;
		res._utm_campaign = query.utm_campaign;
		res._utm_term = query.utm_term;
		res._utm_content = query.utm_content;
		res._utm_medium = query.utm_medium;

		return res;
	}

	function getMtid(req, appid, res, callback) {
		console.log("=====get mtid");
		var mtid = getCookie(req, "mtid");
		console.log("mtid: "+mtid);
		if (mtid === undefined || mtid.length != 24) {
			mtid = req.params._mtid;
			if (mtid === undefined) {
				return actionmgr.setupRaw(appid, function (mtid) {
					setCookie(res, "mtid", mtid, appid);
					callback(mtid);
				});
			}
		}

		// check if mtid is valid
		actionmgr.ismtidValid(appid, mtid, function (ret) {
			if (ret) callback(mtid);
			else {
				eraseCookie(res, "mtid", appid);
				actionmgr.setupRaw(appid, function (mtid) {
					setCookie(res, "mtid", mtid, appid);
					callback(mtid);
				});
			}
		});
	}

	function eraseCookie(res, name, path) {
		res.setHeader('Set-Cookie', name + "=x; expires=Wed, 21 Aug 1995 11:11:11 GMT; path=/" + path);
	}

	function eraseCookieDuplicate(res, name, path) {
		res.setHeader('Set-Cookie', name + "=x; expires=Wed, 21 Aug 1995 11:11:11 GMT");
	}

	function clearDuplicate(req, res){
		console.log("clear duplicate");
		eraseCookieDuplicate(res, 'mtid', req.appid);
		res.end();
	}

	function clear(req, res) {
		console.log("=====clear");
		// delete the cookie
		eraseCookie(res, 'mtid', req.appid);
		res.end();
	}

	function track(req, res) {
		console.log("=====track");
		var appid = req.appid;
		var data = trackBasic(req);
		var callback = (data._callback == 'true' || data._callback == true);
		delete data._callback;

		getMtid(req, appid, res, function (mtid) {
			data._mtid = mtid;
			actionmgr.saveRaw(appid, data, function (actionid) {
				me.onchange(appid, "type." + data._typeid);

				if (callback === true)
				{
					res.setHeader('Content-Type', 'application/javascript');
					res.end("mt.actionid = \"" + mtid + "\";");
				}
				else {
					res.setHeader('Content-Type', 'text/plain');
					res.end("\"" + mtid + "\"");
				}

			});
		});
	}

	// identify an user
	// if mtid not exists in the parameter ->create one
	function info(req, res) {
		console.log("=====info");
		var appid = req.appid;
		getMtid(req, appid, res, function (mtid) {
			var data = {};
			for (var i in req.params) if (req.params.hasOwnProperty(i))
				if (i.startsWith('_') === false) data[i] = isNaN(req.params[i]) ? req.params[i] : parseFloat(req.params[i]);

			actionmgr.identifyRaw(appid, { mtid: mtid, user: data }, function (mtid) {
				//set new mtid if need
				setCookie(res, "mtid", mtid, appid);
				res.setHeader('Content-Type', 'text/plain');

				res.end("\"" + mtid + "\"");
			});
		});
	}

	function x(req, res) {
		actionmgr.x(req, res, function () {
		});
	}

	function checkMtid(req,res){
		// check duplicate
		if(checkNotDuplicateMtid(req,res)){
			console.log("true");
			// check worng mtid
		var mtid = getCookie(req,"mtid");
		if(mtid !== undefined && mtid.length != 24){
				console.log("wrong mtid");
				clear(req, res);
			}
		}
			res.setHeader('Content-Type', 'text/plain');
			res.end();

	}

	function checkNotDuplicateMtid(req,res){
		console.log("checkDuplicate");
		var cookieHeaders = req.headers.cookie;
		if(cookieHeaders === undefined) return true;
		var cookie = cookieHeaders.split(';');
		var count = 0;
		var value = "";
		for(var i =0; i< cookie.length;i++){
			var c = cookie[i];
			while (c.charAt(0)==' ') {
				c = c.substring(1);
			}
			if (c.indexOf("mtid") == 0) {
				if(count == 0){
					var cmtid = "mtid=";
					value = c.substring(cmtid.length,c.length);
				}
				count++;
			}
		}
		if(count > 1){
			clearDuplicate(req,res);
			return false;
		}
		return true;
	}

	function getCookie(req, name) {
		var list = {}, rc = req.headers.cookie;
		rc && rc.split(';').forEach(function (cookie) {
			var parts = cookie.split('=');
			list[parts.shift().trim()] = decodeURIComponent(parts.join('='));
		});
		return list[name];
	}

	function setCookie(res, name, value, path) {
		var tenyearlater = new Date().getYear() + 10 + 1900;
		res.setHeader('Set-Cookie', name + '=' + encodeURIComponent(value) + "; expires=Wed, 21 Aug " + tenyearlater + " 11:11:11 GMT; path=/" + path);

	}

	function fix(req, res) {
		console.log("=====fix");
		var appid = req.appid;
		var actionid = req.actionid;
		var lastactionid = req.lastactionid;
		var data = trackBasic(req);
		getMtid(req, appid, res, function (mtid) {
			data._mtid = mtid;
			actionmgr.fixRaw(appid, actionid, lastactionid, data, function () {
				res.end();
			});
		});
	}

	function suggest(req, res) {
		console.log("=====suggest");
		valuemgr.suggest(req.appid + "", req.typeid + "", req.field + "", req.qr + "", function (results) {
			res.setHeader('Content-Type', 'application/json');
			res.setHeader('Access-Control-Allow-Origin', '*');
			//res.setHeader('Access-Control-Allow-Origin', 'https://app.meotrics.com env=HTTPS');

			res.setHeader('Access-Control-Allow-Methods', 'GET');
			res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
			res.setHeader('Access-Control-Allow-Credentials', true);
			res.end(JSON.stringify(results));
		});
	}

	function pageview(req, res) {
		console.log("=====pageview");
		var appid = req.appid;
		// record an new pageview
		var data = trackBasic(req);
		getMtid(req, appid, res, function (mtid) {
			data._mtid = mtid;
			data._typeid = 'pageview';

			actionmgr.saveRaw(appid, data, function (actionid) {
				me.onchange(appid, 'type.pageview');
				// return code
				loadCode(appid, actionid, function (code) {
					res.setHeader('Content-Type', 'text/css');
					res.end(code);
				});
			});
		});
	}

	this.route = function (req, res) {
		var me = this;
		trycatch(function () {
			var url_parts = url.parse(req.url, true);
			if (req.method === 'POST') {
				var body = '';
				req.on('data', function (data) {
					body += data;
				});
				req.on('end', function () {
				req['params'] = qs.parse(body);
				handle(req, res, url_parts.pathname);
			});
		}
			else if (req.method === 'GET') {
				req['params'] = url_parts.query;
				handle(req, res, url_parts.pathname);
			}

			function handle(req, res, path) {
				var parts = path.split('/');
				res.statusCode = 200;
				req['appid'] = parts[1];
				var action = parts[2];
				if (action === 'track') track(req, res);
				else if(action == 'check') checkMtid(req,res);
				else if (action === '' || action === undefined) pageview(req, res);
				else if (action === 'clear') clear(req, res);
				else if (action === 'info') info(req, res);
				else if (action === 'x') {
					req['actionid'] = parts[3];

					x(req, res);
				}
				else if (action === 'suggest') {
					req['typeid'] = parts[3];
					req['field'] = parts[4];
					req['qr'] = parts[5];
					suggest(req, res);
				}
				else if (action === 'fix') {
					var query = url.parse(path, true).query;
					req['actionid'] = req.params.actionid;
					req['lastactionid'] = req.params.lastactionid;
						if(req.params.actionid==null) console.log('errfix: ', path);
					fix(req, res);
				} else {
					res.statusCode = 404;
					res.end('action "' + action + '" not found, action must be one of [x, clear, info, fix, track]');
				}
			}
		}, function (err) {
			res.statusCode = 500;
			res.end();
			console.log(err, err.stack);
		});
	};
};
<|endoftext|>


<commit_before>
//
// Copyright 2016 Kary Foundation, Inc.
//   Author: Pouya Kary <k@karyfoundation.org>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

//
// ─── CONSTANTS ──────────────────────────────────────────────────────────────────
//

    // Quartet: regex-sequence.quartet
    const sequenceRegEx = /^(?:\(.*\)|\[.*\])$/;

    // Quartet: quartet-html-entities.quartet
    const htmlSpecialEntities = /&(?:nbsp|#160|gt|lt);/g;

    // Quartet: html-entities.quartet
    const selectedBlockHTMl = /\<span class\="console\-highlight\-active\-block"\>(.*)\<\/span\>/g;

//
// ─── TO UNICODE ─────────────────────────────────────────────────────────────────
//

    function quartetUnicodify ( text ) {
        let result = [ ];
        for ( let character of text ) {
            result.push( getUnicodeCharacter( character ) );
        }
        return result.join('');
    }

//
// ─── GET REGEX FROM HTML ────────────────────────────────────────────────────────
//

    function quartetGetWildcardFromQuartetEncodedHTML ( code ) {
        return code.replace( selectedBlockHTMl  , ( val, matchOne ) => matchOne )
                   .replace( / /g          , ' '                           )
                   .replace( />/g            , '>'                           )
                   .replace( /</g            , '<'                           );
    }

//
// ─── GET UNICODE CHARACTER ──────────────────────────────────────────────────────
//

    function getUnicodeCharacter ( character ) {
        let num = character.charCodeAt( 0 ).toString( 16 ).toUpperCase( );
        let size = 4 - num.length;
        for ( let index = 0; index < size; index++ ) num = '0' + num;
        return '\\u' + num;
    }

//
// ─── SEQUENCE MAKER ─────────────────────────────────────────────────────────────
//

    function quartetSequence ( code ) {
        if ( sequenceRegEx.test( code ) ) return code;
        return ( quartetGetStringLength( code ) <= 1 )? code : '(?:' + code + ')';
    }

//
// ─── ENCODE TEXT ────────────────────────────────────────────────────────────────
//

    function quartetEncodeText ( code ) {
        let result = [ ];
        for ( let character of code ) {
            switch ( character ) {
                case '.':
                case '\\':
                case '+':
                case '*':
                case '?':
                case '[':
                case '^':
                case ']':
                case '$':
                case '(':
                case ')':
                case '{':
                case '}':
                case '=':
                case '!':
                case '<':
                case '>':
                case '|':
                case ':':
                case '-':
                case '/':
                    result.push( `\\${ character }` );
                    break;
                case ' ':
                    result.push( ' ');
                    break;
                default:
                    result.push( character );
            }
        }
        return quartetEncodeHTML( result.join('') );
    }

//
// ─── SPACE ENCODE ───────────────────────────────────────────────────────────────
//

    function quartetSpaceEncode ( text ) {
        return text.replace(/ /g, ' ');
    }

//
// ─── GET STRING LENGTH ──────────────────────────────────────────────────────────
//

    function quartetGetStringLength ( code ) {
        return code.replace( htmlSpecialEntities, ' ' ).length;
    }

//
// ─── SPACE DECODE ───────────────────────────────────────────────────────────────
//

    function quartetSpaceDecode ( text ) {
        return text.replace( /\\u0020/g, ' ' );
    }

//
// ─── MAKE ALPHABET ──────────────────────────────────────────────────────────────
//

    function quartetAlphabet ( sigma ) {
        if ( sigma.length === 0 ) return '';
        return quartetEncodeHTML( ( sigma.length === 1 )? sigma[ 0 ] : '[' + sigma.join('') + ']' );
    }

//
// ─── ENCODE FOR HTML ────────────────────────────────────────────────────────────
//

    function quartetEncodeHTML ( text ) {
        return text.replace( /<|>/, match => {
            switch ( match ) {
                case '<':
                    return '<';
                case '>':
                    return '>';
            }
        });
    }

// ────────────────────────────────────────────────────────────────────────────────
<commit_msg>paths optimized for orchestra connector<commit_after>
//
// Copyright 2016 Kary Foundation, Inc.
//   Author: Pouya Kary <k@karyfoundation.org>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

//
// ─── CONSTANTS ──────────────────────────────────────────────────────────────────
//

    // ../../quartets/regex-sequence.quartet
    const sequenceRegEx = /^(?:\(.*\)|\[.*\])$/;

    // ../../quartets/quartet-html-entities.quartet
    const htmlSpecialEntities = /&(?:nbsp|#160|gt|lt);/g;

    // ../../quartets/html-entities.quartet
    const selectedBlockHTMl = /\<span class\="console\-highlight\-active\-block"\>(.*)\<\/span\>/g;

//
// ─── TO UNICODE ─────────────────────────────────────────────────────────────────
//

    function quartetUnicodify ( text ) {
        let result = [ ];
        for ( let character of text ) {
            result.push( getUnicodeCharacter( character ) );
        }
        return result.join('');
    }

//
// ─── GET REGEX FROM HTML ────────────────────────────────────────────────────────
//

    function quartetGetWildcardFromQuartetEncodedHTML ( code ) {
        return code.replace( selectedBlockHTMl  , ( val, matchOne ) => matchOne )
                   .replace( / /g          , ' '                           )
                   .replace( />/g            , '>'                           )
                   .replace( /</g            , '<'                           );
    }

//
// ─── GET UNICODE CHARACTER ──────────────────────────────────────────────────────
//

    function getUnicodeCharacter ( character ) {
        let num = character.charCodeAt( 0 ).toString( 16 ).toUpperCase( );
        let size = 4 - num.length;
        for ( let index = 0; index < size; index++ ) num = '0' + num;
        return '\\u' + num;
    }

//
// ─── SEQUENCE MAKER ─────────────────────────────────────────────────────────────
//

    function quartetSequence ( code ) {
        if ( sequenceRegEx.test( code ) ) return code;
        return ( quartetGetStringLength( code ) <= 1 )? code : '(?:' + code + ')';
    }

//
// ─── ENCODE TEXT ────────────────────────────────────────────────────────────────
//

    function quartetEncodeText ( code ) {
        let result = [ ];
        for ( let character of code ) {
            switch ( character ) {
                case '.':
                case '\\':
                case '+':
                case '*':
                case '?':
                case '[':
                case '^':
                case ']':
                case '$':
                case '(':
                case ')':
                case '{':
                case '}':
                case '=':
                case '!':
                case '<':
                case '>':
                case '|':
                case ':':
                case '-':
                case '/':
                    result.push( `\\${ character }` );
                    break;
                case ' ':
                    result.push( ' ');
                    break;
                default:
                    result.push( character );
            }
        }
        return quartetEncodeHTML( result.join('') );
    }

//
// ─── SPACE ENCODE ───────────────────────────────────────────────────────────────
//

    function quartetSpaceEncode ( text ) {
        return text.replace(/ /g, ' ');
    }

//
// ─── GET STRING LENGTH ──────────────────────────────────────────────────────────
//

    function quartetGetStringLength ( code ) {
        return code.replace( htmlSpecialEntities, ' ' ).length;
    }

//
// ─── SPACE DECODE ───────────────────────────────────────────────────────────────
//

    function quartetSpaceDecode ( text ) {
        return text.replace( /\\u0020/g, ' ' );
    }

//
// ─── MAKE ALPHABET ──────────────────────────────────────────────────────────────
//

    function quartetAlphabet ( sigma ) {
        if ( sigma.length === 0 ) return '';
        return quartetEncodeHTML( ( sigma.length === 1 )? sigma[ 0 ] : '[' + sigma.join('') + ']' );
    }

//
// ─── ENCODE FOR HTML ────────────────────────────────────────────────────────────
//

    function quartetEncodeHTML ( text ) {
        return text.replace( /<|>/, match => {
            switch ( match ) {
                case '<':
                    return '<';
                case '>':
                    return '>';
            }
        });
    }

// ────────────────────────────────────────────────────────────────────────────────
<|endoftext|>


<commit_before>E2.plugins["key_press_generator"] = function(core, node) {
	var self = this;

	this.desc = 'Emits true on the next update after the key matching the set filter has been pressed and false once after it has been released.';
	this.input_slots = [];
	this.output_slots = [
		{ name: 'signal', dt: core.datatypes.BOOL, desc: 'Emits true once when the set key is pressed.' }
	];
	this.state = { key: 0, type: 0 };

	this.update_output = function(slot)
	{
		return self.output;
	};

	this.create_ui = function()
	{
		var bm = core.renderer.blend_mode;
		var items = [
			[-1, '[Pick a key]'],
			[8, 'Backspace'], [9, 'Tab'], [13, 'Enter'],
			[33, 'Page up'], [34, 'Page down'], [35, 'End'],
			[36, 'Home'], [37, 'Left arrow'], [38, 'Up arrow'],
			[39, 'Right arrow'], [40, 'Down arrow'], [45, 'Insert'], [46, 'Delete'],
			[48, '0'], [49, '1'], [50, '2'],
			[51, '3'], [52, '4'], [53, '5'],
			[54, '6'], [55, '7'], [56, '8'],
			[57, '9'],
			[65, 'a'],
			[66, 'b'], [67, 'c'], [68, 'd'],
			[69, 'e'], [70, 'f'], [71, 'g'],
			[72, 'h'], [73, 'i'], [74, 'j'],
			[75, 'k'], [76, 'l'], [77, 'm'],
			[78, 'n'], [79, 'o'], [80, 'p'],
			[81, 'q'], [82, 'r'], [83, 's'],
			[84, 't'], [85, 'u'], [86, 'v'],
			[87, 'w'], [88, 'x'], [89, 'y'],
			[90, 'z']
		];

		var dom = make('div');
		var inp = $('<select id="key" title="Select key" />', { selectedIndex: 0 });
		var inp_type = $('<select id="o_type" title="Select output type" />', { selectedIndex: 0 });

		for(var i = 0, len = items.length; i < len; i++)
		{
			var item = items[i];
			$('<option />', { value: item[0], text: item[1] }).appendTo(inp);
		}

		$('<option />', { value: 0, text: 'Impulse' }).appendTo(inp_type);
		$('<option />', { value: 1, text: 'Continuous' }).appendTo(inp_type);

		inp.change(function()
		{
			self.state.key = parseInt(inp.val());
			self.reset_keystate();
			self.updated = true;
			inp.blur();
		});

		inp_type.change(function()
		{
			self.state.type = parseInt(inp_type.val());
			self.updated = true;
			inp_type.blur();
		});

		inp.css('width', '100px');
		inp_type.css('width', '100px');

		dom.append(inp);
		dom.append(make('br'));
		dom.append(inp_type);

		return dom;
	};

	this.key_down = function(e)
	{
		if(e.originalEvent.keyCode !== self.state.key)
			return;

		self.key_state = true;
		self.updated = true;
	};

	this.key_up = function(e)
	{
		if(e.originalEvent.keyCode !== self.state.key)
			return;

		self.key_state = false;
		self.updated = true;
	};

	this.reset_keystate = function()
	{
		self.output = self.last_state = self.key_state = false;
	};

	this.update_state = function(delta_t)
	{
		self.output = (self.state.type === 0 && self.key_state === self.last_state) ? false : self.key_state;
		self.last_state = self.key_state;
	};

	this.state_changed = function(ui)
	{
		if(!ui)
		{
			self.reset_keystate();
			$(document).keydown(self.key_down);
			$(document).keyup(self.key_up);
		}
		else
		{
			ui.find('#key').val(self.state.key);
			ui.find('#o_type').val(self.state.type);
		}
	};
};
<commit_msg>Refinement of the keyboard input generator's new impulse logic, such that a false value is not continuously emitted in impulse more.<commit_after>E2.plugins["key_press_generator"] = function(core, node) {
	var self = this;

	this.desc = 'Emits true on the next update after the key matching the set filter has been pressed and false once after it has been released.';
	this.input_slots = [];
	this.output_slots = [
		{ name: 'signal', dt: core.datatypes.BOOL, desc: 'Emits true once when the set key is pressed.' }
	];
	this.state = { key: 0, type: 0 };

	this.update_output = function(slot)
	{
		return self.key_state;
	};

	this.create_ui = function()
	{
		var bm = core.renderer.blend_mode;
		var items = [
			[-1, '[Pick a key]'],
			[8, 'Backspace'], [9, 'Tab'], [13, 'Enter'],
			[33, 'Page up'], [34, 'Page down'], [35, 'End'],
			[36, 'Home'], [37, 'Left arrow'], [38, 'Up arrow'],
			[39, 'Right arrow'], [40, 'Down arrow'], [45, 'Insert'], [46, 'Delete'],
			[48, '0'], [49, '1'], [50, '2'],
			[51, '3'], [52, '4'], [53, '5'],
			[54, '6'], [55, '7'], [56, '8'],
			[57, '9'],
			[65, 'a'],
			[66, 'b'], [67, 'c'], [68, 'd'],
			[69, 'e'], [70, 'f'], [71, 'g'],
			[72, 'h'], [73, 'i'], [74, 'j'],
			[75, 'k'], [76, 'l'], [77, 'm'],
			[78, 'n'], [79, 'o'], [80, 'p'],
			[81, 'q'], [82, 'r'], [83, 's'],
			[84, 't'], [85, 'u'], [86, 'v'],
			[87, 'w'], [88, 'x'], [89, 'y'],
			[90, 'z']
		];

		var dom = make('div');
		var inp = $('<select id="key" title="Select key" />', { selectedIndex: 0 });
		var inp_type = $('<select id="o_type" title="Select output type" />', { selectedIndex: 0 });

		for(var i = 0, len = items.length; i < len; i++)
		{
			var item = items[i];
			$('<option />', { value: item[0], text: item[1] }).appendTo(inp);
		}

		$('<option />', { value: 0, text: 'Impulse' }).appendTo(inp_type);
		$('<option />', { value: 1, text: 'Continuous' }).appendTo(inp_type);

		inp.change(function()
		{
			self.state.key = parseInt(inp.val());
			self.reset_keystate();
			self.updated = true;
			inp.blur();
		});

		inp_type.change(function()
		{
			self.state.type = parseInt(inp_type.val());
			self.updated = true;
			inp_type.blur();
		});

		inp.css('width', '100px');
		inp_type.css('width', '100px');

		dom.append(inp);
		dom.append(make('br'));
		dom.append(inp_type);

		return dom;
	};

	this.key_down = function(e)
	{
		if(e.originalEvent.keyCode !== self.state.key)
			return;

		self.key_state = true;
		self.updated = true;
	};

	this.key_up = function(e)
	{
		if(e.originalEvent.keyCode !== self.state.key)
			return;

		self.key_state = false;
		self.updated = true;
	};

	this.reset_keystate = function()
	{
		self.last_state = self.key_state = false;
	};

	this.update_state = function(delta_t)
	{
		if(self.state.type === 0 && self.key_state === self.last_state)
			self.updated = false;

		self.last_state = self.key_state;
	};

	this.state_changed = function(ui)
	{
		if(!ui)
		{
			self.reset_keystate();
			$(document).keydown(self.key_down);
			$(document).keyup(self.key_up);
		}
		else
		{
			ui.find('#key').val(self.state.key);
			ui.find('#o_type').val(self.state.type);
		}
	};
};
<|endoftext|>


<commit_before>var click_handlers = (function () {

// We don't actually export anything yet; this is just for consistency.
var exports = {};

// You won't find every click handler here, but it's a good place to start!

$(function () {

    // MOUSE MOVING DETECTION
    var clicking = false;
    var mouse_moved = false;

    function mousedown() {
        mouse_moved = false;
        clicking = true;
    }

    function mousemove() {
        if (clicking) {
            mouse_moved = true;
        }
    }

    $("#main_div").on("mousedown", ".messagebox", mousedown);
    $("#main_div").on("mousemove", ".messagebox", mousemove);

    // MESSAGE CLICKING

    function is_clickable_message_element(target) {
        return target.is("a") || target.is("img.message_inline_image") || target.is("img.twitter-avatar") ||
            target.is("div.message_length_controller") || target.is("textarea") || target.is("input") ||
            target.is("i.edit_content_button");
    }

    $("#main_div").on("click", ".messagebox", function (e) {
        if (is_clickable_message_element($(e.target))) {
            // If this click came from a hyperlink, don't trigger the
            // reply action.  The simple way of doing this is simply
            // to call e.stopPropagation() from within the link's
            // click handler.
            //
            // Unfortunately, on Firefox, this breaks Ctrl-click and
            // Shift-click, because those are (apparently) implemented
            // by adding an event listener on link clicks, and
            // stopPropagation prevents them from being called.
            return;
        }
        if (!(clicking && mouse_moved)) {
            // Was a click (not a click-and-drag).
            var row = $(this).closest(".message_row");
            var id = rows.id(row);

            if (message_edit.is_editing(id)) {
                // Clicks on a message being edited shouldn't trigger a reply.
                return;
            }

            current_msg_list.select_id(id);
            compose.respond_to_message({trigger: 'message click'});
            e.stopPropagation();
            popovers.hide_all();
        }
        mouse_moved = false;
        clicking = false;
    });

    function toggle_star(message_id) {
        // Update the message object pointed to by the various message
        // lists.
        var message = ui.find_message(message_id);

        unread.mark_message_as_read(message);
        ui.update_starred(message.id, message.starred !== true);
        message_flags.send_starred([message], message.starred);
    }

    $("#main_div").on("click", ".star", function (e) {
        e.stopPropagation();
        popovers.hide_all();
        toggle_star(rows.id($(this).closest(".message_row")));
    });

    // MESSAGE EDITING

    $('body').on('click', '.edit_content_button', function (e) {
        var row = current_msg_list.get_row(rows.id($(this).closest(".message_row")));
        current_msg_list.select_id(rows.id(row));
        message_edit.start(row);
        e.stopPropagation();
        popovers.hide_all();
    });
    $('body').on('click','.always_visible_topic_edit,.on_hover_topic_edit', function (e) {
        var recipient_row = $(this).closest(".recipient_row");
        message_edit.start_topic_edit(recipient_row);
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".topic_edit_save", function (e) {
        var recipient_row = $(this).closest(".recipient_row");
        if (message_edit.save(recipient_row, true) === true) {
            current_msg_list.hide_edit_topic(recipient_row);
        }
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".topic_edit_cancel", function (e) {
        var recipient_row = $(this).closest(".recipient_row");
        current_msg_list.hide_edit_topic(recipient_row);
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".message_edit_save", function (e) {
        var row = $(this).closest(".message_row");
        if (message_edit.save(row, false) === true) {
            // Re-fetch the message row in case .save() re-rendered the message list
            message_edit.end($(this).closest(".message_row"));
        }
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".message_edit_cancel", function (e) {
        var row = $(this).closest(".message_row");
        message_edit.end(row);
        e.stopPropagation();
        popovers.hide_all();
    });

    // RECIPIENT BARS

    function get_row_id_for_narrowing(narrow_link_elem) {
        var group = rows.get_closest_group(narrow_link_elem);
        var msg_id = rows.id_for_recipient_row(group);

        var nearest = current_msg_list.get(msg_id);
        var selected = current_msg_list.selected_message();
        if (util.same_recipient(nearest, selected)) {
            return selected.id;
        } else {
            return nearest.id;
        }
    }

    $("#home").on("click", ".narrows_by_recipient", function (e) {
        if (e.metaKey || e.ctrlKey) {
            return;
        }
        e.preventDefault();
        var row_id = get_row_id_for_narrowing(this);
        narrow.by_recipient(row_id, {trigger: 'message header'});
    });

    $("#home").on("click", ".narrows_by_subject", function (e) {
        if (e.metaKey || e.ctrlKey) {
            return;
        }
        e.preventDefault();
        var row_id = get_row_id_for_narrowing(this);
        narrow.by_subject(row_id, {trigger: 'message header'});
    });

    // SIDEBARS

    $("#userlist-toggle-button").on("click", function (e) {
        e.preventDefault();
        e.stopPropagation();

        var sidebarHidden = !$(".app-main .column-right").hasClass("expanded");
        popovers.hide_all();
        if (sidebarHidden) {
            popovers.show_userlist_sidebar();
        }
    });

    $("#streamlist-toggle-button").on("click", function (e) {
        e.preventDefault();
        e.stopPropagation();

        var sidebarHidden = !$(".app-main .column-left").hasClass("expanded");
        popovers.hide_all();
        if (sidebarHidden) {
            popovers.show_streamlist_sidebar();
        }
    });

    $('#user_presences').expectOne().on('click', '.selectable_sidebar_block', function (e) {
        var email = $(e.target).parents('li').data('email');
        narrow.by('pm-with', email, {select_first_unread: true, trigger: 'sidebar'});
        // The preventDefault is necessary so that clicking the
        // link doesn't jump us to the top of the page.
        e.preventDefault();
        // The stopPropagation is necessary so that we don't
        // see the following sequence of events:
        // 1. This click "opens" the composebox
        // 2. This event propagates to the body, which says "oh, hey, the
        //    composebox is open and you clicked out of it, you must want to
        //    stop composing!"
        e.stopPropagation();
        // Since we're stopping propagation we have to manually close any
        // open popovers.
        popovers.hide_all();
    });

    $('#group-pms').expectOne().on('click', '.selectable_sidebar_block', function (e) {
        var emails = $(e.target).parents('li').data('emails');
        narrow.by('pm-with', emails, {select_first_unread: true, trigger: 'sidebar'});
        e.preventDefault();
        e.stopPropagation();
        popovers.hide_all();
    });

    // HOME

    // Capture both the left-sidebar Home click and the tab breadcrumb Home
    $(document).on('click', "li[data-name='home']", function (e) {
        ui.change_tab_to('#home');
        narrow.deactivate();
        // We need to maybe scroll to the selected message
        // once we have the proper viewport set up
        setTimeout(navigate.maybe_scroll_to_selected, 0);
        e.preventDefault();
    });

    // This is obsolete, I believe.
    $(".brand").on('click', function (e) {
        if (ui.home_tab_obscured()) {
            ui.change_tab_to('#home');
        } else {
            narrow.restore_home_state();
        }
        navigate.maybe_scroll_to_selected();
        e.preventDefault();
    });

    // MISC

    $('#streams_inline_cog').click(function (e) {
        ui.change_tab_to('#subscriptions');

        e.preventDefault();
    });

    popovers.register_click_handlers();
    notifications.register_click_handlers();

    $('.logout_button').click(function (e) {
        $('#logout_form').submit();
    });
    $('.restart_get_events_button').click(function (e) {
        server_events.restart_get_events({dont_block: true});
    });

    // COMPOSE

    // NB: This just binds to current elements, and won't bind to elements
    // created after ready() is called.
    $('#send-status .send-status-close').click(
        function () { $('#send-status').stop(true).fadeOut(500); }
    );

    $('.compose_stream_button').click(function (e) {
        compose.start('stream');
    });
    $('.compose_private_button').click(function (e) {
        compose.start('private');
    });

    $('.empty_feed_compose_stream').click(function (e) {
        compose.start('stream', {trigger: 'empty feed message'});
        e.preventDefault();
    });
    $('.empty_feed_compose_private').click(function (e) {
        compose.start('private', {trigger: 'empty feed message'});
        e.preventDefault();
    });

    function handle_compose_click(e) {
        // Don't let clicks in the compose area count as
        // "unfocusing" our compose -- in other words, e.g.
        // clicking "Press enter to send" should not
        // trigger the composebox-closing code above.
        // But do allow our formatting link.
        if (!$(e.target).is("a")) {
            e.stopPropagation();
        }
        // Still hide the popovers, however
        popovers.hide_all();
    }

    $("#compose_buttons").click(handle_compose_click);
    $(".compose-content").click(handle_compose_click);

    $("#compose_close").click(function (e) {
        compose.cancel();
    });

    // FEEDBACK

    // Keep these 2 feedback bot triggers separate because they have to
    // propagate the event differently.
    $('.feedback').click(function (e) {
        compose.start('private', { 'private_message_recipient': 'feedback@zulip.com',
                                   trigger: 'feedback menu item' });

    });
    $('#feedback_button').click(function (e) {
        e.stopPropagation();
        popovers.hide_all();
        compose.start('private', { 'private_message_recipient': 'feedback@zulip.com',
                                   trigger: 'feedback button' });

    });

    // WEBATHENA

    $('#right-sidebar, #top_navbar').on('click', '.webathena_login', function (e) {
        $("#zephyr-mirror-error").hide();
        var principal = ["zephyr", "zephyr"];
        WinChan.open({
            url: "https://webathena.mit.edu/#!request_ticket_v1",
            relay_url: "https://webathena.mit.edu/relay.html",
            params: {
                realm: "ATHENA.MIT.EDU",
                principal: principal
            }
        }, function (err, r) {
            if (err) {
                blueslip.warn(err);
                return;
            }
            if (r.status !== "OK") {
                blueslip.warn(r);
                return;
            }

            channel.post({
                url:      "/accounts/webathena_kerberos_login/",
                data:     {cred: JSON.stringify(r.session)},
                success: function (data, success) {
                    $("#zephyr-mirror-error").hide();
                },
                error: function (data, success) {
                    $("#zephyr-mirror-error").show();
                }
            });
        });
        $('#settings-dropdown').dropdown("toggle");
        e.preventDefault();
        e.stopPropagation();
    });
    // End Webathena code

    // BANKRUPTCY

    $(".bankruptcy_button").click(function (e) {
        unread.enable();
    });

    $('#yes-bankrupt').click(function (e) {
        pointer.fast_forward_pointer();
        $("#yes-bankrupt").hide();
        $("#no-bankrupt").hide();
        $(this).after($("<div>").addClass("alert alert-info settings_committed")
               .text("Bringing you to your latest messages…"));
    });

    (function () {
        $("#main_div").on("click", ".message_inline_image a", function (e) {
            var img = e.target,
                row = rows.id($(img).closest(".message_row")),
                user = current_msg_list.get(row).sender_full_name;

            // prevent the link from opening in a new page.
            e.preventDefault();
            // prevent the message compose dialog from happening.
            e.stopPropagation();

            ui.lightbox_photo(img, user);
        });

        $("#overlay .exit").click(function (e) {
            ui.exit_lightbox_photo();
        });

        $("#overlay .download").click(function (e) {
          this.blur();
        });
    }());

    // MAIN CLICK HANDLER

    $(document).on('click', function (e) {
        if (e.button !== 0) {
            // Firefox emits right click events on the document, but not on
            // the child nodes, so the #compose stopPropagation doesn't get a
            // chance to capture right clicks.
            return;
        }

        // Dismiss popovers if the user has clicked outside them
        if ($('.popover-inner').has(e.target).length === 0) {
            popovers.hide_all();
        }

        // Unfocus our compose area if we click out of it. Don't let exits out
        // of modals or selecting text (for copy+paste) trigger cancelling.
        if (compose.composing() && !$(e.target).is("a") &&
            ($(e.target).closest(".modal").length === 0) &&
            window.getSelection().toString() === "") {
            compose.cancel();
        }
    });

    // Workaround for Bootstrap issue #5900, which basically makes dropdowns
    // unclickable on mobile devices.
    // https://github.com/twitter/bootstrap/issues/5900
    $('a.dropdown-toggle, .dropdown-menu a').on('touchstart', function (e) {
        e.stopPropagation();
    });
});

return exports;

}());
<commit_msg>On stream click, blur so key events unbind from sidebar.<commit_after>var click_handlers = (function () {

// We don't actually export anything yet; this is just for consistency.
var exports = {};

// You won't find every click handler here, but it's a good place to start!

$(function () {

    // MOUSE MOVING DETECTION
    var clicking = false;
    var mouse_moved = false;

    function mousedown() {
        mouse_moved = false;
        clicking = true;
    }

    function mousemove() {
        if (clicking) {
            mouse_moved = true;
        }
    }

    $("#main_div").on("mousedown", ".messagebox", mousedown);
    $("#main_div").on("mousemove", ".messagebox", mousemove);

    // MESSAGE CLICKING

    function is_clickable_message_element(target) {
        return target.is("a") || target.is("img.message_inline_image") || target.is("img.twitter-avatar") ||
            target.is("div.message_length_controller") || target.is("textarea") || target.is("input") ||
            target.is("i.edit_content_button");
    }

    $("#main_div").on("click", ".messagebox", function (e) {
        if (is_clickable_message_element($(e.target))) {
            // If this click came from a hyperlink, don't trigger the
            // reply action.  The simple way of doing this is simply
            // to call e.stopPropagation() from within the link's
            // click handler.
            //
            // Unfortunately, on Firefox, this breaks Ctrl-click and
            // Shift-click, because those are (apparently) implemented
            // by adding an event listener on link clicks, and
            // stopPropagation prevents them from being called.
            return;
        }
        if (!(clicking && mouse_moved)) {
            // Was a click (not a click-and-drag).
            var row = $(this).closest(".message_row");
            var id = rows.id(row);

            if (message_edit.is_editing(id)) {
                // Clicks on a message being edited shouldn't trigger a reply.
                return;
            }

            current_msg_list.select_id(id);
            compose.respond_to_message({trigger: 'message click'});
            e.stopPropagation();
            popovers.hide_all();
        }
        mouse_moved = false;
        clicking = false;
    });

    function toggle_star(message_id) {
        // Update the message object pointed to by the various message
        // lists.
        var message = ui.find_message(message_id);

        unread.mark_message_as_read(message);
        ui.update_starred(message.id, message.starred !== true);
        message_flags.send_starred([message], message.starred);
    }

    $("#main_div").on("click", ".star", function (e) {
        e.stopPropagation();
        popovers.hide_all();
        toggle_star(rows.id($(this).closest(".message_row")));
    });

    // MESSAGE EDITING

    $('body').on('click', '.edit_content_button', function (e) {
        var row = current_msg_list.get_row(rows.id($(this).closest(".message_row")));
        current_msg_list.select_id(rows.id(row));
        message_edit.start(row);
        e.stopPropagation();
        popovers.hide_all();
    });
    $('body').on('click','.always_visible_topic_edit,.on_hover_topic_edit', function (e) {
        var recipient_row = $(this).closest(".recipient_row");
        message_edit.start_topic_edit(recipient_row);
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".topic_edit_save", function (e) {
        var recipient_row = $(this).closest(".recipient_row");
        if (message_edit.save(recipient_row, true) === true) {
            current_msg_list.hide_edit_topic(recipient_row);
        }
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".topic_edit_cancel", function (e) {
        var recipient_row = $(this).closest(".recipient_row");
        current_msg_list.hide_edit_topic(recipient_row);
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".message_edit_save", function (e) {
        var row = $(this).closest(".message_row");
        if (message_edit.save(row, false) === true) {
            // Re-fetch the message row in case .save() re-rendered the message list
            message_edit.end($(this).closest(".message_row"));
        }
        e.stopPropagation();
        popovers.hide_all();
    });
    $("body").on("click", ".message_edit_cancel", function (e) {
        var row = $(this).closest(".message_row");
        message_edit.end(row);
        e.stopPropagation();
        popovers.hide_all();
    });

    // RECIPIENT BARS

    function get_row_id_for_narrowing(narrow_link_elem) {
        var group = rows.get_closest_group(narrow_link_elem);
        var msg_id = rows.id_for_recipient_row(group);

        var nearest = current_msg_list.get(msg_id);
        var selected = current_msg_list.selected_message();
        if (util.same_recipient(nearest, selected)) {
            return selected.id;
        } else {
            return nearest.id;
        }
    }

    $("#home").on("click", ".narrows_by_recipient", function (e) {
        if (e.metaKey || e.ctrlKey) {
            return;
        }
        e.preventDefault();
        var row_id = get_row_id_for_narrowing(this);
        narrow.by_recipient(row_id, {trigger: 'message header'});
    });

    $("#home").on("click", ".narrows_by_subject", function (e) {
        if (e.metaKey || e.ctrlKey) {
            return;
        }
        e.preventDefault();
        var row_id = get_row_id_for_narrowing(this);
        narrow.by_subject(row_id, {trigger: 'message header'});
    });

    // SIDEBARS

    $("#userlist-toggle-button").on("click", function (e) {
        e.preventDefault();
        e.stopPropagation();

        var sidebarHidden = !$(".app-main .column-right").hasClass("expanded");
        popovers.hide_all();
        if (sidebarHidden) {
            popovers.show_userlist_sidebar();
        }
    });

    $("#streamlist-toggle-button").on("click", function (e) {
        e.preventDefault();
        e.stopPropagation();

        var sidebarHidden = !$(".app-main .column-left").hasClass("expanded");
        popovers.hide_all();
        if (sidebarHidden) {
            popovers.show_streamlist_sidebar();
        }
    });

    $('#user_presences').expectOne().on('click', '.selectable_sidebar_block', function (e) {
        var email = $(e.target).parents('li').data('email');
        narrow.by('pm-with', email, {select_first_unread: true, trigger: 'sidebar'});
        // The preventDefault is necessary so that clicking the
        // link doesn't jump us to the top of the page.
        e.preventDefault();
        // The stopPropagation is necessary so that we don't
        // see the following sequence of events:
        // 1. This click "opens" the composebox
        // 2. This event propagates to the body, which says "oh, hey, the
        //    composebox is open and you clicked out of it, you must want to
        //    stop composing!"
        e.stopPropagation();
        // Since we're stopping propagation we have to manually close any
        // open popovers.
        popovers.hide_all();
    });

    $('#group-pms').expectOne().on('click', '.selectable_sidebar_block', function (e) {
        var emails = $(e.target).parents('li').data('emails');
        narrow.by('pm-with', emails, {select_first_unread: true, trigger: 'sidebar'});
        e.preventDefault();
        e.stopPropagation();
        popovers.hide_all();
    });

    // HOME

    // Capture both the left-sidebar Home click and the tab breadcrumb Home
    $(document).on('click', "li[data-name='home']", function (e) {
        ui.change_tab_to('#home');
        narrow.deactivate();
        // We need to maybe scroll to the selected message
        // once we have the proper viewport set up
        setTimeout(navigate.maybe_scroll_to_selected, 0);
        e.preventDefault();
    });

    // This is obsolete, I believe.
    $(".brand").on('click', function (e) {
        if (ui.home_tab_obscured()) {
            ui.change_tab_to('#home');
        } else {
            narrow.restore_home_state();
        }
        navigate.maybe_scroll_to_selected();
        e.preventDefault();
    });

    // MISC

    $('#streams_inline_cog').click(function (e) {
        ui.change_tab_to('#subscriptions');

        e.preventDefault();
    });

    (function () {
        var sel = ["#group-pm-list", "#stream_filters", "#global_filters", "#user_presences"].join(", ");

        $(sel).on("click", "a", function () {
            this.blur();
        });
    }());

    popovers.register_click_handlers();
    notifications.register_click_handlers();

    $('.logout_button').click(function (e) {
        $('#logout_form').submit();
    });
    $('.restart_get_events_button').click(function (e) {
        server_events.restart_get_events({dont_block: true});
    });

    // COMPOSE

    // NB: This just binds to current elements, and won't bind to elements
    // created after ready() is called.
    $('#send-status .send-status-close').click(
        function () { $('#send-status').stop(true).fadeOut(500); }
    );

    $('.compose_stream_button').click(function (e) {
        compose.start('stream');
    });
    $('.compose_private_button').click(function (e) {
        compose.start('private');
    });

    $('.empty_feed_compose_stream').click(function (e) {
        compose.start('stream', {trigger: 'empty feed message'});
        e.preventDefault();
    });
    $('.empty_feed_compose_private').click(function (e) {
        compose.start('private', {trigger: 'empty feed message'});
        e.preventDefault();
    });

    function handle_compose_click(e) {
        // Don't let clicks in the compose area count as
        // "unfocusing" our compose -- in other words, e.g.
        // clicking "Press enter to send" should not
        // trigger the composebox-closing code above.
        // But do allow our formatting link.
        if (!$(e.target).is("a")) {
            e.stopPropagation();
        }
        // Still hide the popovers, however
        popovers.hide_all();
    }

    $("#compose_buttons").click(handle_compose_click);
    $(".compose-content").click(handle_compose_click);

    $("#compose_close").click(function (e) {
        compose.cancel();
    });

    // FEEDBACK

    // Keep these 2 feedback bot triggers separate because they have to
    // propagate the event differently.
    $('.feedback').click(function (e) {
        compose.start('private', { 'private_message_recipient': 'feedback@zulip.com',
                                   trigger: 'feedback menu item' });

    });
    $('#feedback_button').click(function (e) {
        e.stopPropagation();
        popovers.hide_all();
        compose.start('private', { 'private_message_recipient': 'feedback@zulip.com',
                                   trigger: 'feedback button' });

    });

    // WEBATHENA

    $('#right-sidebar, #top_navbar').on('click', '.webathena_login', function (e) {
        $("#zephyr-mirror-error").hide();
        var principal = ["zephyr", "zephyr"];
        WinChan.open({
            url: "https://webathena.mit.edu/#!request_ticket_v1",
            relay_url: "https://webathena.mit.edu/relay.html",
            params: {
                realm: "ATHENA.MIT.EDU",
                principal: principal
            }
        }, function (err, r) {
            if (err) {
                blueslip.warn(err);
                return;
            }
            if (r.status !== "OK") {
                blueslip.warn(r);
                return;
            }

            channel.post({
                url:      "/accounts/webathena_kerberos_login/",
                data:     {cred: JSON.stringify(r.session)},
                success: function (data, success) {
                    $("#zephyr-mirror-error").hide();
                },
                error: function (data, success) {
                    $("#zephyr-mirror-error").show();
                }
            });
        });
        $('#settings-dropdown').dropdown("toggle");
        e.preventDefault();
        e.stopPropagation();
    });
    // End Webathena code

    // BANKRUPTCY

    $(".bankruptcy_button").click(function (e) {
        unread.enable();
    });

    $('#yes-bankrupt').click(function (e) {
        pointer.fast_forward_pointer();
        $("#yes-bankrupt").hide();
        $("#no-bankrupt").hide();
        $(this).after($("<div>").addClass("alert alert-info settings_committed")
               .text("Bringing you to your latest messages…"));
    });

    (function () {
        $("#main_div").on("click", ".message_inline_image a", function (e) {
            var img = e.target,
                row = rows.id($(img).closest(".message_row")),
                user = current_msg_list.get(row).sender_full_name;

            // prevent the link from opening in a new page.
            e.preventDefault();
            // prevent the message compose dialog from happening.
            e.stopPropagation();

            ui.lightbox_photo(img, user);
        });

        $("#overlay .exit").click(function (e) {
            ui.exit_lightbox_photo();
        });

        $("#overlay .download").click(function (e) {
          this.blur();
        });
    }());

    // MAIN CLICK HANDLER

    $(document).on('click', function (e) {
        if (e.button !== 0) {
            // Firefox emits right click events on the document, but not on
            // the child nodes, so the #compose stopPropagation doesn't get a
            // chance to capture right clicks.
            return;
        }

        // Dismiss popovers if the user has clicked outside them
        if ($('.popover-inner').has(e.target).length === 0) {
            popovers.hide_all();
        }

        // Unfocus our compose area if we click out of it. Don't let exits out
        // of modals or selecting text (for copy+paste) trigger cancelling.
        if (compose.composing() && !$(e.target).is("a") &&
            ($(e.target).closest(".modal").length === 0) &&
            window.getSelection().toString() === "") {
            compose.cancel();
        }
    });

    // Workaround for Bootstrap issue #5900, which basically makes dropdowns
    // unclickable on mobile devices.
    // https://github.com/twitter/bootstrap/issues/5900
    $('a.dropdown-toggle, .dropdown-menu a').on('touchstart', function (e) {
        e.stopPropagation();
    });
});

return exports;

}());
<|endoftext|>